/*
* "$Id$"
*
- * Libusb interface code for CUPS.
+ * LIBUSB interface code for CUPS.
*
- * Copyright 2007-2010 by Apple Inc.
+ * Copyright 2007-2013 by Apple Inc.
*
* These coded instructions, statements, and computer programs are the
* property of Apple Inc. and are protected by Federal copyright
*
* 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.
+ * list_devices() - List the available printers.
+ * print_device() - Print a file to a USB device.
+ * close_device() - Close the connection to the USB printer.
+ * compare_quirks() - Compare two quirks entries.
+ * find_device() - Find or enumerate USB printers.
+ * find_quirks() - Find the quirks for the given printer, if any.
+ * get_device_id() - Get the IEEE-1284 device ID for the printer.
+ * list_cb() - List USB printers for discovery.
+ * load_quirks() - Load all quirks files in the /usr/share/cups/usb
+ * directory.
+ * 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.
+ * read_thread() - Thread to read the backchannel data on.
+ * sidechannel_thread() - Handle side-channel requests.
+ * soft_reset() - Send a soft reset to the device.
+ * soft_reset_printer() - Do the soft reset request specific to printers
*/
/*
* Include necessary headers...
*/
-#include <usb.h>
-#include <poll.h>
+#include <libusb.h>
#include <cups/cups-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_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);
/*
void
list_devices(void)
{
+ load_quirks();
+
fputs("DEBUG: list_devices\n", stderr);
find_device(list_cb, NULL);
}
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...
+ */
- fputs("DEBUG: print_device\n", stderr);
+ have_sidechannel = !fstat(CUPS_SC_FD, &sidechannel_info) &&
+ S_ISSOCK(sidechannel_info.st_mode);
+
+ 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)
{
_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
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, 3600000) < 0)
+ /*
+ * Read error - bail if we don't see EAGAIN or EINTR...
+ */
+
+ if (errno != EAGAIN && errno != EINTR)
{
_cupsLangPrintFilter(stderr, "ERROR",
- _("Unable to send data to printer."));
- tbytes = -1;
- break;
+ _("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, 60000);
+ /*
+ * 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, 60000);
+ }
+
+ /*
+ * 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, 60000);
+ }
+
+ 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)
+ {
+ fprintf(stderr, "DEBUG: Wrote %d bytes of print data...\n",
+ (int)bytes);
+
+ g.print_bytes -= bytes;
+ print_ptr += bytes;
+ total_bytes += bytes;
}
- else if (bytes == 0 || (bytes < 0 && errno != EAGAIN && errno != EINTR))
+ }
+
+ 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...
+ */
+
+ 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);
}
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)
{
/*
* 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;
}
}
+/*
+ * '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.
*/
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 */
* 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, "DEBUG: Unable to initialize USB access via libusb, "
+ "libusb error %i\n", (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 ++)
* 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;
+ 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 = -1;
write_endp = -1;
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;
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);
+ }
}
/*
* 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.
*/
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);
* bytes. The 1284 spec says the length is stored MSB first...
*/
- length = (((unsigned)buffer[0] & 255) << 8) +
+ length = (((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) +
+ if (length > bufsize || length < 14)
+ length = (((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;
/*
* 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...
}
+/*
+ * '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, " 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.
*/
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 */
if ((sern = cupsGetOption("SERIALNUMBER", num_values, values)) == NULL)
if ((sern = cupsGetOption("SERN", num_values, values)) == NULL)
if ((sern = cupsGetOption("SN", num_values, values)) == NULL &&
- printer->device->descriptor.iSerialNumber)
+ ((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';
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...
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...
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...
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 */
/*
* 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 (usb_set_configuration(printer->handle, number) < 0)
+ if ((errcode = libusb_get_device_descriptor(printer->device, &devdesc)) < 0)
{
- /*
- * 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...
- */
-
- if (errno != EBUSY)
- fprintf(stderr, "DEBUG: Failed to set configuration %d for %04x:%04x\n",
- number, printer->device->descriptor.idVendor,
- printer->device->descriptor.idProduct);
+ fprintf(stderr, "DEBUG: Failed to get device descriptor, code: %d\n",
+ errcode);
+ goto error;
}
/*
- * Claim interfaces as needed...
+ * Get the "usblp" kernel module out of the way. This backend only
+ * works without the module attached.
*/
- number = printer->device->config[printer->conf].interface[printer->iface].
- altsetting[printer->altset].bInterfaceNumber;
- while (usb_claim_interface(printer->handle, number) < 0)
+ errcode = libusb_kernel_driver_active(printer->handle, printer->iface);
+ if (errcode == 0)
+ printer->usblp_attached = 0;
+ else if (errcode == 1)
+ {
+ 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
{
- 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));
+ printer->usblp_attached = 0;
+ fprintf(stderr, "DEBUG: Failed to check whether %04x:%04x has the \"usblp\" kernel module attached\n",
+ devdesc.idVendor, devdesc.idProduct);
+ goto error;
+ }
+ /*
+ * 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.
+ */
+
+ if (libusb_control_transfer(printer->handle,
+ LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_ENDPOINT_IN |
+ LIBUSB_RECIPIENT_DEVICE,
+ 8, /* GET_CONFIGURATION */
+ 0, 0, (unsigned char *)¤t, 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 (number1 != current)
+ {
+ fprintf(stderr, "DEBUG: Switching USB device configuration: %d -> %d\n",
+ current, number1);
+ if ((errcode = libusb_set_configuration(printer->handle, number1)) < 0)
+ {
+ /*
+ * 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...
+ */
+
+ if (errcode != LIBUSB_ERROR_BUSY)
+ fprintf(stderr, "DEBUG: Failed to set configuration %d for %04x:%04x\n",
+ number1, devdesc.idVendor, devdesc.idProduct);
+ }
+ }
+
+ /*
+ * Claim interfaces as needed...
+ */
+
+ number1 = confptr->interface[printer->iface].
+ altsetting[printer->altset].bInterfaceNumber;
- if (number != 0)
- while (usb_claim_interface(printer->handle, 0) < 0)
+ while ((errcode = libusb_claim_interface(printer->handle, number1)) < 0)
+ {
+ if (errcode != LIBUSB_ERROR_BUSY)
{
- 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));
+ fprintf(stderr,
+ "DEBUG: Failed to claim interface %d for %04x:%04x: %s\n",
+ number1, devdesc.idVendor, devdesc.idProduct, strerror(errno));
goto error;
}
+ }
/*
- * Set alternate setting...
+ * Set alternate setting, but only if there is more than one option. Some
+ * printers (e.g., Samsung) don't like usb_set_altinterface.
*/
- number = printer->device->config[printer->conf].interface[printer->iface].
- altsetting[printer->altset].bAlternateSetting;
- while (usb_set_altinterface(printer->handle, number) < 0)
+ if (confptr->interface[printer->iface].num_altsetting > 1)
{
- if (errno != EBUSY)
- 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));
+ 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;
+ goto error;
+ }
+ }
}
+ libusb_free_config_descriptor(confptr);
+
if (verbose)
fputs("STATE: -connecting-to-device\n", stderr);
if (verbose)
fputs("STATE: -connecting-to-device\n", stderr);
- usb_close(printer->handle);
+ libusb_close(printer->handle);
printer->handle = NULL;
return (-1);
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, 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 ((readstatus != LIBUSB_SUCCESS || rbytes == 0) &&
+ (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)
- {
- _cupsLangPrintFilter(stderr, "ERROR",
- _("Unable to send data to printer."));
- 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;
- tbytes += bytes;
+ 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;
+
+ 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);
+
+ 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);
+}
- 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;
- default :
- status = CUPS_SC_STATUS_NOT_IMPLEMENTED;
- datalen = 0;
+/*
+ * '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...
+ */
+
+ 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);
+
+ /*
+ * 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;
- return (tbytes);
+ 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);
+}
+
+
+/*
+ * '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);
}