/*************************************************************************** PlugCD.c CDDeviceInterface Created by Gil Pedersen on Fri July 18 2003. Copyright (c) 2003,2004 Gil Pedersen. ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. See also the license.txt file for * * additional informations. * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include "plugins.h" #include "PlugCD.h" //#define USE_DEVICE_INTERFACE long CDRclose(void); ///////////////////////////////////////////////////////// typedef void* HWND; #include "psemu_plugin_defs.h" const char *LibName = "CD-ROM Device Interface"; const int version = 0; const int revision = 1; const int build = 0; const char *PSEgetLibName(void) { return LibName; } unsigned long PSEgetLibType(void) { return PSE_LT_CDR; } unsigned long PSEgetLibVersion(void) { return version << 16 | revision << 8 | build; } ///////////////////////////////////////////////////////// //#define SysPrintf printf #define SysPrintf(X) #define UseMultiThreaded 1 #define NoIdleSleep 1 struct CdrStat { unsigned long Type; unsigned long Status; unsigned char Time[3]; }; MMCDeviceInterface **cdInterface; pthread_t readThread; pthread_cond_t readCond; pthread_mutex_t readMutex; char deviceFilePath[ 256 ]; 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 ) { printf( "IOMasterPort returned %d\n", kernResult ); return kernResult; } // CD media are instances of class kIOCDMediaClass. classesToMatch = IOServiceMatching( kIOCDMediaClass ); if ( classesToMatch == NULL ) classesToMatch = IOServiceMatching( kIODVDMediaClass ); if ( classesToMatch == NULL ) printf( "IOServiceMatching returned a NULL dictionary.\n" ); else { // Each IOMedia object has a property with key kIOMediaEjectable // 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) ) printf( "No ejectable CD media found.\n kernResult = %d\n", kernResult ); return kernResult; } kern_return_t GetDeviceFilePath( io_service_t media, char *deviceFilePath, CFIndex maxPathSize ) { kern_return_t kernResult = KERN_FAILURE; CFTypeRef deviceFilePathAsCFString; deviceFilePathAsCFString = IORegistryEntryCreateCFProperty( media, CFSTR( kIOBSDNameKey ), kCFAllocatorDefault, 0 ); *deviceFilePath = '\0'; if ( deviceFilePathAsCFString ) { size_t devPathLength = strlen( _PATH_DEV )+0;//+1; strcpy( deviceFilePath, _PATH_DEV ); // Add "r" before the BSD node name from the I/O Registry // to specify the raw disk node. The raw disk node receives // I/O requests directly and does not go through the // buffer cache. // strcat( deviceFilePath, "r"); // apparently som cd-rom drives don't like this if ( CFStringGetCString( deviceFilePathAsCFString, deviceFilePath + devPathLength, maxPathSize - devPathLength, kCFStringEncodingASCII ) ) { printf( "BSD path: %s\n", deviceFilePath ); kernResult = KERN_SUCCESS; } CFRelease( deviceFilePathAsCFString ); } return kernResult; } MMCDeviceInterface ** GetMMCInterfaceForDevice(io_service_t service) { SInt32 score; HRESULT herr; kern_return_t err; IOCFPlugInInterface **plugInInterface = NULL; MMCDeviceInterface **mmcInterface = NULL; // Create the IOCFPlugIn interface so we can query it. err = IOCreatePlugInInterfaceForService ( service, kIOMMCDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score ); if ( err != noErr ) { printf("IOCreatePlugInInterfaceForService returned %d\n", err); return NULL; } // Query the interface for the MMCDeviceInterface. herr = ( *plugInInterface )->QueryInterface ( plugInInterface, CFUUIDGetUUIDBytes ( kIOMMCDeviceInterfaceID ), ( LPVOID ) &mmcInterface ); if ( herr != S_OK ) { printf("QueryInterface returned %ld\n", herr); return NULL; } // ( *mmcInterface )->Release ( mmcInterface ); // IODestroyPlugInInterface ( plugInInterface ); return mmcInterface; } int GetCDROMServices() { mach_port_t masterPort; CFMutableDictionaryRef matchingDict; CFMutableDictionaryRef subDict; io_service_t nextDevice; io_iterator_t iterator; kern_return_t kr; int retVal = -1; // first create a master_port for my task kr = IOMasterPort(MACH_PORT_NULL, &masterPort); if (kr || !masterPort) { printf("ERR: Couldn't create a master IOKit Port(%08x)\n", kr); return -1; } // Create the dictionaries matchingDict = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL ); subDict = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL ); // Create a dictionary with the "SCSITaskDeviceCategory" key = "SCSITaskAuthoringDevice" // TODO: examine if this also work on non-authoring capable devices CFDictionarySetValue ( subDict, CFSTR ( kIOPropertySCSITaskDeviceCategory ), CFSTR ( kIOPropertySCSITaskAuthoringDevice ) ); // Add the dictionary to the main dictionary with the key "IOPropertyMatch" to // narrow the search to the above dictionary. CFDictionarySetValue ( matchingDict, CFSTR ( kIOPropertyMatchKey ), subDict ); kr = IOServiceGetMatchingServices( masterPort, matchingDict, &iterator ); if ( (kr != KERN_SUCCESS) || (iterator == NULL) ) { printf( "No CDROM drives found.\n kernResult = %d\n", kr ); goto error; } // Find best cdrom drive - i.e. the first :-) while(nextDevice = IOIteratorNext( iterator )) { cdInterface = GetMMCInterfaceForDevice(nextDevice); kr = IOObjectRelease(nextDevice); break; // hack } IOObjectRelease( iterator ); retVal = 0; error: // Now done with the master_port if (masterPort) mach_port_deallocate(mach_task_self(), masterPort); return retVal; } /* determine if the tray is open for our cd-drive */ int TrayIsOpen() { #ifdef USE_DEVICE_INTERFACE if (cdInterface) { if ((*cdInterface)->GetTrayState) { IOReturn res; char state; res = (*cdInterface)->GetTrayState(cdInterface, &state); if (kIOReturnSuccess == res) { if (kMMCDeviceTrayOpen == state) return true; } } } #endif return true; } /* opens tray if no media is in the device */ void TrayOpen() { if (TrayIsOpen()) return; if (cdInterface) { if ((*cdInterface)->SetTrayState) { (*cdInterface)->SetTrayState(cdInterface, kMMCDeviceTrayOpen); } } } long openDisc() { mach_port_t masterPort = NULL; kern_return_t kernResult; io_iterator_t mediaIterator; io_service_t media; CFTypeRef toc_cf; //dk_cd_read_disc_info_t cd_read_disc_info; //dk_cd_read_track_info_t cd_read_track_info; //CDDiscInfo di; CDTrackInfo ti; u_int16_t speed; int i; CD.cd = 0; CD.status = 0x10; // Find 1st CD kernResult = FindEjectableCDMedia( &mediaIterator, &masterPort ); if ( kernResult != KERN_SUCCESS ) return -1; media = IOIteratorNext( mediaIterator ); if (NULL == media) return -1; // Release the iterator. IOObjectRelease( mediaIterator ); // Get The device path kernResult = GetDeviceFilePath( media, deviceFilePath, sizeof( deviceFilePath ) ); if ( kernResult != KERN_SUCCESS ) return -1; toc_cf = IORegistryEntryCreateCFProperty(media,CFSTR(kIOCDMediaTOCKey),kCFAllocatorDefault,0); if(toc_cf != nil) { CDTOC *toc = (CDTOC *)CFDataGetBytePtr(toc_cf); int ndesc; ndesc = CDTOCGetDescriptorCount(toc); if (CD.tl) free(CD.tl); CD.tl = calloc(ndesc, sizeof(Track)); if (NULL == CD.tl) return -1; CD.numtracks = 0; for (i=0; idescriptors[i]; if(desc->point < 100) { CD.tl[CD.numtracks].type = ((desc->control & 0x0f) != 0) ? Mode2 : Audio; // TODO: set correct type CD.tl[CD.numtracks].num = desc->point; CD.tl[CD.numtracks].start[2] = desc->p.frame; CD.tl[CD.numtracks].start[1] = desc->p.second; CD.tl[CD.numtracks].start[0] = desc->p.minute; if (CD.numtracks) { CD.tl[CD.numtracks-1].end[2] = desc->p.frame; CD.tl[CD.numtracks-1].end[1] = desc->p.second; CD.tl[CD.numtracks-1].end[0] = desc->p.minute; } //normalizeTime(CD.tl[0].start); //normalizeTime(CD.tl[0].end); CD.numtracks++; } } CDMSF end = CDConvertTrackNumberToMSF(0xa2, toc); CD.tl[CD.numtracks-1].end[2] = end.frame; CD.tl[CD.numtracks-1].end[1] = end.second; CD.tl[CD.numtracks-1].end[0] = end.minute; CFRelease(toc_cf); } else { printf("failed to read cdrom toc information\n"); IOObjectRelease( mediaIterator ); return -1; } IOObjectRelease( media ); // Free master port if we created one. if (masterPort) mach_port_deallocate(mach_task_self(), masterPort); // Now open it CD.cd = open(deviceFilePath, O_RDONLY, 0); if (CD.cd <= 0) { perror("failed to open cd: "); CD.cd = 0; return -1; } // get number of tracks /* memset(&cd_read_disc_info, 0, sizeof(dk_cd_read_disc_info_t)); cd_read_disc_info.bufferLength = sizeof(CDDiscInfo); cd_read_disc_info.buffer = &di; if (ioctl(CD.cd, DKIOCCDREADDISCINFO, &cd_read_disc_info) < 0) { perror("error reading cd info: "); CD.numtracks = 1; } else { CD.numtracks = di.lastTrackNumberInLastSessionLSB-di.firstTrackNumberInLastSessionLSB+1; } CD.tl = calloc(CD.numtracks, sizeof(Track)); for (i=0; i return total length of cd // otherwise return start in bcd time format long getTD(int track, unsigned char* buffer) { // lasttrack just keeps track of which track TD was requested last (go fig) if (track > getNumTracks()) { // printf("getTD bad %2d\n", track); return -1; } if (track == 0) { buffer[0] = CD.tl[CD.numtracks-1].end[0]; buffer[1] = CD.tl[CD.numtracks-1].end[1]; buffer[2] = CD.tl[CD.numtracks-1].end[2]; } else { buffer[0] = CD.tl[track-1].start[0]; buffer[1] = CD.tl[track-1].start[1]; buffer[2] = CD.tl[track-1].start[2]; } // printf("getTD %2d %02d:%02d:%02d\n", track, (int)buffer[0], // (int)buffer[1], (int)buffer[2]); // bcd encode it buffer[0] = intToBCD(buffer[0]); buffer[1] = intToBCD(buffer[1]); buffer[2] = intToBCD(buffer[2]); // SysPrintf("end getTD()\r\n"); return 0; } // return the sector address - the buffer address + 12 bytes for subheader offset. unsigned char* getSector(int subchannel) { SysPrintf("getSector()\n"); if (readThread) { int err; // wait until we can obtain a lock */ err = pthread_mutex_lock(&readMutex); if (err != 0) { printf("failed to lock mutex, error = %i\n", err); } else { err = pthread_mutex_unlock(&readMutex); if (err != 0) { printf("failed to unlock mutex, error = %i\n", err); } } } if (CD.sector == -1) return NULL; else { return CD.buffer /*+ (CD.sector - CD.bufferPos)*/ + ((subchannel) ? 0 : 12); } } // returns the number of tracks char getNumTracks() { // SysPrintf("start getNumTracks()\r\n"); // if there's no open cd, return -1 if (CD.cd == 0) { return -1; } // printf("numtracks %d\n",CD.numtracks); // SysPrintf("end getNumTracks()\r\n"); return CD.numtracks; } // read the sector pointed to by pos int readSector(off_t pos, unsigned char *buffer) { int len; SysPrintf("start readit()\n"); if (0 == CD.cd) return 0; // go to the sector pos = lseek(CD.cd, pos, SEEK_SET); if (pos < 0) goto error; // and read it into the buffer len = read(CD.cd, buffer, 2352); if (len < 2352) goto error; SysPrintf("end readit()\n"); return 2352; error: perror("CD read error"); return 0; } void seekSector(const unsigned char m, const unsigned char s, const unsigned char f) { int err; SysPrintf("start seekSector()\n"); if (0 == CD.cd) return; if (readThread) { // wait until we're done reading err = pthread_mutex_lock(&readMutex); if (err != 0) { printf("failed to lock mutex, error = %i\n", err); } else { // calc byte to search for CD.sector = (( (m * 60) + (s - 2)) * 75 + f) * 2352; // unlock again, since the mutex won't be set until // we signal a cond later in this function err = pthread_mutex_unlock(&readMutex); if (err != 0) { printf("failed to unlock mutex, error = %i\n", err); } } } else { // calc byte to search for CD.sector = (( (m * 60) + (s - 2)) * 75 + f) * 2352; } // printf("seek %d %02d:%02d:%02d",CD.sector, (int)m, (int)s, (int)f); // is it cached? #if 0 if ((CD.sector >= CD.bufferPos) && (CD.sector < (CD.bufferPos + CD.bufferSize)) ) { // printf(" cached %d %d\n",CD.sector - CD.bufferPos,BUFFER_SIZE); // SysPrintf("end seekSector()\r\n"); return; } // not cached - read a few blocks into the cache else #endif { if (readThread) { SysPrintf("end seekSector()\n"); // signal that a new sector is ready to be read if (pthread_cond_broadcast(&readCond) == 0) return; printf("failed to signal 'readCond'\n"); } CD.bufferSize = readSector(CD.sector, CD.buffer); if (CD.bufferSize==0) CD.sector = -1; else CD.bufferPos = CD.sector; } SysPrintf("end seekSector()\n"); } /* handles reading from the cd */ void *read_thread(void *arg) { struct timespec dT = { 20, 0 }; // 20 s //struct timespec dT = { 0, 400*1000*1000 }; // 400 ms int err; pthread_mutex_lock(&readMutex); //pthread_cleanup_push(pthread_exit, 0); for (;;) { // wait until we're signalled if (NoIdleSleep) { err = pthread_cond_timedwait_relative_np(&readCond, &readMutex, &dT); pthread_testcancel(); } else { err = pthread_cond_wait(&readCond, &readMutex); } if (err == EINVAL) { printf("failed cond wait for 'readCond', error = %i\n", err); return (void *)-1; } CD.bufferSize = readSector(CD.sector, CD.buffer); if (CD.bufferSize==0) CD.sector = -1; else CD.bufferPos = CD.sector; } //pthread_cleanup_pop(1); return 0; } long CDRopen(void) { SysPrintf("CDR_open()\n"); if (UseMultiThreaded && !readThread) { int err; err = pthread_cond_init(&readCond, NULL); if (err!=0) { printf("failed to create conditional, error=%i\n" "going to single thread mode\n", err); } else { err = pthread_mutex_init(&readMutex, NULL); if (err!=0) { printf("failed to create mutex, error=%i\n" "going to single thread mode\n", err); } else { struct sched_param params; int policy; err = pthread_create(&readThread, NULL, read_thread, NULL); if (err!=0) { printf("failed to create read thread, error=%i\n" "going to single thread mode\n", err); } else { // set the thread to maximum priority pthread_getschedparam(readThread, &policy, ¶ms); params.sched_priority = sched_get_priority_max(policy); pthread_setschedparam(readThread, policy, ¶ms); } } } } if (openDisc() < 0) { TrayOpen(); return 0; } seekSector(0,2,0); SysPrintf("end CDR_open()\n"); return 0; } long CDRinit(void) { #ifdef USE_DEVICE_INTERFACE return GetCDROMServices(); #endif return 0; } long CDRshutdown(void) { // do cleanup CDRclose(); return 0; } long CDRclose(void) { SysPrintf("start CDR_close()\n"); if (!CD.cd) return 0; if (readThread) { int termVal = 0; // make sure we're done reading pthread_mutex_lock(&readMutex); pthread_mutex_unlock(&readMutex); // kill read thread pthread_cancel(readThread); //pthread_kill(readThread, SIGTERM); pthread_cond_broadcast(&readCond); pthread_join(readThread, (void **)&termVal); // remove fluff pthread_mutex_destroy(&readMutex); pthread_cond_destroy(&readCond); readThread = 0; } close(CD.cd); CD.cd = 0; SysPrintf("end CDR_close()\n"); return 0; } long CDRgetTN(unsigned char *buffer) { //printf("start CDRgetTN()\n"); return getTN(buffer); } long CDRgetTD(unsigned char track, unsigned char *buffer) { unsigned char temp[3]; int result = getTD((int)track, temp); // printf("start CDRgetTD()\n"); if (result == -1) return -1; buffer[1] = temp[1]; buffer[2] = temp[0]; return 0; } /* called when the psx requests a read */ long CDRreadTrack(unsigned char *time) { // SysPrintf("start CDR_readTrack()\r\n"); //printf("readTrack at %02d:%02d:%02d\n", BCDToInt(time[0]), BCDToInt(time[1]), BCDToInt(time[2])); if (CD.cd != 0) seekSector(BCDToInt(time[0]), BCDToInt(time[1]), BCDToInt(time[2])); // SysPrintf("end CDR_readTrack()\r\n"); return PSE_CDR_ERR_SUCCESS; } /* called after the read should be finished, and the data is needed */ unsigned char *CDRgetBuffer(void) { //printf("start CDR_getBuffer()\n"); // SysPrintf("start CDR_getBuffer()\r\n"); if (CD.cd == 0) return NULL; return getSector(0); } unsigned char *CDRgetBufferSub(void) { //printf("start CDR_getBuffer()\n"); // SysPrintf("start CDR_getBuffer()\r\n"); return getSector(1); } /* from PSX manual p. 83 */ #define CDR_STATUS_UNKNOWN 0x00 #define CDR_STATUS_ERROR 0x02 /* command error detected */ #define CDR_STATUS_STANDBY 0x04 /* spindle motor rotating */ #define CDR_STATUS_SEEK_ERROR 0x08 /* seek error detected */ #define CDR_STATUS_SHELL_OPEN 0x10 /* once shell open */ #define CDR_STATUS_READING 0x20 /* reading data sectors */ #define CDR_STATUS_SEEKING 0x40 #define CDR_STATUS_PLAYING 0x80 /* playing CD-DA */ // reads cdr status - from old plugin // type: // 0x00 - unknown // 0x01 - data // 0x02 - audio // 0xff - no cdrom // status: (only shell open supported) // 0x00 - unknown // 0x01 - error // 0x04 - seek error -> no disk??? // 0x10 - shell open -> tray open // 0x20 - reading // 0x40 - seeking // 0x80 - playing // time: // byte 0 - minute // byte 1 - second // byte 2 - frame long CDRgetStatus(struct CdrStat *stat) { if (CD.cd == 0) { // no cd - check for disc if (openDisc() < 0) { if (TrayIsOpen()) { stat->Type = 0xff;//0x00; stat->Status |= CDR_STATUS_SHELL_OPEN; } else { TrayOpen(); stat->Type = 0xff; // indicates no cd stat->Status = CDR_STATUS_UNKNOWN; } stat->Time[0] = stat->Time[1] = stat->Time[2] = 0; return 0; } } if (CD.tl[0].type == Mode1 || CD.tl[0].type == Mode2) { stat->Type = 0x01; stat->Status = CDR_STATUS_UNKNOWN; } else if (CD.tl[0].type == Audio) { stat->Type = 0x02; stat->Status = CDR_STATUS_UNKNOWN; // FIXME } else { stat->Type = 0x00; stat->Status = CDR_STATUS_UNKNOWN; // FIXME } stat->Time[0] = stat->Time[1] = stat->Time[2] = 0; // FIXME return 0; } char *CDRgetDriveLetter(void) { //printf("start CDR_getBuffer()\n"); // SysPrintf("start CDR_getBuffer()\r\n"); return deviceFilePath; } #if 0 AudioFilePlayID cdFilePlayID; long CDRplay(unsigned char *sector) { // TODO: find the correct track... NewAudioFilePlayID(&fsref, &cdFilePlayID); return 0; } long CDRstop(void) { return 0; } #endif /* long (CALLBACK* CDRconfigure)(void); long (CALLBACK* CDRtest)(void); void (CALLBACK* CDRabout)(void); */ #ifdef TEST int main (int argc, int *argv) { CDRopen(); CDRclose(); return 0; } #endif