[sane-devel] Fujitsu USB patches

Ron Cemer ron@roncemer.com
Thu, 13 Feb 2003 15:40:44 -0800


This is a multi-part message in MIME format.
--------------040803030607030006060400
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Here are updated patches that get the Fujitsu fi-4220C scanner working, 
and add USB support for Fujitsu scanners.

It now supports libusb or regular usb scanner devices, as described in 
the sane-usb man page.

It uses SCSI commands over USB, using a non-standard implementation of 
the USB mass storage protocol.

Thanks to Henning for various suggestions to get it in line with the 
SANE standards! :-)

I'm sending this to both the sane-devel mailing list and the current 
Fujitsu backend maintaner, in hopes that the changes will be implemented 
as quickly as possible.

Code formatting may not be exactly as you would expect, so please feel 
free to use indent to correct it if you like.

Thanks!
Ron Cemer


--------------040803030607030006060400
Content-Type: text/plain;
 name="fi-4220C-USB.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="fi-4220C-USB.diff"

--- ./backend/Makefile.in.orig	2003-01-13 11:49:46.000000000 -0800
+++ ./backend/Makefile.in	2003-02-12 14:00:17.000000000 -0800
@@ -302,6 +302,7 @@
 libsane-fujitsu.la: ../sanei/sanei_config2.lo
 libsane-fujitsu.la: ../sanei/sanei_constrain_value.lo
 libsane-fujitsu.la: ../sanei/sanei_scsi.lo
+libsane-fujitsu.la: ../sanei/sanei_usb.lo
 libsane-gphoto2.la: ../sanei/sanei_constrain_value.lo djpeg.lo
 libsane-gt68xx.la: ../sanei/sanei_constrain_value.lo
 libsane-gt68xx.la: ../sanei/sanei_usb.lo
--- ./backend/fujitsu-scsi.h.orig	2002-09-16 05:19:52.000000000 -0700
+++ ./backend/fujitsu-scsi.h	2003-02-12 14:00:17.000000000 -0800
@@ -230,6 +230,16 @@
 static scsiblk set_windowB = { set_windowC, sizeof (set_windowC) };
 #define set_SW_xferlen(sb, len) putnbyte(sb + 0x06, len, 3)
 
+	/* With the fi-series scanners, we have to use a 12-byte command
+	 * instead of a 10-byte command when communicating via USB.  This
+	 * may be a firmware bug. */
+static unsigned char set_usb_windowC[] =
+  { SET_WINDOW, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00 };
+/* opcode,  lun,  _____4 X reserved____,  transfer length, control byte */
+static scsiblk set_usb_windowB = { set_usb_windowC, sizeof (set_usb_windowC) };
+#define set_SW_xferlen(sb, len) putnbyte(sb + 0x06, len, 3)
+
 /* ==================================================================== */
 
 static unsigned char object_positionC[] =
@@ -378,6 +388,17 @@
 };
 
 
+	/* With the fi-series scanners, we have to use a 10-byte header
+	 * instead of a 4-byte header when communicating via USB.  This
+	 * may be a firmware bug. */
+static unsigned char mode_select_usb_headerC[] = {
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static scsiblk mode_select_usb_headerB = {
+  mode_select_usb_headerC, sizeof (mode_select_usb_headerC)
+};
+
+
 static unsigned char mode_select_parameter_blockC[] = {
   0x00, 0x00, 0x00,
   0x00, 0x00, 0x00,
--- ./backend/fujitsu.c.orig	2002-09-16 05:19:52.000000000 -0700
+++ ./backend/fujitsu.c	2003-02-13 15:03:17.000000000 -0800
@@ -77,6 +77,10 @@
          - 3092 support (mgoppold@tbz-pariv.de)
          - tested 4097 support
          - changed some functions to receive compressed data
+      V 1.4, 13-Feb-2003
+         - fi-4220C support (ron@roncemer.com)
+         - USB support for scanners which send SCSI commands over usb
+           (ron@roncemer.com)
 
    SANE FLOW DIAGRAM
 
@@ -124,6 +128,7 @@
 
 #include "sane/sanei_backend.h"
 #include "sane/sanei_scsi.h"
+#include "sane/sanei_usb.h"
 #include "sane/saneopts.h"
 #include "sane/sanei_config.h"
 
@@ -338,6 +343,11 @@
 static const SANE_Device **devlist = 0;
 
 /*
+ * used by attachScanner and attachOne
+ */
+static Fujitsu_Connection_Type mostRecentConfigConnectionType = SANE_FUJITSU_SCSI;
+
+/*
  * @@ Section 2 - SANE Interface
  */
 
@@ -364,11 +374,14 @@
   size_t len;
   FILE *fp;
 
+  mostRecentConfigConnectionType = SANE_FUJITSU_SCSI;
   authorize = authorize;        /* get rid of compiler warning */
 
   DBG_INIT ();
   DBG (10, "sane_init\n");
 
+  sanei_usb_init();
+
   if (version_code)
     *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0);
   fp = sanei_config_open (FUJITSU_CONFIG_FILE);
@@ -393,6 +406,7 @@
 
   while (sanei_config_read (line, PATH_MAX, fp))
     {
+      int vendor, product;
 
       /* ignore comments */
       if (line[0] == '#')
@@ -457,11 +471,23 @@
                    lp);
             }
         }
