summaryrefslogtreecommitdiff
path: root/backend/snapscan-usb.c
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
commit6e9c41a892ed0e0da326e0278b3221ce3f5713b8 (patch)
tree2e301d871bbeeb44aa57ff9cc070fcf3be484487 /backend/snapscan-usb.c
Initial import of sane-backends version 1.0.24-1.2
Diffstat (limited to 'backend/snapscan-usb.c')
-rw-r--r--backend/snapscan-usb.c656
1 files changed, 656 insertions, 0 deletions
diff --git a/backend/snapscan-usb.c b/backend/snapscan-usb.c
new file mode 100644
index 0000000..ec6a53e
--- /dev/null
+++ b/backend/snapscan-usb.c
@@ -0,0 +1,656 @@
+/*
+ Snapscan 1212U modifications for the Snapscan SANE backend
+
+ Copyright (C) 2000 Henrik Johansson
+
+ Henrik Johansson (henrikjo@post.urfors.se)
+
+ 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 of the
+ License, 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+
+ This file implements USB equivalents to the SCSI routines used by the Snapscan
+ backend.
+
+ History
+
+ 0.1 2000-02-01
+
+ First version released
+
+ 0.2 2000-02-12
+
+ The Send Diagnostics SCSI command seems to hang some 1212U scanners.
+ Bypassing this command fixes the problem. This bug was reported by
+ Dmitri (dmitri@advantrix.com).
+
+ 0.3 2000-02-13
+
+ The "Set window" command returns with status "Device busy" when the
+ scanner is busy. One consequence is that some frontends exits with an
+ error message if it's started when the scanner is warming up.
+ A solution was suggested by Dmitri (dmitri@advantrix.com)
+ The idea is that a SCSI command which returns "device busy" is stored
+ in a "TODO" queue. The send command function is modified to first send
+ commands in the queue before the intended command.
+ So far this strategy has worked flawlessly. Thanks Dmitri!
+*/
+
+/* $Id$
+ SnapScan backend scan data sources */
+
+#include "snapscan-usb.h"
+#include "snapscan-mutex.c"
+
+#ifndef SHM_R
+#define SHM_R 0
+#endif
+
+#ifndef SHM_W
+#define SHM_W 0
+#endif
+
+/* Global variables */
+
+static snapscan_mutex_t snapscan_mutex;
+static sense_handler_type usb_sense_handler;
+static void* usb_pss;
+
+struct urb_counters_t {
+ unsigned long read_urbs;
+ unsigned long write_urbs;
+};
+
+static struct urb_counters_t* urb_counters = NULL;
+
+/* Forward declarations */
+static SANE_Status usb_request_sense(SnapScan_Scanner *pss);
+
+static SANE_Status snapscani_usb_cmd(int fd, const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+{
+ static const char me[] = "snapscani_usb_cmd";
+ int status;
+
+ DBG (DL_CALL_TRACE, "%s(%d,0x%lx,%lu,0x%lx,0x%lx (%lu))\n", me,
+ fd, (u_long) src,(u_long) src_size,(u_long) dst, (u_long) dst_size,(u_long) (dst_size ? *dst_size : 0));
+
+ while(bqhead) {
+ status = atomic_usb_cmd(fd, bqhead->src, bqhead->src_size, NULL, NULL);
+ if(status == SANE_STATUS_DEVICE_BUSY) {
+ if(is_queueable(src)) {
+ enqueue_bq(fd,src,src_size);
+ return SANE_STATUS_GOOD;
+ } else {
+ sleep(1);
+ continue;
+ }
+ }
+ dequeue_bq();
+ }
+
+ status = atomic_usb_cmd(fd,src,src_size,dst,dst_size);
+
+ if ((status == SANE_STATUS_DEVICE_BUSY) && is_queueable(src) ) {
+ enqueue_bq(fd,src,src_size);
+ return SANE_STATUS_GOOD;
+ }
+
+ return status;
+}
+
+static SANE_Status atomic_usb_cmd(int fd, const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+{
+ static const char me[] = "atomic_usb_cmd";
+
+ int status;
+ sigset_t all,oldset;
+
+ DBG (DL_CALL_TRACE, "%s(%d,0x%lx,%lu,0x%lx,0x%lx (%lu))\n", me,
+ fd, (u_long) src,(u_long) src_size,(u_long) dst, (u_long) dst_size,(u_long) (dst_size ? *dst_size : 0));
+
+ /* Prevent the calling process from being killed */
+ sigfillset(&all);
+ sigprocmask(SIG_BLOCK, &all, &oldset);
+
+ /* Make sure we are alone */
+ snapscani_mutex_lock(&snapscan_mutex);
+
+ status = usb_cmd(fd,src,src_size,dst,dst_size);
+
+ snapscani_mutex_unlock(&snapscan_mutex);
+
+ /* Now it is ok to be killed */
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+
+ return status;
+
+}
+
+static SANE_Status snapscani_usb_open(const char *dev, int *fdp,
+ sense_handler_type sense_handler, void* pss)
+{
+ static const char me[] = "snapscani_usb_open";
+
+ DBG (DL_CALL_TRACE, "%s(%s)\n", me, dev);
+
+ if(!snapscani_mutex_open(&snapscan_mutex, dev)) {
+ DBG (DL_MAJOR_ERROR, "%s: Can't get semaphore\n", me);
+ return SANE_STATUS_INVAL;
+ }
+ usb_sense_handler=sense_handler;
+ usb_pss = pss;
+ urb_counters->read_urbs = 0;
+ urb_counters->write_urbs = 0;
+ return sanei_usb_open(dev, fdp);
+}
+
+
+static void snapscani_usb_close(int fd) {
+ static const char me[] = "snapscani_usb_close";
+ SANE_Word vendor_id, product_id;
+
+ DBG (DL_CALL_TRACE, "%s(%d)\n", me, fd);
+ DBG (DL_DATA_TRACE,"1st read %ld write %ld\n", urb_counters->read_urbs, urb_counters->write_urbs);
+
+ /* Check if URB counting is needed. If yes, ensure the number of sent and
+ received URBs is even.
+ Odd number of URBs only cause problems with libusb and certain
+ scanner models. On other scanner models, sending additional commands
+ seems to cause problems (e.g. 1212u_2).
+ If sanei_usb_get_vendor_product returns an error there's probably no
+ libusb, so everything's fine.
+ */
+ if (sanei_usb_get_vendor_product(fd, &vendor_id, &product_id) == SANE_STATUS_GOOD)
+ {
+ /* Exclude 1212u_2 */
+ if (!((vendor_id == USB_VENDOR_AGFA) && (product_id == USB_PRODUCT_1212U2)))
+ {
+ if ((urb_counters->read_urbs & 0x01) && (urb_counters->write_urbs & 0x01))
+ {
+ char cmd[] = {TEST_UNIT_READY, 0, 0, 0, 0, 0};
+
+ snapscani_usb_cmd (fd, cmd, sizeof (cmd), NULL, 0);
+ }
+ else if (urb_counters->read_urbs & 0x01)
+ {
+ size_t read_bytes;
+ char cmd[] = {TEST_UNIT_READY, 0, 0, 0, 0, 0};
+ char cmd2[] = {INQUIRY, 0, 0, 0, 120, 0};
+ char data[120];
+
+ read_bytes = 120;
+ snapscani_usb_cmd (fd, cmd2, sizeof (cmd2), data, &read_bytes);
+ snapscani_usb_cmd (fd, cmd, sizeof (cmd), NULL, 0);
+ }
+ else if (urb_counters->write_urbs & 0x01)
+ {
+ size_t read_bytes;
+ char cmd[] = {INQUIRY, 0, 0, 0, 120, 0};
+ char data[120];
+
+ read_bytes = 120;
+ snapscani_usb_cmd (fd, cmd, sizeof (cmd), data, &read_bytes);
+ }
+ DBG (DL_DATA_TRACE,"2nd read %ld write %ld\n", urb_counters->read_urbs,
+ urb_counters->write_urbs);
+ }
+ }
+ urb_counters->read_urbs = 0;
+ urb_counters->write_urbs = 0;
+ snapscani_mutex_close(&snapscan_mutex);
+ sanei_usb_close(fd);
+}
+
+static int usb_cmdlen(int cmd)
+{
+ switch(cmd) {
+ case TEST_UNIT_READY:
+ case INQUIRY:
+ case SCAN:
+ case REQUEST_SENSE:
+ case RESERVE_UNIT:
+ case RELEASE_UNIT:
+ case SEND_DIAGNOSTIC:
+ return 6;
+ case SEND:
+ case SET_WINDOW:
+ case READ:
+ case GET_DATA_BUFFER_STATUS:
+ return 10;
+ }
+ return 0;
+}
+
+static char *usb_debug_data(char *str,const char *data, int len) {
+ char tmpstr[10];
+ int i;
+
+ str[0]=0;
+ for(i=0; i < (len < 10 ? len : 10); i++) {
+ sprintf(tmpstr," 0x%02x",((int)data[i]) & 0xff);
+ if(i%16 == 0 && i != 0)
+ strcat(str,"\n");
+ strcat(str,tmpstr);
+ }
+ if(i < len)
+ strcat(str," ...");
+ return str;
+}
+
+#define RETURN_ON_FAILURE(x) if((status = x) != SANE_STATUS_GOOD) return status;
+
+static SANE_Status usb_write(int fd, const void *buf, size_t n) {
+ char dbgmsg[16384];
+ SANE_Status status;
+ size_t bytes_written = n;
+
+ static const char me[] = "usb_write";
+ DBG(DL_DATA_TRACE, "%s: writing: %s\n",me,usb_debug_data(dbgmsg,buf,n));
+
+ status = sanei_usb_write_bulk(fd, (const SANE_Byte*)buf, &bytes_written);
+ if(bytes_written != n) {
+ DBG (DL_MAJOR_ERROR, "%s Only %lu bytes written\n",me, (u_long) bytes_written);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ urb_counters->write_urbs += (bytes_written + 7) / 8;
+ DBG (DL_DATA_TRACE, "Written %lu bytes\n", (u_long) bytes_written);
+ return status;
+}
+
+static SANE_Status usb_read(SANE_Int fd, void *buf, size_t n) {
+ char dbgmsg[16384];
+ static const char me[] = "usb_read";
+ SANE_Status status;
+ size_t bytes_read = n;
+
+ status = sanei_usb_read_bulk(fd, (SANE_Byte*)buf, &bytes_read);
+ if (bytes_read != n) {
+ DBG (DL_MAJOR_ERROR, "%s Only %lu bytes read\n",me, (u_long) bytes_read);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ urb_counters->read_urbs += ((63 + bytes_read) / 64);
+ DBG(DL_DATA_TRACE, "%s: reading: %s\n",me,usb_debug_data(dbgmsg,buf,n));
+ DBG(DL_DATA_TRACE, "Read %lu bytes\n", (u_long) bytes_read);
+ return status;
+}
+
+static SANE_Status usb_read_status(int fd, int *scsistatus, int *transaction_status,
+ char command)
+{
+ static const char me[] = "usb_read_status";
+ unsigned char status_buf[8];
+ int scsistat;
+ int status;
+
+ RETURN_ON_FAILURE(usb_read(fd,status_buf,8));
+
+ if(transaction_status)
+ *transaction_status = status_buf[0];
+
+ scsistat = (status_buf[1] & STATUS_MASK) >> 1;
+
+ if(scsistatus)
+ *scsistatus = scsistat;
+
+ switch(scsistat) {
+ case GOOD:
+ return SANE_STATUS_GOOD;
+ case CHECK_CONDITION:
+ if (usb_pss != NULL) {
+ if (command != REQUEST_SENSE) {
+ return usb_request_sense(usb_pss);
+ }
+ else {
+ /* Avoid recursive calls of usb_request_sense */
+ return SANE_STATUS_GOOD;
+ }
+ } else {
+ DBG (DL_MAJOR_ERROR, "%s: scanner structure not set, returning default error\n",
+ me);
+ return SANE_STATUS_DEVICE_BUSY;
+ }
+ break;
+ case BUSY:
+ return SANE_STATUS_DEVICE_BUSY;
+ default:
+ return SANE_STATUS_IO_ERROR;
+ }
+}
+
+
+static SANE_Status usb_cmd(int fd, const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+{
+ static const char me[] = "usb_cmd";
+ int status,tstatus;
+ int cmdlen,datalen;
+ char command;
+
+ DBG (DL_CALL_TRACE, "%s(%d,0x%lx,%lu,0x%lx,0x%lx (%lu))\n", me,
+ fd, (u_long) src,(u_long) src_size,(u_long) dst, (u_long) dst_size,(u_long) (dst_size ? *dst_size : 0));
+
+ /* Since the "Send Diagnostic" command isn't supported by
+ all Snapscan USB-scanners it's disabled .
+ */
+ command = ((const char *)src)[0];
+ if(command == SEND_DIAGNOSTIC)
+ return(SANE_STATUS_GOOD);
+
+ cmdlen = usb_cmdlen(*((const char *)src));
+ datalen = src_size - cmdlen;
+
+ DBG(DL_DATA_TRACE, "%s: cmdlen=%d, datalen=%d\n",me,cmdlen,datalen);
+
+ /* Send command to scanner */
+ RETURN_ON_FAILURE( usb_write(fd,src,cmdlen) );
+
+ /* Read status */
+ RETURN_ON_FAILURE( usb_read_status(fd, NULL, &tstatus, command) );
+
+ /* Send data only if the scanner is expecting it */
+ if(datalen > 0 && (tstatus == TRANSACTION_WRITE)) {
+ /* Send data to scanner */
+ RETURN_ON_FAILURE( usb_write(fd, ((const SANE_Byte *) src) + cmdlen, datalen) );
+
+ /* Read status */
+ RETURN_ON_FAILURE( usb_read_status(fd, NULL, &tstatus, command) );
+ }
+
+ /* Receive data only when new data is waiting */
+ if(dst_size && *dst_size && (tstatus == TRANSACTION_READ)) {
+ RETURN_ON_FAILURE( usb_read(fd,dst,*dst_size) );
+
+ /* Read status */
+ RETURN_ON_FAILURE( usb_read_status(fd, NULL, &tstatus, command) );
+ }
+
+ if(tstatus != TRANSACTION_COMPLETED) {
+ if(tstatus == TRANSACTION_WRITE)
+ DBG(DL_MAJOR_ERROR,
+ "%s: The transaction should now be completed, but the scanner is expecting more data" ,me);
+ else
+ DBG(DL_MAJOR_ERROR,
+ "%s: The transaction should now be completed, but the scanner has more data to send" ,me);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ return status;
+}
+
+/* Busy queue data structures and function implementations*/
+
+static int is_queueable(const char *src)
+{
+ switch(src[0]) {
+ case SEND:
+ case SET_WINDOW:
+ case SEND_DIAGNOSTIC:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static struct usb_busy_queue *bqhead=NULL,*bqtail=NULL;
+static int bqelements=0;
+
+static int enqueue_bq(int fd,const void *src, size_t src_size)
+{
+ static const char me[] = "enqueue_bq";
+ struct usb_busy_queue *bqe;
+
+ DBG (DL_CALL_TRACE, "%s(%d,%p,%lu)\n", me, fd,src, (u_long) src_size);
+
+ if((bqe = malloc(sizeof(struct usb_busy_queue))) == NULL)
+ return -1;
+
+ if((bqe->src = malloc(src_size)) == NULL)
+ return -1;
+
+ memcpy(bqe->src,src,src_size);
+ bqe->src_size=src_size;
+
+ bqe->next=NULL;
+
+ if(bqtail) {
+ bqtail->next=bqe;
+ bqtail = bqe;
+ } else
+ bqhead = bqtail = bqe;
+
+ bqelements++;
+ DBG(DL_DATA_TRACE, "%s: Busy queue: elements=%d, bqhead=%p, bqtail=%p\n",
+ me,bqelements,(void*)bqhead,(void*)bqtail);
+ return 0;
+}
+
+static void dequeue_bq()
+{
+ static const char me[] = "dequeue_bq";
+ struct usb_busy_queue *tbqe;
+
+ DBG (DL_CALL_TRACE, "%s()\n", me);
+
+ if(!bqhead)
+ return;
+
+ tbqe = bqhead;
+ bqhead = bqhead->next;
+ if(!bqhead)
+ bqtail=NULL;
+
+ if(tbqe->src)
+ free(tbqe->src);
+ free(tbqe);
+
+ bqelements--;
+ DBG(DL_DATA_TRACE, "%s: Busy queue: elements=%d, bqhead=%p, bqtail=%p\n",
+ me,bqelements,(void*)bqhead,(void*)bqtail);
+}
+
+static SANE_Status usb_request_sense(SnapScan_Scanner *pss) {
+ static const char *me = "usb_request_sense";
+ size_t read_bytes = 0;
+ u_char cmd[] = {REQUEST_SENSE, 0, 0, 0, 20, 0};
+ u_char data[20];
+ SANE_Status status;
+
+ read_bytes = 20;
+
+ DBG (DL_CALL_TRACE, "%s\n", me);
+ status = usb_cmd (pss->fd, cmd, sizeof (cmd), data, &read_bytes);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (DL_MAJOR_ERROR, "%s: usb command error: %s\n",
+ me, sane_strstatus (status));
+ }
+ else
+ {
+ if (usb_sense_handler) {
+ status = usb_sense_handler (pss->fd, data, (void *) pss);
+ } else {
+ DBG (DL_MAJOR_ERROR, "%s: No sense handler for USB\n", me);
+ status = SANE_STATUS_UNSUPPORTED;
+ }
+ }
+ return status;
+}
+
+#if defined USE_PTHREAD || defined HAVE_OS2_H || defined __BEOS__
+static SANE_Status snapscani_usb_shm_init(void)
+{
+ unsigned int shm_size = sizeof(struct urb_counters_t);
+ urb_counters = (struct urb_counters_t*) malloc(shm_size);
+ if (urb_counters == NULL)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+ memset(urb_counters, 0, shm_size);
+ return SANE_STATUS_GOOD;
+
+}
+
+static void snapscani_usb_shm_exit(void)
+{
+ if (urb_counters)
+ {
+ free ((void*)urb_counters);
+ urb_counters = NULL;
+ }
+}
+#else
+#include <sys/ipc.h>
+#include <sys/shm.h>
+static SANE_Status snapscani_usb_shm_init(void)
+{
+ unsigned int shm_size = sizeof(struct urb_counters_t);
+ void* shm_area = NULL;
+ int shm_id = shmget (IPC_PRIVATE, shm_size, IPC_CREAT | SHM_R | SHM_W);
+ if (shm_id == -1)
+ {
+ DBG (DL_MAJOR_ERROR, "snapscani_usb_shm_init: cannot create shared memory segment: %s\n",
+ strerror (errno));
+ return SANE_STATUS_NO_MEM;
+ }
+
+ shm_area = shmat (shm_id, NULL, 0);
+ if (shm_area == (void *) -1)
+ {
+ DBG (DL_MAJOR_ERROR, "snapscani_usb_shm_init: cannot attach to shared memory segment: %s\n",
+ strerror (errno));
+ shmctl (shm_id, IPC_RMID, NULL);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ if (shmctl (shm_id, IPC_RMID, NULL) == -1)
+ {
+ DBG (DL_MAJOR_ERROR, "snapscani_usb_shm_init: cannot remove shared memory segment id: %s\n",
+ strerror (errno));
+ shmdt (shm_area);
+ shmctl (shm_id, IPC_RMID, NULL);
+ return SANE_STATUS_NO_MEM;
+ }
+ urb_counters = (struct urb_counters_t*) shm_area;
+ memset(urb_counters, 0, shm_size);
+ return SANE_STATUS_GOOD;
+}
+
+static void snapscani_usb_shm_exit(void)
+{
+ if (urb_counters)
+ {
+ shmdt (urb_counters);
+ urb_counters = NULL;
+ }
+}
+#endif
+/*
+ * $Log$
+ * Revision 1.22 2006/01/26 17:42:30 hmg-guest
+ * Added #defines for SHM_R/W for cygwin (patch from Philip Aston <philipa@mail.com>).
+ *
+ * Revision 1.21 2005/11/02 19:22:06 oliver-guest
+ * Fixes for Benq 5000
+ *
+ * Revision 1.20 2005/07/18 17:37:37 oliver-guest
+ * ZETA changes for SnapScan backend
+ *
+ * Revision 1.19 2004/10/03 17:34:36 hmg-guest
+ * 64 bit platform fixes (bug #300799).
+ *
+ * Revision 1.18 2004/06/16 19:52:26 oliver-guest
+ * Don't enforce even number of URB packages on 1212u_2. Fixes bug #300753.
+ *
+ * Revision 1.17 2004/06/06 14:50:36 oliver-guest
+ * Use shared memory functions only when needed
+ *
+ * Revision 1.16 2004/05/26 22:37:01 oliver-guest
+ * Use shared memory for urb counters in snapscan backend
+ *
+ * Revision 1.15 2004/04/09 11:59:02 oliver-guest
+ * Fixes for pthread implementation
+ *
+ * Revision 1.14 2004/04/08 22:48:13 oliver-guest
+ * Use URB counting in snapscan-usb.c (thanks to Jose Alberto Reguero)
+ *
+ * Revision 1.13 2003/11/08 09:50:27 oliver-guest
+ * Fix TPO scanning range for Epson 1670
+ *
+ * Revision 1.12 2003/07/26 17:16:55 oliverschwartz
+ * Changed licence to GPL + SANE exception for snapscan-usb.[ch]
+ *
+ * Revision 1.11 2002/07/12 23:29:06 oliverschwartz
+ * SnapScan backend 1.4.15
+ *
+ * Revision 1.21 2002/07/12 22:52:42 oliverschwartz
+ * use sanei_usb_read_bulk() and sanei_usb_write_bulk()
+ *
+ * Revision 1.20 2002/04/27 14:36:25 oliverschwartz
+ * Pass a char as 'proj' argument for ftok()
+ *
+ * Revision 1.19 2002/04/10 21:00:33 oliverschwartz
+ * Make bqelements static
+ *
+ * Revision 1.18 2002/03/24 12:16:09 oliverschwartz
+ * Better error report in usb_read
+ *
+ * Revision 1.17 2002/02/05 19:32:39 oliverschwartz
+ * Only define union semun if not already defined in <sys/sem.h>. Fixes
+ * compilation bugs on Irix and FreeBSD. Fixed by Henning Meier-Geinitz.
+ *
+ * Revision 1.16 2002/01/14 21:11:56 oliverschwartz
+ * Add workaround for bug semctl() call in libc for PPC
+ *
+ * Revision 1.15 2001/12/09 23:06:44 oliverschwartz
+ * - use sense handler for USB if scanner reports CHECK_CONDITION
+ *
+ * Revision 1.14 2001/11/16 20:23:16 oliverschwartz
+ * Merge with sane-1.0.6
+ * - Check USB vendor IDs to avoid hanging scanners
+ * - fix bug in dither matrix computation
+ *
+ * Revision 1.13 2001/10/09 22:34:23 oliverschwartz
+ * fix compiler warnings
+ *
+ * Revision 1.12 2001/09/18 15:01:07 oliverschwartz
+ * - Read scanner id string again after firmware upload
+ * to indentify correct model
+ * - Make firmware upload work for AGFA scanners
+ * - Change copyright notice
+ *
+ * */
+