]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - backend/usb-libusb.c
Merge pull request #5621 from zdohnal/cgigetarray-sigsegv
[thirdparty/cups.git] / backend / usb-libusb.c
index 2efece7efba21036e3841806ff69ed1fa6301d18..1c4d9f11721d41df3a762a3177ca3f0fcaea0a40 100644 (file)
@@ -1,36 +1,37 @@
 /*
- * "$Id$"
+ * LIBUSB interface code for CUPS.
  *
- *   Libusb interface code for the Common UNIX Printing System (CUPS).
+ * Copyright 2007-2019 by Apple Inc.
  *
- *   Copyright 2007-2009 by Apple Inc.
- *
- *   These coded instructions, statements, and computer programs are the
- *   property of Apple Inc. and are protected by Federal copyright
- *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- *   which should have been included with this file.  If this file is
- *   file is missing or damaged, see the license at "http://www.cups.org/".
- *
- * Contents:
- *
- *   list_devices()    - List the available printers.
- *   print_device()    - Print a file to a USB device.
- *   close_device()    - Close the connection to the USB printer.
- *   find_device()     - Find or enumerate USB printers.
- *   get_device_id()   - Get the IEEE-1284 device ID for the printer.
- *   list_cb()         - List USB printers for discovery.
- *   make_device_uri() - Create a device URI for a USB printer.
- *   open_device()     - Open a connection to the USB printer.
- *   print_cb()        - Find a USB printer for printing.
- *   side_cb()         - Handle side-channel requests.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more
+ * information.
  */
 
 /*
  * Include necessary headers...
  */
 
-#include <usb.h>
-#include <poll.h>
+#include <libusb.h>
+#include <cups/cups-private.h>
+#include <cups/ppd-private.h>
+#include <cups/dir.h>
+#include <pthread.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+
+/*
+ * WAIT_EOF_DELAY is number of seconds we'll wait for responses from
+ * the printer after we've finished sending all the data
+ */
+
+#define WAIT_EOF                       0
+#define WAIT_EOF_DELAY                 7
+#define WAIT_SIDE_DELAY                        3
+#define DEFAULT_TIMEOUT                        5000L
 
 
 /*
 
 typedef struct usb_printer_s           /**** USB Printer Data ****/
 {
-  struct usb_device    *device;        /* Device info */
+  struct libusb_device *device;        /* Device info */
   int                  conf,           /* Configuration */
+                       origconf,       /* Original configuration */
                        iface,          /* Interface */
                        altset,         /* Alternate setting */
                        write_endp,     /* Write endpoint */
-                       read_endp;      /* Read endpoint */
-  struct usb_dev_handle        *handle;        /* Open handle to device */
+                       read_endp,      /* Read endpoint */
+                       protocol,       /* Protocol: 1 = Uni-di, 2 = Bi-di. */
+                       usblp_attached, /* "usblp" kernel module attached? */
+                       reset_after_job;/* Set to 1 by print_device() */
+  unsigned             quirks;         /* Quirks flags */
+  struct libusb_device_handle *handle; /* Open handle to device */
 } usb_printer_t;
 
 typedef int (*usb_cb_t)(usb_printer_t *, const char *, const char *,
                         const void *);
 
+typedef struct usb_globals_s           /* Global USB printer information */
+{
+  usb_printer_t                *printer;       /* Printer */
+
+  pthread_mutex_t      read_thread_mutex;
+  pthread_cond_t       read_thread_cond;
+  int                  read_thread_stop;
+  int                  read_thread_done;
+
+  pthread_mutex_t      readwrite_lock_mutex;
+  pthread_cond_t       readwrite_lock_cond;
+  int                  readwrite_lock;
+
+  int                  print_fd;       /* File descriptor to print */
+  ssize_t              print_bytes;    /* Print bytes read */
+
+  int                  wait_eof;
+  int                  drain_output;   /* Drain all pending output */
+  int                  bidi_flag;      /* 0=unidirectional, 1=bidirectional */
+
+  pthread_mutex_t      sidechannel_thread_mutex;
+  pthread_cond_t       sidechannel_thread_cond;
+  int                  sidechannel_thread_stop;
+  int                  sidechannel_thread_done;
+} usb_globals_t;
+
+/*
+ * Quirks: various printer quirks are handled by this structure and its flags.
+ *
+ * The quirks table used to be compiled into the backend but is now loaded from
+ * one or more files in the /usr/share/cups/usb directory.
+ */
+
+#define USB_QUIRK_BLACKLIST    0x0001  /* Does not conform to the spec */
+#define USB_QUIRK_NO_REATTACH  0x0002  /* After printing we cannot re-attach
+                                          the usblp kernel module */
+#define USB_QUIRK_SOFT_RESET   0x0004  /* After printing do a soft reset
+                                          for clean-up */
+#define USB_QUIRK_UNIDIR       0x0008  /* Requires unidirectional mode */
+#define USB_QUIRK_USB_INIT     0x0010  /* Needs vendor USB init string */
+#define USB_QUIRK_VENDOR_CLASS 0x0020  /* Descriptor uses vendor-specific
+                                          Class or SubClass */
+#define USB_QUIRK_DELAY_CLOSE  0x0040  /* Delay close */
+#define USB_QUIRK_WHITELIST    0x0000  /* no quirks */
+
+
+typedef struct usb_quirk_s             /* USB "quirk" information */
+{
+  int          vendor_id,              /* Affected vendor ID */
+               product_id;             /* Affected product ID or 0 for all */
+  unsigned     quirks;                 /* Quirks bitfield */
+} usb_quirk_t;
+
+
+
+
+/*
+ * Globals...
+ */
+
+cups_array_t           *all_quirks;    /* Array of printer quirks */
+usb_globals_t          g = { 0 };      /* Globals */
+libusb_device          **all_list;     /* List of connected USB devices */
+
 
 /*
  * Local functions...
  */
 
 static int             close_device(usb_printer_t *printer);
+static int             compare_quirks(usb_quirk_t *a, usb_quirk_t *b);
 static usb_printer_t   *find_device(usb_cb_t cb, const void *data);
+static unsigned                find_quirks(int vendor_id, int product_id);
 static int             get_device_id(usb_printer_t *printer, char *buffer,
                                      size_t bufsize);
 static int             list_cb(usb_printer_t *printer, const char *device_uri,
                                const char *device_id, const void *data);
+static void            load_quirks(void);
 static char            *make_device_uri(usb_printer_t *printer,
                                         const char *device_id,
                                         char *uri, size_t uri_size);
 static int             open_device(usb_printer_t *printer, int verbose);
 static int             print_cb(usb_printer_t *printer, const char *device_uri,
                                 const char *device_id, const void *data);
-static ssize_t         side_cb(usb_printer_t *printer, int print_fd);
+static void            *read_thread(void *reference);
+static void            *sidechannel_thread(void *reference);
+static void            soft_reset(void);
+static int             soft_reset_printer(usb_printer_t *printer);
 
 
 /*
@@ -78,6 +154,8 @@ static ssize_t               side_cb(usb_printer_t *printer, int print_fd);
 void
 list_devices(void)
 {
+  load_quirks();
+
   fputs("DEBUG: list_devices\n", stderr);
   find_device(list_cb, NULL);
 }
@@ -97,27 +175,59 @@ print_device(const char *uri,              /* I - Device URI */
             int        argc,           /* I - Number of command-line arguments (6 or 7) */
             char       *argv[])        /* I - Command-line arguments */
 {
-  usb_printer_t        *printer;               /* Printer */
-  ssize_t      bytes,                  /* Bytes read/written */
-               tbytes;                 /* Total bytes written */
-  char         buffer[8192];           /* Print data buffer */
+  int          bytes;                  /* Bytes written */
+  ssize_t      total_bytes;            /* Total bytes written */
   struct sigaction action;             /* Actions for POSIX signals */
-  struct pollfd        pfds[2];                /* Poll descriptors */
+  int          status = CUPS_BACKEND_OK,
+                                       /* Function results */
+               iostatus;               /* Current IO status */
+  pthread_t    read_thread_id,         /* Read thread */
+               sidechannel_thread_id;  /* Side-channel thread */
+  int          have_sidechannel = 0,   /* Was the side-channel thread started? */
+               have_backchannel = 0;   /* Do we have a back channel? */
+  struct stat   sidechannel_info;      /* Side-channel file descriptor info */
+  unsigned char        print_buffer[8192],     /* Print data buffer */
+               *print_ptr;             /* Pointer into print data buffer */
+  fd_set       input_set;              /* Input set for select() */
+  int          nfds;                   /* Number of file descriptors */
+  struct timeval *timeout,             /* Timeout pointer */
+               tv;                     /* Time value */
+  struct timespec cond_timeout;                /* pthread condition timeout */
+  int          num_opts;               /* Number of options */
+  cups_option_t        *opts;                  /* Options */
+  const char   *val;                   /* Option value */
+
+
+  load_quirks();
+
+ /*
+  * See if the side-channel descriptor is valid...
+  */
 
+  have_sidechannel = !fstat(CUPS_SC_FD, &sidechannel_info) &&
+                     S_ISSOCK(sidechannel_info.st_mode);
 
-  fputs("DEBUG: print_device\n", stderr);
+  g.wait_eof = WAIT_EOF;
 
  /*
   * Connect to the printer...
   */
 
-  while ((printer = find_device(print_cb, uri)) == NULL)
+  fprintf(stderr, "DEBUG: Printing on printer with URI: %s\n", uri);
+  while ((g.printer = find_device(print_cb, uri)) == NULL)
   {
-    _cupsLangPuts(stderr,
-                 _("INFO: Waiting for printer to become available...\n"));
+    _cupsLangPrintFilter(stderr, "INFO",
+                        _("Waiting for printer to become available."));
     sleep(5);
   }
 
+  g.print_fd = print_fd;
+
+ /*
+  * Some devices need a reset after finishing a job, these devices are
+  * marked with the USB_QUIRK_SOFT_RESET quirk.
+  */
+  g.printer->reset_after_job = (g.printer->quirks & USB_QUIRK_SOFT_RESET ? 1 : 0);
 
  /*
   * If we are printing data from a print driver on stdin, ignore SIGTERM
@@ -135,75 +245,412 @@ print_device(const char *uri,            /* I - Device URI */
     sigaction(SIGTERM, &action, NULL);
   }
 
