1 /*
2 * Native implementation of cdrdao's SCSI interface for Mac OS X.
3 * Copyright (C) by Edgar Fu�, Bonn, July 2007.
4 * Do with this whatever you like, as long as you are either me or you keep
5 * this message intact and both
6 * - acknowledge that I wrote it for cdrdao in the first place, and
7 * - don't blame me if it doesn't do what you like or expect.
8 * These routines do exactly what they do. If that's not what you expect them
9 * or would like them to do, don't complain with me, the cdrdao project, my
10 * neighbour's brother-in-law or anybody else, but rewrite them to your taste.
11 */
12
13 /* standard includes */
14 #include <stdio.h>
15 #include <ctype.h>
16 #include <string.h>
17
18 /* cdrdao specific includes and prototype */
19 #include "ScsiIf.h"
20 #include "trackdb/util.h"
21 extern void message(int level, const char *fmt, ...);
22 #include "decodeSense.cc"
23
24 /* Mac OS X specific includes */
25 #include <CoreFoundation/CFPlugInCOM.h>
26 #include <IOKit/IOKitLib.h>
27 #include <IOKit/scsi/SCSITaskLib.h>
28 #include <IOKit/scsi/SCSICommandOperationCodes.h>
29 #include <IOKit/scsi/SCSICmds_INQUIRY_Definitions.h>
30
31 class ScsiIfImpl {
32 public:
33 int num_; /* number of device for compatibility mode */
34 char *path_; /* native (IO registry) pathname of device */
35 io_object_t object_;
36 IOCFPlugInInterface **plugin_;
37 MMCDeviceInterface **mmc_;
38 SCSITaskDeviceInterface **scsi_;
39 int exclusive_;
40 long timeout_; /* in ms */
41 char *error_; /* sendCmd() internal error string */
42 SCSIServiceResponse response_;
43 SCSITaskStatus status_;
44 struct SCSI_Sense_Data sense_;
45 };
46
ScsiIf(const char * name)47 ScsiIf::ScsiIf(const char *name)
48 {
49 int len;
50 int bus, targ, lun, count;
51
52 impl_ = new ScsiIfImpl;
53 impl_->num_ = 0;
54 impl_->path_ = NULL;
55 len = strlen(name);
56 if (len) {
57 if (isdigit(name[0])) {
58 /* Compatibility mode. Just add bus+targ+lun */
59 if (sscanf(name, "%i,%i,%i%n", &bus, &targ, &lun, &count) == 3 && count == len) {
60 if ((bus >= 0) && (targ >= 0) && (lun >= 0))
61 impl_->num_ = 1 + bus + targ + lun;
62 }
63 } else {
64 /* Native mode. Take name as IOreg path */
65 impl_->path_ = strdupCC(name);
66 }
67 }
68 impl_->object_ = 0;
69 impl_->plugin_ = NULL;
70 impl_->mmc_ = NULL;
71 impl_->scsi_ = NULL;
72 impl_->exclusive_ = 0;
73 impl_->timeout_ = 10*1000;
74 impl_->error_ = NULL;
75
76 vendor_[0] = 0;
77 product_[0] = 0;
78 revision_[0] = 0;
79
80 maxDataLen_ = 64*1024; /* XXX */
81 }
82
~ScsiIf()83 ScsiIf::~ScsiIf()
84 {
85 if (impl_->scsi_) {
86 if (impl_->exclusive_)
87 (*impl_->scsi_)->ReleaseExclusiveAccess(impl_->scsi_);
88 (*impl_->scsi_)->Release(impl_->scsi_);
89 }
90 if (impl_->mmc_)
91 (*impl_->mmc_)->Release(impl_->mmc_);
92 if (impl_->plugin_)
93 IODestroyPlugInInterface(impl_->plugin_);
94 if (impl_->object_)
95 IOObjectRelease(impl_->object_);
96 if (impl_->path_ != NULL) delete[] impl_->path_;
97 if (impl_->error_ != NULL) delete[] impl_->error_;
98 delete impl_;
99 }
100
init()101 int ScsiIf::init()
102 {
103 CFMutableDictionaryRef dict = NULL;
104 CFMutableDictionaryRef sub = NULL;
105 io_iterator_t iterator = 0;
106 kern_return_t err;
107 SInt32 score;
108 HRESULT herr;
109 int i;
110
111 if (impl_->num_) {
112 /*
113 * Compatibility mode.
114 * Build dictionaries to search for num_'th device having the
115 * authoring property using an IO iterator.
116 */
117 dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
118 sub = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
119 CFDictionarySetValue(sub, CFSTR(kIOPropertySCSITaskDeviceCategory),
120 CFSTR(kIOPropertySCSITaskAuthoringDevice));
121 CFDictionarySetValue(dict, CFSTR(kIOPropertyMatchKey), sub);
122 IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator);
123 if (!iterator) message(-2, "init: no iterator");
124 if (iterator) {
125 i = impl_->num_;
126 do {
127 impl_->object_ = IOIteratorNext(iterator);
128 i--;
129 } while (i && impl_->object_);
130 IOObjectRelease(iterator);
131 }
132 } else if (impl_->path_) {
133 /* Native mode. Just use the IO Registry pathname */
134 impl_->object_ = IORegistryEntryFromPath(kIOMasterPortDefault, impl_->path_);
135 }
136 /* Strange if (!x) ... if (x) style so you can #ifdef out the !x part */
137 if (!impl_->object_) message(-2, "init: no object");
138 if (impl_->object_) {
139 /* Get intermediate (IOCFPlugIn) plug-in for MMC device */
140 err = IOCreatePlugInInterfaceForService(impl_->object_,
141 kIOMMCDeviceUserClientTypeID, kIOCFPlugInInterfaceID,
142 &impl_->plugin_, &score);
143 if (err != noErr)
144 message(-2, "init: IOCreatePlugInInterfaceForService failed: %d", err);
145 }
146 if (!impl_->plugin_) message(-2, "init: no plugin");
147 if (impl_->plugin_) {
148 /* Get the MMC interface (MMCDeviceInterface) */
149 herr = (*impl_->plugin_)->QueryInterface(impl_->plugin_,
150 CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
151 /*
152 * Most of Apple's examples erroneously cast to LPVOID,
153 * not LPVOID *.
154 */
155 (LPVOID *)&impl_->mmc_);
156 if (herr != S_OK)
157 message(-2, "init: QueryInterface failed: %d", herr);
158 }
159 if (!impl_->mmc_) message(-2, "init: no mmc");
160 if (impl_->mmc_) {
161 /* Get the SCSI interface */
162 impl_->scsi_ = (*impl_->mmc_)->GetSCSITaskDeviceInterface(impl_->mmc_);
163 }
164 if (!impl_->scsi_) message(-2, "init: no scsi");
165 if (impl_->scsi_) {
166 /* Obtain exclusive access to device */
167 err = (*impl_->scsi_)->ObtainExclusiveAccess(impl_->scsi_);
168 if (err != noErr)
169 message(-2, "init: ObtainExclusiveAccess failed: %d", err);
170 if (err == noErr) {
171 impl_->exclusive_ = 1;
172 /* Send SCSI inquiry command */
173 i = inquiry();
174 if (i != 0)
175 message(-2, "init: inquiry failed: %d", i);
176 return (i == 0) ? 0 : 2;
177 }
178 }
179 message(-2, "init: failed");
180 return 1;
181 }
182
timeout(int t)183 int ScsiIf::timeout(int t)
184 {
185 int ret = impl_->timeout_/1000;
186 impl_->timeout_ = t*1000;
187 return ret;
188 }
189
sendCmd(const unsigned char * cmd,int cmdLen,const unsigned char * dataOut,int dataOutLen,unsigned char * dataIn,int dataInLen,int showMessage)190 int ScsiIf::sendCmd(const unsigned char *cmd, int cmdLen,
191 const unsigned char *dataOut, int dataOutLen,
192 unsigned char *dataIn, int dataInLen,
193 int showMessage)
194 {
195 SCSITaskInterface **task;
196 IOVirtualRange range;
197 IOReturn ret;
198 UInt64 len;
199
200 if (impl_->error_ != NULL) { delete[] impl_->error_; impl_->error_ = NULL; }
201
202 #define ERROR(msg) do {\
203 impl_->error_ = new char[9 + strlen(msg) + 1];\
204 strcpy(impl_->error_, "sendCmd: ");\
205 strcat(impl_->error_, msg);\
206 if (showMessage) printError();\
207 if (task) (*task)->Release(task);\
208 return 1;\
209 } while(0)
210
211 task = (*impl_->scsi_)->CreateSCSITask(impl_->scsi_);
212 if (!task) ERROR("no task");
213 ret = (*task)->SetCommandDescriptorBlock(task, (UInt8 *)cmd, cmdLen);
214 if (ret != kIOReturnSuccess) ERROR("SetCommandDescriptorBlock failed");
215 /* The OSX SCSI interface can't deal with two data phases */
216 if (dataIn && dataOut) ERROR("dataIn && dataOut");
217 if (dataIn) {
218 range.address = (IOVirtualAddress)dataIn;
219 range.length = dataInLen;
220 ret = (*task)->SetScatterGatherEntries(task, &range, 1,
221 dataInLen, kSCSIDataTransfer_FromTargetToInitiator);
222 } else if (dataOut) {
223 range.address = (IOVirtualAddress)dataOut;
224 range.length = dataOutLen;
225 ret = (*task)->SetScatterGatherEntries(task, &range, 1,
226 dataOutLen, kSCSIDataTransfer_FromInitiatorToTarget);
227 } else {
228 /* Just to make sure. We pass in zero ranges anyway */
229 range.address = (IOVirtualAddress)NULL;
230 range.length = 0;
231 ret = (*task)->SetScatterGatherEntries(task, &range, 0,
232 0, kSCSIDataTransfer_NoDataTransfer);
233 }
234 if (ret != kIOReturnSuccess) ERROR("SetScatterGatherEntries failed");
235 ret = (*task)->SetTimeoutDuration(task, impl_->timeout_);
236 if (ret != kIOReturnSuccess) ERROR("SetTimeoutDuration failed");
237 ret = (*task)->ExecuteTaskSync(task, &impl_->sense_, &impl_->status_, &len);
238 if (ret != kIOReturnSuccess) ERROR("ExecuteTaskSync failed");
239 ret = (*task)->GetSCSIServiceResponse(task, &impl_->response_);
240 if (ret != kIOReturnSuccess) ERROR("GetSCSIServiceResponse failed");
241 (*task)->Release(task);
242 if (impl_->response_ == kSCSIServiceResponse_TASK_COMPLETE) {
243 if (impl_->status_ == kSCSITaskStatus_GOOD) return 0;
244 if (impl_->status_ == kSCSITaskStatus_CHECK_CONDITION) return 2;
245 }
246 if (impl_->response_ == kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE) return 1;
247 return 1 /* XXX This shouldn't happen */;
248
249 #undef ERROR
250 }
251
getSense(int & len) const252 const unsigned char *ScsiIf::getSense(int &len) const
253 {
254 len = kSenseDefaultSize;
255 return (unsigned char *)&impl_->sense_;
256 }
257
printError()258 void ScsiIf::printError()
259 {
260 char *s;
261
262 if (impl_->error_)
263 /* Internal error in sendCmd(). We saved a message string. */
264 s = impl_->error_;
265 else switch (impl_->response_) {
266 case kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE:
267 /* The SCSI command didn't complete */
268 switch (impl_->status_) {
269 case kSCSITaskStatus_TaskTimeoutOccurred:
270 s = "task timeout"; break;
271 case kSCSITaskStatus_ProtocolTimeoutOccurred:
272 s = "protocol timeout"; break;
273 case kSCSITaskStatus_DeviceNotResponding:
274 s = "device not responding"; break;
275 case kSCSITaskStatus_DeviceNotPresent:
276 s = "device not present"; break;
277 case kSCSITaskStatus_DeliveryFailure:
278 s = "delivery failure"; break;
279 case kSCSITaskStatus_No_Status:
280 s = "no status"; break;
281 default:
282 s = "failure, unknown status"; break;
283 }
284 break;
285 case kSCSIServiceResponse_TASK_COMPLETE:
286 /* The SCSI command did complete */
287 switch (impl_->status_) {
288 case kSCSITaskStatus_GOOD:
289 s = "good"; break;
290 case kSCSITaskStatus_CHECK_CONDITION:
291 decodeSense((unsigned char *)&impl_->sense_, sizeof(impl_->sense_));
292 s = NULL; break;
293 case kSCSITaskStatus_CONDITION_MET:
294 s = "condition met"; break;
295 case kSCSITaskStatus_BUSY:
296 s = "busy"; break;
297 case kSCSITaskStatus_INTERMEDIATE:
298 s = "intermediate"; break;
299 case kSCSITaskStatus_INTERMEDIATE_CONDITION_MET:
300 s = "intermediate, condition met"; break;
301 case kSCSITaskStatus_RESERVATION_CONFLICT:
302 s = "reservation conflict"; break;
303 case kSCSITaskStatus_TASK_SET_FULL:
304 s = "task set full"; break;
305 case kSCSITaskStatus_ACA_ACTIVE:
306 s = "aca active"; break;
307 default:
308 s = "complete, unknown status"; break;
309 }
310 break;
311 default:
312 s = "unknown response"; break;
313 }
314 if (s) message(-2, s);
315 }
316
317 /*
318 * Internal form or inquiry command.
319 * Used by both inquiry() and scanData(), but with different data.
320 */
inq(SCSITaskDeviceInterface ** scsi,SCSIServiceResponse * response,SCSITaskStatus * status,struct SCSI_Sense_Data * sense,char * vend,char * prod,char * rev)321 int inq(SCSITaskDeviceInterface **scsi,
322 SCSIServiceResponse *response,
323 SCSITaskStatus *status,
324 struct SCSI_Sense_Data *sense,
325 char *vend, char *prod, char *rev)
326 {
327 SCSICmd_INQUIRY_StandardData inq_data;
328 SCSICommandDescriptorBlock cdb;
329 SCSITaskInterface **task;
330 IOVirtualRange range;
331 IOReturn ret;
332 UInt64 len;
333 int i;
334
335 task = (*scsi)->CreateSCSITask(scsi);
336 if (!task) {
337 message(-2, "inq: no task");
338 return 1;
339 }
340 bzero(cdb, sizeof(cdb));
341 cdb[0] = kSCSICmd_INQUIRY;
342 cdb[4] = sizeof(inq_data);
343 ret = (*task)->SetCommandDescriptorBlock(task, cdb, kSCSICDBSize_6Byte);
344 if (ret != kIOReturnSuccess) {
345 message(-2, "inq: SetCommandDescriptorBlock failed: %d", ret);
346 (*task)->Release(task);
347 return 1;
348 }
349 range.address = (IOVirtualAddress)&inq_data;
350 range.length = sizeof(inq_data);
351 ret = (*task)->SetScatterGatherEntries(task, &range, 1,
352 sizeof(inq_data), kSCSIDataTransfer_FromTargetToInitiator);
353 if (ret != kIOReturnSuccess) {
354 message(-2, "inq: SetScatterGatherEntries failed: %d", ret);
355 (*task)->Release(task);
356 return 1;
357 }
358 ret = (*task)->SetTimeoutDuration(task, 1000);
359 if (ret != kIOReturnSuccess) {
360 message(-2, "inq: SetTimeoutDuration failed: %d", ret);
361 (*task)->Release(task);
362 return 1;
363 }
364 ret = (*task)->ExecuteTaskSync(task, sense, status, &len);
365 if (ret != kIOReturnSuccess) {
366 message(-2, "inq: ExecuteTaskSync failed: %d", ret);
367 (*task)->Release(task);
368 return 1;
369 }
370 ret = (*task)->GetSCSIServiceResponse(task, response);
371 if (ret != kIOReturnSuccess) {
372 message(-2, "inq: GetSCSIServiceResponse failed: %d", ret);
373 (*task)->Release(task);
374 return 1;
375 }
376 if (*response != kSCSIServiceResponse_TASK_COMPLETE) {
377 message(-2, "inq: response=%d", *response);
378 (*task)->Release(task);
379 return 1;
380 }
381 if (*status != kSCSITaskStatus_GOOD) {
382 message(-2, "inq: status=%d", *status);
383 (*task)->Release(task);
384 return 1;
385 }
386 (*task)->Release(task);
387 /* Copy vendor/product/revision stripping traiiling spaces */
388 i = kINQUIRY_VENDOR_IDENTIFICATION_Length;
389 while (i > 0 && inq_data.VENDOR_IDENTIFICATION[i - 1] == ' ') i--;
390 memcpy(vend, inq_data.VENDOR_IDENTIFICATION, i);
391 vend[i] = '\0';
392 i = kINQUIRY_PRODUCT_IDENTIFICATION_Length;
393 while (i > 0 && inq_data.PRODUCT_IDENTIFICATION[i - 1] == ' ') i--;
394 memcpy(prod, inq_data.PRODUCT_IDENTIFICATION, i);
395 prod[i] = '\0';
396 i = kINQUIRY_PRODUCT_REVISION_LEVEL_Length;
397 while (i > 0 && inq_data.PRODUCT_REVISION_LEVEL[i - 1] == ' ') i--;
398 memcpy(rev, inq_data.PRODUCT_REVISION_LEVEL, i);
399 rev[i] = '\0';
400 return 0;
401 }
402
inquiry()403 int ScsiIf::inquiry()
404 {
405 return inq(impl_->scsi_, &impl_->response_, &impl_->status_, &impl_->sense_,
406 vendor_, product_, revision_);
407 }
408
409 #define MAX_SCAN 10
410
scan(int * len,char * dev)411 ScsiIf::ScanData *ScsiIf::scan(int *len, char *dev)
412 {
413 ScanData *scanData;
414 CFMutableDictionaryRef dict = NULL;
415 CFMutableDictionaryRef sub = NULL;
416 io_iterator_t iterator = 0;
417 io_object_t object = 0;
418 IOCFPlugInInterface **plugin = NULL;
419 MMCDeviceInterface **mmc = NULL;
420 SCSITaskDeviceInterface **scsi = NULL;
421 SCSIServiceResponse response;
422 SCSITaskStatus status;
423 int exclusive = 0;
424 io_string_t path;
425 kern_return_t err;
426 SInt32 score;
427 HRESULT herr;
428 int ret;
429 int i;
430
431 /* Ignore dev. We don't support different kinds of busses that way. */
432 /* Build matching dictionaries to find authoring decices. See init(). */
433 dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
434 sub = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
435 CFDictionarySetValue(sub, CFSTR(kIOPropertySCSITaskDeviceCategory),
436 CFSTR(kIOPropertySCSITaskAuthoringDevice));
437 CFDictionarySetValue(dict, CFSTR(kIOPropertyMatchKey), sub);
438 IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator);
439 if (!iterator) {
440 message(-2, "scan: no iterator");
441 *len = 0;
442 return NULL;
443 }
444 scanData = new ScanData[MAX_SCAN]; *len = 0;
445 for (i = 0; ; i++) {
446 object = IOIteratorNext(iterator);
447 if (!object) break;
448 if (*len == MAX_SCAN) break;
449 /* Get native (IO Registry) pathname of this device. */
450 err = IORegistryEntryGetPath(object, kIOServicePlane, path);
451 if (err == noErr) {
452 scanData[*len].dev = strdupCC(path);
453 }
454 /* See init() for a description of the plugin/interface tour. */
455 err = IOCreatePlugInInterfaceForService(object,
456 kIOMMCDeviceUserClientTypeID, kIOCFPlugInInterfaceID,
457 &plugin, &score);
458 if (err != noErr) {
459 message(-2, "scan: IOCreatePlugInInterfaceForService failed: %d", err);
460 goto clean;
461 }
462 if (!plugin) {
463 message(-2, "scan: no plugin");
464 goto clean;
465 }
466 herr = (*plugin)->QueryInterface(plugin,
467 CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
468 (LPVOID *)&mmc);
469 if (herr != S_OK) {
470 message(-2, "scan: QueryInterface failed: %d", herr);
471 goto clean;
472 }
473 if (!mmc) {
474 message(-2, "scan: no mmc");
475 goto clean;
476 }
477 scsi = (*mmc)->GetSCSITaskDeviceInterface(mmc);
478 if (!scsi) {
479 message(-2, "scan: no scsi");
480 goto clean;
481 }
482 err = (*scsi)->ObtainExclusiveAccess(scsi);
483 if (err != noErr) {
484 message(-2, "scan: ObtainExclusiveAccess failed: %d", err);
485 goto clean;
486 }
487 ret = inq(scsi, &response, &status, NULL,
488 scanData[*len].vendor,
489 scanData[*len].product,
490 scanData[*len].revision);
491 if (ret != 0) {
492 message(-2, "scan: inq failed: %d", ret);
493 goto clean;
494 }
495 (*len)++;
496 clean:
497 if (exclusive) (*scsi)->ReleaseExclusiveAccess(scsi);
498 if (scsi) (*scsi)->Release(scsi);
499 if (mmc) (*mmc)->Release(mmc);
500 if (plugin) IODestroyPlugInInterface(plugin);
501 if (object) IOObjectRelease(object);
502 }
503 IOObjectRelease(iterator);
504 return scanData;
505 }
506
507 #include "ScsiIf-common.cc"
508