xref: /haiku/src/add-ons/kernel/busses/scsi/usb/transform_procs.c (revision f75a7bf508f3156d63a14f8fd77c5e0ca4d08c42)
1 /*
2  * Copyright 2004-2007, Haiku, Inc. All RightsReserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Author:
6  *		Siarzhuk Zharski <imker@gmx.li>
7  */
8 
9 /*! SCSI commands transformations support */
10 
11 #include "usb_scsi.h"
12 
13 #include "device_info.h"
14 #include "transform_procs.h"
15 #include "tracing.h"
16 #include "scsi_commands.h"
17 #include "settings.h"
18 #include "strings.h"
19 
20 #define UFI_COMMAND_LEN		12
21 #define ATAPI_COMMAND_LEN	12
22 
23 
24 /*! Transforms a 6-byte command to 10-byte one if required. Transformed command
25 	is returned in rcmd parameter. In case if no transformation was performed
26 	the return buffer is untouched.
27 
28 	\param cmd: SCSI command buffer to be transformed
29 	\param len: length of buffer, pointed by cmd parameter
30 	\param rcmd: a place for buffer pointer with transformed command
31 	\param rlen: a place for length of transformed command
32 	\return: true if transformation was performed
33 */
34 static void
35 transform_6_to_10(uint8	*cmd, uint8	len, uint8 **rcmd, uint8 *rlen)
36 {
37 	scsi_cmd_generic_6 *from = (scsi_cmd_generic_6 *)cmd;
38 
39 	*rlen = 10;
40 	memset(*rcmd, 0, 10);
41 
42 	switch (from->opcode) {
43 		case READ_6:
44 		case WRITE_6:
45 		{
46 			scsi_cmd_rw_10 *to = (scsi_cmd_rw_10 *)(*rcmd);
47 
48 			to->opcode = (from->opcode == READ_6) ? READ_10 : WRITE_10;
49 			to->lba = B_HOST_TO_BENDIAN_INT32(((from->addr[0] & 0x1f) << 16)
50 				| (from->addr[1] << 8) | from->addr[0]);
51 			to->lun = (from->addr[0] & CMD_LUN) >> CMD_LUN_SHIFT;
52 			to->control = from->ctrl;
53 			if (from->len == 0) {
54 				/* special case! in 6-byte R/W commands	*/
55 				/* the length 0x00 assume transfering 0x100 blocks! */
56 				to->length = B_HOST_TO_BENDIAN_INT16((uint16)256);
57 			} else
58 				to->length = B_HOST_TO_BENDIAN_INT16((uint16)from->len);
59 		}
60 
61 		case MODE_SENSE_6:
62 		case MODE_SELECT_6:
63 		{
64 			scsi_cmd_generic_10 *to = (scsi_cmd_generic_10 *)(*rcmd);
65 
66 			if (from->opcode == MODE_SENSE_6) {
67 				to->opcode = MODE_SENSE_10;
68 				((scsi_cmd_mode_sense_10 *)to)->byte3
69 					= ((scsi_cmd_mode_sense_6 *)from)->byte3;
70 			} else
71 				to->opcode = MODE_SELECT_10;
72 			to->byte2 = from->addr[0];
73 			to->len[1] = from->len + 4; /*TODO: hardcoded length*/
74 			to->ctrl = from->ctrl;
75 		}
76 
77 		default:
78 			/* no transformation needed */
79 			break;
80 	}
81 }
82 
83 
84 /*!	Transforms a 6-byte command to 10-byte depending on information provided
85 	with udi object.
86 
87 	\param udi: usb_device_info object for wich transformation is requested
88 	\param cmd: SCSI command buffer to be transformed
89 	\param len: length of buffer, pointed by cmd parameter
90 	\param rcmd: a place for buffer pointer with transformed command
91 	\param rlen: a place for length of transformed command
92 	\return: true if transformation was performed
93 */
94 static bool
95 transform_cmd_6_to_10(usb_device_info *udi, uint8 *cmd, uint8 len,
96 	uint8 **rcmd, uint8 *rlen)
97 {
98 	scsi_cmd_generic_6 *from = (scsi_cmd_generic_6 *)cmd;
99 
100 	switch (from->opcode) {
101 		case READ_6:
102 		case WRITE_6:
103 		{
104 			if (!HAS_FIXES(udi->properties, FIX_FORCE_RW_TO_6)) {
105 				transform_6_to_10(cmd, len, rcmd, rlen);
106 				return true;
107 			}
108 			break;
109 		}
110 
111 		case MODE_SENSE_6:
112 		case MODE_SELECT_6:
113 		{
114 			if (HAS_FIXES(udi->properties, FIX_FORCE_MS_TO_10)) {
115 				transform_6_to_10(cmd, len, rcmd, rlen);
116 				return true;
117 			}
118 			break;
119 		}
120 	}
121 
122 	return false;
123 }
124 
125 
126 /*!	Transforms a TEST_UNIT_COMAND SCSI command to START_STOP_UNIT one depending
127 	on properties provided with udi object.
128 
129 	\param udi: usb_device_info object for wich transformation is requested
130 	\param cmd: SCSI command buffer to be transformed
131 	\param len: length of buffer, pointed by cmd parameter
132 	\param rcmd: a place for buffer pointer with transformed command
133 	\param rlen: a place for length of transformed command
134 	\return: true if transformation was performed
135 */
136 static bool
137 transform_cmd_test_unit_ready(usb_device_info *udi, uint8 *cmd, uint8 len,
138 	uint8 **rcmd, uint8	*rlen)
139 {
140 	scsi_cmd_start_stop_unit *command = (scsi_cmd_start_stop_unit *)(*rcmd);
141 
142 	if (!HAS_FIXES(udi->properties, FIX_TRANS_TEST_UNIT))
143 		return false;
144 
145 	memset(*rcmd, 0, *rlen);
146 	command->opcode = START_STOP_UNIT;
147 	command->start_loej = CMD_SSU_START;
148 	*rlen = 6;
149 
150 	return true;
151 }
152 
153 
154 //	#pragma mark -
155 
156 
157 /*!	This is the "transformation procedure" for transparent SCSI (0x06) USB
158 	subclass. It performs all SCSI commands transformations required by this
159 	protocol. Additionally it tries to make some workarounds for "broken" USB
160 	devices. If no transformation was performed resulting command buffer
161 	points to original one.
162 
163 	\param udi: usb_device_info object for wich transformation is requested
164 	\param cmd: SCSI command buffer to be transformed
165 	\param len: length of buffer, pointed by cmd parameter
166 	\param rcmd: a place for buffer pointer with transformed command
167 	\param rlen: a place for length of transformed command
168 	\return: B_OK if transformation was successfull, B_ERROR otherwise
169 */
170 static status_t
171 scsi_transform(usb_device_info *udi, uint8 *cmd, uint8 len, uint8 **rcmd,
172 	uint8 *rlen)
173 {
174 	bool transformed = false;
175 	scsi_cmd_generic *command = (scsi_cmd_generic *)cmd;
176 	TRACE_SCSI_COMMAND(cmd, len);
177 
178 	switch (command->opcode) {
179 		case READ_6:
180 		case WRITE_6:
181 		case MODE_SENSE_6:
182 		case MODE_SELECT_6:
183 			transformed = transform_cmd_6_to_10(udi, cmd, len, rcmd, rlen);
184 			break;
185 
186 		case TEST_UNIT_READY:
187 			transformed = transform_cmd_test_unit_ready(udi, cmd, len, rcmd,
188 				rlen);
189 			break;
190 
191 		default:
192 			break;
193 	}
194 
195 	if (!transformed) {
196 		/* transformation was not required */
197 		*rcmd = cmd;
198 		*rlen = len;
199 	} else
200 		TRACE_SCSI_COMMAND_HLIGHT(*rcmd, *rlen);
201 
202 	return B_OK;
203 }
204 
205 
206 /*!	This is the "transformation procedure" for RBC USB subclass (0x01). It
207 	performs all SCSI commands transformations required by this protocol.
208 	Additionally it tries to make some workarounds for "broken" USB devices.
209 	If no transformation was performed resulting command buffer points to
210 	original one.
211 
212 	\param udi: usb_device_info object for wich transformation is requested
213 	\param cmd: SCSI command buffer to be transformed
214 	\param len: length of buffer, pointed by cmd parameter
215 	\param rcmd: a place for buffer pointer with transformed command
216 	\param rlen: a place for length of transformed command
217 	\return: B_OK if transformation was successfull, B_ERROR otherwise
218 */
219 static status_t
220 rbc_transform(usb_device_info *udi, uint8 *cmd, uint8 len, uint8 **rcmd,
221 	uint8 *rlen)
222 {
223 	bool transformed = false;
224 	scsi_cmd_generic *command = (scsi_cmd_generic *)cmd;
225 	TRACE_SCSI_COMMAND(cmd, len);
226 
227 	switch (command->opcode) {
228 		case TEST_UNIT_READY:
229 			transformed = transform_cmd_test_unit_ready(udi, cmd, len, rcmd, rlen);
230 			break;
231 
232 		case READ_6:
233 		case WRITE_6: /* there are no such command in list of allowed - transform*/
234 			transformed = transform_cmd_6_to_10(udi, cmd, len, rcmd, rlen);
235 			break;
236 
237 		/* TODO: all following ones are not checked against specs !!!*/
238 		case FORMAT_UNIT:
239 		case INQUIRY: /*TODO: check !!! */
240 		case MODE_SELECT_6: /*TODO: check !!! */
241 		case MODE_SENSE_6: /*TODO: check !!! */
242 		case PERSISTENT_RESERVE_IN: /*TODO: check !!! */
243 		case PERSISTENT_RESERVE_OUT: /*TODO: check !!! */
244 		case PREVENT_ALLOW_MEDIA_REMOVAL: /*TODO: check !!! */
245 		case READ_10: /*TODO: check !!! */
246 		case READ_CAPACITY: /*TODO: check !!! */
247 		case RELEASE_6: /*TODO: check !!! */
248 		case REQUEST_SENSE: /*TODO: check !!! */
249 		case RESERVE_6: /*TODO: check !!! */
250 		case START_STOP_UNIT: /*TODO: check !!! */
251 		case SYNCHRONIZE_CACHE: /*TODO: check !!! */
252 		case VERIFY: /*TODO: check !!! */
253 		case WRITE_10: /*TODO: check !!! */
254 		case WRITE_BUFFER: /*TODO Check correctnes of such translation!*/
255 			*rcmd = cmd;		/* No need to copy */
256 			*rlen = len;	/*TODO: check !!! */
257 			break;
258 
259 		default:
260 			TRACE_ALWAYS("An unsupported RBC command: %08x\n", command->opcode);
261 			return B_ERROR;
262 	}
263 
264 	if (transformed)
265 		TRACE_SCSI_COMMAND_HLIGHT(*rcmd, *rlen);
266 
267 	return B_OK;
268 }
269 
270 
271 /*!	This is the "transformation procedure" for UFI USB subclass (0x04). It
272 	performs all SCSI commands transformations required by this protocol.
273 	Additionally it tries to make some workarounds for "broken" USB devices.
274 	If no transformation was performed resulting command buffer points to
275 	the original one.
276 
277 	\param udi: usb_device_info object for wich transformation is requested
278 	\param cmd: SCSI command buffer to be transformed
279 	\param len: length of buffer, pointed by cmd parameter
280 	\param rcmd: a place for buffer pointer with transformed command
281 	\param rlen: a place for length of transformed command
282 	\return: B_OK if transformation was successfull, B_ERROR otherwise
283 */
284 static status_t
285 ufi_transform(usb_device_info *udi, uint8 *cmd, uint8 len, uint8 **rcmd,
286 	uint8 *rlen)
287 {
288 	scsi_cmd_generic *command = (scsi_cmd_generic *)cmd;
289 	status_t status = B_OK;
290 
291 	TRACE_SCSI_COMMAND(cmd, len);
292 	memset(*rcmd, 0, UFI_COMMAND_LEN);
293 
294 	switch (command->opcode) {
295 		case READ_6:
296 		case WRITE_6:
297 		case MODE_SENSE_6:
298 		case MODE_SELECT_6:
299 			// TODO: not transform_cmd_*()?
300 			transform_6_to_10(cmd, len, rcmd, rlen);
301 			break;
302 		case TEST_UNIT_READY:
303 			if (transform_cmd_test_unit_ready(udi, cmd, len, rcmd, rlen))
304 				break; /* if TEST UNIT READY was transformed*/
305 		case FORMAT_UNIT:	/* TODO: mismatch */
306 		case INQUIRY:
307 		case START_STOP_UNIT:
308 		case MODE_SELECT_10:
309 		case MODE_SENSE_10:
310 		case PREVENT_ALLOW_MEDIA_REMOVAL:
311 		case READ_10:
312 		case READ_12:
313 		case READ_CAPACITY:
314 		case READ_FORMAT_CAPACITY: /* TODO: not in the SCSI-2 specs */
315 		case REQUEST_SENSE:
316 		case REZERO_UNIT:
317 		case SEEK_10:
318 		case SEND_DIAGNOSTICS: /* TODO: parameter list len mismatch */
319 		case VERIFY:
320 		case WRITE_10:
321 		case WRITE_12: /* TODO: EBP. mismatch */
322 		case WRITE_AND_VERIFY:
323 			memcpy(*rcmd, cmd, len);
324 			/*TODO what about control? ignored in UFI?*/
325 			break;
326 		default:
327 			TRACE_ALWAYS("An unsupported UFI command: %08x\n",
328 				command->opcode);
329 			status = B_ERROR;
330 			break;
331 	}
332 
333 	*rlen = UFI_COMMAND_LEN; /* override any value set in transform funcs !!!*/
334 
335 	if (status == B_OK)
336 		TRACE_SCSI_COMMAND_HLIGHT(*rcmd, *rlen);
337 
338 	return status;
339 }
340 
341 
342 /*!	This is the "transformation procedure" for SFF8020I and SFF8070I
343 	USB subclassses (0x02 and 0x05). It performs all SCSI commands
344 	transformations required by this protocol. Additionally it tries to make
345 	some workarounds for "broken" USB devices. If no transformation was
346 	performed resulting command buffer points to the original one.
347 
348 	\param udi: usb_device_info object for wich transformation is requested
349 	\param cmd: SCSI command buffer to be transformed
350 	\param len: length of buffer, pointed by cmd parameter
351 	\param rcmd: a place for buffer pointer with transformed command
352 	\param rlen: a place for length of transformed command
353 	\return: B_OK if transformation was successfull, B_ERROR otherwise
354 */
355 static status_t
356 atapi_transform(usb_device_info *udi, uint8 *cmd, uint8 len, uint8 **rcmd,
357 	uint8 *rlen)
358 {
359 	scsi_cmd_generic *command = (scsi_cmd_generic *)cmd;
360 	status_t status = B_OK;
361 
362 	TRACE_SCSI_COMMAND(cmd, len);
363 	memset(*rcmd, 0, ATAPI_COMMAND_LEN);
364 
365 	switch (command->opcode) {
366 		case READ_6:
367 		case WRITE_6:
368 		case MODE_SENSE_6:
369 		case MODE_SELECT_6:
370 			// TODO: not transform_cmd_*()?
371 			transform_6_to_10(cmd, len, rcmd, rlen);
372 			break;
373 		case TEST_UNIT_READY:
374 			if (transform_cmd_test_unit_ready(udi, cmd, len, rcmd, rlen))
375 				break;
376 		case FORMAT_UNIT:
377 		case INQUIRY:
378 		case MODE_SELECT_10:
379 		case MODE_SENSE_10:
380 		case PREVENT_ALLOW_MEDIA_REMOVAL:
381 		case READ_10:
382 		case READ_12: /* mismatch in byte 1 */
383 		case READ_CAPACITY: /* mismatch. no transf len defined... */
384 		case READ_FORMAT_CAPACITY: /* TODO: check!!! */
385 		case REQUEST_SENSE:
386 		case SEEK_10:
387 		case START_STOP_UNIT:
388 		case VERIFY: /* mismatch DPO */
389 		case WRITE_10: /* mismatch in byte 1 */
390 		case WRITE_12: /* mismatch in byte 1 */
391 		case WRITE_AND_VERIFY: /* mismatch byte 1 */
392 		case PAUSE_RESUME:
393 		case PLAY_AUDIO:
394 		case PLAY_AUDIO_MSF:
395 		case REWIND:
396 		case PLAY_AUDIO_TRACK:
397 		/* are in FreeBSD driver but no in 8070/8020 specs ...
398 		//case REZERO_UNIT:
399 		//case SEND_DIAGNOSTIC:
400 		//case POSITION_TO_ELEMENT:	*/
401 		case GET_CONFIGURATION:
402 		case SYNCHRONIZE_CACHE:
403 		case READ_BUFFER:
404 	 	case READ_SUBCHANNEL:
405 		case READ_TOC: /* some mismatch */
406 		case READ_HEADER:
407 		case READ_DISK_INFO:
408 		case READ_TRACK_INFO:
409 		case SEND_OPC:
410 		case READ_MASTER_CUE:
411 		case CLOSE_TR_SESSION:
412 		case READ_BUFFER_CAP:
413 		case SEND_CUE_SHEET:
414 		case BLANK:
415 		case EXCHANGE_MEDIUM:
416 		case READ_DVD_STRUCTURE:
417 		case SET_CD_SPEED:
418 		case DVD_REPORT_KEY:
419 		case DVD_SEND_KEY:
420 		//case 0xe5: /* READ_TRACK_INFO_PHILIPS *//* TODO: check!!! */
421 			memcpy(*rcmd, cmd, len); /* TODO: check!!! */
422 			break;
423 
424 		default:
425 			TRACE_ALWAYS("An unsupported (?) ATAPI command: %08x\n",
426 				command->opcode);
427 			status = B_ERROR;
428 			break;
429 	}
430 
431 	*rlen = ATAPI_COMMAND_LEN;
432 
433 	if (status == B_OK)
434 		TRACE_SCSI_COMMAND_HLIGHT(*rcmd, *rlen);
435 
436 	return status;
437 }
438 
439 
440 /*!	This is the "transformation procedure" for QIC157 USB subclass (0x03). It
441 	performs all SCSI commands transformations required by this protocol.
442 	Additionally it tries to make some workarounds for "broken" USB devices.
443 	If no transformation was performed the resulting command buffer points to
444 	the original one.
445 
446 	\param udi: usb_device_info object for wich transformation is requested
447 	\param cmd: SCSI command buffer to be transformed
448 	\param len: length of buffer, pointed by cmd parameter
449 	\param rcmd: a place for buffer pointer with transformed command
450 	\param rlen: a place for length of transformed command
451 	\return: B_OK if transformation was successfull, B_ERROR otherwise
452 */
453 static status_t
454 qic157_transform(usb_device_info *udi, uint8 *cmd, uint8 len, uint8 **rcmd,
455 	uint8 *rlen)
456 {
457 	scsi_cmd_generic *command = (scsi_cmd_generic *)cmd;
458 	status_t status = B_OK;
459 
460 	TRACE_SCSI_COMMAND(cmd, len);
461 	*rlen = ATAPI_COMMAND_LEN;
462 	memset(*rcmd, 0, *rlen);
463 
464 	switch (command->opcode) {
465 		case READ_6:
466 		case WRITE_6:
467 		case MODE_SENSE_6:
468 		case MODE_SELECT_6:
469 			// TODO: not transform_cmd_*()?
470 			transform_6_to_10(cmd, len, rcmd, rlen);
471 			break;
472 		case TEST_UNIT_READY:
473 			if (transform_cmd_test_unit_ready(udi, cmd, len, rcmd, rlen))
474 				break; // if TEST UNIT READY was transformed
475 		case ERASE: /*TODO: check !!! */
476 		case INQUIRY: /*TODO: check !!! */
477 		case LOAD_UNLOAD: /*TODO: check !!! */
478 		case LOCATE: /*TODO: check !!! */
479 		case LOG_SELECT: /*TODO: check !!! */
480 		case LOG_SENSE: /*TODO: check !!! */
481 		case READ_POSITION: /*TODO: check !!! */
482 		case REQUEST_SENSE: /*TODO: check !!! */
483 		case REWIND: /*TODO: check !!! */
484 		case SPACE: /*TODO: check !!! */
485 		case WRITE_FILEMARK: /*TODO: check !!! */
486 			*rcmd = cmd; /*TODO: check !!! */
487 			*rlen = len;
488 			break;
489 		default:
490 			TRACE_ALWAYS("An unsupported QIC-157 command: %08x\n",
491 				command->opcode);
492 			status = B_ERROR;
493 			break;
494 	}
495 
496 	if (status == B_OK)
497 		TRACE_SCSI_COMMAND_HLIGHT(*rcmd, *rlen);
498 
499 	return status;
500 }
501 
502 
503 transform_module_info scsi_transform_m = {
504 	{0, 0, 0}, /* this is not a real kernel module - just interface */
505 	scsi_transform,
506 };
507 
508 transform_module_info rbc_transform_m = {
509 	{0, 0, 0}, /* this is not a real kernel module - just interface */
510 	rbc_transform,
511 };
512 
513 transform_module_info ufi_transform_m = {
514 	{0, 0, 0}, /* this is not a real kernel module - just interface */
515 	ufi_transform,
516 };
517 
518 transform_module_info atapi_transform_m = {
519 	{0, 0, 0}, /* this is not a real kernel module - just interface */
520 	atapi_transform,
521 };
522 
523 transform_module_info qic157_transform_m = {
524 	{0, 0, 0}, /* this is not a real kernel module - just interface */
525 	qic157_transform,
526 };
527