2024-08-16 02:21:21 +00:00
|
|
|
diff --git a/backend/usb-oh.c b/backend/usb-oh.c
|
|
|
|
new file mode 100644
|
2024-08-23 01:43:33 +00:00
|
|
|
index 0000000..668ea73
|
2024-08-16 02:21:21 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/backend/usb-oh.c
|
2024-08-23 01:43:33 +00:00
|
|
|
@@ -0,0 +1,1818 @@
|
2024-08-16 02:21:21 +00:00
|
|
|
+/*
|
|
|
|
+ * 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 <cups/cups-private.h>
|
|
|
|
+#include <cups/ppd-private.h>
|
|
|
|
+#include <cups/dir.h>
|
|
|
|
+#include <pthread.h>
|
|
|
|
+#include <sys/select.h>
|
|
|
|
+#include <sys/types.h>
|
|
|
|
+#include <sys/stat.h>
|
|
|
|
+#include <sys/time.h>
|
|
|
|
+#include <unistd.h>
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * WAIT_EOF_DELAY is number of seconds we'll wait for responses from
|
|
|
|
+ * the printer after we've finished sending all the data
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#define WAIT_EOF 0
|
|
|
|
+#define WAIT_EOF_DELAY 7
|
|
|
|
+#define WAIT_SIDE_DELAY 3
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * 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;
|
2024-08-23 01:43:33 +00:00
|
|
|
+ printer.pipe = NULL;
|
2024-08-16 02:21:21 +00:00
|
|
|
+
|
|
|
|
+ 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, printer->origconf = %d\n",
|
|
|
|
+ current, printer->origconf);
|
|
|
|
+
|
|
|
|
+ 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"
|