///////////////////////////////////////////////////////////////////////// // $Id$ ///////////////////////////////////////////////////////////////////////// // // Copyright (C) 2002-2011 The Bochs Project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ///////////////////////////////////////////////////////////////////////// // These are the low-level CDROM functions which are called // from 'harddrv.cc'. They effect the OS specific functionality // needed by the CDROM emulation in 'harddrv.cc'. Mostly, just // ioctl() calls and such. Should be fairly easy to add support // for your OS if it is not supported yet. // Define BX_PLUGGABLE in files that can be compiled into plugins. For // platforms that require a special tag on exported symbols, BX_PLUGGABLE // is used to know when we are exporting symbols and when we are importing. #define BX_PLUGGABLE #include "bochs.h" #if BX_SUPPORT_CDROM #include "cdrom.h" #define LOG_THIS /* no SMF tricks here, not needed */ extern "C" { #include } #if defined(__APPLE__) #include #include #include #include #if defined (__GNUC__) && (__GNUC__ >= 4) #include #else #include #endif #include #include #include #include #include #include #include #include #include // These definitions were taken from mount_cd9660.c // There are some similar definitions in IOCDTypes.h // however there seems to be some dissagreement in // the definition of CDTOC.length struct _CDMSF { u_char minute; u_char second; u_char frame; }; #define MSF_TO_LBA(msf) \ (((((msf).minute * 60UL) + (msf).second) * 75UL) + (msf).frame - 150) struct _CDTOC_Desc { u_char session; u_char ctrl_adr; /* typed to be machine and compiler independent */ u_char tno; u_char point; struct _CDMSF address; u_char zero; struct _CDMSF p; }; struct _CDTOC { u_short length; /* in native cpu endian */ u_char first_session; u_char last_session; struct _CDTOC_Desc trackdesc[1]; }; static kern_return_t FindEjectableCDMedia(io_iterator_t *mediaIterator, mach_port_t *masterPort); static kern_return_t GetDeviceFilePath(io_iterator_t mediaIterator, char *deviceFilePath, CFIndex maxPathSize); //int OpenDrive(const char *deviceFilePath); static struct _CDTOC *ReadTOC(const char *devpath); static char CDDevicePath[MAXPATHLEN]; #define BX_CD_FRAMESIZE 2048 #define CD_FRAMESIZE 2048 #include static kern_return_t FindEjectableCDMedia(io_iterator_t *mediaIterator, mach_port_t *masterPort) { kern_return_t kernResult; CFMutableDictionaryRef classesToMatch; kernResult = IOMasterPort(bootstrap_port, masterPort); if (kernResult != KERN_SUCCESS) { fprintf (stderr, "IOMasterPort returned %d\n", kernResult); return kernResult; } // CD media are instances of class kIOCDMediaClass. classesToMatch = IOServiceMatching(kIOCDMediaClass); if (classesToMatch == NULL) fprintf (stderr, "IOServiceMatching returned a NULL dictionary.\n"); else { // Each IOMedia object has a property with key kIOMediaEjectableKey // which is true if the media is indeed ejectable. So add property // to CFDictionary for matching. CFDictionarySetValue(classesToMatch, CFSTR(kIOMediaEjectableKey), kCFBooleanTrue); } kernResult = IOServiceGetMatchingServices(*masterPort, classesToMatch, mediaIterator); if ((kernResult != KERN_SUCCESS) || (*mediaIterator == NULL)) fprintf(stderr, "No ejectable CD media found.\n kernResult = %d\n", kernResult); return kernResult; } static kern_return_t GetDeviceFilePath(io_iterator_t mediaIterator, char *deviceFilePath, CFIndex maxPathSize) { io_object_t nextMedia; kern_return_t kernResult = KERN_FAILURE; nextMedia = IOIteratorNext(mediaIterator); if (nextMedia == NULL) { *deviceFilePath = '\0'; } else { CFTypeRef deviceFilePathAsCFString; deviceFilePathAsCFString = IORegistryEntryCreateCFProperty(nextMedia, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0); *deviceFilePath = '\0'; if (deviceFilePathAsCFString) { size_t devPathLength = strlen(_PATH_DEV); strcpy(deviceFilePath, _PATH_DEV); if (CFStringGetCString((const __CFString *) deviceFilePathAsCFString, deviceFilePath + devPathLength, maxPathSize - devPathLength, kCFStringEncodingASCII)) { // fprintf(stderr, "BSD path: %s\n", deviceFilePath); kernResult = KERN_SUCCESS; } CFRelease(deviceFilePathAsCFString); } } IOObjectRelease(nextMedia); return kernResult; } static int OpenDrive(const char *deviceFilePath) { int fileDescriptor = open(deviceFilePath, O_RDONLY); if (fileDescriptor == -1) fprintf(stderr, "Error %d opening device %s.\n", errno, deviceFilePath); return fileDescriptor; } static struct _CDTOC * ReadTOC(const char *devpath) { struct _CDTOC * toc_p = NULL; io_iterator_t iterator = 0; io_registry_entry_t service = 0; CFDictionaryRef properties = 0; CFDataRef data = 0; mach_port_t port = 0; char *devname; if ((devname = strrchr(devpath, '/')) != NULL) { ++devname; } else { devname = (char *) devpath; } if (IOMasterPort(bootstrap_port, &port) != KERN_SUCCESS) { fprintf(stderr, "IOMasterPort failed\n"); goto Exit; } if (IOServiceGetMatchingServices(port, IOBSDNameMatching(port, 0, devname), &iterator) != KERN_SUCCESS) { fprintf(stderr, "IOServiceGetMatchingServices failed\n"); goto Exit; } service = IOIteratorNext(iterator); IOObjectRelease(iterator); iterator = 0; while (service && !IOObjectConformsTo(service, "IOCDMedia")) { if (IORegistryEntryGetParentIterator(service, kIOServicePlane, &iterator) != KERN_SUCCESS) { fprintf(stderr, "IORegistryEntryGetParentIterator failed\n"); goto Exit; } IOObjectRelease(service); service = IOIteratorNext(iterator); IOObjectRelease(iterator); } if (service == NULL) { fprintf(stderr, "CD media not found\n"); goto Exit; } if (IORegistryEntryCreateCFProperties(service, (__CFDictionary **) &properties, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS) { fprintf(stderr, "IORegistryEntryGetParentIterator failed\n"); goto Exit; } data = (CFDataRef) CFDictionaryGetValue(properties, CFSTR(kIOCDMediaTOCKey)); if (data == NULL) { fprintf(stderr, "CFDictionaryGetValue failed\n"); goto Exit; } else { CFRange range; CFIndex buflen; buflen = CFDataGetLength(data) + 1; range = CFRangeMake(0, buflen); toc_p = (struct _CDTOC *) malloc(buflen); if (toc_p == NULL) { fprintf(stderr, "Out of memory\n"); goto Exit; } else { CFDataGetBytes(data, range, (unsigned char *) toc_p); } /* fprintf(stderr, "Table of contents\n length %d first %d last %d\n", toc_p->length, toc_p->first_session, toc_p->last_session); */ CFRelease(properties); } Exit: if (service) { IOObjectRelease(service); } return toc_p; } static unsigned int cdrom_count = 0; cdrom_interface::cdrom_interface(const char *dev) { char prefix[6]; sprintf(prefix, "CD%d", ++cdrom_count); put(prefix); fd = -1; // File descriptor not yet allocated if (dev == NULL) { path = NULL; } else { path = strdup(dev); } using_file=0; } cdrom_interface::~cdrom_interface(void) { if (fd >= 0) close(fd); if (path) free(path); BX_DEBUG(("Exit")); } bx_bool cdrom_interface::insert_cdrom(const char *dev) { unsigned char buffer[BX_CD_FRAMESIZE]; ssize_t ret; // Load CD-ROM. Returns 0 if CD is not ready. if (dev != NULL) path = strdup(dev); BX_INFO (("load cdrom with path=%s", path)); if(strcmp(path, "drive") == 0) { mach_port_t masterPort = NULL; io_iterator_t mediaIterator; kern_return_t kernResult; BX_INFO(("Insert CDROM")); kernResult = FindEjectableCDMedia(&mediaIterator, &masterPort); if (kernResult != KERN_SUCCESS) { BX_INFO(("Unable to find CDROM")); return 0; } kernResult = GetDeviceFilePath(mediaIterator, CDDevicePath, sizeof(CDDevicePath)); if (kernResult != KERN_SUCCESS) { BX_INFO(("Unable to get CDROM device file path")); return 0; } // Here a cdrom was found so see if we can read from it. // At this point a failure will result in panic. if (strlen(CDDevicePath)) { fd = open(CDDevicePath, O_RDONLY); } } else { fd = open(path, O_RDONLY); } if (fd < 0) { BX_ERROR(("open cd failed for %s: %s", path, strerror(errno))); return 0; } // do fstat to determine if it's a file or a device, then set using_file. struct stat stat_buf; ret = fstat (fd, &stat_buf); if (ret) { BX_PANIC (("fstat cdrom file returned error: %s", strerror (errno))); } if (S_ISREG (stat_buf.st_mode)) { using_file = 1; BX_INFO (("Opening image file as a cd.")); } else { using_file = 0; BX_INFO (("Using direct access for cdrom.")); } // I just see if I can read a sector to verify that a // CD is in the drive and readable. return read_block(buffer, 0, 2048); } bx_bool cdrom_interface::start_cdrom() { // Spin up the cdrom drive. if (fd >= 0) { BX_INFO(("start_cdrom: your OS is not supported yet")); return 0; // OS not supported yet, return 0 always } return 0; } void cdrom_interface::eject_cdrom() { // Logically eject the CD. I suppose we could stick in // some ioctl() calls to really eject the CD as well. if (fd >= 0) { close(fd); fd = -1; } } bx_bool cdrom_interface::read_toc(Bit8u* buf, int* length, bx_bool msf, int start_track, int format) { // Read CD TOC. Returns 0 if start track is out of bounds. if (fd < 0) { BX_PANIC(("cdrom: read_toc: file not open.")); return 0; } // This is a hack and works okay if there's one rom track only if (using_file || (format != 0)) { return create_toc(buf, length, msf, start_track, format); } // all these implementations below are the platform-dependent code required // to read the TOC from a physical cdrom. { struct _CDTOC *toc = ReadTOC(CDDevicePath); if ((start_track > toc->last_session) && (start_track != 0xaa)) return 0; buf[2] = toc->first_session; buf[3] = toc->last_session; if (start_track < toc->first_session) start_track = toc->first_session; int len = 4; for (int i = start_track; i <= toc->last_session; i++) { buf[len++] = 0; // Reserved buf[len++] = toc->trackdesc[i].ctrl_adr ; // ADR, control buf[len++] = i; // Track number buf[len++] = 0; // Reserved // Start address if (msf) { buf[len++] = 0; // reserved buf[len++] = toc->trackdesc[i].address.minute; buf[len++] = toc->trackdesc[i].address.second; buf[len++] = toc->trackdesc[i].address.frame; } else { unsigned lba = (unsigned)(MSF_TO_LBA(toc->trackdesc[i].address)); buf[len++] = (lba >> 24) & 0xff; buf[len++] = (lba >> 16) & 0xff; buf[len++] = (lba >> 8) & 0xff; buf[len++] = (lba >> 0) & 0xff; } } // Lead out track buf[len++] = 0; // Reserved buf[len++] = 0x16; // ADR, control buf[len++] = 0xaa; // Track number buf[len++] = 0; // Reserved Bit32u blocks = capacity(); // Start address if (msf) { buf[len++] = 0; // reserved buf[len++] = (Bit8u)(((blocks + 150) / 75) / 60); // minute buf[len++] = (Bit8u)(((blocks + 150) / 75) % 60); // second buf[len++] = (Bit8u)((blocks + 150) % 75); // frame; } else { buf[len++] = (blocks >> 24) & 0xff; buf[len++] = (blocks >> 16) & 0xff; buf[len++] = (blocks >> 8) & 0xff; buf[len++] = (blocks >> 0) & 0xff; } buf[0] = ((len-2) >> 8) & 0xff; buf[1] = (len-2) & 0xff; *length = len; return 1; } } Bit32u cdrom_interface::capacity() { // Return CD-ROM capacity. I believe you want to return // the number of blocks of capacity the actual media has. if (using_file) { // return length of the image file struct stat stat_buf; int ret = fstat (fd, &stat_buf); if (ret) { BX_PANIC (("fstat on cdrom image returned err: %s", strerror(errno))); } if ((stat_buf.st_size % 2048) != 0) { BX_ERROR (("expected cdrom image to be a multiple of 2048 bytes")); } return (stat_buf.st_size / 2048); } // Find the size of the first data track on the cd. This has produced // the same results as the linux version on every cd I have tried, about // 5. The differences here seem to be that the entries in the TOC when // retrieved from the IOKit interface appear in a reversed order when // compared with the linux READTOCENTRY ioctl. { // Return CD-ROM capacity. I believe you want to return // the number of bytes of capacity the actual media has. BX_INFO(("Capacity")); struct _CDTOC *toc = ReadTOC(CDDevicePath); if (toc == NULL) { BX_PANIC(("capacity: Failed to read toc")); } size_t toc_entries = (toc->length - 2) / sizeof(struct _CDTOC_Desc); BX_DEBUG(("reading %d toc entries\n", toc_entries)); int start_sector = -1; int data_track = -1; // Iterate through the list backward. Pick the first data track and // get the address of the immediately previous (or following depending // on how you look at it). The difference in the sector numbers // is returned as the sized of the data track. for (int i=toc_entries - 1; i>=0; i--) { BX_DEBUG(("session %d ctl_adr %d tno %d point %d lba %d z %d p lba %d\n", (int)toc->trackdesc[i].session, (int)toc->trackdesc[i].ctrl_adr, (int)toc->trackdesc[i].tno, (int)toc->trackdesc[i].point, MSF_TO_LBA(toc->trackdesc[i].address), (int)toc->trackdesc[i].zero, MSF_TO_LBA(toc->trackdesc[i].p))); if (start_sector != -1) { start_sector = MSF_TO_LBA(toc->trackdesc[i].p) - start_sector; break; } if ((toc->trackdesc[i].ctrl_adr >> 4) != 1) continue; if (toc->trackdesc[i].ctrl_adr & 0x04) { data_track = toc->trackdesc[i].point; start_sector = MSF_TO_LBA(toc->trackdesc[i].p); } } free(toc); if (start_sector == -1) { start_sector = 0; } BX_INFO(("first data track %d data size is %d", data_track, start_sector)); return start_sector; } } bx_bool BX_CPP_AttrRegparmN(3) cdrom_interface::read_block(Bit8u* buf, Bit32u lba, int blocksize) { // Read a single block from the CD off_t pos; ssize_t n = 0; Bit8u try_count = 3; Bit8u* buf1; if (blocksize == 2352) { memset(buf, 0, 2352); memset(buf+1, 0xff, 10); Bit32u raw_block = lba + 150; buf[12] = (raw_block / 75) / 60; buf[13] = (raw_block / 75) % 60; buf[14] = (raw_block % 75); buf[15] = 0x01; buf1 = buf + 16; } else { buf1 = buf; } do { #define CD_SEEK_DISTANCE kCDSectorSizeWhole if(using_file) { pos = lseek(fd, (off_t) lba * BX_CD_FRAMESIZE, SEEK_SET); if (pos < 0) { BX_PANIC(("cdrom: read_block: lseek returned error.")); } else { n = read(fd, buf1, BX_CD_FRAMESIZE); } } else { // This seek will leave us 16 bytes from the start of the data // hence the magic number. pos = lseek(fd, (off_t) lba * CD_SEEK_DISTANCE + 16, SEEK_SET); if (pos < 0) { BX_PANIC(("cdrom: read_block: lseek returned error.")); } else { n = read(fd, buf1, CD_FRAMESIZE); } } } while ((n != BX_CD_FRAMESIZE) && (--try_count > 0)); return (n == BX_CD_FRAMESIZE); } #endif /* if defined(__APPLE__) */ #endif /* if BX_SUPPORT_CDROM */