/* * \file: freecom.c * \brief: USB SCSI module extention for Freecom USB/IDE adaptor * * This file is a part of BeOS USB SCSI interface module project. * Copyright (c) 2003-2004 by Siarzhuk Zharski * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. * * This protocol extension module was developed using information from * "Driver for Freecom USB/IDE adaptor" in Linux usb storage driver and * "Mac OS X driver for the Freecom Traveller CD writer". * * $Source: /cvsroot/sis4be/usb_scsi/freecom/freecom.c,v $ * $Author: zharik $ * $Revision: 1.11 $ * $Date: 2005/04/01 22:39:23 $ * */ #include "usb_scsi.h" #include #include "device_info.h" #include "proto_module.h" #define FREECOM_MODULE_NAME "freecom" #define FREECOM_PROTOCOL_MODULE_NAME \ MODULE_PREFIX FREECOM_MODULE_NAME PROTOCOL_SUFFIX /** \struct:fcm_command */ typedef struct { uint8 type; /* block type. */ #define FCMT_ATAPI 0x21 /* send command. low bit indicates waiting for interrupt */ #define FCMT_STATUS 0x20 /* request for status */ #define FCMT_IN 0x81 /* request read data transfer. */ #define FCMT_OUT 0x01 /* request write data transfer */ #define FCMT_REG_IN 0xC0 /* read value from IDE regsiter */ #define FCMT_REG_OUT 0x40 /* write value to IDE register */ /* to use FCMT_REG_ combine it with register */ #define FCMR_DATA 0x10 /* data register */ /* ... */ #define FCMR_CMD 0x16 /* status-command register */ #define FCMR_INT 0x17 /* interrupt-error register */ uint8 timeout; /* timeout (seconds?) */ #define FCMTO_DEF 0xfe /* default timeout */ #define FCMTO_TU 0x14 /* timeout for TEST UNIT READY command */ union{ uint8 cmd[12]; /* An ATAPI command. */ uint32 count; /* number of bytes to transfer. */ uint16 reg_val; /* Value to write to IDE register. */ uint8 pad[62]; /* Padding Data. All FREECOM commands are 64 bytes long */ }_PACKED data; }_PACKED fcm_command; /** \struct:fcm_status */ typedef struct { uint8 status; #define FCMS_BSY 0x80 /* device is busy */ #define FCMS_DRDY 0x40 /* */ #define FCMS_DMA 0x20 /* */ #define FCMS_SEEK 0x10 /* */ #define FCMS_DRQ 0x08 /* */ #define FCMS_CORR 0x04 /* */ #define FCMS_INTR 0x02 /*FREECOM specific: use obsolete bit*/ #define FCMS_CHECK 0x01 /* device is in error condition */ uint8 reason; #define FCMR_REL 0x04 /* */ #define FCMR_IO 0x02 /* */ #define FCMR_COD 0x01 /* */ uint16 count; }_PACKED fcm_status; // Timeout value (in us) for the freecom packet USB BulkRead and BulkWrite functions #define FREECOM_USB_TIMEOUT 30000000 /** \fn: \param udi: \param st: \return: */ void trace_status(usb_device_info *udi, const fcm_status *st) { char ch_status[] = "BDFSRCIE"; char ch_reason[] = ".....RIC"; int i = 0; for(; i < 8; i++){ if(!(st->status & (1 << i))) ch_status[7 - i] = '.'; if(!(st->reason & (1 << i))) ch_reason[7 - i] = '.'; } PTRACE(udi, "FCM:Status:{%s; Reason:%s; Count:%d}\n", ch_status, ch_reason, st->count); } /** \fn:freecom_initialize \param udi: device on wich we should perform initialization \return:error code if initialization failed or B_OK if it passed initialize procedure. */ status_t freecom_initialize(usb_device_info *udi) { status_t status = B_OK; #define INIT_BUFFER_SIZE 0x20 char buffer[INIT_BUFFER_SIZE + 1]; size_t len = 0; if(B_OK != (status = (*udi->usb_m->send_request)(udi->device, USB_REQTYPE_DEVICE_IN | USB_REQTYPE_VENDOR, 0x4c, 0x4346, 0x0, INIT_BUFFER_SIZE, buffer, &len))) { PTRACE_ALWAYS(udi, "FCM:init[%d]: init failed: %08x\n", udi->dev_num, status); } else { buffer[len] = 0; PTRACE(udi, "FCM:init[%d]: init '%s' OK\n", udi->dev_num, buffer); } if(B_OK != (status = (*udi->usb_m->send_request)(udi->device, USB_REQTYPE_DEVICE_OUT | USB_REQTYPE_VENDOR, 0x4d, 0x24d8, 0x0, 0, 0, &len))) { PTRACE_ALWAYS(udi, "FCM:init[%d]: reset on failed:%08x\n", udi->dev_num, status); } snooze(250000); if(B_OK != (status = (*udi->usb_m->send_request)(udi->device, USB_REQTYPE_DEVICE_OUT | USB_REQTYPE_VENDOR, 0x4d, 0x24f8, 0x0, 0, 0, &len))) { PTRACE_ALWAYS(udi, "FCM:init[%d]: reset off failed:%08x\n", udi->dev_num, status); } snooze(3 * 1000000); return status; } /** \fn:freecom_reset \param udi: device on wich we should perform reset \return:error code if reset failed or B_OK if it passed reset procedure. */ status_t freecom_reset(usb_device_info *udi) { status_t status = B_OK; /* not required ? */ return status; } /** \fn:usb_callback \param cookie:??? \param status:??? \param data:??? \param actual_len:??? \return:??? ??? */ static void usb_callback(void *cookie, uint32 status, void *data, uint32 actual_len) { if(cookie){ usb_device_info *udi = (usb_device_info *)cookie; // PTRACE(udi, "FCM:usb_callback:status:0x%08x,len:%d\n", status, actual_len); udi->status = status; udi->data = data; udi->actual_len = actual_len; if(udi->status != B_CANCELED) release_sem(udi->trans_sem); } } /** \fn:queue_bulk \param udi: device for which que_bulk request is performed \param buffer: data buffer, used in bulk i/o operation \param len: length of data buffer \param b_in: is "true" if input (device->host) data transfer, "false" otherwise \return: status of operation. performs queue_bulk USB request for corresponding pipe and handle timeout of this operation. */ static status_t queue_bulk(usb_device_info *udi, void *buffer, size_t len, bool b_in) { status_t status = B_OK; usb_pipe pipe = b_in ? udi->pipe_in : udi->pipe_out; status = (*udi->usb_m->queue_bulk)(pipe, buffer, len, usb_callback, udi); if(status != B_OK){ PTRACE_ALWAYS(udi, "FCM:queue_bulk:failed:%08x\n", status); } else { status = acquire_sem_etc(udi->trans_sem, 1, B_RELATIVE_TIMEOUT, FREECOM_USB_TIMEOUT/*udi->trans_timeout*/); if(status != B_OK){ PTRACE_ALWAYS(udi, "FCM:queue_bulk:acquire_sem_etc failed:%08x\n", status); (*udi->usb_m->cancel_queued_transfers)(pipe); } } return status; } /** \fn:write_command */ static status_t write_command(usb_device_info *udi, uint8 type, uint8 *cmd, uint8 timeout) { status_t status = B_OK; /* initialize and fill in command block*/ fcm_command fc = { .type = type, .timeout = FCMTO_DEF, }; if(0 != cmd){ memcpy(fc.data.cmd, cmd, sizeof(fc.data.cmd)); if(cmd[0] == 0x00){ fc.timeout = FCMTO_TU; } } PTRACE(udi, "FCM:FC:{Type:0x%02x; TO:%d;}\n", fc.type, fc.timeout); udi->trace_bytes("FCM:FC::Cmd:\n", fc.data.cmd, sizeof(fc.data.cmd)); udi->trace_bytes("FCM:FC::Pad:\n", fc.data.pad, sizeof(fc.data.pad)); PTRACE(udi,"sizeof:%d\n",sizeof(fc)); /* send command block to device */ status = queue_bulk(udi, &fc, sizeof(fc), false); if(status != B_OK || udi->status != B_OK){ PTRACE_ALWAYS(udi, "FCM:write_command failed:status:%08x usb status:%08x\n", status, udi->status); //(*udi->protocol_m->reset)(udi); status = B_CMD_WIRE_FAILED; } return status; } /** \fn:read_status */ static status_t read_status(usb_device_info *udi, fcm_status *fst) { status_t status = B_OK; do{ /* cleanup structure */ memset(fst, 0, sizeof(fcm_status)); /* read status */ status = queue_bulk(udi, fst, sizeof(fcm_status), true); if(status != B_OK || udi->status != B_OK){ PTRACE_ALWAYS(udi, "FCM:read_status failed:" "status:%08x usb status:%08x\n", status, udi->status); //(*udi->protocol_m->reset)(udi); status = B_CMD_WIRE_FAILED; break; } if(udi->actual_len != sizeof(fcm_status)){ PTRACE_ALWAYS(udi, "FCM:read_status failed:requested:%d, readed %d bytes\n", sizeof(fcm_status), udi->actual_len); status = B_CMD_WIRE_FAILED; break; } /* trace readed status info. if required. */ trace_status(udi, fst); if(fst->status & FCMS_BSY){ PTRACE(udi, "FCM:read_status:Timeout. Poll device with another status request.\n"); if(B_OK != (status = write_command(udi, FCMT_STATUS, 0, 0))){ PTRACE_ALWAYS(udi, "FCM:read_status failed:write_command_block failed %08x\n", status); break; } } }while(fst->status & FCMS_BSY); /* repeat until device is busy */ return status; } /** \fn:request_transfer */ static status_t request_transfer(usb_device_info *udi, uint8 type, uint32 length, uint8 timeout) { status_t status = B_OK; /* initialize and fill in Command Block */ fcm_command fc = { .type = type, .timeout = timeout, }; fc.data.count = length; PTRACE(udi, "FCM:FC:{Type:0x%02x; TO:%d; Count:%d}\n", fc.type, fc.timeout, fc.data.count); udi->trace_bytes("FCM:FC::Pad:\n", fc.data.pad, sizeof(fc.data.pad)); PTRACE(udi,"sizeof:%d\n",sizeof(fc)); /* send command block to device */ status = queue_bulk(udi, &fc, sizeof(fc), false); if(status != B_OK || udi->status != B_OK){ PTRACE_ALWAYS(udi, "FCM:request_transfer:fc send failed:" "status:%08x usb status:%08x\n", status, udi->status); status = B_CMD_WIRE_FAILED; } return status; } /** \fn:transfer_sg \param udi: corresponding device \param data_sg: io vectors array with data to transfer \param sglist_count: count of entries in io vector array \param offset: \param block_len: \param b_in: direction of data transfer) */ static status_t transfer_sg(usb_device_info *udi, iovec *data_sg, int32 sglist_count, int32 offset, int32 *block_len, bool b_in) { status_t status = B_OK; usb_pipe pipe = (b_in) ? udi->pipe_in : udi->pipe_out; int32 off = offset; int32 to_xfer = *block_len; /* iterate through SG list */ int i = 0; for(; i < sglist_count && to_xfer > 0; i++) { if(off < data_sg[i].iov_len) { int len = min(to_xfer, (data_sg[i].iov_len - off)); char *buf = ((char *)data_sg[i].iov_base) + off; /* transfer linear block of data to/from device */ if(B_OK == (status = (*udi->usb_m->queue_bulk)(pipe, buf, len, usb_callback, udi))) { status = acquire_sem_etc(udi->trans_sem, 1, B_RELATIVE_TIMEOUT, FREECOM_USB_TIMEOUT); if(status == B_OK){ status = udi->status; if(B_OK != status){ PTRACE_ALWAYS(udi, "FCM:transfer_sg:usb transfer status is not OK:%08x\n", status); } } else { PTRACE_ALWAYS(udi, "FCM:transfer_sg:acquire_sem_etc failed:%08x\n", status); goto finalize; } } else { PTRACE_ALWAYS(udi, "FCM:transfer_sg:queue_bulk failed:%08x\n", status); goto finalize; } /*check amount of transferred data*/ if(len != udi->actual_len){ PTRACE_ALWAYS(udi, "FCM:transfer_sg:WARNING!!!Length reported:%d required:%d\n", udi->actual_len, len); } /* handle offset logic */ to_xfer -= len; off = 0; } else { off -= data_sg[i].iov_len; } } finalize: *block_len -= to_xfer; return (B_OK != status) ? B_CMD_WIRE_FAILED : status; } static status_t transfer_data(usb_device_info *udi, iovec *data_sg, int32 sglist_count, int32 transfer_length, int32 *residue, fcm_status *fst, bool b_in) { status_t status = B_OK; int32 readed_len = 0; uint8 xfer_type = b_in ? FCMT_IN : FCMT_OUT; int32 block_len = (FCMR_COD == (fst->reason & FCMR_COD)) ? transfer_length : fst->count; /* Strange situation with SCSIProbe - device say about 6 bytes available for 5-bytes request. Read 5 and all is ok. Hmm... */ if(block_len > transfer_length){ PTRACE_ALWAYS(udi, "FCM:transfer_data:TRUNCATED! " "requested:%d available:%d.\n", transfer_length, block_len); block_len = transfer_length; } /* iterate until data will be transferred */ while(readed_len < transfer_length && block_len > 0){ /* send transfer block */ if(B_OK != (status = request_transfer(udi, xfer_type, block_len, 0))){ goto finalize; } /* check if data available/awaited */ if(FCMS_DRQ != (fst->status & FCMS_DRQ)){ PTRACE_ALWAYS(udi, "FCM:transfer_data:device doesn't want to transfer anything!!!\n"); status = B_CMD_FAILED; goto finalize; } /* check the device state */ if(!((fst->reason & FCMR_REL) == 0 && (fst->reason & FCMR_IO) == ((b_in) ? FCMR_IO : 0) && (fst->reason & FCMR_COD) == 0)) { PTRACE_ALWAYS(udi, "FCM:transfer_data:device doesn't ready to transfer?\n"); status = B_CMD_FAILED; goto finalize; } /* transfer scatter/gather safely only for length bytes at specified offset */ if(B_OK != (status = transfer_sg(udi, data_sg, sglist_count, readed_len, &block_len, b_in))) { goto finalize; } /* read status */ if(B_OK != (status = read_status(udi, fst))){ goto finalize; } /* check device error state */ if(fst->status & FCMS_CHECK){ PTRACE(udi, "FCM:transfer_data:data transfer failed?\n", fst->status); status = B_CMD_FAILED; goto finalize; } /* we have read block successfully */ readed_len += block_len; /* check device state and amount of data */ if((fst->reason & FCMR_COD) == FCMR_COD){ #if 0 /* too frequently reported - disabled to prevent log trashing */ if(readed_len < transfer_length){ PTRACE_ALWAYS(udi, "FCM:transfer_data:Device had LESS data that we wanted.\n"); } #endif break; /* exit iterations - device has finished the talking */ } else { if(readed_len >= transfer_length){ PTRACE_ALWAYS(udi, "FCM:transfer_data:Device had MORE data that we wanted.\n"); break; } block_len = fst->count; /* still have something to read */ } } /* calculate residue */ *residue -= readed_len; if(*residue < 0) *residue = 0; /* TODO: consistency between SG length and transfer length - in MODE_SENSE_10 converted commands f.example ... */ PTRACE(udi,"FCM:transfer_data:was requested:%d, residue:%d\n", transfer_length, *residue); finalize: return status; /* TODO: MUST return B_CMD_*** error codes !!!!! */ } /** \fn:freecom_transfer \param udi: corresponding device \param cmd: SCSI command to be performed on USB device \param cmdlen: length of SCSI command \param data_sg: io vectors array with data to transfer \param sglist_count: count of entries in io vector array \param transfer_len: overall length of data to be transferred \param dir: direction of data transfer \param ccbio: CCB_SCSIIO struct for original SCSI command \param cb: callback to handle of final stage of command performing (autosense \ request etc.) transfer procedure for bulk-only protocol. Performs SCSI command on USB device [2] */ void freecom_transfer(usb_device_info *udi, uint8 *cmd, uint8 cmdlen, iovec*sg_data, int32 sg_count, int32 transfer_len, EDirection dir, CCB_SCSIIO *ccbio, ud_transfer_callback cb) { status_t command_status = B_CMD_WIRE_FAILED; int32 residue = transfer_len; fcm_status fst = {0}; /* Current BeOS scsi_cd driver bombs us with lot of MODE_SENSE/MODE_SELECT commands. Unfortunately some of them hangs the Freecom firmware. Try to ignore translated ones */ #if 1 if((MODE_SENSE_10 == cmd[0] && 0x0e == cmd[2]) || (MODE_SELECT_10 == cmd[0] && 0x10 == cmd[1])) { uint8 *cmd_org = 0; /* set the command data pointer */ if(ccbio->cam_ch.cam_flags & CAM_CDB_POINTER){ cmd_org = ccbio->cam_cdb_io.cam_cdb_ptr; }else{ cmd_org = ccbio->cam_cdb_io.cam_cdb_bytes; } if(cmd_org[0] != cmd[0]){ if(MODE_SENSE_10 == cmd[0]){ /* emulate answer - return empty pages */ scsi_mode_param_header_10 *hdr = (scsi_mode_param_header_10*)sg_data[0].iov_base; memset(hdr, 0, sizeof(scsi_mode_param_header_10)); hdr->mode_data_len[1] = 6; PTRACE(udi, "FCM:freecom_transfer:MODE_SENSE_10 ignored %02x->02x\n", cmd_org[0], cmd[0]); } else { PTRACE(udi, "FCM:freecom_transfer:MODE_SELECT_10 ignored %02x->%02x\n", cmd_org[0], cmd[0]); } command_status = B_OK; /* just say that command processed OK */ goto finalize; } } #endif /* write command to device */ if(B_OK != (command_status = write_command(udi, FCMT_ATAPI, cmd, 0))){ PTRACE_ALWAYS(udi, "FCM:freecom_transfer:" "send command failed. Status:%08x\n", command_status); goto finalize; } /* obtain status */ if(B_OK != (command_status = read_status(udi, &fst))){ PTRACE_ALWAYS(udi, "FCM:freecom_transfer:" "read status failed. Status:%08x\n", command_status); goto finalize; } /* check device error state */ if(fst.status & FCMS_CHECK){ PTRACE_ALWAYS(udi, "FCM:freecom_transfer:FST.Status is not OK\n"); command_status = B_CMD_FAILED; goto finalize; } /* transfer data */ if(transfer_len != 0x0 && dir != eDirNone){ command_status = transfer_data(udi, sg_data, sg_count, transfer_len, &residue, &fst, (eDirIn == dir)); } finalize: /* finalize transfer */ cb(udi, ccbio, residue, command_status); } static status_t std_ops(int32 op, ...) { switch(op) { case B_MODULE_INIT: return B_OK; case B_MODULE_UNINIT: return B_OK; default: return B_ERROR; } } static protocol_module_info freecom_protocol_module = { { FREECOM_PROTOCOL_MODULE_NAME, 0, std_ops }, freecom_initialize, freecom_reset, freecom_transfer, }; _EXPORT protocol_module_info *modules[] = { &freecom_protocol_module, NULL };