diff --git a/backend/usb-oh.c b/backend/usb-oh.c new file mode 100644 index 0000000..6268f77 --- /dev/null +++ b/backend/usb-oh.c @@ -0,0 +1,1817 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * OH USB interface code for CUPS. + */ + +/* + * Include necessary headers. + */ + +#include "usb_manager.h" +#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 + +/* + * 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 */ + +/* + * Local types... + */ + +typedef struct usb_printer_s /**** USB Printer Data ****/ +{ + ohusb_device_descriptor *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 */ + ohusb_pipe *pipe; /* Open pipe to device */ +} usb_printer_t; + +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; + +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; + +/* + * Globals... + */ + +cups_array_t *all_quirks; /* Array of printer quirks */ +usb_globals_t g = { 0 }; /* Globals */ +ohusb_device_descriptor **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); + fputs("DEBUG: list_devices out\n", stderr); +} + +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 */ + + fputs("DEBUG: print_device\n", stderr); + + 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) + { + ohusb_transfer_pipe tpipe = { + g.printer->iface, + g.printer->write_endp + }; + iostatus = OH_BulkTransferWrite(g.printer->pipe, &tpipe, + print_buffer, g.print_bytes, &bytes, 0); + /* + * Ignore timeout errors, but retain the number of bytes written to + * avoid sending duplicate data... + */ + + if (iostatus == OHUSB_ERROR_TIMEOUT) + { + fputs("DEBUG: Got USB transaction timeout during write.\n", stderr); + iostatus = 0; + } + + /* + * Retry a write after an aborted write since we probably just got + * SIGTERM... + */ + + else if (iostatus == OHUSB_ERROR_NO_DEVICE) + { + fputs("DEBUG: Got USB return aborted during write.\n", stderr); + + iostatus = OH_BulkTransferWrite(g.printer->pipe, &tpipe, + 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: ohusb 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; + } + fprintf(stderr, "DEBUG: %d bytes left to write...\n", (int)g.print_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); + + fputs("DEBUG: print_device out\n", stderr); + return (status); +} + +/* + * '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 */ +{ + ohusb_device_descriptor *list = NULL; /* List of connected USB devices */ + ohusb_device_descriptor *device = NULL; /* Current device */ + ohusb_config_descriptor *confptr = NULL; /* Pointer to current configuration */ + const ohusb_interface *ifaceptr = NULL; /* Pointer to current interface */ + const ohusb_interface_descriptor *altptr = NULL; /* Pointer to current alternate setting */ + const ohusb_endpoint_descriptor *endpptr = NULL; /* Pointer to current endpoint */ + ssize_t 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 */ + int32_t ret; + + /* + * get ohusb devices... + */ + + fputs("DEBUG: find_device\n", stderr); + ret = OH_GetDevices(&list, &numdevs); + if (ret != OHUSB_SUCCESS) { + fprintf(stderr, "DEBUG: find_device OH_GetDevices fail\n"); + return (NULL); + } + fprintf(stderr, "DEBUG: find_device OH_GetDevices numdevs=%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 (device == NULL) + continue; + + fprintf(stderr, "DEBUG: OH_GetDevices index=%zd, busNum=%d, devAddr=%d, idVendor=%d, idProduct=%d\n", + i, device->busNum, device->devAddr, device->idVendor, device->idProduct); + + if (!device->bNumConfigurations || !device->idVendor || !device->idProduct) + continue; + + printer.quirks = find_quirks(device->idVendor, device->idProduct); + fprintf(stderr, "DEBUG: find_device quirks=%d\n", printer.quirks); + + /* + * Ignore blacklisted printers... + */ + + if (printer.quirks & USB_QUIRK_BLACKLIST) + continue; + + for (conf = 0, confptr = device->config; conf < device->bNumConfigurations; conf ++, confptr ++) + { + if (confptr == NULL) + continue; + for (iface = 0, ifaceptr = confptr->interface; + iface < confptr->bNumInterfaces; + iface ++, ifaceptr ++) + { + /* + * Some printers offer multiple interfaces... + */ + + protocol = 0; + if (ifaceptr == NULL) + continue; + + for (altset = 0, altptr = ifaceptr->altsetting; + altset < ifaceptr->num_altsetting; + altset ++, altptr ++) + { + if (altptr == NULL) + continue; + /* + * Currently we only support unidirectional and bidirectional + * printers. Future versions of this code will support the + * 1284.4 (packet mode) protocol as well. + */ + fprintf(stderr, "DEBUG: find_device class=%x, subclass=%x, protocol=%x\n", + altptr->bInterfaceClass, altptr->bInterfaceSubClass, altptr->bInterfaceProtocol); + if (((altptr->bInterfaceClass != OHUSB_CLASS_PRINTER || altptr->bInterfaceSubClass != 1) && + ((printer.quirks & USB_QUIRK_VENDOR_CLASS) == 0)) || + (altptr->bInterfaceProtocol != 1 && /* Unidirectional */ + altptr->bInterfaceProtocol != 2) || /* Bidirectional */ + altptr->bInterfaceProtocol < protocol) + { + fprintf(stderr, "DEBUG: Not a usb printer.\n"); + 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 & OHUSB_TRANSFER_TYPE_MASK) == OHUSB_TRANSFER_TYPE_BULK) + { + if (endpptr->bEndpointAddress & OHUSB_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.pipe = 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"); + } + return (&printer); + } + + close_device(&printer); + } + } + } + } + } + + fputs("DEBUG: find_device out\n", stderr); + return (NULL); +} + +/* + * '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? */ +{ + ohusb_config_descriptor confptr; + /* Pointer to current configuration */ + int number1 = -1, /* Configuration/interface/altset */ + number2 = -1, /* numbers */ + errcode = 0; + char current; /* Current configuration */ + + fprintf(stderr, "DEBUG: open_device\n"); + + /* + * Return immediately if we are already connected... + */ + + if (printer->pipe) + return (0); + + /* + * Try opening the printer... + */ + + if ((errcode = OH_OpenDevice(printer->device, &(printer->pipe))) < 0) + { + fprintf(stderr, "DEBUG: Failed to open device, code: %d\n", errcode); + return (-1); + } + fprintf(stderr, "DEBUG: OH_OpenDevice success busNum=%d, devAddr=%d\n", + printer->pipe->busNum, printer->pipe->devAddr); + + printer->usblp_attached = 0; + printer->reset_after_job = 0; + + if (verbose) + fputs("STATE: +connecting-to-device\n", stderr); + if (printer->device == NULL) + { + fprintf(stderr, "DEBUG: Failed to get device descriptor, code: %d\n", errcode); + goto error; + } + + /* + * Set the desired configuration, but only if it needs changing. Some + * printers (e.g., Samsung) don't like OH_SetConfiguration. It will + * succeed, but the following print job is sometimes silently lost by the + * printer. + */ + + ohusb_control_transfer_parameter ctrlParam = { + OHUSB_REQUEST_TYPE_STANDARD | OHUSB_ENDPOINT_IN | OHUSB_RECIPIENT_DEVICE, + 8, 0, 0, 5000 + }; + + if (OH_ControlTransferRead(printer->pipe, &ctrlParam, (unsigned char *)¤t, 1) < 0) + { + current = 0; /* Assume not configured */ + } + + printer->origconf = current; + fprintf(stderr, "DEBUG: open_device OH_ControlTransferRead current = %c\n", current); + + confptr = printer->device->config[printer->conf]; + number1 = confptr.iConfiguration; + + if (number1 != current) + { + fprintf(stderr, "DEBUG: Switching USB device configuration: %d -> %d\n", + current, number1); + if ((errcode = OH_SetConfiguration(printer->pipe, 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 != OHUSB_ERROR_BUSY) + fprintf(stderr, "DEBUG: Failed to set configuration %d for %04x:%04x\n", + number1, printer->device->idVendor, printer->device->idProduct); + } + } + + /* + * Claim interfaces as needed... + */ + + number1 = confptr.interface[printer->iface].altsetting[printer->altset].bInterfaceNumber; + + if ((errcode = OH_ClaimInterface(printer->pipe, number1, true)) < 0) + { + fprintf(stderr, + "DEBUG: Failed to claim interface %d for %04x:%04x: %s\n", + number1, printer->device->idVendor, printer->device->idProduct, strerror(errno)); + + goto error; + } + + /* + * 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 = OH_SetInterface(printer->pipe, number1, number2)) < 0) + { + if (errcode != OHUSB_ERROR_BUSY) + { + fprintf(stderr, + "DEBUG: Failed to set alternate interface %d for %04x:%04x: " + "%s\n", + number2, printer->device->idVendor, printer->device->idProduct, strerror(errno)); + + goto error; + } + } + } + + 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); + + OH_CloseDevice(printer->pipe); + printer->pipe = NULL; + + return (-1); +} + +/* + * '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 */ +{ + fprintf(stderr, "DEBUG: get_device_id\n"); + + int length; /* Length of device ID */ + + ohusb_control_transfer_parameter ctrlParam = { + OHUSB_REQUEST_TYPE_CLASS | OHUSB_ENDPOINT_IN | OHUSB_RECIPIENT_INTERFACE, + 0, printer->conf, (printer->iface << 8) | printer->altset, 5000 + }; + + if (OH_ControlTransferRead(printer->pipe, &ctrlParam, (unsigned char *)buffer, bufsize) < 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'; + fprintf(stderr, "DEBUG: get_device_id Invalid device ID\n"); + return (-1); + } + + length -= 2; + fprintf(stderr, "DEBUG: get_device_id length = %d\n", length); + + /* + * 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); +} + +/* + * '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 */ +{ + 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 */ + + fprintf(stderr, "DEBUG: make_device_uri\n"); + + /* + * Get the make, model, and serial numbers... + */ + + num_values = _cupsGet1284Values(device_id, &values); + + if (printer->device != NULL && printer->device->iSerialNumber) + { + // Try getting the serial number from the device itself... + int length = OH_GetStringDescriptor(printer->pipe, printer->device->iSerialNumber, (unsigned char *)tempsern, sizeof(tempsern) - 1); + if (length > 0) + { + tempsern[length] = '\0'; + sern = tempsern; + } + else + fputs("DEBUG2: iSerialNumber could not be read.\n", stderr); + } + else + fputs("DEBUG2: iSerialNumber is not present.\n", stderr); + + if ((mfg = cupsGetOption("MANUFACTURER", num_values, values)) == NULL) + { + if ((mfg = cupsGetOption("MFG", num_values, values)) == NULL && printer->device->iManufacturer) + { + int length = OH_GetStringDescriptor(printer->pipe, printer->device->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 && printer->device->iProduct) + { + int length = OH_GetStringDescriptor(printer->pipe, printer->device->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); + + fprintf(stderr, "DEBUG2: make_device_uri sern=\"%s\", mfg=\"%s\", mdl=\"%s\"\n", sern, mfg, mdl); + + cupsFreeOptions(num_values, values); + + return (uri); +} + +/* + * '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 */ +{ + ohusb_config_descriptor confptr; /* Pointer to current configuration */ + + if (printer->pipe) + { + /* + * Release interfaces before closing so that we know all data is written + * to the device... + */ + + int errcode; /* Return value of ohusb function */ + int number1, /* Interface number */ + number2; /* Configuration number */ + + if (printer->device != NULL && printer->device->config != NULL) + { + confptr = printer->device->config[printer->conf]; + number1 = confptr.interface[printer->iface]. + altsetting[printer->altset].bInterfaceNumber; + OH_ReleaseInterface(printer->pipe, number1); + + number2 = confptr.iConfiguration; + + /* + * 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 = OH_SetConfiguration(printer->pipe, printer->origconf)) < 0) + { + fprintf(stderr, + "DEBUG: Failed to set configuration %d for %04x:%04x\n", + printer->origconf, printer->device->idVendor, printer->device->idProduct); + } + } + } + else + fprintf(stderr, + "DEBUG: Failed to get configuration descriptor %d\n", + printer->conf); + + /* + * Close the interface and return... + */ + + if (OH_CloseDevice(printer->pipe) == OHUSB_SUCCESS) + printer->pipe = NULL; + } + + return (0); +} + +/* + * 'read_thread()' - Thread to read the backchannel data on. + */ + +static void *read_thread(void *reference) +{ + unsigned char readbuffer[512]; + int rbytes; + int readstatus; + ohusb_transfer_pipe tpipe = { + g.printer->iface, + g.printer->read_endp + }; + + + (void)reference; + + do + { + /* + * Try reading from the OUT (to host) endpoint... + */ + + rbytes = sizeof(readbuffer); + readstatus = OH_BulkTransferRead(g.printer->pipe, + &tpipe, readbuffer, rbytes, &rbytes); + if (readstatus == OHUSB_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 + fprintf(stderr, "DEBUG: Got USB transaction error during read, errorcode=%d\n", readstatus); + + /* + * Make sure this loop executes no more than once every 250 miliseconds... + */ + + if ((readstatus != OHUSB_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->device ? 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); +} + +/* + * '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); +} + +/* + * '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); +} + +/* + * '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); +} + +/* + * '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)); + + /* + * ohusb-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 ohusb. + * + * 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)); +} + +/* + * '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); +} + +/* + * '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 OH_ResetDevice(). 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 */ +{ + ohusb_config_descriptor confptr; /* Pointer to current configuration */ + int interface, /* Interface to reset */ + errcode; /* Error code */ + + + if (printer->device == NULL) { + interface = printer->iface; + } else { + confptr = printer->device->config[printer->conf]; + interface = confptr.interface[printer->iface]. + altsetting[printer->altset].bInterfaceNumber; + } + + ohusb_control_transfer_parameter ctrlParam = { + OHUSB_REQUEST_TYPE_CLASS | OHUSB_ENDPOINT_OUT | OHUSB_RECIPIENT_OTHER, + 2, 0, interface, 5000 + }; + + if ((errcode = OH_ControlTransferWrite(printer->pipe, &ctrlParam, NULL, 0)) < 0) + { + ctrlParam.requestType = OHUSB_REQUEST_TYPE_CLASS | OHUSB_ENDPOINT_OUT | OHUSB_RECIPIENT_INTERFACE; + errcode = OH_ControlTransferWrite(printer->pipe, &ctrlParam, NULL, 0); + } + + + return (errcode); +} \ No newline at end of file diff --git a/backend/usb.c b/backend/usb.c index e1b2c03..50c1bc3 100644 --- a/backend/usb.c +++ b/backend/usb.c @@ -42,7 +42,9 @@ int print_device(const char *uri, const char *hostname, * Include the vendor-specific USB implementation... */ -#ifdef HAVE_LIBUSB +#ifdef HAVE_OPENHARMONY +# include "usb-oh.c" +#elif defined(HAVE_LIBUSB) # include "usb-libusb.c" #elif defined(__APPLE__) # include "usb-darwin.c"