[MOD] STR #4128: libusb-based USB backend: Further fixes

Till Kamppeter <[email protected]>
Newsgroups gmane.comp.printing.cups.bugs
Message-ID <[email protected]>
DO NOT REPLY TO THIS MESSAGE.  INSTEAD, POST ANY RESPONSES TO THE LINK BELOW.

[STR New]

Based on work together with Ubuntu bug reporters I succeeded to fix several
issues with the libusb-based CUPS backend. Now practically all real USB
printers (no USB->Parallel adapters) work.

Patches for 1.6.x and 1.5.x are attached.

Following Ubuntu bugs are solved:

https://bugs.launchpad.net/ubuntu/+source/cups/+bug/1000253
  Brother HL-1440 printing extra page with PJL codes

https://bugs.launchpad.net/ubuntu/+source/cups/+bug/995111
  Print failure since upgrade to 12.04

For following bug I have added a configuration option:

https://bugs.launchpad.net/ubuntu/+source/cups/+bug/1001028
  Delay after every print job on USB

Blacklisting the usblp kernel module is not needed any more, the usblp
backend correctly detaches and re-attaches the module.

The changes I have done are:

- Support for mon-directional devices, both protocol-1 devices and 
  devices where no read endpoint is found.

- Soft reset specific to the "PRINTER" device class. This allows a 
  reset without reconnecting.

- When closing the device, it will also get reset to its original 
  configuration, before re-attaching the usblp kernel module.

- Added option "usb-unidir" to force the backend into uni-directional 
  mode. This allows to work around problems with bi-di communications,
  especially also a delay at the end of the job caused by closing the 
  read channel (happens only for some devices). Also useful for
  debugging.

- Some extra debug messages.

- Added a missing libusb_free_config_descriptor().

Can this be put into CUPS 1.6.x/1.5.x? Thanks.

Link: http://www.cups.org/str.php?L4128
Version: 1.6-current

_______________________________________________
cups-bugs mailing list
[email protected]
http://lists.easysw.com/mailman/listinfo/cups-bugs
usb-libusb-c-update-1-6-x.patch (text/plain, 13 KB)
--- backend/usb-libusb.c	2012-06-22 11:53:18.043277991 +0200
+++ backend/usb-libusb.c	2012-06-22 11:52:50.723163432 +0200
@@ -22,6 +22,8 @@
  *   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.
+ *   printer_class_soft_reset()' - Do the soft reset request specific to
+ *                          printers
  *   read_thread()	  - Thread to read the backchannel data on.
  *   sidechannel_thread() - Handle side-channel requests.
  *   soft_reset()	  - Send a soft reset to the device.
@@ -60,12 +62,13 @@
 {
   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 */
+			read_endp,	/* Read endpoint */
 			protocol,	/* Protocol: 1 = Uni-di, 2 = Bi-di. */
-                        usblp_attached; /* Is the "usblp" kernel module
+			usblp_attached;	/* Is the "usblp" kernel module
 					   attached? */
   struct libusb_device_handle *handle;	/* Open handle to device */
 } usb_printer_t;
@@ -124,6 +127,7 @@
 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 int		printer_class_soft_reset(usb_printer_t *printer);
 static void		*read_thread(void *reference);
 static void		*sidechannel_thread(void *reference);
 static void		soft_reset(void);
@@ -163,7 +167,8 @@
 		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? */
+  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 */
@@ -172,6 +177,9 @@
   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 */
 
 
  /*
@@ -187,6 +195,7 @@
   * Connect to the printer...
   */
 
+  fprintf(stderr, "DEBUG: Printing on printer with URI: %s\n", uri);
   while ((g.printer = find_device(print_cb, uri)) == NULL)
   {
     _cupsLangPrintFilter(stderr, "INFO",
@@ -240,24 +249,47 @@
   }
 
  /*
-  * Get the read thread going...
+  * Debug mode: If option "usb-unidir" is given, always deactivate
+  * backchannel
   */
 
-  g.read_thread_stop = 0;
-  g.read_thread_done = 0;
+  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");
+  }
 
-  pthread_cond_init(&g.read_thread_cond, NULL);
-  pthread_mutex_init(&g.read_thread_mutex, NULL);
+ /*
+  * Get the read thread going...
+  */
 
-  if (pthread_create(&read_thread_id, NULL, read_thread, NULL))
+  if (g.printer->read_endp != -1)
   {
-    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);
+    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...
@@ -515,38 +547,18 @@
   * Signal the read thread to exit then wait 7 seconds for it to complete...
   */
 
-  g.read_thread_stop = 1;
-
-  pthread_mutex_lock(&g.read_thread_mutex);
-
-  if (!g.read_thread_done)
+  if (have_backchannel)
   {
-    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;
+    g.read_thread_stop = 1;
 
-    while (!g.read_thread_done)
-    {
-      if (pthread_cond_timedwait(&g.read_thread_cond, &g.read_thread_mutex,
-				 &cond_timeout) != 0)
-	break;
-    }
-
-   /*
-    * If it didn't exit abort the pending read and wait an additional second...
-    */
+    pthread_mutex_lock(&g.read_thread_mutex);
 
     if (!g.read_thread_done)
     {
-      fputs("DEBUG: Read thread still active, aborting the pending read...\n",
-	    stderr);
-
-      g.wait_eof = 0;
+      fputs("DEBUG: Waiting for read thread to exit...\n", stderr);
 
       gettimeofday(&tv, NULL);
-      cond_timeout.tv_sec  = tv.tv_sec + 1;
+      cond_timeout.tv_sec  = tv.tv_sec + WAIT_EOF_DELAY;
       cond_timeout.tv_nsec = tv.tv_usec * 1000;
 
       while (!g.read_thread_done)
@@ -555,10 +567,34 @@
 				   &cond_timeout) != 0)
 	  break;
       }
