1/*
2 * \file: freecom.c
3 * \brief: USB SCSI module extention for Freecom USB/IDE adaptor
4 *
5 * This file is a part of BeOS USB SCSI interface module project.
6 * Copyright (c) 2003-2004 by Siarzhuk Zharski <imker@gmx.li>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2, or (at your option) any
11 * later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 675 Mass Ave, Cambridge, MA 02139, USA.
21 *
22 * This protocol extension module was developed using information from
23 * "Driver for Freecom USB/IDE adaptor" in Linux usb storage driver and
24 * "Mac OS X driver for the Freecom Traveller CD writer".
25 *
26 * $Source: /cvsroot/sis4be/usb_scsi/freecom/freecom.c,v $
27 * $Author: zharik $
28 * $Revision: 1.11 $
29 * $Date: 2005/04/01 22:39:23 $
30 *
31 */
32#include "usb_scsi.h"
33
34#include <malloc.h>
35#include "device_info.h"
36#include "proto_module.h"
37
38#define FREECOM_MODULE_NAME "freecom"
39#define FREECOM_PROTOCOL_MODULE_NAME \
40           MODULE_PREFIX FREECOM_MODULE_NAME PROTOCOL_SUFFIX
41
42/**
43  \struct:fcm_command
44*/
45typedef struct {
46  uint8 type;        /* block type.        */
47#define FCMT_ATAPI   0x21 /* send command. low bit indicates waiting for interrupt */
48#define FCMT_STATUS  0x20 /* request for status */
49#define FCMT_IN      0x81 /* request read data transfer. */
50#define FCMT_OUT     0x01 /* request write data transfer */
51#define FCMT_REG_IN  0xC0 /* read value from IDE regsiter */
52#define FCMT_REG_OUT 0x40 /* write value to IDE register */
53                          /* to use FCMT_REG_ combine it with register */
54#define  FCMR_DATA   0x10 /* data register */
55                          /* ... */
56#define  FCMR_CMD    0x16 /* status-command register */
57#define  FCMR_INT    0x17 /* interrupt-error register */
58  uint8 timeout;    /* timeout (seconds?) */
59#define  FCMTO_DEF  0xfe /* default timeout */
60#define  FCMTO_TU   0x14 /* timeout for TEST UNIT READY command */
61  union{
62    uint8  cmd[12]; /* An ATAPI command.  */
63    uint32 count;   /* number of bytes to transfer. */
64    uint16 reg_val; /* Value to write to IDE register. */
65    uint8  pad[62]; /* Padding Data. All FREECOM commands are 64 bytes long */
66  }_PACKED data;
67}_PACKED fcm_command;
68
69/**
70  \struct:fcm_status
71*/
72typedef struct {
73    uint8    status;
74#define FCMS_BSY   0x80 /* device is busy */
75#define FCMS_DRDY  0x40 /*  */
76#define FCMS_DMA   0x20 /*  */
77#define FCMS_SEEK  0x10 /*  */
78#define FCMS_DRQ   0x08 /*  */
79#define FCMS_CORR  0x04 /*  */
80#define FCMS_INTR  0x02 /*FREECOM specific: use obsolete bit*/
81#define FCMS_CHECK 0x01 /* device is in error condition */
82    uint8    reason;
83#define FCMR_REL   0x04 /*  */
84#define FCMR_IO    0x02 /*  */
85#define FCMR_COD   0x01 /*  */
86    uint16   count;
87}_PACKED fcm_status;
88
89// Timeout value (in us) for the freecom packet USB BulkRead and BulkWrite functions
90#define FREECOM_USB_TIMEOUT		30000000
91
92/**
93  \fn:
94  \param udi:
95  \param st:
96  \return:
97
98*/
99void trace_status(usb_device_info *udi, const fcm_status *st)
100{
101  char ch_status[] = "BDFSRCIE";
102  char ch_reason[] = ".....RIC";
103  int i = 0;
104  for(; i < 8; i++){
105    if(!(st->status & (1 << i)))
106      ch_status[7 - i] =  '.';
107    if(!(st->reason & (1 << i)))
108      ch_reason[7 - i] =  '.';
109  }
110  PTRACE(udi, "FCM:Status:{%s; Reason:%s; Count:%d}\n",
111                                       ch_status, ch_reason, st->count);
112}
113
114/**
115  \fn:freecom_initialize
116  \param udi: device on wich we should perform initialization
117  \return:error code if initialization failed or B_OK if it passed
118
119  initialize procedure.
120*/
121status_t
122freecom_initialize(usb_device_info *udi)
123{
124  status_t status = B_OK;
125#define INIT_BUFFER_SIZE 0x20
126  char buffer[INIT_BUFFER_SIZE + 1];
127  size_t len = 0;
128
129  if(B_OK != (status = (*udi->usb_m->send_request)(udi->device,
130                             USB_REQTYPE_DEVICE_IN | USB_REQTYPE_VENDOR,
131                             0x4c, 0x4346, 0x0, INIT_BUFFER_SIZE,
132                             buffer, &len)))
133  {
134    PTRACE_ALWAYS(udi, "FCM:init[%d]: init failed: %08x\n", udi->dev_num, status);
135  } else {
136    buffer[len] = 0;
137    PTRACE(udi, "FCM:init[%d]: init '%s' OK\n", udi->dev_num, buffer);
138  }
139
140  if(B_OK != (status = (*udi->usb_m->send_request)(udi->device,
141                             USB_REQTYPE_DEVICE_OUT | USB_REQTYPE_VENDOR,
142                             0x4d, 0x24d8, 0x0, 0, 0, &len)))
143  {
144    PTRACE_ALWAYS(udi, "FCM:init[%d]: reset on failed:%08x\n", udi->dev_num, status);
145  }
146
147  snooze(250000);
148
149  if(B_OK != (status = (*udi->usb_m->send_request)(udi->device,
150                             USB_REQTYPE_DEVICE_OUT | USB_REQTYPE_VENDOR,
151                             0x4d, 0x24f8, 0x0, 0, 0, &len)))
152  {
153    PTRACE_ALWAYS(udi, "FCM:init[%d]: reset off failed:%08x\n", udi->dev_num, status);
154  }
155
156  snooze(3 * 1000000);
157
158  return status;
159}
160/**
161  \fn:freecom_reset
162  \param udi: device on wich we should perform reset
163  \return:error code if reset failed or B_OK if it passed
164
165  reset procedure.
166*/
167status_t
168freecom_reset(usb_device_info *udi)
169{
170  status_t status = B_OK;
171  /* not required ? */
172  return status;
173}
174
175/**
176  \fn:usb_callback
177  \param cookie:???
178  \param status:???
179  \param data:???
180  \param actual_len:???
181  \return:???
182
183  ???
184*/
185static void usb_callback(void  *cookie,
186                   uint32 status,
187                   void  *data,
188                   uint32 actual_len)
189{
190  if(cookie){
191    usb_device_info *udi = (usb_device_info *)cookie;
192//  PTRACE(udi, "FCM:usb_callback:status:0x%08x,len:%d\n", status, actual_len);
193    udi->status = status;
194    udi->data = data;
195    udi->actual_len = actual_len;
196    if(udi->status != B_CANCELED)
197      release_sem(udi->trans_sem);
198  }
199}
200
201/**
202  \fn:queue_bulk
203  \param udi: device for which que_bulk request is performed
204  \param buffer: data buffer, used in bulk i/o operation
205  \param len: length of data buffer
206  \param b_in: is "true" if input (device->host) data transfer, "false" otherwise
207  \return: status of operation.
208
209  performs queue_bulk USB request for corresponding pipe and handle timeout of this
210  operation.
211*/
212static status_t
213queue_bulk(usb_device_info *udi,
214                     void  *buffer,
215                     size_t len,
216                     bool   b_in)
217{
218  status_t status = B_OK;
219  usb_pipe pipe = b_in ? udi->pipe_in : udi->pipe_out;
220  status = (*udi->usb_m->queue_bulk)(pipe, buffer, len, usb_callback, udi);
221  if(status != B_OK){
222    PTRACE_ALWAYS(udi, "FCM:queue_bulk:failed:%08x\n", status);
223  } else {
224    status = acquire_sem_etc(udi->trans_sem, 1, B_RELATIVE_TIMEOUT, FREECOM_USB_TIMEOUT/*udi->trans_timeout*/);
225    if(status != B_OK){
226      PTRACE_ALWAYS(udi, "FCM:queue_bulk:acquire_sem_etc failed:%08x\n", status);
227      (*udi->usb_m->cancel_queued_transfers)(pipe);
228    }
229  }
230  return status;
231}
232
233/**
234  \fn:write_command
235*/
236static status_t
237write_command(usb_device_info *udi, uint8 type, uint8 *cmd, uint8 timeout)
238{
239  status_t status = B_OK;
240  /* initialize and fill in command block*/
241  fcm_command fc = {
242    .type    = type,
243    .timeout = FCMTO_DEF,
244  };
245  if(0 != cmd){
246    memcpy(fc.data.cmd, cmd, sizeof(fc.data.cmd));
247    if(cmd[0] == 0x00){
248      fc.timeout = FCMTO_TU;
249    }
250  }
251  PTRACE(udi, "FCM:FC:{Type:0x%02x; TO:%d;}\n", fc.type, fc.timeout);
252  udi->trace_bytes("FCM:FC::Cmd:\n",  fc.data.cmd, sizeof(fc.data.cmd));
253  udi->trace_bytes("FCM:FC::Pad:\n",  fc.data.pad, sizeof(fc.data.pad));
254PTRACE(udi,"sizeof:%d\n",sizeof(fc));
255  /* send command block to device */
256  status = queue_bulk(udi, &fc, sizeof(fc), false);
257  if(status != B_OK || udi->status != B_OK){
258    PTRACE_ALWAYS(udi, "FCM:write_command failed:status:%08x usb status:%08x\n",
259                                                                      status, udi->status);
260    //(*udi->protocol_m->reset)(udi);
261    status = B_CMD_WIRE_FAILED;
262  }
263  return status;
264}
265
266/**
267  \fn:read_status
268*/
269static status_t
270read_status(usb_device_info *udi, fcm_status *fst)
271{
272  status_t status = B_OK;
273  do{
274    /* cleanup structure */
275    memset(fst, 0, sizeof(fcm_status));
276    /* read status */
277    status = queue_bulk(udi, fst, sizeof(fcm_status), true);
278    if(status != B_OK || udi->status != B_OK){
279      PTRACE_ALWAYS(udi, "FCM:read_status failed:"
280                            "status:%08x usb status:%08x\n", status, udi->status);
281      //(*udi->protocol_m->reset)(udi);
282      status = B_CMD_WIRE_FAILED;
283      break;
284    }
285    if(udi->actual_len != sizeof(fcm_status)){
286      PTRACE_ALWAYS(udi, "FCM:read_status failed:requested:%d, readed %d bytes\n",
287                                         sizeof(fcm_status), udi->actual_len);
288      status = B_CMD_WIRE_FAILED;
289      break;
290    }
291    /* trace readed status info. if required. */
292    trace_status(udi, fst);
293    if(fst->status & FCMS_BSY){
294      PTRACE(udi, "FCM:read_status:Timeout. Poll device with another status request.\n");
295      if(B_OK != (status = write_command(udi, FCMT_STATUS, 0, 0))){
296        PTRACE_ALWAYS(udi, "FCM:read_status failed:write_command_block failed %08x\n", status);
297        break;
298      }
299    }
300  }while(fst->status & FCMS_BSY); /* repeat until device is busy */
301  return status;
302}
303
304/**
305  \fn:request_transfer
306*/
307static status_t
308request_transfer(usb_device_info *udi, uint8 type, uint32 length, uint8 timeout)
309{
310  status_t status = B_OK;
311  /* initialize and fill in Command Block */
312  fcm_command fc = {
313    .type    = type,
314    .timeout = timeout,
315  };
316  fc.data.count = length;
317
318  PTRACE(udi, "FCM:FC:{Type:0x%02x; TO:%d; Count:%d}\n", fc.type, fc.timeout, fc.data.count);
319  udi->trace_bytes("FCM:FC::Pad:\n",  fc.data.pad, sizeof(fc.data.pad));
320PTRACE(udi,"sizeof:%d\n",sizeof(fc));
321  /* send command block to device */
322  status = queue_bulk(udi, &fc, sizeof(fc), false);
323  if(status != B_OK || udi->status != B_OK){
324    PTRACE_ALWAYS(udi, "FCM:request_transfer:fc send failed:"
325                                 "status:%08x usb status:%08x\n", status, udi->status);
326    status = B_CMD_WIRE_FAILED;
327  }
328  return status;
329}
330
331
332/**
333  \fn:transfer_sg
334  \param udi: corresponding device
335  \param data_sg: io vectors array with data to transfer
336  \param sglist_count: count of entries in io vector array
337  \param offset:
338  \param block_len:
339  \param b_in: direction of data transfer)
340
341
342*/
343static status_t
344transfer_sg(usb_device_info *udi,
345                      iovec *data_sg,
346                      int32  sglist_count,
347                      int32  offset,
348                      int32 *block_len,
349                      bool   b_in)
350{
351  status_t status = B_OK;
352  usb_pipe pipe = (b_in) ? udi->pipe_in : udi->pipe_out;
353  int32 off = offset;
354  int32 to_xfer = *block_len;
355  /* iterate through SG list */
356  int i = 0;
357  for(; i < sglist_count && to_xfer > 0; i++) {
358    if(off < data_sg[i].iov_len) {
359      int len = min(to_xfer, (data_sg[i].iov_len - off));
360      char *buf = ((char *)data_sg[i].iov_base) + off;
361      /* transfer linear block of data to/from device */
362      if(B_OK == (status = (*udi->usb_m->queue_bulk)(pipe, buf, len, usb_callback, udi))) {
363        status = acquire_sem_etc(udi->trans_sem, 1, B_RELATIVE_TIMEOUT, FREECOM_USB_TIMEOUT);
364        if(status == B_OK){
365          status = udi->status;
366          if(B_OK != status){
367            PTRACE_ALWAYS(udi, "FCM:transfer_sg:usb transfer status is not OK:%08x\n", status);
368          }
369        } else {
370          PTRACE_ALWAYS(udi, "FCM:transfer_sg:acquire_sem_etc failed:%08x\n", status);
371          goto finalize;
372        }
373      } else {
374        PTRACE_ALWAYS(udi, "FCM:transfer_sg:queue_bulk failed:%08x\n", status);
375        goto finalize;
376      }
377      /*check amount of transferred data*/
378      if(len != udi->actual_len){
379        PTRACE_ALWAYS(udi, "FCM:transfer_sg:WARNING!!!Length reported:%d required:%d\n",
380                                                                      udi->actual_len, len);
381      }
382      /* handle offset logic */
383      to_xfer -= len;
384      off = 0;
385    } else {
386      off -= data_sg[i].iov_len;
387    }
388  }
389finalize:
390  *block_len -= to_xfer;
391  return (B_OK != status) ? B_CMD_WIRE_FAILED : status;
392}
393
394static status_t
395transfer_data(usb_device_info *udi,
396                        iovec *data_sg,
397                        int32  sglist_count,
398                        int32  transfer_length,
399                        int32 *residue,
400                        fcm_status *fst,
401                        bool   b_in)
402{
403  status_t status  = B_OK;
404  int32 readed_len = 0;
405  uint8 xfer_type  = b_in ? FCMT_IN : FCMT_OUT;
406  int32 block_len  = (FCMR_COD == (fst->reason & FCMR_COD)) ?
407                                                         transfer_length : fst->count;
408  /* Strange situation with SCSIProbe - device say about 6 bytes available for 5-bytes
409     request. Read 5 and all is ok. Hmm... */
410  if(block_len > transfer_length){
411    PTRACE_ALWAYS(udi, "FCM:transfer_data:TRUNCATED! "
412                    "requested:%d available:%d.\n", transfer_length, block_len);
413    block_len  = transfer_length;
414  }
415  /* iterate until data will be transferred */
416  while(readed_len < transfer_length && block_len > 0){
417    /* send transfer block */
418    if(B_OK != (status = request_transfer(udi, xfer_type, block_len, 0))){
419      goto finalize;
420    }
421    /* check if data available/awaited */
422    if(FCMS_DRQ != (fst->status & FCMS_DRQ)){
423      PTRACE_ALWAYS(udi, "FCM:transfer_data:device doesn't want to transfer anything!!!\n");
424      status = B_CMD_FAILED;
425      goto finalize;
426    }
427    /* check the device state */
428    if(!((fst->reason & FCMR_REL) == 0 &&
429         (fst->reason & FCMR_IO)      == ((b_in) ? FCMR_IO : 0) &&
430         (fst->reason & FCMR_COD)     == 0))
431    {
432      PTRACE_ALWAYS(udi, "FCM:transfer_data:device doesn't ready to transfer?\n");
433      status = B_CMD_FAILED;
434      goto finalize;
435    }
436    /* transfer scatter/gather safely only for length bytes at specified offset */
437    if(B_OK != (status = transfer_sg(udi, data_sg,
438                                     sglist_count, readed_len, &block_len, b_in)))
439    {
440      goto finalize;
441    }
442    /* read status */
443    if(B_OK != (status = read_status(udi, fst))){
444      goto finalize;
445    }
446    /* check device error state */
447    if(fst->status & FCMS_CHECK){
448       PTRACE(udi, "FCM:transfer_data:data transfer failed?\n", fst->status);
449       status = B_CMD_FAILED;
450       goto finalize;
451    }
452    /* we have read block successfully */
453    readed_len += block_len;
454    /* check device state and amount of data */
455    if((fst->reason & FCMR_COD) == FCMR_COD){
456#if 0 /* too frequently reported - disabled to prevent log trashing */
457      if(readed_len < transfer_length){
458        PTRACE_ALWAYS(udi, "FCM:transfer_data:Device had LESS data that we wanted.\n");
459      }
460#endif
461      break; /* exit iterations - device has finished the talking */
462    } else {
463      if(readed_len >= transfer_length){
464        PTRACE_ALWAYS(udi, "FCM:transfer_data:Device had MORE data that we wanted.\n");
465        break;
466      }
467      block_len = fst->count; /* still have something to read */
468    }
469  }
470  /* calculate residue */
471  *residue -= readed_len;
472  if(*residue < 0)
473    *residue = 0; /* TODO: consistency between SG length and transfer length -
474                           in MODE_SENSE_10 converted commands f.example ... */
475  PTRACE(udi,"FCM:transfer_data:was requested:%d, residue:%d\n", transfer_length, *residue);
476finalize:
477  return status; /* TODO: MUST return B_CMD_*** error codes !!!!! */
478}
479
480/**
481  \fn:freecom_transfer
482  \param udi: corresponding device
483  \param cmd: SCSI command to be performed on USB device
484  \param cmdlen: length of SCSI command
485  \param data_sg: io vectors array with data to transfer
486  \param sglist_count: count of entries in io vector array
487  \param transfer_len: overall length of data to be transferred
488  \param dir: direction of data transfer
489  \param ccbio: CCB_SCSIIO struct for original SCSI command
490  \param cb: callback to handle of final stage of command performing (autosense \
491             request etc.)
492
493  transfer procedure for bulk-only protocol. Performs  SCSI command on USB device
494  [2]
495*/
496void
497freecom_transfer(usb_device_info *udi,
498                             uint8 *cmd,
499                             uint8  cmdlen,
500                             iovec*sg_data,
501                             int32 sg_count,
502                             int32  transfer_len,
503                        EDirection  dir,
504                        CCB_SCSIIO *ccbio,
505               ud_transfer_callback cb)
506{
507  status_t command_status = B_CMD_WIRE_FAILED;
508  int32 residue           = transfer_len;
509  fcm_status   fst = {0};
510
511  /* Current BeOS scsi_cd driver bombs us with lot of
512     MODE_SENSE/MODE_SELECT commands. Unfortunately some of them
513     hangs the Freecom firmware. Try to ignore translated ones */
514#if 1
515  if((MODE_SENSE_10  == cmd[0] && 0x0e == cmd[2]) ||
516     (MODE_SELECT_10 == cmd[0] && 0x10 == cmd[1]))
517  {
518    uint8 *cmd_org = 0;
519     /* set the command data pointer */
520    if(ccbio->cam_ch.cam_flags & CAM_CDB_POINTER){
521      cmd_org = ccbio->cam_cdb_io.cam_cdb_ptr;
522    }else{
523      cmd_org = ccbio->cam_cdb_io.cam_cdb_bytes;
524    }
525    if(cmd_org[0] != cmd[0]){
526      if(MODE_SENSE_10 == cmd[0]){ /* emulate answer - return empty pages */
527        scsi_mode_param_header_10 *hdr = (scsi_mode_param_header_10*)sg_data[0].iov_base;
528        memset(hdr, 0, sizeof(scsi_mode_param_header_10));
529        hdr->mode_data_len[1] = 6;
530        PTRACE(udi, "FCM:freecom_transfer:MODE_SENSE_10 ignored %02x->02x\n", cmd_org[0], cmd[0]);
531      } else {
532        PTRACE(udi, "FCM:freecom_transfer:MODE_SELECT_10 ignored %02x->%02x\n", cmd_org[0], cmd[0]);
533      }
534      command_status = B_OK; /* just say that command processed OK */
535      goto finalize;
536    }
537  }
538#endif
539  /* write command to device */
540  if(B_OK != (command_status = write_command(udi, FCMT_ATAPI, cmd, 0))){
541    PTRACE_ALWAYS(udi, "FCM:freecom_transfer:"
542                             "send command failed. Status:%08x\n", command_status);
543    goto finalize;
544  }
545  /* obtain status */
546  if(B_OK != (command_status = read_status(udi, &fst))){
547    PTRACE_ALWAYS(udi, "FCM:freecom_transfer:"
548                            "read status failed. Status:%08x\n", command_status);
549    goto finalize;
550  }
551  /* check device error state */
552  if(fst.status & FCMS_CHECK){
553    PTRACE_ALWAYS(udi, "FCM:freecom_transfer:FST.Status is not OK\n");
554    command_status = B_CMD_FAILED;
555    goto finalize;
556  }
557  /* transfer data */
558  if(transfer_len != 0x0 && dir != eDirNone){
559    command_status = transfer_data(udi, sg_data, sg_count, transfer_len,
560                                                   &residue, &fst, (eDirIn == dir));
561  }
562finalize:
563  /* finalize transfer */
564  cb(udi, ccbio, residue, command_status);
565}
566
567static status_t
568std_ops(int32 op, ...)
569{
570  switch(op) {
571  case B_MODULE_INIT:
572    return B_OK;
573  case B_MODULE_UNINIT:
574    return B_OK;
575  default:
576    return B_ERROR;
577  }
578}
579
580static protocol_module_info freecom_protocol_module = {
581  { FREECOM_PROTOCOL_MODULE_NAME, 0, std_ops },
582  freecom_initialize,
583  freecom_reset,
584  freecom_transfer,
585};
586
587_EXPORT protocol_module_info *modules[] = {
588  &freecom_protocol_module,
589  NULL
590};
591