-  tbytes = 0;
+ /*
+  * Start the side channel thread if the descriptor is valid...
+  */
+
+  pthread_mutex_init(&g.readwrite_lock_mutex, NULL);
+  pthread_cond_init(&g.readwrite_lock_cond, NULL);
+  g.readwrite_lock = 1;
+
+  if (have_sidechannel)
+  {
+    g.sidechannel_thread_stop = 0;
+    g.sidechannel_thread_done = 0;
+
+    pthread_cond_init(&g.sidechannel_thread_cond, NULL);
+    pthread_mutex_init(&g.sidechannel_thread_mutex, NULL);
+
+    if (pthread_create(&sidechannel_thread_id, NULL, sidechannel_thread, NULL))
+    {
+      fprintf(stderr, "DEBUG: Fatal USB error.\n");
+      _cupsLangPrintFilter(stderr, "ERROR",
+                          _("There was an unrecoverable USB error."));
+      fputs("DEBUG: Couldn't create side-channel thread.\n", stderr);
+      close_device(g.printer);
+      return (CUPS_BACKEND_STOP);
+    }
+  }
+
+ /*
+  * Debug mode: If option "usb-unidir" is given, always deactivate
+  * backchannel
+  */
+
+  num_opts = cupsParseOptions(argv[5], 0, &opts);
+  val = cupsGetOption("usb-unidir", num_opts, opts);
+  if (val && strcasecmp(val, "no") && strcasecmp(val, "off") &&
+      strcasecmp(val, "false"))
+  {
+    g.printer->read_endp = -1;
+    fprintf(stderr, "DEBUG: Forced uni-directional communication "
+           "via \"usb-unidir\" option.\n");
+  }
+
+ /*
+  * Debug mode: If option "usb-no-reattach" is given, do not re-attach
+  * the usblp kernel module after the job has completed.
+  */
+
+  val = cupsGetOption("usb-no-reattach", num_opts, opts);
+  if (val && strcasecmp(val, "no") && strcasecmp(val, "off") &&
+      strcasecmp(val, "false"))
+  {
+    g.printer->usblp_attached = 0;
+    fprintf(stderr, "DEBUG: Forced not re-attaching the usblp kernel module "
+           "after the job via \"usb-no-reattach\" option.\n");
+  }
+
+ /*
+  * Get the read thread going...
+  */
+
+  if (g.printer->read_endp != -1)
+  {
+    have_backchannel = 1;
+
+    g.read_thread_stop = 0;
+    g.read_thread_done = 0;
+
+    pthread_cond_init(&g.read_thread_cond, NULL);
+    pthread_mutex_init(&g.read_thread_mutex, NULL);
+
+    if (pthread_create(&read_thread_id, NULL, read_thread, NULL))
+    {
+      fprintf(stderr, "DEBUG: Fatal USB error.\n");
+      _cupsLangPrintFilter(stderr, "ERROR",
+                          _("There was an unrecoverable USB error."));
+      fputs("DEBUG: Couldn't create read thread.\n", stderr);
+      close_device(g.printer);
+      return (CUPS_BACKEND_STOP);
+    }
+  }
+  else
+    fprintf(stderr, "DEBUG: Uni-directional device/mode, back channel "
+           "deactivated.\n");
+
+ /*
+  * The main thread sends the print file...
+  */
 
-  pfds[0].fd     = print_fd;
-  pfds[0].events = POLLIN;
-  pfds[1].fd     = CUPS_SC_FD;
-  pfds[1].events = POLLIN;
+  g.drain_output = 0;
+  g.print_bytes         = 0;
+  total_bytes   = 0;
+  print_ptr     = print_buffer;
 
