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
transform_6_to_10(uint8 * cmd,uint8 len,uint8 ** rcmd,uint8 * rlen)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
transform_cmd_6_to_10(usb_device_info * udi,uint8 * cmd,uint8 len,uint8 ** rcmd,uint8 * rlen)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
transform_cmd_test_unit_ready(usb_device_info * udi,uint8 * cmd,uint8 len,uint8 ** rcmd,uint8 * rlen)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
scsi_transform(usb_device_info * udi,uint8 * cmd,uint8 len,uint8 ** rcmd,uint8 * rlen)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
rbc_transform(usb_device_info * udi,uint8 * cmd,uint8 len,uint8 ** rcmd,uint8 * rlen)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
ufi_transform(usb_device_info * udi,uint8 * cmd,uint8 len,uint8 ** rcmd,uint8 * rlen)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
atapi_transform(usb_device_info * udi,uint8 * cmd,uint8 len,uint8 ** rcmd,uint8 * rlen)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
qic157_transform(usb_device_info * udi,uint8 * cmd,uint8 len,uint8 ** rcmd,uint8 * rlen)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