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*/
34static void
35transform_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*/
94static bool
95transform_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*/
136static bool
137transform_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*/
170static status_t
171scsi_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*/
219static status_t
220rbc_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*/
284static status_t
285ufi_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*/
355static status_t
356atapi_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*/
453static status_t
454qic157_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
503transform_module_info scsi_transform_m = {
504	{0, 0, 0}, /* this is not a real kernel module - just interface */
505	scsi_transform,
506};
507
508transform_module_info rbc_transform_m = {
509	{0, 0, 0}, /* this is not a real kernel module - just interface */
510	rbc_transform,
511};
512
513transform_module_info ufi_transform_m = {
514	{0, 0, 0}, /* this is not a real kernel module - just interface */
515	ufi_transform,
516};
517
518transform_module_info atapi_transform_m = {
519	{0, 0, 0}, /* this is not a real kernel module - just interface */
520	atapi_transform,
521};
522
523transform_module_info qic157_transform_m = {
524	{0, 0, 0}, /* this is not a real kernel module - just interface */
525	qic157_transform,
526};
527