-  while (copies > 0 && tbytes >= 0)
+  while (status == CUPS_BACKEND_OK && copies-- > 0)
   {
-    copies --;
+    _cupsLangPrintFilter(stderr, "INFO", _("Sending data to printer."));
 
-    if (print_fd != 0)
+    if (print_fd != STDIN_FILENO)
     {
       fputs("PAGE: 1 1\n", stderr);
       lseek(print_fd, 0, SEEK_SET);
     }
 
-   /*
-    * TODO: Add back-channel support, along with better write error handling.
-    */
-
-    while (poll(pfds, 2, -1) > 0)
+    while (status == CUPS_BACKEND_OK)
     {
+      FD_ZERO(&input_set);
+
+      if (!g.print_bytes)
+       FD_SET(print_fd, &input_set);
+
+     /*
+      * Calculate select timeout...
+      *   If we have data waiting to send timeout is 100ms.
+      *   else if we're draining print_fd timeout is 0.
+      *   else we're waiting forever...
+      */
+
+      if (g.print_bytes)
+      {
+       tv.tv_sec  = 0;
+       tv.tv_usec = 100000;            /* 100ms */
+       timeout    = &tv;
+      }
+      else if (g.drain_output)
+      {
+       tv.tv_sec  = 0;
+       tv.tv_usec = 0;
+       timeout    = &tv;
+      }
+      else
+       timeout = NULL;
+
+     /*
+      * I/O is unlocked around select...
+      */
+
+      pthread_mutex_lock(&g.readwrite_lock_mutex);
+      g.readwrite_lock = 0;
+      pthread_cond_signal(&g.readwrite_lock_cond);
+      pthread_mutex_unlock(&g.readwrite_lock_mutex);
+
+      nfds = select(print_fd + 1, &input_set, NULL, NULL, timeout);
+
+     /*
+      * Reacquire the lock...
+      */
+
+      pthread_mutex_lock(&g.readwrite_lock_mutex);
+      while (g.readwrite_lock)
+       pthread_cond_wait(&g.readwrite_lock_cond, &g.readwrite_lock_mutex);
+      g.readwrite_lock = 1;
+      pthread_mutex_unlock(&g.readwrite_lock_mutex);
+
+      if (nfds < 0)
+      {
+       if (errno == EINTR && total_bytes == 0)
+       {
+         fputs("DEBUG: Received an interrupt before any bytes were "
+               "written, aborting.\n", stderr);
+         close_device(g.printer);
+          return (CUPS_BACKEND_OK);
+       }
+       else if (errno != EAGAIN && errno != EINTR)
+       {
+         _cupsLangPrintFilter(stderr, "ERROR",
+                              _("Unable to read print data."));
+         perror("DEBUG: select");
+         close_device(g.printer);
+          return (CUPS_BACKEND_FAILED);
+       }
+      }
+
+     /*
+      * If drain output has finished send a response...
+      */
+
+      if (g.drain_output && !nfds && !g.print_bytes)
+      {
+       /* Send a response... */
+       cupsSideChannelWrite(CUPS_SC_CMD_DRAIN_OUTPUT, CUPS_SC_STATUS_OK, NULL, 0, 1.0);
+       g.drain_output = 0;
+      }
+
      /*
-      * CUPS STR #3318: USB process hangs on end-of-file, making further
-      *                 printing impossible
-      *
-      * From a strict interpretation of POSIX poll(), POLLHUP should never be
-      * set without POLLIN, since POLLIN is the event you request.  That said,
-      * it appears that some versions of Linux break this.
+      * Check if we have print data ready...
       */
 
-      if (pfds[0].revents & (POLLIN | POLLHUP))
+      if (FD_ISSET(print_fd, &input_set))
       {
-       if ((bytes = read(print_fd, buffer, sizeof(buffer))) > 0)
+       g.print_bytes = read(print_fd, print_buffer, sizeof(print_buffer));
+
+       if (g.print_bytes < 0)
        {
-         if (usb_bulk_write(printer->handle, printer->write_endp, buffer,
-                               bytes, 45000) < 0)
+        /*
+         * Read error - bail if we don't see EAGAIN or EINTR...
+         */
+
+         if (errno != EAGAIN && errno != EINTR)
          {
-           _cupsLangPrintf(stderr,
-                           _("ERROR: Unable to write %d bytes to printer\n"),
-                           (int)bytes);
-           tbytes = -1;
-           break;
+           _cupsLangPrintFilter(stderr, "ERROR",
+                                _("Unable to read print data."));
+           perror("DEBUG: read");
+           close_device(g.printer);
+           return (CUPS_BACKEND_FAILED);
          }
 
-         tbytes += bytes;
+         g.print_bytes = 0;
+       }
+       else if (g.print_bytes == 0)
+       {
+        /*
+         * End of file, break out of the loop...
+         */
+
+         break;
+       }
+
+       print_ptr = print_buffer;
+
+       fprintf(stderr, "DEBUG: Read %d bytes of print data...\n",
+               (int)g.print_bytes);
+      }
+
+      if (g.print_bytes)
+      {
+       iostatus = libusb_bulk_transfer(g.printer->handle,
+                                       g.printer->write_endp,
+                                       print_buffer, g.print_bytes,
+                                       &bytes, 0);
+       /*
+       * Ignore timeout errors, but retain the number of bytes written to
+       * avoid sending duplicate data...
+       */
+
+       if (iostatus == LIBUSB_ERROR_TIMEOUT)
+       {
+         fputs("DEBUG: Got USB transaction timeout during write.\n", stderr);
+         iostatus = 0;
+       }
+
+       /*
+        * If we've stalled, retry the write...
+       */
+
+       else if (iostatus == LIBUSB_ERROR_PIPE)
+       {
+         fputs("DEBUG: Got USB pipe stalled during write.\n", stderr);
+
+         iostatus = libusb_bulk_transfer(g.printer->handle,
+                                         g.printer->write_endp,
+                                         print_buffer, g.print_bytes,
+                                         &bytes, 0);
+       }
+
+       /*
+       * Retry a write after an aborted write since we probably just got
+       * SIGTERM...
+       */
+
+       else if (iostatus == LIBUSB_ERROR_INTERRUPTED)
+       {
+         fputs("DEBUG: Got USB return aborted during write.\n", stderr);
+
+         iostatus = libusb_bulk_transfer(g.printer->handle,
+                                         g.printer->write_endp,
+                                         print_buffer, g.print_bytes,
+                                         &bytes, 0);
+        }
+
+       if (iostatus)
+       {
+        /*
+         * Write error - bail if we don't see an error we can retry...
+         */
+
+         _cupsLangPrintFilter(stderr, "ERROR",
+                              _("Unable to send data to printer."));
+         fprintf(stderr, "DEBUG: libusb write operation returned %x.\n",
+                 iostatus);
+
+         status = CUPS_BACKEND_FAILED;
+         break;
        }
-       else if (bytes == 0 || (bytes < 0 && errno != EAGAIN && errno != EINTR))
+       else if (bytes > 0)
+       {
+         fprintf(stderr, "DEBUG: Wrote %d bytes of print data...\n",
+                 (int)bytes);
+
+         g.print_bytes -= bytes;
+         print_ptr   += bytes;
+         total_bytes += bytes;
+       }
+      }
+
+      if (print_fd != 0 && status == CUPS_BACKEND_OK)
+       fprintf(stderr, "DEBUG: Sending print file, " CUPS_LLFMT " bytes...\n",
+               CUPS_LLCAST total_bytes);
+    }
+  }
+
+  fprintf(stderr, "DEBUG: Sent " CUPS_LLFMT " bytes...\n",
+          CUPS_LLCAST total_bytes);
+
+ /*
+  * Signal the side channel thread to exit...
+  */
+
+  if (have_sidechannel)
+  {
+    close(CUPS_SC_FD);
+    pthread_mutex_lock(&g.readwrite_lock_mutex);
+    g.readwrite_lock = 0;
+    pthread_cond_signal(&g.readwrite_lock_cond);
+    pthread_mutex_unlock(&g.readwrite_lock_mutex);
+
+    g.sidechannel_thread_stop = 1;
+    pthread_mutex_lock(&g.sidechannel_thread_mutex);
+
+    if (!g.sidechannel_thread_done)
+    {
+      gettimeofday(&tv, NULL);
+      cond_timeout.tv_sec  = tv.tv_sec + WAIT_SIDE_DELAY;
+      cond_timeout.tv_nsec = tv.tv_usec * 1000;
+
+      while (!g.sidechannel_thread_done)
+      {
+       if (pthread_cond_timedwait(&g.sidechannel_thread_cond,
+                                  &g.sidechannel_thread_mutex,
+                                  &cond_timeout) != 0)
+         break;
+      }
+    }
+
+    pthread_mutex_unlock(&g.sidechannel_thread_mutex);
+  }
+
+ /*
+  * Signal the read thread to exit then wait 7 seconds for it to complete...
+  */
+
+  if (have_backchannel)
+  {
+    g.read_thread_stop = 1;
+
+    pthread_mutex_lock(&g.read_thread_mutex);
+
+    if (!g.read_thread_done)
+    {
+      fputs("DEBUG: Waiting for read thread to exit...\n", stderr);
+
+      gettimeofday(&tv, NULL);
+      cond_timeout.tv_sec  = tv.tv_sec + WAIT_EOF_DELAY;
+      cond_timeout.tv_nsec = tv.tv_usec * 1000;
+
+      while (!g.read_thread_done)
+      {
+       if (pthread_cond_timedwait(&g.read_thread_cond, &g.read_thread_mutex,
+                                  &cond_timeout) != 0)
          break;
       }
 
-      if (pfds[1].revents & (POLLIN | POLLHUP))
+      /*
+       * If it didn't exit abort the pending read and wait an additional
+       * second...
+       */
+
+      if (!g.read_thread_done)
       {
-        if ((bytes = side_cb(printer, print_fd)) < 0)
-         pfds[1].events = 0;           /* Filter has gone away... */
-       else
-          tbytes += bytes;
+       fputs("DEBUG: Read thread still active, aborting the pending read...\n",
+             stderr);
+
+       g.wait_eof = 0;
+
+       gettimeofday(&tv, NULL);
+       cond_timeout.tv_sec  = tv.tv_sec + 1;
+       cond_timeout.tv_nsec = tv.tv_usec * 1000;
+
+       while (!g.read_thread_done)
+       {
+         if (pthread_cond_timedwait(&g.read_thread_cond, &g.read_thread_mutex,
+                                    &cond_timeout) != 0)
+           break;
+       }
       }
     }
+
+    pthread_mutex_unlock(&g.read_thread_mutex);
   }
 
  /*
-  * Close our connection and return...
+  * Close the connection and input file and general clean up...
+  */
+
+  if (g.printer->quirks & USB_QUIRK_DELAY_CLOSE)
+    sleep(1);
+
+  close_device(g.printer);
+
+ /*
+  * Clean up ....
   */
 
-  close_device(printer);
+  libusb_free_device_list(all_list, 1);
+  libusb_exit(NULL);
 
-  return (CUPS_BACKEND_OK);
+  return (status);
 }
 
 