+
+      /*
+       * If it didn't exit abort the pending read and wait an additional
+       * second...
+       */
+  
+      if (!g.read_thread_done)
+      {
+	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);
+    pthread_mutex_unlock(&g.read_thread_mutex);
+  }
 
   if (print_fd)
     close(print_fd);
@@ -601,24 +637,51 @@
     */
 
     int errcode;			/* Return value of libusb function */
-    int number;				/* Interface number */
+    int number1,			/* Interface number */
+	number2;			/* Configuration number */
 
-    errcode =
+    errcode = 
       libusb_get_config_descriptor (printer->device, printer->conf, &confptr);
     if (errcode >= 0)
     {
-      number = confptr->interface[printer->iface].
+      number1 = confptr->interface[printer->iface].
 	altsetting[printer->altset].bInterfaceNumber;
-      libusb_release_interface(printer->handle, number);
-      if (number != 0)
-	libusb_release_interface(printer->handle, 0);
+      libusb_release_interface(printer->handle, number1);
+
+      number2 = confptr->bConfigurationValue;
+
+      libusb_free_config_descriptor(confptr);
+
+     /*
+      * If we have changed the configuration, restore the old one
+      */
+      if (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, printer->iface) < 0)
+	if (libusb_attach_kernel_driver(printer->handle, number1) < 0)
 	{
 	  errcode = libusb_get_device_descriptor (printer->device, &devdesc);
 	  if (errcode < 0)
@@ -629,9 +692,11 @@
 		    "DEBUG: Failed to re-attach \"usblp\" kernel module to "
 		    "%04x:%04x\n", devdesc.idVendor, devdesc.idProduct);
 	}
-
-      libusb_free_config_descriptor(confptr);
     }
+    else
+      fprintf(stderr,
+	      "DEBUG: Failed to get configuration descriptor %d\n",
+	      printer->conf);
 
    /*
     * Close the interface and return...
@@ -764,7 +829,10 @@
               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;
 	    }
 	  }
 
@@ -782,16 +850,29 @@
 	      make_device_uri(&printer, device_id, device_uri,
 			      sizeof(device_uri));
 
+	      fprintf(stderr, "DEBUG: Printer found with device ID: %s "
+		      "Device URI: %s\n",
+		      device_id, device_uri);
+
 	      if ((*cb)(&printer, device_uri, device_id, data))
 	      {
-		printer.read_endp  = confptr->interface[printer.iface].
-					   altsetting[printer.altset].
-					   endpoint[printer.read_endp].
-					   bEndpointAddress;
+		fprintf(stderr, "DEBUG: Device protocol: %d\n",
+			printer.protocol);
+		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;
+		libusb_free_config_descriptor(confptr);
 		return (&printer);
               }
 
@@ -1095,8 +1176,12 @@
   * Try opening the printer...
   */
 
-  if (libusb_open(printer->device, &printer->handle) < 0)
+  if ((errcode = libusb_open(printer->device, &printer->handle)) < 0)
+  {
+    fprintf(stderr, "DEBUG: Failed to open device, code: %d\n",
+	    errcode);
     return (-1);
+  }
 
   printer->usblp_attached = 0;
 
@@ -1132,14 +1217,9 @@
   else
   {
     printer->usblp_attached = 0;
-
-    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;
-    }
+    fprintf(stderr, "DEBUG: Failed to check whether %04x:%04x has the \"usblp\" kernel module attached\n",
+	      devdesc.idVendor, devdesc.idProduct);
+    goto error;
   }
 
  /*
@@ -1156,7 +1236,9 @@
 		0, 0, (unsigned char *)&current, 1, 5000) < 0)
     current = 0;			/* Assume not configured */
 
-  if ((errcode =
+  printer->origconf = current;
+
+  if ((errcode = 
        libusb_get_config_descriptor (printer->device, printer->conf, &confptr))
       < 0)
   {
@@ -1168,6 +1250,8 @@
 
   if (number1 != current)
   {
+    fprintf(stderr, "DEBUG: Switching USB device configuration: %d -> %d\n", 
+	    current, number1);
     if ((errcode = libusb_set_configuration(printer->handle, number1)) < 0)
     {
      /*
@@ -1347,6 +1431,39 @@
 
 
 /*
+ * 'printer_class_soft_reset()' - 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 */
+printer_class_soft_reset(usb_printer_t *printer) /* I - Printer */
+{
+  struct libusb_config_descriptor *confptr = NULL;
+                                        /* Pointer to current configuration */
+  int interface;
+
+  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);
+  return libusb_control_transfer(printer->handle,
+				 LIBUSB_REQUEST_TYPE_CLASS |
+				 LIBUSB_ENDPOINT_OUT |
+				 LIBUSB_RECIPIENT_INTERFACE,
+				 2, 0, interface, NULL, 0, 5000);
+}
+
+
+/*
  * 'read_thread()' - Thread to read the backchannel data on.
  */
 
@@ -1620,7 +1737,7 @@
   * Send the reset...
   */
 
-  libusb_reset_device (g.printer->handle);
+  printer_class_soft_reset (g.printer);
 
  /*
   * Release the I/O lock...
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.