#include <IOKit/IOLib.h>
#include <IOKit/IOReturn.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/scsi/IOSCSIDeviceInterface.h>
#include <IOKit/storage/scsi/IOSCSICDDrive.h>
#include <IOKit/storage/scsi/IOSCSICDDriveNub.h>
#define super IOSCSIHDDrive
OSDefineMetaClassAndStructors(IOSCSICDDrive,IOSCSIHDDrive)
static void __inline ConvertBCDToHex(UInt8 *value)
{
*value = (((*value) >> 4) * 10) + ((*value) & 0x0f);
}
void
IOSCSICDDrive_gc_glue(IOService *object,void *param)
{
IOSCSICDDrive *self;
struct IOBasicSCSI::context *cx;
self = (IOSCSICDDrive *)object;
cx = (struct IOBasicSCSI::context *)param;
self->genericCompletion(cx);
}
IOReturn
IOSCSICDDrive::audioPause(bool pause)
{
struct context *cx;
SCSICDBInfo scsiCmd;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
bzero(&scsiCmd, sizeof(scsiCmd));
scsiCmd.cdbLength = 10;
scsiCmd.cdb[0] = kIOSCSICommandPauseResume;
scsiCmd.cdb[8] = pause ? 0x00 : 0x01;
cx->scsireq->setCDB(&scsiCmd);
cx->scsireq->setPointers(cx->memory, 0, false);
cx->scsireq->setPointers(cx->senseDataDesc, 255, false, true);
cx->scsireq->setTimeout(5000);
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
IOReturn
IOSCSICDDrive::audioPlay(CDMSF timeStart,CDMSF timeStop)
{
return(doAudioPlayCommand(timeStart,timeStop));
}
IOReturn
IOSCSICDDrive::audioScan(CDMSF timeStart,bool reverse)
{
struct context *cx;
SCSICDBInfo scsiCmd;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
bzero(&scsiCmd, sizeof(scsiCmd));
scsiCmd.cdbLength = 10;
scsiCmd.cdb[0] = 0xCD;
scsiCmd.cdb[1] = reverse ? 0x10 : 0x00;
scsiCmd.cdb[3] = timeStart.minute;
scsiCmd.cdb[4] = timeStart.second;
scsiCmd.cdb[5] = timeStart.frame;
scsiCmd.cdb[9] = 0x40;
cx->scsireq->setCDB(&scsiCmd);
cx->scsireq->setPointers(cx->memory, 0, false);
cx->scsireq->setPointers(cx->senseDataDesc, 255, false, true);
cx->scsireq->setTimeout(5000);
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
IOReturn
IOSCSICDDrive::audioStop()
{
struct context *cx;
SCSICDBInfo scsiCmd;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
bzero(&scsiCmd, sizeof(scsiCmd));
scsiCmd.cdbLength = 6;
scsiCmd.cdb[0] = 0x01;
cx->scsireq->setCDB(&scsiCmd);
cx->scsireq->setPointers(cx->memory, 0, false);
cx->scsireq->setPointers(cx->senseDataDesc, 255, false, true);
cx->scsireq->setTimeout(5000);
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
bool
IOSCSICDDrive::deviceTypeMatches(UInt8 inqBuf[],UInt32 inqLen,SInt32 *score)
{
if ((inqBuf[0] & 0x1f) == kIOSCSIDeviceTypeCDROM) {
*score = 0;
return(true);
} else {
return(false);
}
}
IOReturn
IOSCSICDDrive::doAsyncReadCD(IOMemoryDescriptor *buffer,
UInt32 block,UInt32 nblks,
CDSectorArea sectorArea,
CDSectorType sectorType,
IOStorageCompletion completion)
{
struct context *cx;
SCSICDBInfo scsiCmd;
assert(buffer->getDirection() == kIODirectionIn);
bzero(&scsiCmd, sizeof(scsiCmd));
if (sectorArea == kCDSectorAreaUser) {
if (sectorType == kCDSectorTypeCDDA) {
scsiCmd.cdbLength = 12;
scsiCmd.cdb[ 0] = 0xD8;
scsiCmd.cdb[ 2] = (block >> 24) & 0xFF;
scsiCmd.cdb[ 3] = (block >> 16) & 0xFF;
scsiCmd.cdb[ 4] = (block >> 8) & 0xFF;
scsiCmd.cdb[ 5] = (block ) & 0xFF;
scsiCmd.cdb[ 6] = (nblks >> 24) & 0xFF;
scsiCmd.cdb[ 7] = (nblks >> 16) & 0xFF;
scsiCmd.cdb[ 8] = (nblks >> 8) & 0xFF;
scsiCmd.cdb[ 9] = (nblks ) & 0xFF;
} else if (sectorType == kCDSectorTypeMode1 ||
sectorType == kCDSectorTypeMode2Form1) {
return doAsyncReadWrite(buffer,block,nblks,completion);
}
}
if (scsiCmd.cdbLength == 0) {
return(kIOReturnUnsupported);
}
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
buffer->retain();
cx->memory = buffer;
cx->completion = completion;
cx->state = kAsyncReadWrite;
cx->scsireq->setCallback(this, (CallbackFn)IOSCSICDDrive_gc_glue, cx);
cx->scsireq->setCDB(&scsiCmd);
cx->scsireq->setPointers(buffer, buffer->getLength(), false);
cx->scsireq->setPointers(cx->senseDataDesc, 255, false, true);
cx->scsireq->setTimeout(60000);
queueCommand(cx,kAsync,getReadWritePowerState());
return(kIOReturnSuccess);
}
IOReturn
IOSCSICDDrive::doAsyncReadWrite(IOMemoryDescriptor *buffer,
UInt32 block,UInt32 nblks,
IOStorageCompletion completion)
{
if (buffer->getDirection() == kIODirectionOut) {
return(kIOReturnNotWritable);
}
return(super::doAsyncReadWrite(buffer,block,nblks,completion));
}
IOReturn
IOSCSICDDrive::doSyncReadWrite(IOMemoryDescriptor *buffer,UInt32 block,UInt32 nblks)
{
if (buffer->getDirection() == kIODirectionOut) {
return(kIOReturnNotWritable);
}
return(super::doSyncReadWrite(buffer,block,nblks));
}
IOReturn
IOSCSICDDrive::doAudioPlayCommand(CDMSF timeStart,CDMSF timeStop)
{
struct context *cx;
struct IOAudioPlayMSFcdb *p;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(scsiCDB) );
p = (struct IOAudioPlayMSFcdb *)scsiCDB.cdb;
p->opcode = kIOSCSICommandPlayAudioMSF;
p->lunbits = 0;
p->reserved1 = 0;
p->start_m = timeStart.minute;
p->start_s = timeStart.second;
p->start_f = timeStart.frame;
p->end_m = timeStop.minute;
p->end_s = timeStop.second;
p->end_f = timeStop.frame;
p->ctlbyte = 0;
scsiCDB.cdbLength = 10;
req->setCDB( &scsiCDB );
req->setPointers(cx->senseDataDesc, 255, false, true);
req->setPointers( cx->memory, 0, false );
req->setTimeout( 5000 );
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
IOReturn
IOSCSICDDrive::doFormatMedia(UInt64 )
{
return(kIOReturnUnsupported);
}
UInt32
IOSCSICDDrive::doGetFormatCapacities(UInt64 * ,UInt32 ) const
{
return(kIOReturnUnsupported);
}
IOReturn
IOSCSICDDrive::doSynchronizeCache(void)
{
return(kIOReturnUnsupported);
}
IOReturn
IOSCSICDDrive::getAudioStatus(CDAudioStatus *status)
{
IOReturn result;
UInt8 *tempBuf;
result = allocateTempBuffer(&tempBuf,16);
if (result != kIOReturnSuccess) {
return(kIOReturnNoMemory);
}
result = readSubChannel(tempBuf,16,IORSCcdb::kCurrentPosition,0);
if (result == kIOReturnSuccess) {
assert(tempBuf[2] == 0);
assert(tempBuf[3] == 12);
assert(tempBuf[4] == 1);
status->status = tempBuf[ 1];
status->position.track.number = tempBuf[ 6];
status->position.track.index = tempBuf[ 7];
status->position.time.minute = tempBuf[ 9];
status->position.time.second = tempBuf[10];
status->position.time.frame = tempBuf[11];
status->position.track.time.minute = tempBuf[13];
status->position.track.time.second = tempBuf[14];
status->position.track.time.frame = tempBuf[15];
}
deleteTempBuffer(tempBuf,16);
return(result);
}
IOReturn
IOSCSICDDrive::getAudioVolume(UInt8 *leftVolume,UInt8 *rightVolume)
{
struct context *cx;
SCSICDBInfo scsiCmd;
IOReturn result;
UInt8 audio_control[28];
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
cx->memory = IOMemoryDescriptor::withAddress(audio_control,
sizeof(audio_control),
kIODirectionIn);
if (cx->memory == NULL) {
deleteContext(cx);
return(kIOReturnNoMemory);
}
bzero(&scsiCmd, sizeof(scsiCmd));
scsiCmd.cdbLength = 6;
scsiCmd.cdb[0] = 0x1a;
scsiCmd.cdb[2] = 0x0e;
scsiCmd.cdb[4] = sizeof(audio_control);
cx->scsireq->setCDB(&scsiCmd);
cx->scsireq->setPointers(cx->memory, sizeof(audio_control), true);
cx->scsireq->setPointers(cx->senseDataDesc, 255, false, true);
cx->scsireq->setTimeout(5000);
result = simpleSynchIO(cx);
if (result == kIOReturnSuccess) {
assert((audio_control[ 0] ) == 28-1);
assert((audio_control[ 3] ) == 0x08);
assert((audio_control[12] & 0x3f) == 0x0e);
assert((audio_control[13] ) == 0x0e);
*leftVolume = audio_control[21];
*rightVolume = audio_control[23];
}
deleteContext(cx);
return(result);
}
const char *
IOSCSICDDrive::getDeviceTypeName(void)
{
return(kIOBlockStorageDeviceTypeCDROM);
}
bool
IOSCSICDDrive::init(OSDictionary * properties)
{
return(super::init(properties));
}
IOService *
IOSCSICDDrive::instantiateNub(void)
{
IOService *nub;
nub = new IOSCSICDDriveNub;
return(nub);
}
void
IOSCSICDDrive::mediaArrived(void)
{
}
void
IOSCSICDDrive::mediaGone(void)
{
}
IOReturn
IOSCSICDDrive::readISRC(UInt8 track,CDISRC isrc)
{
IOReturn result;
UInt8 *tempBuf;
result = allocateTempBuffer(&tempBuf,24);
if (result != kIOReturnSuccess) {
return(kIOReturnNoMemory);
}
result = readSubChannel(tempBuf,24,IORSCcdb::kISRC,track);
if (result == kIOReturnSuccess) {
assert(tempBuf[2] == 0);
assert(tempBuf[3] == 20);
assert(tempBuf[4] == 3);
if ((tempBuf[8] & 0x80)) {
bcopy(&tempBuf[9],isrc,kCDISRCMaxLength);
isrc[kCDISRCMaxLength] = '\0';
} else {
result = kIOReturnNotFound;
}
}
deleteTempBuffer(tempBuf,24);
return(result);
}
IOReturn
IOSCSICDDrive::readMCN(CDMCN mcn)
{
IOReturn result;
UInt8 *tempBuf;
result = allocateTempBuffer(&tempBuf,24);
if (result != kIOReturnSuccess) {
return(kIOReturnNoMemory);
}
result = readSubChannel(tempBuf,24,IORSCcdb::kMCN,0);
if (result == kIOReturnSuccess) {
assert(tempBuf[2] == 0);
assert(tempBuf[3] == 20);
assert(tempBuf[4] == 2);
if ((tempBuf[8] & 0x80)) {
bcopy(&tempBuf[9],mcn,kCDMCNMaxLength);
mcn[kCDMCNMaxLength] = '\0';
} else {
result = kIOReturnNotFound;
}
}
deleteTempBuffer(tempBuf,24);
return(result);
}
IOReturn
IOSCSICDDrive::readSubChannel(UInt8 *buffer,UInt32 length,UInt8 dataFormat,UInt8 trackNumber)
{
struct context *cx;
struct IORSCcdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(scsiCDB) );
bzero(buffer,length);
c = (struct IORSCcdb *)(scsiCDB.cdb);
c->opcode = kIOSCSICommandReadSubChannel;
c->lunbits = 0;
c->lunbits |= IORSCcdb::kMSF;
c->subq = IORSCcdb::kSubq;
c->dataformat = dataFormat;
c->track = trackNumber;
c->reserved1 = 0;
c->reserved2 = 0;
c->len_hi = length >> 8;
c->len_lo = length & 0xff;
c->ctlbyte = 0;
scsiCDB.cdbLength = 10;
req->setCDB( &scsiCDB );
req->setPointers(cx->senseDataDesc, 255, false, true);
cx->memory = IOMemoryDescriptor::withAddress((void *)buffer,
length,
kIODirectionIn);
req->setPointers( cx->memory, length, false );
req->setTimeout( 5000 );
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
IOReturn
IOSCSICDDrive::readTOC(IOMemoryDescriptor *buffer)
{
struct context *cx;
struct IOReadToccdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(scsiCDB) );
c = (struct IOReadToccdb *)scsiCDB.cdb;
c->opcode = kIOSCSICommandReadTOC;
c->lunbits = IOReadToccdb::kMSF;
c->reserved1 = 0;
c->reserved2 = 0;
c->reserved3 = 0;
c->reserved4 = 0;
c->start_trk_session = 0;
c->len_hi = buffer->getLength() >> 8;
c->len_lo = buffer->getLength() & 0xff;
c->ctlbyte = IOReadToccdb::kFullTOC << 6;
scsiCDB.cdbLength = 10;
req->setCDB( &scsiCDB );
req->setPointers(cx->senseDataDesc, 255, false, true);
cx->memory = buffer;
req->setPointers( cx->memory, cx->memory->getLength(), false );
req->setTimeout( 5000 );
result = simpleSynchIO(cx);
deleteContext(cx);
#ifdef HOLLYWOOD_BCD_TO_HEX_SUPPORT
IOByteCount tocMaxSize;
CDTOC *toc = buffer->getVirtualSegment(0, &tocMaxSize);
if (toc && tocMaxSize >= sizeof(UInt32)) {
UInt32 count = (tocMaxSize - sizeof(UInt32)) / sizeof(CDTOCDescriptor);
for (UInt32 index = 0; index < count; index++) {
if (toc->descriptors[index].point <= 0x99) {
ConvertBCDToHex(&toc->descriptors[index].point);
}
if ((toc->descriptors[index].point & 0xf0) == 0xb0) {
ConvertBCDToHex(&toc->descriptors[index].address.minute);
ConvertBCDToHex(&toc->descriptors[index].address.second);
ConvertBCDToHex(&toc->descriptors[index].address.frame);
ConvertBCDToHex(&toc->descriptors[index].zero);
}
if ( toc->descriptors[index].point <= 0x99 ||
( toc->descriptors[index].point >= 0xa0 &&
toc->descriptors[index].point <= 0xc0 ) ) {
ConvertBCDToHex(&toc->descriptors[index].p.minute);
if (toc->descriptors[index].point != 0xa0) {
ConvertBCDToHex(&toc->descriptors[index].p.second);
}
ConvertBCDToHex(&toc->descriptors[index].p.frame);
}
}
}
#endif HOLLYWOOD_BCD_TO_HEX_SUPPORT
return(result);
}
IOReturn
IOSCSICDDrive::reportMaxWriteTransfer(UInt64 ,UInt64 * )
{
return(0);
}
IOReturn
IOSCSICDDrive::reportMediaState(bool *mediaPresent,bool *changed)
{
IOReturn result;
result = super::reportMediaState(mediaPresent,changed);
if (result != kIOReturnSuccess) {
IOLog("%s[IOSCSICDDrive]::reportMediaState; result=%s, changed = %s, present = %s\n",
getName(),stringFromReturn(result),*changed ? "Y" : "N", *mediaPresent ? "Y" : "N");
}
if ((result == kIOReturnSuccess) && *changed) {
if (*mediaPresent) {
mediaArrived();
} else {
mediaGone();
}
}
return(result);
}
IOReturn
IOSCSICDDrive::reportWriteProtection(bool *isWriteProtected)
{
*isWriteProtected = true;
return(kIOReturnSuccess);
}
IOReturn
IOSCSICDDrive::setAudioVolume(UInt8 leftVolume,UInt8 rightVolume)
{
struct context *cx;
SCSICDBInfo scsiCmd;
IOReturn result;
UInt8 audio_control[28];
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
cx->memory = IOMemoryDescriptor::withAddress(audio_control,
sizeof(audio_control),
kIODirectionIn);
if (cx->memory == NULL) {
deleteContext(cx);
return(kIOReturnNoMemory);
}
bzero(&scsiCmd, sizeof(scsiCmd));
scsiCmd.cdbLength = 6;
scsiCmd.cdb[0] = 0x1a;
scsiCmd.cdb[2] = 0x0e;
scsiCmd.cdb[4] = sizeof(audio_control);
cx->scsireq->setCDB(&scsiCmd);
cx->scsireq->setPointers(cx->memory, sizeof(audio_control), true);
cx->scsireq->setPointers(cx->senseDataDesc, 255, false, true);
cx->scsireq->setTimeout(5000);
result = simpleSynchIO(cx);
if (result == kIOReturnSuccess) {
assert((audio_control[ 0] ) == 28-1);
assert((audio_control[ 3] ) == 0x08);
assert((audio_control[12] & 0x3f) == 0x0e);
assert((audio_control[13] ) == 0x0e);
audio_control[21] = audio_control[25] = leftVolume;
audio_control[23] = audio_control[27] = rightVolume;
deleteContext(cx);
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
cx->memory = IOMemoryDescriptor::withAddress(audio_control,
sizeof(audio_control),
kIODirectionOut);
if (cx->memory == NULL) {
deleteContext(cx);
return(kIOReturnNoMemory);
}
bzero(&scsiCmd, sizeof(scsiCmd));
scsiCmd.cdbLength = 6;
scsiCmd.cdb[0] = 0x15;
scsiCmd.cdb[4] = sizeof(audio_control);
cx->scsireq->setCDB(&scsiCmd);
cx->scsireq->setPointers(cx->memory, sizeof(audio_control), true);
cx->scsireq->setPointers(cx->senseDataDesc, 255, false, true);
cx->scsireq->setTimeout(5000);
result = simpleSynchIO(cx);
}
deleteContext(cx);
return(result);
}