-      else                      /* must be a device name if it's not an option */
+        else if (sscanf(lp, "usb %i %i", &vendor, &product) == 2)
         {
+	    mostRecentConfigConnectionType = SANE_FUJITSU_USB;
+	    sanei_usb_attach_matching_devices(lp, attachOne);
+	    mostRecentConfigConnectionType = SANE_FUJITSU_SCSI;
+        }
+        else                      /* must be a device name if it's not an option */
+        {
+	  if ((strncmp ("usb", lp, 3) == 0) && isspace (lp[3])) {
+	    lp += 3;
+	    lp = sanei_config_skip_whitespace (lp);
+	    mostRecentConfigConnectionType = SANE_FUJITSU_USB;
+	  }
           strncpy (devName, lp, sizeof (devName));
           devName[sizeof (devName) - 1] = '\0';
           sanei_config_attach_matching_devices (devName, attachOne);
+	  mostRecentConfigConnectionType = SANE_FUJITSU_SCSI;
         }
     }
   fclose (fp);
@@ -568,7 +594,10 @@
     case MODEL_3093:
     case MODEL_4097:
     case MODEL_FI:
-      setDefaults3096 (scanner);
+      if ( strstr (scanner->productName, "4220") ) 
+        setDefaults3091 (scanner);
+      else
+        setDefaults3096 (scanner);
       break;
 
     case MODEL_SP15:
@@ -1351,7 +1380,10 @@
             case MODEL_3097:
             case MODEL_4097:
             case MODEL_FI:
-              return (setMode3096 (scanner, newMode));
+    		if ( strstr (scanner->productName, "4220") ) 
+            	    return (setMode3091 (scanner, newMode));
+    		else
+            	    return (setMode3096 (scanner, newMode));
             case MODEL_SP15:
               return (setModeSP15 (scanner, newMode));
             }
@@ -1871,23 +1903,35 @@
   if (scanner->sfd < 0)
     {
       /* first call */
-      if (sanei_scsi_open (scanner->sane.name, &(scanner->sfd),
-                           senseHandler, 0) != SANE_STATUS_GOOD)
-        {
-          DBG (MSG_ERR, 
-               "sane_start: open of %s failed:\n", scanner->sane.name);
-          return SANE_STATUS_INVAL;
-        }
+      if (scanner->connection == SANE_FUJITSU_USB) {
+	    DBG (10, "sane_start opening USB device\n");
+	    if (sanei_usb_open (scanner->sane.name, &(scanner->sfd)) !=
+		SANE_STATUS_GOOD) {
+		DBG (MSG_ERR, 
+		     "sane_start: open of %s failed:\n", scanner->sane.name);
+		return SANE_STATUS_INVAL;
+	    }
+      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+	    DBG (10, "sane_start opening SCSI device\n");
+	    if (sanei_scsi_open (scanner->sane.name, &(scanner->sfd),
+				 scsiSenseHandler, 0) != SANE_STATUS_GOOD) {
+		DBG (MSG_ERR, 
+		     "sane_start: open of %s failed:\n", scanner->sane.name);
+		return SANE_STATUS_INVAL;
+	    }
+      }
     }
   scanner->object_count = 1;
   scanner->eof = SANE_FALSE;
 