@@ -214,6 +661,12 @@ print_device(const char *uri,              /* I - Device URI */
 static int                             /* I - 0 on success, -1 on failure */
 close_device(usb_printer_t *printer)   /* I - Printer */
 {
+  struct libusb_device_descriptor devdesc;
+                                        /* Current device descriptor */
+  struct libusb_config_descriptor *confptr;
+                                        /* Pointer to current configuration */
+
+
   if (printer->handle)
   {
    /*
@@ -221,19 +674,91 @@ close_device(usb_printer_t *printer)      /* I - Printer */
     * to the device...
     */
 
-    int number = printer->device->config[printer->conf].
-                     interface[printer->iface].
-                    altsetting[printer->altset].bInterfaceNumber;
-    usb_release_interface(printer->handle, number);
+    int errcode;                       /* Return value of libusb function */
+    int number1,                       /* Interface number */
+       number2;                        /* Configuration number */
+
+    errcode =
+      libusb_get_config_descriptor(printer->device, printer->conf, &confptr);
+    if (errcode >= 0)
+    {
+      number1 = confptr->interface[printer->iface].
+       altsetting[printer->altset].bInterfaceNumber;
+      libusb_release_interface(printer->handle, number1);
+
+      number2 = confptr->bConfigurationValue;
+
+      libusb_free_config_descriptor(confptr);
+
+     /*
+      * If we have changed the configuration from one valid configuration
+      * to another, restore the old one
+      */
+      if (printer->origconf > 0 && printer->origconf != number2)
+      {
+       fprintf(stderr, "DEBUG: Restoring USB device configuration: %d -> %d\n",
+               number2, printer->origconf);
+       if ((errcode = libusb_set_configuration(printer->handle,
+                                               printer->origconf)) < 0)
+       {
+         if (errcode != LIBUSB_ERROR_BUSY)
+         {
+           errcode =
+             libusb_get_device_descriptor (printer->device, &devdesc);
+           if (errcode < 0)
+             fprintf(stderr,
+                     "DEBUG: Failed to set configuration %d\n",
+                     printer->origconf);
+           else
+             fprintf(stderr,
+                     "DEBUG: Failed to set configuration %d for %04x:%04x\n",
+                     printer->origconf, devdesc.idVendor, devdesc.idProduct);
+         }
+       }
+      }
+
+     /*
+      * Re-attach "usblp" kernel module if it was attached before using this
+      * device
+      */
+      if (printer->usblp_attached == 1)
+       if (libusb_attach_kernel_driver(printer->handle, number1) < 0)
+       {
+         errcode = libusb_get_device_descriptor (printer->device, &devdesc);
+         if (errcode < 0)
+           fprintf(stderr,
+                   "DEBUG: Failed to re-attach \"usblp\" kernel module\n");
+         else
+           fprintf(stderr,
+                   "DEBUG: Failed to re-attach \"usblp\" kernel module to "
+                   "%04x:%04x\n", devdesc.idVendor, devdesc.idProduct);
+       }
+    }
+    else
+      fprintf(stderr,
+             "DEBUG: Failed to get configuration descriptor %d\n",
+             printer->conf);
+
+   /*
+    * Reset the device to clean up after the job
+    */
 
-    if (number != 0)
-      usb_release_interface(printer->handle, 0);
+    if (printer->reset_after_job == 1)
+    {
+      if ((errcode = libusb_reset_device(printer->handle)) < 0)
+       fprintf(stderr,
+               "DEBUG: Device reset failed, error code: %d\n",
+               errcode);
+      else
+       fprintf(stderr,
+               "DEBUG: Resetting printer.\n");
+    }
 
    /*
     * Close the interface and return...
     */
 
-    usb_close(printer->handle);
+    libusb_close(printer->handle);
     printer->handle = NULL;
   }
 
@@ -241,6 +766,23 @@ close_device(usb_printer_t *printer)       /* I - Printer */
 }
 
 
+/*
+ * 'compare_quirks()' - Compare two quirks entries.
+ */
+
+static int                             /* O - Result of comparison */
+compare_quirks(usb_quirk_t *a,         /* I - First quirk entry */
+               usb_quirk_t *b)         /* I - Second quirk entry */
+{
+  int result;                          /* Result of comparison */
+
+  if ((result = b->vendor_id - a->vendor_id) == 0)
+    result = b->product_id - a->product_id;
+
+  return (result);
+}
+
+
 /*
  * 'find_device()' - Find or enumerate USB printers.
  */
@@ -249,15 +791,22 @@ static usb_printer_t *                    /* O - Found printer */
 find_device(usb_cb_t   cb,             /* I - Callback function */
             const void *data)          /* I - User data for callback */
 {
-  struct usb_bus       *bus;           /* Current bus */
-  struct usb_device    *device;        /* Current device */
-  struct usb_config_descriptor *confptr;/* Pointer to current configuration */
-  struct usb_interface *ifaceptr;      /* Pointer to current interface */
-  struct usb_interface_descriptor *altptr;
+  libusb_device         **list;         /* List of connected USB devices */
+  libusb_device         *device = NULL;        /* Current device */
+  struct libusb_device_descriptor devdesc;
+                                        /* Current device descriptor */
+  struct libusb_config_descriptor *confptr = NULL;
+                                        /* Pointer to current configuration */
+  const struct libusb_interface *ifaceptr = NULL;
+                                        /* Pointer to current interface */
+  const struct libusb_interface_descriptor *altptr = NULL;
                                        /* Pointer to current alternate setting */
-  struct usb_endpoint_descriptor *endpptr;
+  const struct libusb_endpoint_descriptor *endpptr = NULL;
                                        /* Pointer to current endpoint */
-  int                  conf,           /* Current configuration */
+  ssize_t               err = 0,       /* Error code */
+                        numdevs,        /* number of connected devices */
+                        i = 0;
+  uint8_t              conf,           /* Current configuration */
                        iface,          /* Current interface */
                        altset,         /* Current alternate setting */
                        protocol,       /* Current protocol */
@@ -274,29 +823,50 @@ find_device(usb_cb_t   cb,                /* I - Callback function */
   * Initialize libusb...
   */
 
-  usb_init();
-  fprintf(stderr, "DEBUG: usb_find_busses=%d\n", usb_find_busses());
-  fprintf(stderr, "DEBUG: usb_find_devices=%d\n", usb_find_devices());
+  err = libusb_init(NULL);
+  if (err)
+  {
+    fprintf(stderr, "ERROR: Unable to initialize USB access via libusb, libusb error %i (%s)\n", (int)err, libusb_strerror((int)err));
+    return (NULL);
+  }
+
+  numdevs = libusb_get_device_list(NULL, &list);
+  fprintf(stderr, "DEBUG: libusb_get_device_list=%d\n", (int)numdevs);
 
  /*
   * Then loop through the devices it found...
   */
 
-  for (bus = usb_get_busses(); bus; bus = bus->next)
-    for (device = bus->devices; device; device = device->next)
+  if (numdevs > 0)
+    for (i = 0; i < numdevs; i++)
     {
+      device = list[i];
+
      /*
       * Ignore devices with no configuration data and anything that is not
       * a printer...
       */
 
-      if (!device->config || !device->descriptor.idVendor ||
-          !device->descriptor.idProduct)
+      if (libusb_get_device_descriptor(device, &devdesc) < 0)
        continue;
 
-      for (conf = 0, confptr = device->config;
-           conf < device->descriptor.bNumConfigurations;
-          conf ++, confptr ++)
+      if (!devdesc.bNumConfigurations || !devdesc.idVendor ||
+          !devdesc.idProduct)
+       continue;
+
+      printer.quirks = find_quirks(devdesc.idVendor, devdesc.idProduct);
+
+     /*
+      * Ignore blacklisted printers...
+      */
+
+      if (printer.quirks & USB_QUIRK_BLACKLIST)
+        continue;
+
+      for (conf = 0; conf < devdesc.bNumConfigurations; conf ++)
+      {
+       if (libusb_get_config_descriptor(device, conf, &confptr) < 0)
+         continue;
         for (iface = 0, ifaceptr = confptr->interface;
             iface < confptr->bNumInterfaces;
             iface ++, ifaceptr ++)
@@ -317,29 +887,34 @@ find_device(usb_cb_t   cb,                /* I - Callback function */
            * 1284.4 (packet mode) protocol as well.
            */
 
-           if (altptr->bInterfaceClass != USB_CLASS_PRINTER ||
-               altptr->bInterfaceSubClass != 1 ||
+           if (((altptr->bInterfaceClass != LIBUSB_CLASS_PRINTER ||
+                 altptr->bInterfaceSubClass != 1) &&
+                ((printer.quirks & USB_QUIRK_VENDOR_CLASS) == 0)) ||
                (altptr->bInterfaceProtocol != 1 &&     /* Unidirectional */
                 altptr->bInterfaceProtocol != 2) ||    /* Bidirectional */
                altptr->bInterfaceProtocol < protocol)
              continue;
 
-           read_endp  = -1;
-           write_endp = -1;
+           if (printer.quirks & USB_QUIRK_VENDOR_CLASS)
+             fprintf(stderr, "DEBUG: Printer does not report class 7 and/or "
+                     "subclass 1 but works as a printer anyway\n");
+
+           read_endp  = 0xff;
+           write_endp = 0xff;
 
            for (endp = 0, endpptr = altptr->endpoint;
                 endp < altptr->bNumEndpoints;
                 endp ++, endpptr ++)
-              if ((endpptr->bmAttributes & USB_ENDPOINT_TYPE_MASK) ==
-                     USB_ENDPOINT_TYPE_BULK)
+              if ((endpptr->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) ==
+                     LIBUSB_TRANSFER_TYPE_BULK)
              {
-               if (endpptr->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+               if (endpptr->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
                  read_endp = endp;
                else
                  write_endp = endp;
              }
 
-            if (write_endp >= 0)
+            if (write_endp != 0xff)
            {
             /*
              * Save the best match so far...
@@ -348,44 +923,71 @@ find_device(usb_cb_t   cb,                /* I - Callback function */
               protocol           = altptr->bInterfaceProtocol;
              printer.altset     = altset;
              printer.write_endp = write_endp;
-             printer.read_endp  = read_endp;
+             if (protocol > 1)
+               printer.read_endp = read_endp;
+             else
+               printer.read_endp = -1;
            }
          }
 
          if (protocol > 0)
          {
-           printer.device = device;
-           printer.conf   = conf;
-           printer.iface  = iface;
-           printer.handle = NULL;
+           printer.device   = device;
+           printer.conf     = conf;
+           printer.iface    = iface;
+           printer.protocol = protocol;
+           printer.handle   = NULL;
 
             if (!open_device(&printer, data != NULL))
            {
-             if (!get_device_id(&printer, device_id, sizeof(device_id)))
-             {
-                make_device_uri(&printer, device_id, device_uri,
-                               sizeof(device_uri));
+             get_device_id(&printer, device_id, sizeof(device_id));
+             make_device_uri(&printer, device_id, device_uri,
+                             sizeof(device_uri));
 
-               if ((*cb)(&printer, device_uri, device_id, data))
+             fprintf(stderr, "DEBUG2: Printer found with device ID: %s "
+                     "Device URI: %s\n",
+                     device_id, device_uri);
+
+             if ((*cb)(&printer, device_uri, device_id, data))
+             {
+               fprintf(stderr, "DEBUG: Device protocol: %d\n",
+                       printer.protocol);
+               if (printer.quirks & USB_QUIRK_UNIDIR)
                {
-                 printer.read_endp  = printer.device->config[printer.conf].
-                                          interface[printer.iface].
-                                          altsetting[printer.altset].
-                                          endpoint[printer.read_endp].
-                                          bEndpointAddress;
-                 printer.write_endp = printer.device->config[printer.conf].
-                                          interface[printer.iface].
+                 printer.read_endp = -1;
+                 fprintf(stderr, "DEBUG: Printer reports bi-di support "
+                         "but in reality works only uni-directionally\n");
+               }
+               if (printer.read_endp != -1)
+               {
+                 printer.read_endp = confptr->interface[printer.iface].
+                                           altsetting[printer.altset].
+                                           endpoint[printer.read_endp].
+                                           bEndpointAddress;
+               }
+               else
+                 fprintf(stderr, "DEBUG: Uni-directional USB communication "
+                         "only!\n");
+               printer.write_endp = confptr->interface[printer.iface].
                                           altsetting[printer.altset].
                                           endpoint[printer.write_endp].
                                           bEndpointAddress;
-                 return (&printer);
+               if (printer.quirks & USB_QUIRK_NO_REATTACH)
+               {
+                 printer.usblp_attached = 0;
+                 fprintf(stderr, "DEBUG: Printer does not like usblp "
+                         "kernel module to be re-attached after job\n");
                }
+               libusb_free_config_descriptor(confptr);
+               return (&printer);
               }
 
               close_device(&printer);
            }
          }
        }
+       libusb_free_config_descriptor(confptr);
+      }
     }
 
  /*
@@ -393,10 +995,47 @@ find_device(usb_cb_t   cb,                /* I - Callback function */
   * to print to...
   */
 
+ /*
+  * Clean up ....
+  */
+
+  if (numdevs >= 0)
+    libusb_free_device_list(list, 1);
+  libusb_exit(NULL);
+
   return (NULL);
 }
 
 
+/*
+ * 'find_quirks()' - Find the quirks for the given printer, if any.
+ *
+ * First looks for an exact match, then looks for the vendor ID wildcard match.
+ */
+
+static unsigned                                /* O - Quirks flags */
+find_quirks(int vendor_id,             /* I - Vendor ID */
+            int product_id)            /* I - Product ID */
+{
+  usb_quirk_t  key,                    /* Search key */
+               *match;                 /* Matching quirk entry */
+
+
+  key.vendor_id  = vendor_id;
+  key.product_id = product_id;
+
+  if ((match = cupsArrayFind(all_quirks, &key)) != NULL)
+    return (match->quirks);
+
+  key.product_id = 0;
+
+  if ((match = cupsArrayFind(all_quirks, &key)) != NULL)
+    return (match->quirks);
+
+  return (USB_QUIRK_WHITELIST);
+}
+
+
 /*
  * 'get_device_id()' - Get the IEEE-1284 device ID for the printer.
  */
@@ -409,10 +1048,12 @@ get_device_id(usb_printer_t *printer,    /* I - Printer */
   int  length;                         /* Length of device ID */
 
 
-  if (usb_control_msg(printer->handle,
-                      USB_TYPE_CLASS | USB_ENDPOINT_IN | USB_RECIP_INTERFACE,
-                     0, printer->conf, printer->iface,
-                     buffer, bufsize, 5000) < 0)
+  if (libusb_control_transfer(printer->handle,
+                             LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_ENDPOINT_IN |
+                             LIBUSB_RECIPIENT_INTERFACE,
+                             0, printer->conf,
+                             (printer->iface << 8) | printer->altset,
+                             (unsigned char *)buffer, bufsize, 5000) < 0)
   {
     *buffer = '\0';
     return (-1);
@@ -423,22 +1064,32 @@ get_device_id(usb_printer_t *printer,    /* I - Printer */
   * bytes.  The 1284 spec says the length is stored MSB first...
   */
 
-  length = (((unsigned)buffer[0] & 255) << 8) +
-          ((unsigned)buffer[1] & 255);
+  length = (int)((((unsigned)buffer[0] & 255) << 8) | ((unsigned)buffer[1] & 255));
 
  /*
-  * Check to see if the length is larger than our buffer; first
-  * assume that the vendor incorrectly implemented the 1284 spec,
-  * and then limit the length to the size of our buffer...
+  * Check to see if the length is larger than our buffer or less than 14 bytes
+  * (the minimum valid device ID is "MFG:x;MDL:y;" with 2 bytes for the length).
+  *
+  * If the length is out-of-range, assume that the vendor incorrectly
+  * implemented the 1284 spec and re-read the length as LSB first,..
   */
 
-  if (length > bufsize)
-    length = (((unsigned)buffer[1] & 255) << 8) +
-            ((unsigned)buffer[0] & 255);
+  if (length > bufsize || length < 14)
+    length = (int)((((unsigned)buffer[1] & 255) << 8) | ((unsigned)buffer[0] & 255));
 
   if (length > bufsize)
     length = bufsize;
 
+  if (length < 14)
+  {
+   /*
+    * Invalid device ID, clear it!
+    */
+
+    *buffer = '\0';
+    return (-1);
+  }
+
   length -= 2;
 
  /*
@@ -446,7 +1097,7 @@ get_device_id(usb_printer_t *printer,      /* I - Printer */
   * nul-terminate.
   */
 
-  memmove(buffer, buffer + 2, length);
+  memmove(buffer, buffer + 2, (size_t)length);
   buffer[length] = '\0';
 
   return (0);
@@ -470,7 +1121,8 @@ list_cb(usb_printer_t *printer,            /* I - Printer */
   * Get the device URI and make/model strings...
   */
 
-  backendGetMakeModel(device_id, make_model, sizeof(make_model));
+  if (backendGetMakeModel(device_id, make_model, sizeof(make_model)))
+    strlcpy(make_model, "Unknown", sizeof(make_model));
 
  /*
   * Report the printer...
@@ -487,6 +1139,107 @@ list_cb(usb_printer_t *printer,          /* I - Printer */
 }
 
 
+/*
+ * 'load_quirks()' - Load all quirks files in the /usr/share/cups/usb directory.
+ */
+
+static void
+load_quirks(void)
+{
+  const char   *datadir;               /* CUPS_DATADIR environment variable */
+  char         filename[1024],         /* Filename */
+               line[1024];             /* Line from file */
+  cups_dir_t   *dir;                   /* Directory */
+  cups_dentry_t        *dent;                  /* Directory entry */
+  cups_file_t  *fp;                    /* Quirks file */
+  usb_quirk_t  *quirk;                 /* New quirk */
+
+
+  all_quirks = cupsArrayNew((cups_array_func_t)compare_quirks, NULL);
+
+  if ((datadir = getenv("CUPS_DATADIR")) == NULL)
+    datadir = CUPS_DATADIR;
+
+  snprintf(filename, sizeof(filename), "%s/usb", datadir);
+  if ((dir = cupsDirOpen(filename)) == NULL)
+  {
+    perror(filename);
+    return;
+  }
+
+  fprintf(stderr, "DEBUG: Loading USB quirks from \"%s\".\n", filename);
+
+  while ((dent = cupsDirRead(dir)) != NULL)
+  {
+    if (!S_ISREG(dent->fileinfo.st_mode))
+      continue;
+
+    snprintf(filename, sizeof(filename), "%s/usb/%s", datadir, dent->filename);
+    if ((fp = cupsFileOpen(filename, "r")) == NULL)
+    {
+      perror(filename);
+      continue;
+    }
+
+    while (cupsFileGets(fp, line, sizeof(line)))
+    {
+     /*
+      * Skip blank and comment lines...
+      */
+
+      if (line[0] == '#' || !line[0])
+        continue;
+
+     /*
+      * Add a quirk...
+      */
+
+      if ((quirk = calloc(1, sizeof(usb_quirk_t))) == NULL)
+      {
+        perror("DEBUG: Unable to allocate memory for quirk");
+        break;
+      }
+
+      if (sscanf(line, "%x%x", &quirk->vendor_id, &quirk->product_id) < 1)
+      {
+        fprintf(stderr, "DEBUG: Bad line: %s\n", line);
+        free(quirk);
+        continue;
+      }
+
+      if (strstr(line, " blacklist"))
+        quirk->quirks |= USB_QUIRK_BLACKLIST;
+
+      if (strstr(line, " delay-close"))
+        quirk->quirks |= USB_QUIRK_DELAY_CLOSE;
+
+      if (strstr(line, " no-reattach"))
+        quirk->quirks |= USB_QUIRK_NO_REATTACH;
+
+      if (strstr(line, " soft-reset"))
+        quirk->quirks |= USB_QUIRK_SOFT_RESET;
+
+      if (strstr(line, " unidir"))
+        quirk->quirks |= USB_QUIRK_UNIDIR;
+
+      if (strstr(line, " usb-init"))
+        quirk->quirks |= USB_QUIRK_USB_INIT;
+
+      if (strstr(line, " vendor-class"))
+        quirk->quirks |= USB_QUIRK_VENDOR_CLASS;
+
+      cupsArrayAdd(all_quirks, quirk);
+    }
+
+    cupsFileClose(fp);
+  }
+
+  fprintf(stderr, "DEBUG: Loaded %d quirks.\n", cupsArrayCount(all_quirks));
+
+  cupsDirClose(dir);
+}
+
+
 /*
  * 'make_device_uri()' - Create a device URI for a USB printer.
  */
@@ -498,13 +1251,16 @@ make_device_uri(
     char          *uri,                        /* I - Device URI buffer */
     size_t        uri_size)            /* I - Size of device URI buffer */
 {
+  struct libusb_device_descriptor devdesc;
+                                        /* Current device descriptor */
   char         options[1024];          /* Device URI options */
   int          num_values;             /* Number of 1284 parameters */
   cups_option_t        *values;                /* 1284 parameters */
   const char   *mfg,                   /* Manufacturer */
                *mdl,                   /* Model */
-               *des,                   /* Description */
+               *des = NULL,            /* Description */
                *sern;                  /* Serial number */
+  size_t       mfglen;                 /* Length of manufacturer string */
   char         tempmfg[256],           /* Temporary manufacturer string */
                tempsern[256],          /* Temporary serial number string */
                *tempptr;               /* Pointer into temp string */
@@ -514,20 +1270,23 @@ make_device_uri(
   * Get the make, model, and serial numbers...
   */
 
-  num_values = _ppdGet1284Values(device_id, &values);
+  num_values = _cupsGet1284Values(device_id, &values);
 
   if ((sern = cupsGetOption("SERIALNUMBER", num_values, values)) == NULL)
     if ((sern = cupsGetOption("SERN", num_values, values)) == NULL)
-      if ((sern = cupsGetOption("SN", num_values, values)) == NULL)
+      if ((sern = cupsGetOption("SN", num_values, values)) == NULL &&
+         ((libusb_get_device_descriptor(printer->device, &devdesc) >= 0) &&
+          devdesc.iSerialNumber))
       {
        /*
         * Try getting the serial number from the device itself...
        */
 
-        int length = usb_get_string_simple(printer->handle,
-                                          printer->device->descriptor.
-                                              iSerialNumber,
-                                          tempsern, sizeof(tempsern) - 1);
+        int length =
+         libusb_get_string_descriptor_ascii(printer->handle,
+                                            devdesc.iSerialNumber,
+                                            (unsigned char *)tempsern,
+                                            sizeof(tempsern) - 1);
         if (length > 0)
        {
          tempsern[length] = '\0';
@@ -541,15 +1300,6 @@ make_device_uri(
   if ((mdl = cupsGetOption("MODEL", num_values, values)) == NULL)
     mdl = cupsGetOption("MDL", num_values, values);
 
-#ifdef __APPLE__
- /*
-  * To maintain compatibility with the original IOKit-based backend on Mac OS X,
-  * don't map manufacturer names...
-  */
-
-  if (!mfg)
-
-#else
  /*
   * To maintain compatibility with the original character device backend on
   * Linux and *BSD, map manufacturer names...
@@ -557,13 +1307,12 @@ make_device_uri(
 
   if (mfg)
   {
-    if (!strcasecmp(mfg, "Hewlett-Packard"))
+    if (!_cups_strcasecmp(mfg, "Hewlett-Packard"))
       mfg = "HP";
-    else if (!strcasecmp(mfg, "Lexmark International"))
+    else if (!_cups_strcasecmp(mfg, "Lexmark International"))
       mfg = "Lexmark";
   }
   else
-#endif /* __APPLE__ */
   {
    /*
     * No manufacturer?  Use the model string or description...
@@ -583,6 +1332,29 @@ make_device_uri(
     mfg = tempmfg;
   }
 
+  if (!mdl)
+  {
+   /*
+    * No model?  Use description...
+    */
+    if (des)
+      mdl = des; /* We remove the manufacturer name below */
+    else if (!strncasecmp(mfg, "Unknown", 7))
+      mdl = "Printer";
+    else
+      mdl = "Unknown Model";
+  }
+
+  mfglen = strlen(mfg);
+
+  if (!strncasecmp(mdl, mfg, mfglen) && _cups_isspace(mdl[mfglen]))
+  {
+    mdl += mfglen + 1;
+
+    while (_cups_isspace(*mdl))
+      mdl ++;
+  }
+
  /*
   * Generate the device URI from the manufacturer, model, serial number,
   * and interface number...
@@ -618,7 +1390,14 @@ static int                                /* O - 0 on success, -1 on error */
 open_device(usb_printer_t *printer,    /* I - Printer */
             int           verbose)     /* I - Update connecting-to-device state? */
 {
-  int  number;                         /* Configuration/interface/altset numbers */
+  struct libusb_device_descriptor devdesc;
+                                        /* Current device descriptor */
+  struct libusb_config_descriptor *confptr = NULL;
+                                        /* Pointer to current configuration */
+  int  number1 = -1,                   /* Configuration/interface/altset */
+        number2 = -1,                  /* numbers */
+        errcode = 0;
+  char current;                        /* Current configuration */
 
 
  /*
@@ -632,76 +1411,161 @@ open_device(usb_printer_t *printer,     /* I - Printer */
   * Try opening the printer...
   */
 
-  if ((printer->handle = usb_open(printer->device)) == NULL)
+  if ((errcode = libusb_open(printer->device, &printer->handle)) < 0)
+  {
+    fprintf(stderr, "DEBUG: Failed to open device, code: %d\n",
+           errcode);
     return (-1);
+  }
 
- /*
-  * Then set the desired configuration...
-  */
+  printer->usblp_attached = 0;
+  printer->reset_after_job = 0;
 
   if (verbose)
     fputs("STATE: +connecting-to-device\n", stderr);
 
-  number = printer->device->config[printer->conf].bConfigurationValue;
+  if ((errcode = libusb_get_device_descriptor(printer->device, &devdesc)) < 0)
+  {
+    fprintf(stderr, "DEBUG: Failed to get device descriptor, code: %d\n",
+           errcode);
+    goto error;
+  }
 
-  if (usb_set_configuration(printer->handle, number) < 0)
+ /*
+  * Get the "usblp" kernel module out of the way. This backend only
+  * works without the module attached.
+  */
+
+  errcode = libusb_kernel_driver_active(printer->handle, printer->iface);
+  if (errcode == 0)
+    printer->usblp_attached = 0;
+  else if (errcode == 1)
   {
-   /*
-    * If the set fails, chances are that the printer only supports a
-    * single configuration.  Technically these printers don't conform to
-    * the USB printer specification, but otherwise they'll work...
-    */
+    printer->usblp_attached = 1;
+    if ((errcode =
+        libusb_detach_kernel_driver(printer->handle, printer->iface)) < 0)
+    {
+      fprintf(stderr, "DEBUG: Failed to detach \"usblp\" module from %04x:%04x\n",
+             devdesc.idVendor, devdesc.idProduct);
+      goto error;
+    }
+  }
+  else
+  {
+    printer->usblp_attached = 0;
 
-    if (errno != EBUSY)
-      fprintf(stderr, "DEBUG: Failed to set configuration %d for %04x:%04x\n",
-              number, printer->device->descriptor.idVendor,
-             printer->device->descriptor.idProduct);
+    if (errcode != LIBUSB_ERROR_NOT_SUPPORTED)
+    {
+      fprintf(stderr,
+              "DEBUG: Failed to check whether %04x:%04x has the \"usblp\" "
+              "kernel module attached\n", devdesc.idVendor, devdesc.idProduct);
+      goto error;
+    }
   }
 
  /*
-  * Claim interfaces as needed...
+  * Set the desired configuration, but only if it needs changing. Some
+  * printers (e.g., Samsung) don't like libusb_set_configuration. It will
+  * succeed, but the following print job is sometimes silently lost by the
+  * printer.
   */
 
-  number = printer->device->config[printer->conf].interface[printer->iface].
-               altsetting[printer->altset].bInterfaceNumber;
-  while (usb_claim_interface(printer->handle, number) < 0)
-  {
-    if (errno != EBUSY)
-      fprintf(stderr, "DEBUG: Failed to claim interface %d for %04x:%04x: %s\n",
-              number, printer->device->descriptor.idVendor,
-             printer->device->descriptor.idProduct, strerror(errno));
+  if (libusb_control_transfer(printer->handle,
+                LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_ENDPOINT_IN |
+               LIBUSB_RECIPIENT_DEVICE,
+               8, /* GET_CONFIGURATION */
+               0, 0, (unsigned char *)&current, 1, 5000) < 0)
+    current = 0;                       /* Assume not configured */
+
+  printer->origconf = current;
 
+  if ((errcode =
+       libusb_get_config_descriptor (printer->device, printer->conf, &confptr))
+      < 0)
+  {
+    fprintf(stderr, "DEBUG: Failed to get config descriptor for %04x:%04x\n",
+           devdesc.idVendor, devdesc.idProduct);
     goto error;
   }
+  number1 = confptr->bConfigurationValue;
 
-  if (number != 0)
-    while (usb_claim_interface(printer->handle, 0) < 0)
+  if (number1 != current)
+  {
+    fprintf(stderr, "DEBUG: Switching USB device configuration: %d -> %d\n",
+           current, number1);
+    if ((errcode = libusb_set_configuration(printer->handle, number1)) < 0)
     {
-      if (errno != EBUSY)
-       fprintf(stderr, "DEBUG: Failed to claim interface 0 for %04x:%04x: %s\n",
-               printer->device->descriptor.idVendor,
-               printer->device->descriptor.idProduct, strerror(errno));
+     /*
+      * If the set fails, chances are that the printer only supports a
+      * single configuration.  Technically these printers don't conform to
+      * the USB printer specification, but otherwise they'll work...
+      */
 
-      goto error;
+      if (errcode != LIBUSB_ERROR_BUSY)
+        fprintf(stderr, "DEBUG: Failed to set configuration %d for %04x:%04x\n",
+               number1, devdesc.idVendor, devdesc.idProduct);
     }
+  }
 
  /*
-  * Set alternate setting...
+  * Claim interfaces as needed...
   */
 
-  number = printer->device->config[printer->conf].interface[printer->iface].
-               altsetting[printer->altset].bAlternateSetting;
-  while (usb_set_altinterface(printer->handle, number) < 0)
+  number1 = confptr->interface[printer->iface].
+    altsetting[printer->altset].bInterfaceNumber;
+
+  while ((errcode = libusb_claim_interface(printer->handle, number1)) < 0)
   {
-    if (errno != EBUSY)
+    if (errcode != LIBUSB_ERROR_BUSY)
+    {
       fprintf(stderr,
-              "DEBUG: Failed to set alternate interface %d for %04x:%04x: %s\n",
-              number, printer->device->descriptor.idVendor,
-             printer->device->descriptor.idProduct, strerror(errno));
+              "DEBUG: Failed to claim interface %d for %04x:%04x: %s\n",
+              number1, devdesc.idVendor, devdesc.idProduct, strerror(errno));
 
-    goto error;
+      goto error;
+    }
+    else if ((errcode = libusb_detach_kernel_driver(printer->handle, printer->iface)) < 0)
+    {
+      fprintf(stderr,
+              "DEBUG: Failed to detach \"usblp\" module from %04x:%04x\n",
+              devdesc.idVendor, devdesc.idProduct);
+
+      goto error;
+    }
+
+    sleep (1);
   }
 
+ /*
+  * Set alternate setting, but only if there is more than one option.  Some
+  * printers (e.g., Samsung) don't like usb_set_altinterface.
+  */
+
+  if (confptr->interface[printer->iface].num_altsetting > 1)
+  {
+    number1 = confptr->interface[printer->iface].
+                 altsetting[printer->altset].bInterfaceNumber;
+    number2 = confptr->interface[printer->iface].
+                 altsetting[printer->altset].bAlternateSetting;
+
+    while ((errcode =
+           libusb_set_interface_alt_setting(printer->handle, number1, number2))
+          < 0)
+    {
+      if (errcode != LIBUSB_ERROR_BUSY)
+      {
+        fprintf(stderr,
+                "DEBUG: Failed to set alternate interface %d for %04x:%04x: "
+                "%s\n",
+                number2, devdesc.idVendor, devdesc.idProduct, strerror(errno));
+
+       goto error;
+      }
+    }
+  }
+
+  libusb_free_config_descriptor(confptr);
+
   if (verbose)
     fputs("STATE: -connecting-to-device\n", stderr);
 
@@ -716,7 +1580,7 @@ open_device(usb_printer_t *printer,        /* I - Printer */
   if (verbose)
     fputs("STATE: -connecting-to-device\n", stderr);
 
-  usb_close(printer->handle);
+  libusb_close(printer->handle);
   printer->handle = NULL;
 
   return (-1);
@@ -733,100 +1597,418 @@ print_cb(usb_printer_t *printer,       /* I - Printer */
          const char    *device_id,     /* I - IEEE-1284 device ID */
          const void    *data)          /* I - User data (make, model, S/N) */
 {
-  return (!strcmp((char *)data, device_uri));
+  char requested_uri[1024],            /* Requested URI */
+       *requested_ptr,                 /* Pointer into requested URI */
+       detected_uri[1024],             /* Detected URI */
+       *detected_ptr;                  /* Pointer into detected URI */
+
+
+ /*
+  * If we have an exact match, stop now...
+  */
+
+  if (!strcmp((char *)data, device_uri))
+    return (1);
+
+ /*
+  * Work on copies of the URIs...
+  */
+
+  strlcpy(requested_uri, (char *)data, sizeof(requested_uri));
+  strlcpy(detected_uri, device_uri, sizeof(detected_uri));
+
+ /*
+  * libusb-discovered URIs can have an "interface" specification and this
+  * never happens for usblp-discovered URIs, so remove the "interface"
+  * specification from the URI which we are checking currently. This way a
+  * queue for a usblp-discovered printer can now be accessed via libusb.
+  *
+  * Similarly, strip "?serial=NNN...NNN" as needed.
+  */
+
+  if ((requested_ptr = strstr(requested_uri, "?interface=")) == NULL)
+    requested_ptr = strstr(requested_uri, "&interface=");
+  if ((detected_ptr = strstr(detected_uri, "?interface=")) == NULL)
+    detected_ptr = strstr(detected_uri, "&interface=");
+
+  if (!requested_ptr && detected_ptr)
+  {
+   /*
+    * Strip "[?&]interface=nnn" from the detected printer.
+    */
+
+    *detected_ptr = '\0';
+  }
+  else if (requested_ptr && !detected_ptr)
+  {
+   /*
+    * Strip "[?&]interface=nnn" from the requested printer.
+    */
+
+    *requested_ptr = '\0';
+  }
+
+  if ((requested_ptr = strstr(requested_uri, "?serial=?")) != NULL)
+  {
+   /*
+    * Strip "?serial=?" from the requested printer.  This is a special
+    * case, as "?serial=?" means no serial number and not the serial
+    * number '?'.  This is not covered by the checks below...
+    */
+
+    *requested_ptr = '\0';
+  }
+
+  if ((requested_ptr = strstr(requested_uri, "?serial=")) == NULL &&
+      (detected_ptr = strstr(detected_uri, "?serial=")) != NULL)
+  {
+   /*
+    * Strip "?serial=nnn" from the detected printer.
+    */
+
+    *detected_ptr = '\0';
+  }
+  else if (requested_ptr && !detected_ptr)
+  {
+   /*
+    * Strip "?serial=nnn" from the requested printer.
+    */
+
+    *requested_ptr = '\0';
+  }
+
+  return (!strcmp(requested_uri, detected_uri));
+}
+
+
+/*
+ * 'read_thread()' - Thread to read the backchannel data on.
+ */
+
+static void *read_thread(void *reference)
+{
+  unsigned char                readbuffer[512];
+  int                  rbytes;
+  int                  readstatus;
+  struct timeval       now,
+                       delay,
+                       end,
+                       timeleft;
+
+
+  (void)reference;
+
+ /*
+  * Read frequency: once every 250 milliseconds.
+  */
+
+  delay.tv_sec = 0;
+  delay.tv_usec = 250000;
+
+  do
+  {
+   /*
+    * Remember when we started so we can throttle the loop after the read
+    * call...
+    */
+
+    gettimeofday(&now, NULL);
+
+   /*
+    * Calculate what 250 milliSeconds are in absolute time...
+    */
+
+    timeradd(&now, &delay, &end);
+
+    rbytes     = sizeof(readbuffer);
+    readstatus = libusb_bulk_transfer(g.printer->handle,
+                                     g.printer->read_endp,
+                                     readbuffer, rbytes,
+                                     &rbytes, 60000);
+    if (readstatus == LIBUSB_SUCCESS && rbytes > 0)
+    {
+      fprintf(stderr, "DEBUG: Read %d bytes of back-channel data...\n",
+              (int)rbytes);
+      cupsBackChannelWrite((const char *)readbuffer, (size_t)rbytes, 1.0);
+    }
+    else if (readstatus == LIBUSB_ERROR_TIMEOUT)
+      fputs("DEBUG: Got USB transaction timeout during read.\n", stderr);
+    else if (readstatus == LIBUSB_ERROR_PIPE)
+      fputs("DEBUG: Got USB pipe stalled during read.\n", stderr);
+    else if (readstatus == LIBUSB_ERROR_INTERRUPTED)
+      fputs("DEBUG: Got USB return aborted during read.\n", stderr);
+
+   /*
+    * Make sure this loop executes no more than once every 250 miliseconds...
+    */
+
+    if ((g.wait_eof || !g.read_thread_stop))
+    {
+      gettimeofday(&now, NULL);
+      if (timercmp(&now, &end, <))
+      {
+       timersub(&end, &now, &timeleft);
+       usleep(1000000 * timeleft.tv_sec + timeleft.tv_usec);
+      }
+    }
+  } while (g.wait_eof || !g.read_thread_stop);
+
+ /*
+  * Let the main thread know that we have completed the read thread...
+  */
+
+  pthread_mutex_lock(&g.read_thread_mutex);
+  g.read_thread_done = 1;
+  pthread_cond_signal(&g.read_thread_cond);
+  pthread_mutex_unlock(&g.read_thread_mutex);
+
+  return (NULL);
 }
 
 
 /*
- * 'side_cb()' - Handle side-channel requests.
+ * 'sidechannel_thread()' - Handle side-channel requests.
  */
 
-static ssize_t                         /* O - Number of bytes written */
-side_cb(usb_printer_t *printer,                /* I - Printer */
-        int           print_fd)                /* I - File to print */
+static void*
+sidechannel_thread(void *reference)
 {
-  ssize_t              bytes,          /* Bytes read/written */
-                       tbytes;         /* Total bytes written */
-  char                 buffer[8192];   /* Print data buffer */
-  struct pollfd                pfd;            /* Poll descriptor */
   cups_sc_command_t    command;        /* Request command */
   cups_sc_status_t     status;         /* Request/response status */
   char                 data[2048];     /* Request/response data */
   int                  datalen;        /* Request/response data size */
 
 
-  tbytes  = 0;
-  datalen = sizeof(data);
-
-  if (cupsSideChannelRead(&command, &status, data, &datalen, 1.0))
-    return (-1);
+  (void)reference;
 
-  switch (command)
+  do
   {
-    case CUPS_SC_CMD_DRAIN_OUTPUT :
-       pfd.fd     = print_fd;
-       pfd.events = POLLIN;
+    datalen = sizeof(data);
 
-       while (poll(&pfd, 1, 1000) > 0)
-       {
-         if ((bytes = read(print_fd, buffer, sizeof(buffer))) > 0)
-         {
-           while (usb_bulk_write(printer->handle, printer->write_endp, buffer,
-                                 bytes, 5000) < 0)
-           {
-             _cupsLangPrintf(stderr,
-                             _("ERROR: Unable to write %d bytes to printer\n"),
-                             (int)bytes);
-             tbytes = -1;
-             break;
-           }
+    if (cupsSideChannelRead(&command, &status, data, &datalen, 1.0))
+    {
+      if (status == CUPS_SC_STATUS_TIMEOUT)
+       continue;
+      else
+       break;
+    }
+
+    switch (command)
+    {
+      case CUPS_SC_CMD_SOFT_RESET:     /* Do a soft reset */
+         fputs("DEBUG: CUPS_SC_CMD_SOFT_RESET received from driver...\n",
+               stderr);
+
+         soft_reset();
+         cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, NULL, 0, 1.0);
+         fputs("DEBUG: Returning status CUPS_STATUS_OK with no bytes...\n",
+               stderr);
+         break;
+
+      case CUPS_SC_CMD_DRAIN_OUTPUT:   /* Drain all pending output */
+         fputs("DEBUG: CUPS_SC_CMD_DRAIN_OUTPUT received from driver...\n",
+               stderr);
+
+         g.drain_output = 1;
+         break;
 
-           tbytes += bytes;
+      case CUPS_SC_CMD_GET_BIDI:       /* Is the connection bidirectional? */
+         fputs("DEBUG: CUPS_SC_CMD_GET_BIDI received from driver...\n",
+               stderr);
+
+         data[0] = (g.printer->protocol >= 2 ? 1 : 0);
+         cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
+
+         fprintf(stderr,
+                 "DEBUG: Returned CUPS_SC_STATUS_OK with 1 byte (%02X)...\n",
+                 data[0]);
+         break;
+
+      case CUPS_SC_CMD_GET_DEVICE_ID:  /* Return IEEE-1284 device ID */
+         fputs("DEBUG: CUPS_SC_CMD_GET_DEVICE_ID received from driver...\n",
+               stderr);
+
+         datalen = sizeof(data);
+         if (get_device_id(g.printer, data, sizeof(data)))
+         {
+           status  = CUPS_SC_STATUS_IO_ERROR;
+           datalen = 0;
          }
-         else if (bytes < 0 && errno != EAGAIN && errno != EINTR)
-           break;
-        }
+         else
+         {
+           status  = CUPS_SC_STATUS_OK;
+           datalen = strlen(data);
+         }
+         cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, datalen, 1.0);
 
-        if (tbytes < 0)
-         status = CUPS_SC_STATUS_IO_ERROR;
-       else
-         status = CUPS_SC_STATUS_OK;
+          if (datalen < sizeof(data))
+           data[datalen] = '\0';
+         else
+           data[sizeof(data) - 1] = '\0';
 
-       datalen = 0;
-        break;
+         fprintf(stderr,
+                 "DEBUG: Returning CUPS_SC_STATUS_OK with %d bytes (%s)...\n",
+                 datalen, data);
+         break;
 
-    case CUPS_SC_CMD_GET_BIDI :
-       status  = CUPS_SC_STATUS_OK;
-        data[0] = 0;                   /* TODO: Change to 1 when read supported */
-        datalen = 1;
-        break;
+      case CUPS_SC_CMD_GET_STATE:      /* Return device state */
+         fputs("DEBUG: CUPS_SC_CMD_GET_STATE received from driver...\n",
+               stderr);
 
-    case CUPS_SC_CMD_GET_DEVICE_ID :
-       if (get_device_id(printer, data, sizeof(data)))
-        {
-         status  = CUPS_SC_STATUS_IO_ERROR;
-         datalen = 0;
-       }
-       else
-        {
-         status  = CUPS_SC_STATUS_OK;
-         datalen = strlen(data);
-       }
-        break;
+         data[0] = CUPS_SC_STATE_ONLINE;
+         cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
+
+         fprintf(stderr,
+                 "DEBUG: Returned CUPS_SC_STATUS_OK with 1 byte (%02X)...\n",
+                 data[0]);
+         break;
+
+      case CUPS_SC_CMD_GET_CONNECTED:  /* Return whether device is
+                                          connected */
+         fputs("DEBUG: CUPS_SC_CMD_GET_CONNECTED received from driver...\n",
+               stderr);
+
+         data[0] = (g.printer->handle ? 1 : 0);
+         cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
+
+         fprintf(stderr,
+                 "DEBUG: Returned CUPS_SC_STATUS_OK with 1 byte (%02X)...\n",
+                 data[0]);
+         break;
+
+      default:
+         fprintf(stderr, "DEBUG: Unknown side-channel command (%d) received "
+                         "from driver...\n", command);
+
+         cupsSideChannelWrite(command, CUPS_SC_STATUS_NOT_IMPLEMENTED,
+                              NULL, 0, 1.0);
+
+         fputs("DEBUG: Returned CUPS_SC_STATUS_NOT_IMPLEMENTED with no bytes...\n",
+               stderr);
+         break;
+    }
+  }
+  while (!g.sidechannel_thread_stop);
+
+  pthread_mutex_lock(&g.sidechannel_thread_mutex);
+  g.sidechannel_thread_done = 1;
+  pthread_cond_signal(&g.sidechannel_thread_cond);
+  pthread_mutex_unlock(&g.sidechannel_thread_mutex);
+
+  return (NULL);
+}
+
+
+/*
+ * 'soft_reset()' - Send a soft reset to the device.
+ */
+
+static void
+soft_reset(void)
+{
+  fd_set         input_set;            /* Input set for select() */
+  struct timeval  tv;                  /* Time value */
+  char           buffer[2048];         /* Buffer */
+  struct timespec cond_timeout;                /* pthread condition timeout */
+
+
+ /*
+  * Send an abort once a second until the I/O lock is released by the main
+  * thread...
+  */
 
-    default :
-        status  = CUPS_SC_STATUS_NOT_IMPLEMENTED;
-       datalen = 0;
+  pthread_mutex_lock(&g.readwrite_lock_mutex);
+  while (g.readwrite_lock)
+  {
+    gettimeofday(&tv, NULL);
+    cond_timeout.tv_sec  = tv.tv_sec + 1;
+    cond_timeout.tv_nsec = tv.tv_usec * 1000;
+
+    while (g.readwrite_lock)
+    {
+      if (pthread_cond_timedwait(&g.readwrite_lock_cond,
+                                &g.readwrite_lock_mutex,
+                                &cond_timeout) != 0)
        break;
+    }
   }
 
-  cupsSideChannelWrite(command, status, data, datalen, 1.0);
+  g.readwrite_lock = 1;
+  pthread_mutex_unlock(&g.readwrite_lock_mutex);
 
-  return (tbytes);
+ /*
+  * Flush bytes waiting on print_fd...
+  */
+
+  g.print_bytes = 0;
+
+  FD_ZERO(&input_set);
+  FD_SET(g.print_fd, &input_set);
+
+  tv.tv_sec  = 0;
+  tv.tv_usec = 0;
+
+  while (select(g.print_fd+1, &input_set, NULL, NULL, &tv) > 0)
+    if (read(g.print_fd, buffer, sizeof(buffer)) <= 0)
+      break;
+
+ /*
+  * Send the reset...
+  */
+
+  soft_reset_printer(g.printer);
+
+ /*
+  * Release the I/O lock...
+  */
+
+  pthread_mutex_lock(&g.readwrite_lock_mutex);
+  g.readwrite_lock = 0;
+  pthread_cond_signal(&g.readwrite_lock_cond);
+  pthread_mutex_unlock(&g.readwrite_lock_mutex);
 }
 
 
 /*
- * End of "$Id$".
+ * 'soft_reset_printer()' - Do the soft reset request specific to printers
+ *
+ * This soft reset is specific to the printer device class and is much less
+ * invasive than the general USB reset libusb_reset_device(). Especially it
+ * does never happen that the USB addressing and configuration changes. What
+ * is actually done is that all buffers get flushed and the bulk IN and OUT
+ * pipes get reset to their default states. This clears all stall conditions.
+ * See http://cholla.mmto.org/computers/linux/usb/usbprint11.pdf
  */
 
+static int                             /* O - 0 on success, < 0 on error */
+soft_reset_printer(
+    usb_printer_t *printer)            /* I - Printer */
+{
+  struct libusb_config_descriptor *confptr = NULL;
+                                        /* Pointer to current configuration */
+  int interface,                       /* Interface to reset */
+      errcode;                         /* Error code */
+
+
+  if (libusb_get_config_descriptor(printer->device, printer->conf,
+                                   &confptr) < 0)
+    interface = printer->iface;
+  else
+    interface = confptr->interface[printer->iface].
+                         altsetting[printer->altset].bInterfaceNumber;
+
+  libusb_free_config_descriptor(confptr);
+
+  if ((errcode = libusb_control_transfer(printer->handle,
+                                        LIBUSB_REQUEST_TYPE_CLASS |
+                                        LIBUSB_ENDPOINT_OUT |
+                                        LIBUSB_RECIPIENT_OTHER,
+                                        2, 0, interface, NULL, 0, 5000)) < 0)
+    errcode = libusb_control_transfer(printer->handle,
+                                     LIBUSB_REQUEST_TYPE_CLASS |
+                                     LIBUSB_ENDPOINT_OUT |
+                                     LIBUSB_RECIPIENT_INTERFACE,
+                                     2, 0, interface, NULL, 0, 5000);
+
+  return (errcode);
+}