/* * Native implementation of cdrdao's SCSI interface for Mac OS X. * Copyright (C) by Edgar Fuß, Bonn, July 2007. * Do with this whatever you like, as long as you are either me or you keep * this message intact and both * - acknowledge that I wrote it for cdrdao in the first place, and * - don't blame me if it doesn't do what you like or expect. * These routines do exactly what they do. If that's not what you expect them * or would like them to do, don't complain with me, the cdrdao project, my * neighbour's brother-in-law or anybody else, but rewrite them to your taste. */ /* standard includes */ #include #include #include /* cdrdao specific includes and prototype */ #include "ScsiIf.h" #include "trackdb/util.h" extern void message(int level, const char *fmt, ...); #include "decodeSense.cc" /* Mac OS X specific includes */ #include #include #include #include #include class ScsiIfImpl { public: int num_; /* number of device for compatibility mode */ char *path_; /* native (IO registry) pathname of device */ io_object_t object_; IOCFPlugInInterface **plugin_; MMCDeviceInterface **mmc_; SCSITaskDeviceInterface **scsi_; int exclusive_; long timeout_; /* in ms */ char *error_; /* sendCmd() internal error string */ SCSIServiceResponse response_; SCSITaskStatus status_; struct SCSI_Sense_Data sense_; }; ScsiIf::ScsiIf(const char *name) { int len; int bus, targ, lun, count; impl_ = new ScsiIfImpl; impl_->num_ = 0; impl_->path_ = NULL; len = strlen(name); if (len) { if (isdigit(name[0])) { /* Compatibility mode. Just add bus+targ+lun */ if (sscanf(name, "%i,%i,%i%n", &bus, &targ, &lun, &count) == 3 && count == len) { if ((bus >= 0) && (targ >= 0) && (lun >= 0)) impl_->num_ = 1 + bus + targ + lun; } } else { /* Native mode. Take name as IOreg path */ impl_->path_ = strdupCC(name); } } impl_->object_ = 0; impl_->plugin_ = NULL; impl_->mmc_ = NULL; impl_->scsi_ = NULL; impl_->exclusive_ = 0; impl_->timeout_ = 10*1000; impl_->error_ = NULL; vendor_[0] = 0; product_[0] = 0; revision_[0] = 0; maxDataLen_ = 64*1024; /* XXX */ } ScsiIf::~ScsiIf() { if (impl_->scsi_) { if (impl_->exclusive_) (*impl_->scsi_)->ReleaseExclusiveAccess(impl_->scsi_); (*impl_->scsi_)->Release(impl_->scsi_); } if (impl_->mmc_) (*impl_->mmc_)->Release(impl_->mmc_); if (impl_->plugin_) IODestroyPlugInInterface(impl_->plugin_); if (impl_->object_) IOObjectRelease(impl_->object_); if (impl_->path_ != NULL) delete[] impl_->path_; if (impl_->error_ != NULL) delete[] impl_->error_; delete impl_; } int ScsiIf::init() { CFMutableDictionaryRef dict = NULL; CFMutableDictionaryRef sub = NULL; io_iterator_t iterator = 0; kern_return_t err; SInt32 score; HRESULT herr; int i; if (impl_->num_) { /* * Compatibility mode. * Build dictionaries to search for num_'th device having the * authoring property using an IO iterator. */ dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL); sub = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL); CFDictionarySetValue(sub, CFSTR(kIOPropertySCSITaskDeviceCategory), CFSTR(kIOPropertySCSITaskAuthoringDevice)); CFDictionarySetValue(dict, CFSTR(kIOPropertyMatchKey), sub); IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator); if (!iterator) message(-2, "init: no iterator"); if (iterator) { i = impl_->num_; do { impl_->object_ = IOIteratorNext(iterator); i--; } while (i && impl_->object_); IOObjectRelease(iterator); } } else if (impl_->path_) { /* Native mode. Just use the IO Registry pathname */ impl_->object_ = IORegistryEntryFromPath(kIOMasterPortDefault, impl_->path_); } /* Strange if (!x) ... if (x) style so you can #ifdef out the !x part */ if (!impl_->object_) message(-2, "init: no object"); if (impl_->object_) { /* Get intermediate (IOCFPlugIn) plug-in for MMC device */ err = IOCreatePlugInInterfaceForService(impl_->object_, kIOMMCDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &impl_->plugin_, &score); if (err != noErr) message(-2, "init: IOCreatePlugInInterfaceForService failed: %d", err); } if (!impl_->plugin_) message(-2, "init: no plugin"); if (impl_->plugin_) { /* Get the MMC interface (MMCDeviceInterface) */ herr = (*impl_->plugin_)->QueryInterface(impl_->plugin_, CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID), /* * Most of Apple's examples erroneously cast to LPVOID, * not LPVOID *. */ (LPVOID *)&impl_->mmc_); if (herr != S_OK) message(-2, "init: QueryInterface failed: %d", herr); } if (!impl_->mmc_) message(-2, "init: no mmc"); if (impl_->mmc_) { /* Get the SCSI interface */ impl_->scsi_ = (*impl_->mmc_)->GetSCSITaskDeviceInterface(impl_->mmc_); } if (!impl_->scsi_) message(-2, "init: no scsi"); if (impl_->scsi_) { /* Obtain exclusive access to device */ err = (*impl_->scsi_)->ObtainExclusiveAccess(impl_->scsi_); if (err != noErr) message(-2, "init: ObtainExclusiveAccess failed: %d", err); if (err == noErr) { impl_->exclusive_ = 1; /* Send SCSI inquiry command */ i = inquiry(); if (i != 0) message(-2, "init: inquiry failed: %d", i); return (i == 0) ? 0 : 2; } } message(-2, "init: failed"); return 1; } int ScsiIf::timeout(int t) { int ret = impl_->timeout_/1000; impl_->timeout_ = t*1000; return ret; } int ScsiIf::sendCmd(const unsigned char *cmd, int cmdLen, const unsigned char *dataOut, int dataOutLen, unsigned char *dataIn, int dataInLen, int showMessage) { SCSITaskInterface **task; IOVirtualRange range; IOReturn ret; UInt64 len; if (impl_->error_ != NULL) { delete[] impl_->error_; impl_->error_ = NULL; } #define ERROR(msg) do {\ impl_->error_ = new char[9 + strlen(msg) + 1];\ strcpy(impl_->error_, "sendCmd: ");\ strcat(impl_->error_, msg);\ if (showMessage) printError();\ if (task) (*task)->Release(task);\ return 1;\ } while(0) task = (*impl_->scsi_)->CreateSCSITask(impl_->scsi_); if (!task) ERROR("no task"); ret = (*task)->SetCommandDescriptorBlock(task, (UInt8 *)cmd, cmdLen); if (ret != kIOReturnSuccess) ERROR("SetCommandDescriptorBlock failed"); /* The OSX SCSI interface can't deal with two data phases */ if (dataIn && dataOut) ERROR("dataIn && dataOut"); if (dataIn) { range.address = (IOVirtualAddress)dataIn; range.length = dataInLen; ret = (*task)->SetScatterGatherEntries(task, &range, 1, dataInLen, kSCSIDataTransfer_FromTargetToInitiator); } else if (dataOut) { range.address = (IOVirtualAddress)dataOut; range.length = dataOutLen; ret = (*task)->SetScatterGatherEntries(task, &range, 1, dataOutLen, kSCSIDataTransfer_FromInitiatorToTarget); } else { /* Just to make sure. We pass in zero ranges anyway */ range.address = (IOVirtualAddress)NULL; range.length = 0; ret = (*task)->SetScatterGatherEntries(task, &range, 0, 0, kSCSIDataTransfer_NoDataTransfer); } if (ret != kIOReturnSuccess) ERROR("SetScatterGatherEntries failed"); ret = (*task)->SetTimeoutDuration(task, impl_->timeout_); if (ret != kIOReturnSuccess) ERROR("SetTimeoutDuration failed"); ret = (*task)->ExecuteTaskSync(task, &impl_->sense_, &impl_->status_, &len); if (ret != kIOReturnSuccess) ERROR("ExecuteTaskSync failed"); ret = (*task)->GetSCSIServiceResponse(task, &impl_->response_); if (ret != kIOReturnSuccess) ERROR("GetSCSIServiceResponse failed"); (*task)->Release(task); if (impl_->response_ == kSCSIServiceResponse_TASK_COMPLETE) { if (impl_->status_ == kSCSITaskStatus_GOOD) return 0; if (impl_->status_ == kSCSITaskStatus_CHECK_CONDITION) return 2; } if (impl_->response_ == kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE) return 1; return 1 /* XXX This shouldn't happen */; #undef ERROR } const unsigned char *ScsiIf::getSense(int &len) const { len = kSenseDefaultSize; return (unsigned char *)&impl_->sense_; } void ScsiIf::printError() { char *s; if (impl_->error_) /* Internal error in sendCmd(). We saved a message string. */ s = impl_->error_; else switch (impl_->response_) { case kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE: /* The SCSI command didn't complete */ switch (impl_->status_) { case kSCSITaskStatus_TaskTimeoutOccurred: s = "task timeout"; break; case kSCSITaskStatus_ProtocolTimeoutOccurred: s = "protocol timeout"; break; case kSCSITaskStatus_DeviceNotResponding: s = "device not responding"; break; case kSCSITaskStatus_DeviceNotPresent: s = "device not present"; break; case kSCSITaskStatus_DeliveryFailure: s = "delivery failure"; break; case kSCSITaskStatus_No_Status: s = "no status"; break; default: s = "failure, unknown status"; break; } break; case kSCSIServiceResponse_TASK_COMPLETE: /* The SCSI command did complete */ switch (impl_->status_) { case kSCSITaskStatus_GOOD: s = "good"; break; case kSCSITaskStatus_CHECK_CONDITION: decodeSense((unsigned char *)&impl_->sense_, sizeof(impl_->sense_)); s = NULL; break; case kSCSITaskStatus_CONDITION_MET: s = "condition met"; break; case kSCSITaskStatus_BUSY: s = "busy"; break; case kSCSITaskStatus_INTERMEDIATE: s = "intermediate"; break; case kSCSITaskStatus_INTERMEDIATE_CONDITION_MET: s = "intermediate, condition met"; break; case kSCSITaskStatus_RESERVATION_CONFLICT: s = "reservation conflict"; break; case kSCSITaskStatus_TASK_SET_FULL: s = "task set full"; break; case kSCSITaskStatus_ACA_ACTIVE: s = "aca active"; break; default: s = "complete, unknown status"; break; } break; default: s = "unknown response"; break; } if (s) message(-2, s); } /* * Internal form or inquiry command. * Used by both inquiry() and scanData(), but with different data. */ int inq(SCSITaskDeviceInterface **scsi, SCSIServiceResponse *response, SCSITaskStatus *status, struct SCSI_Sense_Data *sense, char *vend, char *prod, char *rev) { SCSICmd_INQUIRY_StandardData inq_data; SCSICommandDescriptorBlock cdb; SCSITaskInterface **task; IOVirtualRange range; IOReturn ret; UInt64 len; int i; task = (*scsi)->CreateSCSITask(scsi); if (!task) { message(-2, "inq: no task"); return 1; } bzero(cdb, sizeof(cdb)); cdb[0] = kSCSICmd_INQUIRY; cdb[4] = sizeof(inq_data); ret = (*task)->SetCommandDescriptorBlock(task, cdb, kSCSICDBSize_6Byte); if (ret != kIOReturnSuccess) { message(-2, "inq: SetCommandDescriptorBlock failed: %d", ret); (*task)->Release(task); return 1; } range.address = (IOVirtualAddress)&inq_data; range.length = sizeof(inq_data); ret = (*task)->SetScatterGatherEntries(task, &range, 1, sizeof(inq_data), kSCSIDataTransfer_FromTargetToInitiator); if (ret != kIOReturnSuccess) { message(-2, "inq: SetScatterGatherEntries failed: %d", ret); (*task)->Release(task); return 1; } ret = (*task)->SetTimeoutDuration(task, 1000); if (ret != kIOReturnSuccess) { message(-2, "inq: SetTimeoutDuration failed: %d", ret); (*task)->Release(task); return 1; } ret = (*task)->ExecuteTaskSync(task, sense, status, &len); if (ret != kIOReturnSuccess) { message(-2, "inq: ExecuteTaskSync failed: %d", ret); (*task)->Release(task); return 1; } ret = (*task)->GetSCSIServiceResponse(task, response); if (ret != kIOReturnSuccess) { message(-2, "inq: GetSCSIServiceResponse failed: %d", ret); (*task)->Release(task); return 1; } if (*response != kSCSIServiceResponse_TASK_COMPLETE) { message(-2, "inq: response=%d", *response); (*task)->Release(task); return 1; } if (*status != kSCSITaskStatus_GOOD) { message(-2, "inq: status=%d", *status); (*task)->Release(task); return 1; } (*task)->Release(task); /* Copy vendor/product/revision stripping traiiling spaces */ i = kINQUIRY_VENDOR_IDENTIFICATION_Length; while (i > 0 && inq_data.VENDOR_IDENTIFICATION[i - 1] == ' ') i--; memcpy(vend, inq_data.VENDOR_IDENTIFICATION, i); vend[i] = '\0'; i = kINQUIRY_PRODUCT_IDENTIFICATION_Length; while (i > 0 && inq_data.PRODUCT_IDENTIFICATION[i - 1] == ' ') i--; memcpy(prod, inq_data.PRODUCT_IDENTIFICATION, i); prod[i] = '\0'; i = kINQUIRY_PRODUCT_REVISION_LEVEL_Length; while (i > 0 && inq_data.PRODUCT_REVISION_LEVEL[i - 1] == ' ') i--; memcpy(rev, inq_data.PRODUCT_REVISION_LEVEL, i); rev[i] = '\0'; return 0; } int ScsiIf::inquiry() { return inq(impl_->scsi_, &impl_->response_, &impl_->status_, &impl_->sense_, vendor_, product_, revision_); } #define MAX_SCAN 10 ScsiIf::ScanData *ScsiIf::scan(int *len, char *dev) { ScanData *scanData; CFMutableDictionaryRef dict = NULL; CFMutableDictionaryRef sub = NULL; io_iterator_t iterator = 0; io_object_t object = 0; IOCFPlugInInterface **plugin = NULL; MMCDeviceInterface **mmc = NULL; SCSITaskDeviceInterface **scsi = NULL; SCSIServiceResponse response; SCSITaskStatus status; int exclusive = 0; io_string_t path; kern_return_t err; SInt32 score; HRESULT herr; int ret; int i; /* Ignore dev. We don't support different kinds of busses that way. */ /* Build matching dictionaries to find authoring decices. See init(). */ dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL); sub = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL); CFDictionarySetValue(sub, CFSTR(kIOPropertySCSITaskDeviceCategory), CFSTR(kIOPropertySCSITaskAuthoringDevice)); CFDictionarySetValue(dict, CFSTR(kIOPropertyMatchKey), sub); IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator); if (!iterator) { message(-2, "scan: no iterator"); *len = 0; return NULL; } scanData = new ScanData[MAX_SCAN]; *len = 0; for (i = 0; ; i++) { object = IOIteratorNext(iterator); if (!object) break; if (*len == MAX_SCAN) break; /* Get native (IO Registry) pathname of this device. */ err = IORegistryEntryGetPath(object, kIOServicePlane, path); if (err == noErr) { scanData[*len].dev = strdupCC(path); } /* See init() for a description of the plugin/interface tour. */ err = IOCreatePlugInInterfaceForService(object, kIOMMCDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score); if (err != noErr) { message(-2, "scan: IOCreatePlugInInterfaceForService failed: %d", err); goto clean; } if (!plugin) { message(-2, "scan: no plugin"); goto clean; } herr = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID), (LPVOID *)&mmc); if (herr != S_OK) { message(-2, "scan: QueryInterface failed: %d", herr); goto clean; } if (!mmc) { message(-2, "scan: no mmc"); goto clean; } scsi = (*mmc)->GetSCSITaskDeviceInterface(mmc); if (!scsi) { message(-2, "scan: no scsi"); goto clean; } err = (*scsi)->ObtainExclusiveAccess(scsi); if (err != noErr) { message(-2, "scan: ObtainExclusiveAccess failed: %d", err); goto clean; } ret = inq(scsi, &response, &status, NULL, scanData[*len].vendor, scanData[*len].product, scanData[*len].revision); if (ret != 0) { message(-2, "scan: inq failed: %d", ret); goto clean; } (*len)++; clean: if (exclusive) (*scsi)->ReleaseExclusiveAccess(scsi); if (scsi) (*scsi)->Release(scsi); if (mmc) (*mmc)->Release(mmc); if (plugin) IODestroyPlugInInterface(plugin); if (object) IOObjectRelease(object); } IOObjectRelease(iterator); return scanData; } #include "ScsiIf-common.cc"