/* * LIBUSB interface code for CUPS. * * Copyright © 2021 by OpenPrinting. * Copyright © 2007-2019 by Apple Inc. * * Licensed under Apache License v2.0. See the file "LICENSE" for more * information. */ /* * Include necessary headers... */ #include #include #include #include #include #include #include #include #include #include /* * 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 /* * Local types... */ typedef struct usb_printer_s /**** USB Printer Data ****/ { 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 */ 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 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); /* * 'list_devices()' - List the available printers. */ void list_devices(void) { load_quirks(); fputs("DEBUG: list_devices\n", stderr); find_device(list_cb, NULL); } /* * 'print_device()' - Print a file to a USB device. */ int /* O - Exit status */ print_device(const char *uri, /* I - Device URI */ const char *hostname, /* I - Hostname/manufacturer */ const char *resource, /* I - Resource/modelname */ char *options, /* I - Device options/serial number */ int print_fd, /* I - File descriptor to print */ int copies, /* I - Copies to print */ int argc, /* I - Number of command-line arguments (6 or 7) */ char *argv[]) /* I - Command-line arguments */ { int bytes; /* Bytes written */ ssize_t total_bytes; /* Total bytes written */ struct sigaction action; /* Actions for POSIX signals */ 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); g.wait_eof = WAIT_EOF; /* * 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", _("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 * so that the driver can finish out any page data, e.g. to eject the * current page. We only do this for stdin printing as otherwise there * is no way to cancel a raw print job... */ if (!print_fd) { memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); action.sa_handler = SIG_IGN; sigaction(SIGTERM, &action, NULL); } /* * 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... */ g.drain_output = 0; g.print_bytes = 0; total_bytes = 0; print_ptr = print_buffer; while (status == CUPS_BACKEND_OK && copies-- > 0) { _cupsLangPrintFilter(stderr, "INFO", _("Sending data to printer.")); if (print_fd != STDIN_FILENO) { fputs("PAGE: 1 1\n", stderr); lseek(print_fd, 0, SEEK_SET); } 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; } /* * Check if we have print data ready... */ if (FD_ISSET(print_fd, &input_set)) { g.print_bytes = read(print_fd, print_buffer, sizeof(print_buffer)); if (g.print_bytes < 0) { /* * Read error - bail if we don't see EAGAIN or EINTR... */ if (errno != EAGAIN && errno != EINTR) { _cupsLangPrintFilter(stderr, "ERROR", _("Unable to read print data.")); perror("DEBUG: read"); close_device(g.printer); return (CUPS_BACKEND_FAILED); } 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) { 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 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); } /* * 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 .... */ libusb_free_device_list(all_list, 1); libusb_exit(NULL); return (status); } /* * 'close_device()' - Close the connection to the USB printer. */ 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) { /* * Release interfaces before closing so that we know all data is written * to the device... */ 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 (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... */ libusb_close(printer->handle); printer->handle = NULL; } return (0); } /* * '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. */ static usb_printer_t * /* O - Found printer */ find_device(usb_cb_t cb, /* I - Callback function */ const void *data) /* I - User data for callback */ { 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 */ const struct libusb_endpoint_descriptor *endpptr = NULL; /* Pointer to current endpoint */ 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 */ endp, /* Current endpoint */ read_endp, /* Current read endpoint */ write_endp; /* Current write endpoint */ char device_id[1024],/* IEEE-1284 device ID */ device_uri[1024]; /* Device URI */ static usb_printer_t printer; /* Current printer */ /* * Initialize libusb... */ 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... */ 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 (libusb_get_device_descriptor(device, &devdesc) < 0) continue; 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 ++) { /* * Some printers offer multiple interfaces... */ protocol = 0; for (altset = 0, altptr = ifaceptr->altsetting; altset < ifaceptr->num_altsetting; // lgtm [cpp/comparison-with-wider-type] altset ++, altptr ++) { /* * Currently we only support unidirectional and bidirectional * printers. Future versions of this code will support the * 1284.4 (packet mode) protocol as well. */ 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 = 0xff; write_endp = 0xff; for (endp = 0, endpptr = altptr->endpoint; endp < altptr->bNumEndpoints; endp ++, endpptr ++) if ((endpptr->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK) { if (endpptr->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) read_endp = endp; else write_endp = endp; } if (write_endp != 0xff) { /* * Save the best match so far... */ protocol = altptr->bInterfaceProtocol; printer.altset = altset; printer.write_endp = write_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.protocol = protocol; printer.handle = NULL; if (!open_device(&printer, data != NULL)) { get_device_id(&printer, device_id, sizeof(device_id)); make_device_uri(&printer, device_id, device_uri, sizeof(device_uri)); 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 = -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; 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); } } /* * If we get this far without returning, then we haven't found a printer * 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. */ static int /* O - 0 on success, -1 on error */ get_device_id(usb_printer_t *printer, /* I - Printer */ char *buffer, /* I - String buffer */ size_t bufsize) /* I - Number of bytes in buffer */ { int length; /* Length of device ID */ 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); } /* * Extract the length of the device ID string from the first two * bytes. The 1284 spec says the length is stored MSB first... */ length = (int)((((unsigned)buffer[0] & 255) << 8) | ((unsigned)buffer[1] & 255)); /* * 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 < 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; /* * Copy the device ID text to the beginning of the buffer and * nul-terminate. */ memmove(buffer, buffer + 2, (size_t)length); buffer[length] = '\0'; return (0); } /* * 'list_cb()' - List USB printers for discovery. */ static int /* O - 0 to continue, 1 to stop */ list_cb(usb_printer_t *printer, /* I - Printer */ const char *device_uri, /* I - Device URI */ const char *device_id, /* I - IEEE-1284 device ID */ const void *data) /* I - User data (not used) */ { char make_model[1024]; /* Make and model */ /* * Get the device URI and make/model strings... */ if (backendGetMakeModel(device_id, make_model, sizeof(make_model))) strlcpy(make_model, "Unknown", sizeof(make_model)); /* * Report the printer... */ cupsBackendReport("direct", device_uri, make_model, make_model, device_id, NULL); /* * Keep going... */ return (0); } /* * '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. */ static char * /* O - Device URI */ make_device_uri( usb_printer_t *printer, /* I - Printer */ const char *device_id, /* I - IEEE-1284 device ID */ 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 = NULL, /* Description */ *sern = NULL; /* Serial number */ size_t mfglen; /* Length of manufacturer string */ char tempmdl[256], /* Temporary model string */ tempmfg[256], /* Temporary manufacturer string */ tempsern[256], /* Temporary serial number string */ *tempptr; /* Pointer into temp string */ /* * Get the make, model, and serial numbers... */ num_values = _cupsGet1284Values(device_id, &values); memset(&devdesc, 0, sizeof(devdesc)); if (libusb_get_device_descriptor(printer->device, &devdesc) >= 0 && devdesc.iSerialNumber) { // Try getting the serial number from the device itself... int length = libusb_get_string_descriptor_ascii(printer->handle, devdesc.iSerialNumber, (unsigned char *)tempsern, sizeof(tempsern) - 1); if (length > 0) { tempsern[length] = '\0'; sern = tempsern; fprintf(stderr, "DEBUG2: iSerialNumber=\"%s\"\n", tempsern); } else fputs("DEBUG2: iSerialNumber could not be read.\n", stderr); } else fputs("DEBUG2: iSerialNumber is not present.\n", stderr); #if 0 if (!sern) { // Fall back on serial number from IEEE-1284 device ID, which on some // printers (Issue #170) is a bogus hardcoded number. if ((sern = cupsGetOption("SERIALNUMBER", num_values, values)) == NULL) if ((sern = cupsGetOption("SERN", num_values, values)) == NULL) sern = cupsGetOption("SN", num_values, values); } #endif // 0 if ((mfg = cupsGetOption("MANUFACTURER", num_values, values)) == NULL) { if ((mfg = cupsGetOption("MFG", num_values, values)) == NULL && devdesc.iManufacturer) { int length = libusb_get_string_descriptor_ascii(printer->handle, devdesc.iManufacturer, (unsigned char *)tempmfg, sizeof(tempmfg) - 1); if (length > 0) { tempmfg[length] = '\0'; mfg = tempmfg; } } } if ((mdl = cupsGetOption("MODEL", num_values, values)) == NULL) { if ((mdl = cupsGetOption("MDL", num_values, values)) == NULL && devdesc.iProduct) { int length = libusb_get_string_descriptor_ascii(printer->handle, devdesc.iProduct, (unsigned char *)tempmdl, sizeof(tempmdl) - 1); if (length > 0) { tempmdl[length] = '\0'; mdl = tempmdl; } } } /* * To maintain compatibility with the original character device backend on * Linux and *BSD, map manufacturer names... */ if (mfg) { if (!_cups_strcasecmp(mfg, "Hewlett-Packard")) mfg = "HP"; else if (!_cups_strcasecmp(mfg, "Lexmark International")) mfg = "Lexmark"; } else { /* * No manufacturer? Use the model string or description... */ if (mdl) _ppdNormalizeMakeAndModel(mdl, tempmfg, sizeof(tempmfg)); else if ((des = cupsGetOption("DESCRIPTION", num_values, values)) != NULL || (des = cupsGetOption("DES", num_values, values)) != NULL) _ppdNormalizeMakeAndModel(des, tempmfg, sizeof(tempmfg)); else strlcpy(tempmfg, "Unknown", sizeof(tempmfg)); if ((tempptr = strchr(tempmfg, ' ')) != NULL) *tempptr = '\0'; 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... */ if (sern) { if (printer->iface > 0) snprintf(options, sizeof(options), "?serial=%s&interface=%d", sern, printer->iface); else snprintf(options, sizeof(options), "?serial=%s", sern); } else if (printer->iface > 0) snprintf(options, sizeof(options), "?interface=%d", printer->iface); else options[0] = '\0'; httpAssembleURIf(HTTP_URI_CODING_ALL, uri, uri_size, "usb", NULL, mfg, 0, "/%s%s", mdl, options); cupsFreeOptions(num_values, values); return (uri); } /* * 'open_device()' - Open a connection to the USB printer. */ 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? */ { 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 */ /* * Return immediately if we are already connected... */ if (printer->handle) return (0); /* * Try opening the printer... */ 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; printer->reset_after_job = 0; if (verbose) fputs("STATE: +connecting-to-device\n", stderr); if ((errcode = libusb_get_device_descriptor(printer->device, &devdesc)) < 0) { fprintf(stderr, "DEBUG: Failed to get device descriptor, code: %d\n", errcode); goto error; } /* * 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) { 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 (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; } } /* * 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; while ((errcode = libusb_claim_interface(printer->handle, number1)) < 0) { if (errcode != LIBUSB_ERROR_BUSY) { fprintf(stderr, "DEBUG: Failed to claim interface %d for %04x:%04x: %s\n", number1, devdesc.idVendor, devdesc.idProduct, strerror(errno)); 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); return (0); /* * If we get here, there was a hard error... */ error: if (verbose) fputs("STATE: -connecting-to-device\n", stderr); libusb_close(printer->handle); printer->handle = NULL; return (-1); } /* * 'print_cb()' - Find a USB printer for printing. */ static int /* O - 0 to continue, 1 to stop (found) */ print_cb(usb_printer_t *printer, /* I - Printer */ const char *device_uri, /* I - Device URI */ const char *device_id, /* I - IEEE-1284 device ID */ const void *data) /* I - User data (make, model, S/N) */ { 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; (void)reference; do { /* * Try reading from the OUT (to host) endpoint... */ 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 ((readstatus != LIBUSB_SUCCESS || rbytes == 0) && (g.wait_eof || !g.read_thread_stop)) usleep(250000); } 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); } /* * 'sidechannel_thread()' - Handle side-channel requests. */ static void* sidechannel_thread(void *reference) { 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 */ (void)reference; do { datalen = sizeof(data); 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; 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 { status = CUPS_SC_STATUS_OK; datalen = strlen(data); } cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, datalen, 1.0); if (datalen < sizeof(data)) data[datalen] = '\0'; else data[sizeof(data) - 1] = '\0'; fprintf(stderr, "DEBUG: Returning CUPS_SC_STATUS_OK with %d bytes (%s)...\n", datalen, data); 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); } /* * '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; } } 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; 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); }