xref: /haiku/src/add-ons/kernel/busses/scsi/usb/freecom/freecom.c (revision b3d94504c208adafebbf2ba62ae7fc4e0b7d68f7)
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 */
45 typedef 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 */
72 typedef 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 */
trace_status(usb_device_info * udi,const fcm_status * st)99 void 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 */
121 status_t
freecom_initialize(usb_device_info * udi)122 freecom_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 */
167 status_t
freecom_reset(usb_device_info * udi)168 freecom_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 */
usb_callback(void * cookie,uint32 status,void * data,uint32 actual_len)185 static 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 */
212 static status_t
queue_bulk(usb_device_info * udi,void * buffer,size_t len,bool b_in)213 queue_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 */
236 static status_t
write_command(usb_device_info * udi,uint8 type,uint8 * cmd,uint8 timeout)237 write_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));
254 PTRACE(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 */
269 static status_t
read_status(usb_device_info * udi,fcm_status * fst)270 read_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 */
307 static status_t
request_transfer(usb_device_info * udi,uint8 type,uint32 length,uint8 timeout)308 request_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));
320 PTRACE(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 */
343 static status_t
transfer_sg(usb_device_info * udi,iovec * data_sg,int32 sglist_count,int32 offset,int32 * block_len,bool b_in)344 transfer_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   }
389 finalize:
390   *block_len -= to_xfer;
391   return (B_OK != status) ? B_CMD_WIRE_FAILED : status;
392 }
393 
394 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)395 transfer_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);
476 finalize:
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 */
496 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)497 freecom_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   }
562 finalize:
563   /* finalize transfer */
564   cb(udi, ccbio, residue, command_status);
565 }
566 
567 static status_t
std_ops(int32 op,...)568 std_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 
580 static 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