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