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