-
-
   if ((ret = grabScanner (scanner)))
     {
       DBG (5, "sane_start: unable to reserve scanner\n");
-      sanei_scsi_close (scanner->sfd);
+      if (scanner->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (scanner->sfd);
+      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (scanner->sfd);
+      }
       scanner->object_count = 0;
       scanner->sfd = -1;
       return ret;
@@ -1907,7 +1951,11 @@
     {
       DBG (5, "sane_start: ERROR: failed to start send command\n");
       freeScanner (scanner);
-      sanei_scsi_close (scanner->sfd);
+      if (scanner->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (scanner->sfd);
+      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (scanner->sfd);
+      }
       scanner->object_count = 0;
       scanner->sfd = -1;
       return ret;
@@ -1917,7 +1965,11 @@
     {
       DBG (5, "sane_start: ERROR: failed to start imprinter command\n");
       freeScanner (scanner);
-      sanei_scsi_close (scanner->sfd);
+      if (scanner->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (scanner->sfd);
+      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (scanner->sfd);
+      }
       scanner->object_count = 0;
       scanner->sfd = -1;
       return ret;
@@ -1929,7 +1981,11 @@
     {
       DBG (5, "sane_start: WARNING: ADF empty\n");
       freeScanner (scanner);
-      sanei_scsi_close (scanner->sfd);
+      if (scanner->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (scanner->sfd);
+      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (scanner->sfd);
+      }
       scanner->object_count = 0;
       scanner->sfd = -1;
       return ret;
@@ -1942,7 +1998,11 @@
     {
       DBG (5, "sane_start: ERROR: failed to set window\n");
       freeScanner (scanner);
-      sanei_scsi_close (scanner->sfd);
+      if (scanner->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (scanner->sfd);
+      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (scanner->sfd);
+      }
       scanner->object_count = 0;
       scanner->sfd = -1;
       return ret;
@@ -1965,7 +2025,11 @@
       DBG (MSG_ERR, "ERROR: could not create pipe\n");
       scanner->object_count = 0;
       freeScanner (scanner);
-      sanei_scsi_close (scanner->sfd);
+      if (scanner->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (scanner->sfd);
+      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (scanner->sfd);
+      }
       scanner->sfd = -1;
       return SANE_STATUS_IO_ERROR;
     }
@@ -1980,7 +2044,11 @@
               DBG (MSG_ERR, "ERROR: could not create temporary file.\n");
               scanner->object_count = 0;
               freeScanner (scanner);
-              sanei_scsi_close (scanner->sfd);
+	      if (scanner->connection == SANE_FUJITSU_USB) {
+		    sanei_usb_close (scanner->sfd);
+	      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+		    sanei_scsi_close (scanner->sfd);
+	      }
               scanner->sfd = -1;
               return SANE_STATUS_IO_ERROR;
             }
@@ -1992,7 +2060,11 @@
               DBG (MSG_ERR, "ERROR: could not create duplex pipe.\n");
               scanner->object_count = 0;
               freeScanner (scanner);
-              sanei_scsi_close (scanner->sfd);
+	      if (scanner->connection == SANE_FUJITSU_USB) {
+		    sanei_usb_close (scanner->sfd);
+	      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+		    sanei_scsi_close (scanner->sfd);
+	      }
               scanner->sfd = -1;
               return SANE_STATUS_IO_ERROR;
             }
@@ -2294,7 +2366,7 @@
 attachScanner (const char *devicename, struct fujitsu **devp)
 {
   struct fujitsu *dev;
-  int sfd;
+  SANE_Int sfd;
 
   DBG (15, "attach_scanner: %s\n", devicename);
 
@@ -2312,11 +2384,19 @@
     }
 
   DBG (15, "attach_scanner: opening %s\n", devicename);
-  if (sanei_scsi_open (devicename, &sfd, senseHandler, 0) != 0)
-    {
-      DBG (5, "attach_scanner: open failed\n");
-      return SANE_STATUS_INVAL;
+  if (mostRecentConfigConnectionType == SANE_FUJITSU_USB) {
+    DBG (15, "attachScanner opening USB device\n");
+    if (sanei_usb_open (devicename, &sfd) != SANE_STATUS_GOOD) {
+        DBG (5, "attach_scanner: open failed\n");
+	return SANE_STATUS_INVAL;
+    }
+  } else if (mostRecentConfigConnectionType == SANE_FUJITSU_SCSI) {
+    DBG (15, "attachScanner opening SCSI device\n");
+    if (sanei_scsi_open (devicename, &sfd, scsiSenseHandler, 0) != 0) {
+	DBG (5, "attach_scanner: open failed\n");
+	return SANE_STATUS_INVAL;
     }
+  }
 
   if (NULL == (dev = malloc (sizeof (*dev))))
     return SANE_STATUS_NO_MEM;
@@ -2327,6 +2407,7 @@
     return SANE_STATUS_NO_MEM;
 
   dev->devicename = strdup (devicename);
+  dev->connection = mostRecentConfigConnectionType;
   dev->sfd = sfd;
 
   /*
@@ -2335,14 +2416,22 @@
   if (identifyScanner (dev) != 0)
     {
       DBG (5, "attach_scanner: scanner identification failed\n");
-      sanei_scsi_close (dev->sfd);
+      if (dev->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (dev->sfd);
+      } else if (dev->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (dev->sfd);
+      }
       free (dev->buffer);
       free (dev);
       return SANE_STATUS_INVAL;
     }
 
   /* Why? */
-  sanei_scsi_close (dev->sfd);
+  if (dev->connection == SANE_FUJITSU_USB) {
+	sanei_usb_close (dev->sfd);
+  } else if (dev->connection == SANE_FUJITSU_SCSI) {
+	sanei_scsi_close (dev->sfd);
+  }
   dev->sfd = -1;
 
   dev->sane.name = dev->devicename;
@@ -2380,7 +2469,7 @@
  * Responsible for producing a meaningful debug message.
  */
 static SANE_Status
-senseHandler (int scsi_fd, u_char * sensed_data, void *arg)
+scsiSenseHandler (int scsi_fd, u_char * sensed_data, void *arg)
 {
   unsigned int ret = SANE_STATUS_IO_ERROR;
   unsigned int sense = get_RS_sense_key (sensed_data);
@@ -2615,7 +2704,8 @@
  
   hexdump (MSG_IO, "inquiry", inquiryB.cmd, inquiryB.size);
 
-  do_scsi_cmd (s->sfd, inquiryB.cmd, inquiryB.size, s->buffer, 96, NULL);
+  do_cmd (s->connection, s->sfd, inquiryB.cmd, inquiryB.size,
+	       s->buffer, 96, NULL);
 }
 
 static SANE_Status
@@ -2631,26 +2721,37 @@
     {
       int sfd;
 
-      if (sanei_scsi_open (s->devicename, &sfd, senseHandler, 0) != 0)
-        {
-          DBG (5, "get_hardware_status: open failed\n");
-          return SANE_STATUS_INVAL;
-        }
-      
+      if (s->connection == SANE_FUJITSU_USB) {
+	    DBG (10, "get_hardware_status opening USB device\n");
+	    if (sanei_usb_open (s->devicename, &sfd) != SANE_STATUS_GOOD) {
+		DBG (5, "get_hardware_status: open failed\n");
+		return SANE_STATUS_INVAL;
+	    }
+      } else if (s->connection == SANE_FUJITSU_SCSI) {
+	    DBG (10, "get_hardware_status opening SCSI device\n");
+	    if (sanei_scsi_open (s->devicename, &sfd, scsiSenseHandler, 0)!=0) {
+		DBG (5, "get_hardware_status: open failed\n");
+		return SANE_STATUS_INVAL;
+	    }
+      }      
       hexdump (MSG_IO, "get_hardware_status", 
                hw_statusB.cmd, hw_statusB.size);
 
-      ret = do_scsi_cmd (sfd, hw_statusB.cmd, hw_statusB.size,
-                         s->buffer, 10, NULL);
-      sanei_scsi_close (sfd);
+      ret = do_cmd (s->connection, sfd, hw_statusB.cmd, hw_statusB.size,
+                    s->buffer, 10, NULL);
+      if (s->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (sfd);
+      } else if (s->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (sfd);
+      }
     }
   else
     {
       hexdump (MSG_IO, "get_hardware_status", 
                hw_statusB.cmd, hw_statusB.size);
 
-      ret = do_scsi_cmd (s->sfd, hw_statusB.cmd, hw_statusB.size,
-                         s->buffer, 10, NULL);
+      ret = do_cmd (s->connection, s->sfd, hw_statusB.cmd, hw_statusB.size,
+                    s->buffer, 10, NULL);
     }
 
   if (ret == SANE_STATUS_GOOD)
@@ -2686,8 +2787,8 @@
   set_IN_page_code (inquiryB.cmd, 0xf0);
 
   hexdump (MSG_IO, "get_vital_product_data", inquiryB.cmd, inquiryB.size);
-  ret = do_scsi_cmd (s->sfd, inquiryB.cmd, inquiryB.size, 
-                     s->buffer, 0x64, NULL);
+  ret = do_cmd (s->connection, s->sfd, inquiryB.cmd, inquiryB.size, 
+                s->buffer, 0x64, NULL);
   if (ret == SANE_STATUS_GOOD)
     {
       DBG (MSG_INFO, "standard options\n");
@@ -2780,15 +2881,31 @@
   return ret;
 }
 
-
+/**
+ * Sends a command to the device. This calls do_scsi_cmd or do_usb_cmd.
+ */
+static int
+do_cmd (Fujitsu_Connection_Type connection, int fd, unsigned char *cmd,
+	     int cmd_len, unsigned char *out, size_t req_out_len,
+	     size_t *res_out_len)
+{
+    if (connection == SANE_FUJITSU_SCSI) {
+	return do_scsi_cmd(fd, cmd, cmd_len, out, req_out_len, res_out_len);
+    }
+    if (connection == SANE_FUJITSU_USB) {
+	return do_usb_cmd(fd, cmd, cmd_len, out, req_out_len, res_out_len);
+    }
+    return SANE_STATUS_INVAL;
+}
 
 /**
  * Sends a SCSI command to the device. This is just a wrapper around 
  * sanei_scsi_cmd with some debug printing.
  */
 static int
-do_scsi_cmd (int fd, unsigned char *cmd, int cmd_len,
-             unsigned char *out, size_t req_out_len, size_t *res_out_len)
+do_scsi_cmd (int fd, unsigned char *cmd,
+	     int cmd_len, unsigned char *out, size_t req_out_len,
+	     size_t *res_out_len)
 {
   int ret;
   size_t ol = req_out_len;
@@ -2823,6 +2940,118 @@
   return ret;
 }
 
+#define USB_CMD_HEADER_BYTES 19
+#define USB_CMD_MIN_BYTES 31
+
+/**
+ * Sends a USB command to the device.
+ */
+static int
+do_usb_cmd (int fd, unsigned char *cmd,
+	     int cmd_len, unsigned char *out, size_t req_out_len,
+	     size_t *res_out_len)
+{
+    int ret = SANE_STATUS_GOOD;
+    size_t cnt, ol;
+    int op_code = 0;
+    int i, j;
+    int tries = 0;
+    unsigned char buf[1024];
+/*    unsigned char sense_bytes[64]; */
+
+retry:
+    hexdump (IO_CMD, "<cmd<", cmd, cmd_len);
+
+    if (cmd_len > 0) op_code = ((int)cmd[0]) & 0xff;
+
+    if ((cmd_len+USB_CMD_HEADER_BYTES) > (int)sizeof(buf)) {
+	    /* Command too long. */
+	return SANE_STATUS_INVAL;
+    }
+    buf[0] = (unsigned char)'C';
+    for (i = 1; i < USB_CMD_HEADER_BYTES; i++) buf[i] = (unsigned char)0;
+    memcpy(&buf[USB_CMD_HEADER_BYTES], cmd, cmd_len);
+    for (i = USB_CMD_HEADER_BYTES+cmd_len; i < USB_CMD_MIN_BYTES; i++) {
+	buf[i] = (unsigned char)0;
+    }
+	/* The SCAN command must be at least 32 bytes long. */
+    if ( (op_code == SCAN) && (i < 32) ) {
+	for (; i < 32; i++) buf[i] = (unsigned char)0;
+    }
+
+    for (j = 0; j < i;) {
+	cnt = i-j;
+	    /* First URB has to be 31 bytes. */
+	    /* All other URBs must be 64 bytes (max) per URB. */
+	if ( (j == 0) && (cnt > 31) ) cnt = 31; else if (cnt > 64) cnt = 64;
+	hexdump (IO_CMD, "*** URB going out:", &buf[j], cnt);
+	DBG (IO_CMD, "try to write %u bytes\n", cnt);
+	ret = sanei_usb_write_bulk(fd, &buf[j], &cnt);
+	DBG (IO_CMD, "wrote %u bytes\n", cnt);
+	if (ret != SANE_STATUS_GOOD) break;
+	j += cnt;
+    }
+    if (ret != SANE_STATUS_GOOD) {
+	DBG (MSG_ERR, "*** Got error %d trying to write\n", ret);
+    }
+
+    ol = 0;
+    if (ret == SANE_STATUS_GOOD) {
+	if ( (out != NULL) && (req_out_len > 0) ) {
+	    while (ol < req_out_len) {
+		cnt = (size_t)(req_out_len-ol);
+		DBG (IO_CMD, "try to read %u bytes\n", cnt);
+		ret = sanei_usb_read_bulk(fd, &out[ol], &cnt);
+		DBG (IO_CMD, "read %u bytes\n", cnt);
+		if (cnt > 0) {
+		    hexdump (IO_CMD, "*** Data read:", &out[ol], cnt);
+		}
+		if (ret != SANE_STATUS_GOOD) {
+		    DBG(MSG_ERR, "*** Got error %d trying to read\n", ret);
+		}
+		if (ret != SANE_STATUS_GOOD) break;
+		ol += cnt;
+	    }
+	}
+
+	DBG(MSG_ERR, "*** Try to read CSW\n");
+	cnt = sizeof(buf);
+	sanei_usb_read_bulk(fd, buf, &cnt);
+	hexdump (IO_CMD, "*** Read CSW", buf, cnt);
+    }
+
+	/* Auto-retry failed data reads, in case the scanner is busy. */
+    if ( (op_code == READ) && (tries < 100) && (ol == 0) ) {
+	usleep(100000L);
+	tries++;
+	goto retry;
+    }
+
+  if (res_out_len != NULL)
+    {
+      *res_out_len = ol;
+    }
+
+  if ((req_out_len != 0) && (req_out_len != ol))
+    {
+      DBG (MSG_ERR, "do_usb_cmd: asked %lu bytes, got %lu\n",
+           (u_long) req_out_len, (u_long) ol);
+    }
+
+  if (ret)
+    {
+      DBG (MSG_ERR, "do_usb_cmd: returning 0x%08x\n", ret);
+    }
+
+  DBG (IO_CMD_RES, "do_usb_cmd: returning %lu bytes:\n", (u_long) ol);
+
+  if (out != NULL && ol != 0)
+    {
+      hexdump (IO_CMD_RES, ">rslt>", out, (ol > 0x60) ? 0x60 : ol);
+    }
+
+  return ret;
+}
 
 /**
  * Prints a hex dump of the given buffer onto the debug output stream.
@@ -2927,8 +3156,8 @@
 #endif
 
   hexdump (MSG_IO, "release_unit", release_unitB.cmd, release_unitB.size);
-  ret = do_scsi_cmd (s->sfd, release_unitB.cmd, release_unitB.size, 
-                     NULL, 0, NULL);
+  ret = do_cmd (s->connection, s->sfd, release_unitB.cmd,
+		release_unitB.size, NULL, 0, NULL);
   if (ret)
     return ret;
 
@@ -2959,8 +3188,8 @@
   wait_scanner (s);
 
   hexdump (MSG_IO, "reserve_unit", reserve_unitB.cmd, reserve_unitB.size);
-  ret = do_scsi_cmd (s->sfd, reserve_unitB.cmd, reserve_unitB.size, 
-                     NULL, 0, NULL);
+  ret = do_cmd (s->connection, s->sfd, reserve_unitB.cmd,
+		reserve_unitB.size, NULL, 0, NULL);
   if (ret)
     return ret;
 
@@ -2968,7 +3197,7 @@
   return 0;
 }
 
-static int fujitsu_wait_scanner(int fd) 
+static int fujitsu_wait_scanner(Fujitsu_Connection_Type connection, int fd) 
 {
   int ret = -1;
   int cnt = 0;
@@ -2979,8 +3208,8 @@
     {
       hexdump (MSG_IO, "test_unit_ready", test_unit_readyB.cmd,
                test_unit_readyB.size);
-      ret = do_scsi_cmd (fd, test_unit_readyB.cmd,
-                         test_unit_readyB.size, 0, 0, NULL);
+      ret = do_cmd (connection, fd, test_unit_readyB.cmd,
+                    test_unit_readyB.size, 0, 0, NULL);
       if (ret == SANE_STATUS_DEVICE_BUSY)
         {
           usleep (500000);      /* wait 0.5 seconds */
@@ -3009,7 +3238,7 @@
 static int
 wait_scanner (struct fujitsu *s)
 {
-  return fujitsu_wait_scanner(s->sfd);
+  return fujitsu_wait_scanner(s->connection, s->sfd);
 }
 
 /**
@@ -3041,7 +3270,8 @@
     }
 
   hexdump (MSG_IO, "object_position", s->buffer, object_positionB.size);
-  ret = do_scsi_cmd (s->sfd, s->buffer, object_positionB.size, NULL, 0, NULL);
+  ret = do_cmd (s->connection, s->sfd, s->buffer, object_positionB.size,
+		NULL, 0, NULL);
   if (ret != SANE_STATUS_GOOD)
     return ret;
   wait_scanner (s);
@@ -3073,7 +3303,8 @@
      set_OP_autofeed (s->buffer, OP_Discharge);
   }
   hexdump (MSG_IO, "object_position", s->buffer, object_positionB.size);
-  ret = do_scsi_cmd (s->sfd, s->buffer, object_positionB.size, NULL, 0, NULL);
+  ret = do_cmd (s->connection, s->sfd, s->buffer, object_positionB.size,
+		NULL, 0, NULL);
   wait_scanner (s);
   DBG (10, "objectDischarge: ok\n");
   return ret;
@@ -3090,8 +3321,8 @@
 
   DBG (10, "doReset\n");
   if (scanner->model == MODEL_3092) {
-     ret = do_scsi_cmd (scanner->sfd, reset_unitB.cmd, reset_unitB.size, 
-			NULL, 0, NULL);
+     ret = do_cmd (scanner->connection, scanner->sfd, reset_unitB.cmd,
+		   reset_unitB.size, NULL, 0, NULL);
      if (ret)
        return ret;
   }
@@ -3140,7 +3371,11 @@
     {
       freeScanner (scanner);
       DBG (10, "doCancel: close filedescriptor\n");
-      sanei_scsi_close (scanner->sfd);
+      if (scanner->connection == SANE_FUJITSU_USB) {
+	    sanei_usb_close (scanner->sfd);
+      } else if (scanner->connection == SANE_FUJITSU_SCSI) {
+	    sanei_scsi_close (scanner->sfd);
+      }
       scanner->sfd = -1;
     }
 
@@ -3210,7 +3445,7 @@
     }
 
   hexdump (MSG_IO, "start_scan", command, cmdsize);
-  ret = do_scsi_cmd (s->sfd, command, cmdsize, NULL, 0, NULL);
+  ret = do_cmd (s->connection, s->sfd, command, cmdsize, NULL, 0, NULL);
 
   free (command);
 
@@ -3259,8 +3494,8 @@
       hexdump (MSG_IO, "mode_select", s->buffer, 
                i_param_size + mode_selectB.size);
 
-      ret = do_scsi_cmd (s->sfd, s->buffer,
-                         i_param_size + mode_selectB.size, NULL, 0, NULL);
+      ret = do_cmd (s->connection, s->sfd, s->buffer,
+                    i_param_size + mode_selectB.size, NULL, 0, NULL);
 
     }
 
@@ -3286,30 +3521,40 @@
 
   if (s->model == MODEL_FI) /*...and others */
     {
+	/* With the fi-series scanners, we have to use a 10-byte header
+	 * instead of a 4-byte header when communicating via USB.  This
+	 * may be a firmware bug. */
+      scsiblk *headerB;
+      int xfer_length_pos_adj;
+      if (s->connection == SANE_FUJITSU_USB) {
+	  headerB = &mode_select_usb_headerB;
+	  xfer_length_pos_adj = mode_select_headerB.size-mode_select_usb_headerB.size;
+      } else {
+	  headerB = &mode_select_headerB;
+	  xfer_length_pos_adj = 0;
+      }
       memcpy (s->buffer, mode_selectB.cmd, mode_selectB.size);
-      memcpy (s->buffer + mode_selectB.size, mode_select_headerB.cmd,
-              mode_select_headerB.size);
-      memcpy (s->buffer + mode_selectB.size + mode_select_headerB.size,
+      memcpy (s->buffer + mode_selectB.size, headerB->cmd,
+              headerB->size);
+      memcpy (s->buffer + mode_selectB.size + headerB->size,
               mode_select_parameter_blockB.cmd,
               mode_select_parameter_blockB.size);
   
-  
-  
-      command = s->buffer + mode_selectB.size + mode_select_headerB.size;
+      command = s->buffer + mode_selectB.size + headerB->size;
       i_cmd_size = 6;
       set_MSEL_len (command, i_cmd_size);
 
       set_MSEL_pagecode (command, MSEL_sleep);
       set_MSEL_sleep_mode(command, s->sleep_time);
   
-      i_param_size = mode_select_headerB.size + i_cmd_size + 2;
-      set_MSEL_xfer_length (s->buffer, i_param_size);
+      i_param_size = headerB->size + i_cmd_size + 2;
+      set_MSEL_xfer_length (s->buffer, i_param_size + xfer_length_pos_adj);
 
       hexdump (MSG_IO, "mode_select", s->buffer, 
                i_param_size + mode_selectB.size);
   
-      ret = do_scsi_cmd (s->sfd, s->buffer,
-                         i_param_size + mode_selectB.size, NULL, 0, NULL);
+      ret = do_cmd (s->connection, s->sfd, s->buffer,
+                    i_param_size + mode_selectB.size, NULL, 0, NULL);
 
       if (!ret)
         {
@@ -3429,8 +3674,8 @@
       set_R_window_id (readB.cmd, i_window_id);
       set_R_xfer_length (readB.cmd, dataToRead);
 
-      status = do_scsi_cmd (s->sfd, readB.cmd, readB.size, myBuffer, 
-                            dataToRead, &data_read);    
+      status = do_cmd (s->connection, s->sfd, readB.cmd, readB.size,
+		       myBuffer, dataToRead, &data_read);    
 
       if (status == SANE_STATUS_EOF)
         {
@@ -4105,22 +4350,34 @@
         {
           int sfd;
 
-          if (sanei_scsi_open (s->devicename, &sfd, senseHandler, 0) != 0)
-            {
-              DBG (5, "imprinter: open failed\n");
-              return SANE_STATUS_INVAL;
-            }
+	  if (s->connection == SANE_FUJITSU_USB) {
+		DBG (10, "imprinter opening USB device\n");
+		if (sanei_usb_open (s->devicename, &sfd) != SANE_STATUS_GOOD) {
+		    DBG (5, "imprinter: open failed\n");
+		    return SANE_STATUS_INVAL;
+		}
+	  } else if (s->connection == SANE_FUJITSU_SCSI) {
+		DBG (10, "imprinter opening SCSI device\n");
+		if (sanei_scsi_open
+			(s->devicename, &sfd, scsiSenseHandler, 0) != 0) {
+		    DBG (5, "imprinter: open failed\n");
+		    return SANE_STATUS_INVAL;
+		}
+	  }
       
-          fujitsu_wait_scanner(sfd);
-          ret = do_scsi_cmd (sfd, s->buffer,
-                             imprinterB.size + i_size,
-                             NULL, 0, NULL);
-          sanei_scsi_close (sfd);
+          fujitsu_wait_scanner(s->connection, sfd);
+          ret = do_cmd (s->connection, sfd, s->buffer, imprinterB.size + i_size,
+                        NULL, 0, NULL);
+	  if (s->connection == SANE_FUJITSU_USB) {
+		sanei_usb_close (sfd);
+	  } else if (s->connection == SANE_FUJITSU_SCSI) {
+		sanei_scsi_close (sfd);
+	  }
         } 
       else 
         {
-          ret = do_scsi_cmd (s->sfd, s->buffer,
-                             imprinterB.size + i_size,
+          ret = do_cmd (s->connection, s->sfd, s->buffer,
+                        imprinterB.size + i_size,
                              NULL, 0, NULL);
         }
 
@@ -4191,9 +4448,9 @@
       hexdump (MSG_IO, "send", s->buffer, 
                i_strlen +  send_imprinterB.size + sendB.size);
 
-      ret = do_scsi_cmd (s->sfd, s->buffer,
-                         i_strlen +  send_imprinterB.size + sendB.size,
-                         NULL, 0, NULL);
+      ret = do_cmd (s->connection, s->sfd, s->buffer,
+                    i_strlen +  send_imprinterB.size + sendB.size,
+                    NULL, 0, NULL);
 
     }
 
@@ -4352,18 +4609,26 @@
    * follows without a header of its own. 
    */
 
+
+    scsiblk *setwinB;
+    if ( (s->model == MODEL_FI) && (s->connection == SANE_FUJITSU_USB) ) {
+	setwinB = &set_usb_windowB;
+    } else {
+	setwinB = &set_windowB;
+    }
+
   /* SET WINDOW command and command descriptor block. 
    * The transfer length will be filled in later using set_SW_xferlen.
    */
-  memcpy (s->buffer, set_windowB.cmd, set_windowB.size);
+  memcpy (s->buffer, setwinB->cmd, setwinB->size);
 
   /* header for first set of window data */
-  memcpy ((s->buffer + set_windowB.size),
+  memcpy ((s->buffer + setwinB->size),
           window_parameter_data_blockB.cmd,
           window_parameter_data_blockB.size);
 
   /* set window data size */
-  set_WPDB_wdblen ((s->buffer + set_windowB.size), windowDataSize);
+  set_WPDB_wdblen ((s->buffer + setwinB->size), windowDataSize);
 
   if (s->duplex_mode == DUPLEX_BACK)
     {
@@ -4375,7 +4640,7 @@
     }
 
   /* now copy window data itself */
-  memcpy (s->buffer + set_windowB.size + window_parameter_data_blockB.size,
+  memcpy (s->buffer + setwinB->size + window_parameter_data_blockB.size,
           buffer, windowDataSize);
 
   /* when in duplex mode, add a second window */
@@ -4394,7 +4659,7 @@
           set_WD_paper_length_Y (buffer, 0);
         }
       hexdump (MSG_IO, "Window set - back", buffer, windowDataSize);
-      memcpy (s->buffer + set_windowB.size +
+      memcpy (s->buffer + setwinB->size +
               window_parameter_data_blockB.size + windowDataSize, buffer,
               windowDataSize);
       set_SW_xferlen (s->buffer,
@@ -4402,7 +4667,7 @@
                        2 * windowDataSize));
       scsiCommandLength =
         window_parameter_data_blockB.size + 2 * windowDataSize +
-        set_windowB.size;
+        setwinB->size;
     }
   else
     {
@@ -4410,10 +4675,11 @@
       set_SW_xferlen (s->buffer, (window_parameter_data_blockB.size +
                                   windowDataSize));
       scsiCommandLength =
-        window_parameter_data_blockB.size + windowDataSize + set_windowB.size;
+        window_parameter_data_blockB.size + windowDataSize + setwinB->size;
     }
 
-  ret = do_scsi_cmd (s->sfd, s->buffer, scsiCommandLength, NULL, 0, NULL);
+  ret = do_cmd (s->connection, s->sfd, s->buffer, scsiCommandLength,
+		NULL, 0, NULL);
   if (ret)
     return ret;
   DBG (10, "set_window_param: ok\n");
@@ -6427,7 +6693,7 @@
   scanner->val[OPT_TL_X].w = 0;
   scanner->val[OPT_TL_Y].w = 0;
   /* this size is just big enough to match letter and A4 formats */
-  scanner->bottom_right_x = SANE_FIX (215.9);
+  scanner->bottom_right_x = SANE_FIX (215.0);
   scanner->bottom_right_y = SANE_FIX (297.0);
   scanner->page_width = FIXED_MM_TO_SCANNER_UNIT (scanner->bottom_right_x);
   scanner->page_height = FIXED_MM_TO_SCANNER_UNIT (scanner->bottom_right_y);
@@ -6453,6 +6719,8 @@
 
   scanner->opt[OPT_DROPOUT_COLOR].cap = SANE_CAP_INACTIVE;
   scanner->dropout_color = MSEL_dropout_DEFAULT;
+  if ( strstr (scanner->productName, "4220" ))
+    scanner->gamma = 0x80;
 
   scanner->sleep_time = 15;
   scanner->use_imprinter = SANE_FALSE;
--- ./backend/fujitsu.h.orig	2002-09-16 05:19:54.000000000 -0700
+++ ./backend/fujitsu.h	2003-02-12 14:00:17.000000000 -0800
@@ -102,6 +102,13 @@
 };
 
 
+typedef enum {				/* hardware connection to the scanner */
+        SANE_FUJITSU_NODEV,		/* default, no HW specified yet */
+	SANE_FUJITSU_SCSI,		/* SCSI interface */
+	SANE_FUJITSU_PIO,		/* parallel interface */
+	SANE_FUJITSU_USB		/* USB interface */
+} Fujitsu_Connection_Type;
+
 struct fujitsu
 {
   struct fujitsu *next;
@@ -121,6 +128,9 @@
   int model;			/* The scanner model.                        */
 
   char *devicename;		/* The name of the scanner device.           */
+
+  Fujitsu_Connection_Type connection;	/* hardware interface type */
+
   int sfd;			/* The scanner device file descriptor.       */
 
   int color_raster_offset;	/* offset between r and b scan line and    */
@@ -466,16 +476,26 @@
 static SANE_Status attachScanner (const char *devicename,
 				  struct fujitsu **devp);
 
-static SANE_Status senseHandler (int scsi_fd, u_char * result, void *arg);
+static SANE_Status scsiSenseHandler (int scsi_fd, u_char * result, void *arg);
 
 static int identifyScanner (struct fujitsu *s);
 
 static void doInquiry (struct fujitsu *s);
 
+static int
+do_cmd (Fujitsu_Connection_Type connection, int fd, unsigned char *cmd,
+	     int cmd_len, unsigned char *out, size_t req_out_len,
+	     size_t *res_out_len);
+
 static int do_scsi_cmd (int fd, unsigned char *cmd, int cmd_len,
 			unsigned char *out, size_t req_out_len, 
 			size_t *res_out_len);
 
+static int
+do_usb_cmd (int fd, unsigned char *cmd,
+	     int cmd_len, unsigned char *out, size_t req_out_len,
+	     size_t *res_out_len);
+
 static void hexdump (int level, char *comment, unsigned char *p, int l);
 
 static SANE_Status init_options (struct fujitsu *scanner);
--- ./backend/fujitsu.conf.orig	2002-04-17 11:50:36.000000000 -0700
+++ ./backend/fujitsu.conf	2003-02-13 14:57:00.000000000 -0800
@@ -1,3 +1,8 @@
 #option force-model fi-4340Cdi
 #/dev/sg1
 scsi FUJITSU
+# For Fujitsu scanners connected via USB on a known device:
+# usb /dev/usb/scanner0
+# For Fujitsu scanners connected via USB where vendor/product Id are known
+# (use lsusb to find these):
+# usb 0x04c5 0x1042
--- ./doc/descriptions/fujitsu.desc.orig	2002-09-23 03:16:54.000000000 -0700
+++ ./doc/descriptions/fujitsu.desc	2003-02-13 15:04:51.000000000 -0800
@@ -37,6 +37,8 @@
 :interface "SCSI"
 :model "M4097"
 :interface "SCSI"
+:model "fi-4220C"
+:interface "SCSI USB"
 :model "fi-4340C"
-:interface "SCSI"
+:interface "SCSI USB"
 
--- ./doc/sane-fujitsu.man.orig	2003-02-13 15:13:15.000000000 -0800
+++ ./doc/sane-fujitsu.man	2002-11-29 12:15:30.000000000 -0800
@@ -4,13 +4,13 @@
 .IX sane-m3096g
 
 .SH NAME
-sane-fujitsu \- SANE backend for Fujitsu flatbed and ADF scanners
+sane-fujitsu \- SANE backend for Fujitsu flatbed scanners
 
 .SH DESCRIPTION
 The
 .B sane-fujitsu
 library implements a SANE (Scanner Access Now Easy) backend which
-provides access to Fujitsu flatbed and ADF scanners.
+provides access to Fujitsu flatbed scanners.
 At present, the following
 scanners are known to work with this backend:
 .PP
@@ -27,8 +27,6 @@
 .br
 FUJITSU  M4097
 .br
-FUJITSU  fi-4220C           
-.br
 FUJITSU  fi-4340C           
 .br
 FUJITSU  M3091DCd         BF21
@@ -159,7 +157,6 @@
 sane(7),
 sane\-scsi(5),
 sane\-sp15c(5)
-sane\-usb(5),
 .br
 Fujitsu ScanPartner 15C OEM Manual, Doc. No. 250-0081-0
 .br
@@ -175,7 +172,6 @@
 3091 driver: Frederik Ramm <frederik@remote.org>
 3093GD,fi-4340C, ipc and cmp options: Oliver Schirrmeister <oschirr@abm.de>
 3092 patch: Mario Goppold <mgoppold@tbzpariv.tcc-chemnitz.de>
-fi-4220C patch and USB support: Ronald B. Cemer <ron@roncemer.com>
 .SH LIMITATIONS
 Only tested with Linux 2.4 
 
@@ -184,4 +180,4 @@
 but I haven't seen them yet.
 I don't know if the ScanPartner 15C still works, because I'm not able
 to test it.
-3091/3092 don't support halftone
+3091/3092 don't support halftone
\ No newline at end of file

--------------040803030607030006060400--