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*/
36static void
37transform_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*/
96static bool
97transform_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*/
138static bool
139transform_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*/
172static status_t
173scsi_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*/
221static status_t
222rbc_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*/
286static status_t
287ufi_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*/
357static status_t
358atapi_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*/
455static status_t
456qic157_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
505transform_module_info scsi_transform_m = {
506	{0, 0, 0}, /* this is not a real kernel module - just interface */
507	scsi_transform,
508};
509
510transform_module_info rbc_transform_m = {
511	{0, 0, 0}, /* this is not a real kernel module - just interface */
512	rbc_transform,
513};
514
515transform_module_info ufi_transform_m = {
516	{0, 0, 0}, /* this is not a real kernel module - just interface */
517	ufi_transform,
518};
519
520transform_module_info atapi_transform_m = {
521	{0, 0, 0}, /* this is not a real kernel module - just interface */
522	atapi_transform,
523};
524
525transform_module_info qic157_transform_m = {
526	{0, 0, 0}, /* this is not a real kernel module - just interface */
527	qic157_transform,
528};
529