Hi, Greg.  I would like to submit the following patch to printer.c for
inclusion into 2.5.  It adds the ioctls I need for the HP OfficeJet Linux
driver project (http://hpoj.sourceforge.net) to dynamically switch
7/1/[123] alternate settings and send an HP vendor-specific command
needed to support the photo-card readers on some HP PhotoSmart printers.
I have tested it heavily over the last several months on a variety of HP
printers, many of them multi-function peripherals.  I also incorporated
some stylistic feedback from Pete Zaitcev (thanks, Pete!).

The patch is against 2.4.13, but 2.5.1's printer.c is the same.  If
possible, I would also like these changes incorporated into 2.4 and 2.2.
I added the necessary "#if"s so that the patched printer.c will work on
2.2, 2.4, and 2.5.  If you'd like, feel free to remove the version-specific
sections that are irrelevant to the version(s) you choose to accept.

I didn't incorporate Oliver Neukum's and Pavel Machek's patches from two
weeks ago, because I don't know whether you incorporated them into your
trees.  I waited for 2.5.1 to come out to see if any new changes showed
up since 2.4.13 that I should merge into my version, but there were none.

Thank you very much, and let me know if you have any questions or if there
are any further changes I should make before you'll accept it.

David

===================================================================
Here's my README file from
"http://hpoj.sourceforge.net/download/experimental/linux-usb/":
-------------------------------------------------------------------
This is an update for the Linux USB printer.c driver for 2.2 and 2.4
kernels.  It also applies to 2.5.1, whose printer.c is exactly the same
as that in 2.4.13.

It includes the following changes:
- ioctls to enable the CVS version of ptal-mlcd (http://hpoj.sourceforge.net)
  to dynamically switch alternate settings and parallel-port-style channels.
  This allows ptal-mlcd to work over USB with HP PhotoSmart printers and
  some DeskJets (9xx, 12xx, cp1160, and some others), and it fixes the
  problem where ptal-mlcd was needed in order to be able to print to the
  LaserJet 1200 and 2200.
  See http://hpoj.sourceforge.net/doc/ioctl_requests.html for more
  information on these ioctls, not all of which are actually implemented yet.
- A "proto_bias" parameter (thanks to Pete Zaitcev) that changes the
  default preferred interface from 7/1/2 to 7/1/3 or 7/1/1.
  NOTE: If you use this version of printer.c with the 0.8 (not latest CVS)
  version of ptal-mlcd, then you'll need to use this feature to set 7/1/3,
  as documented at "http://hpoj.sourceforge.net/todo.shtml";.
- Added MODULE_{INC,DEC}_USE_COUNT to prevent unloading printer.o while it's
  active, which causes OOPSes.
- Fixed buffer overflow in usblp_table when maximum number of connected
  printers was exceeded.
- Various code cleanups, especially to consolidate duplicated code.
- A backport to 2.2.

Files in this directory:
- README -- this file
- printer.c -- the hacked version of printer.c
- Makefile -- the makefile I use so I don't have to rebuild the whole kernel
- printer-2.4.13.c -- for comparison, the printer.c from kernel 2.4.13
- printer.c.diff -- a diff between printer-2.4.13.c and printer.c
- printer.c.udiff -- a unified diff between printer-2.4.13.c and printer.c

-- David Paschal ([EMAIL PROTECTED], [EMAIL PROTECTED])

========================================================================
And here's the patch in "diff -u" format:
------------------------------------------------------------------------
--- printer-2.4.13.c    Mon Oct 29 18:04:10 2001
+++ printer.c   Thu Dec 20 04:02:02 2001
@@ -1,10 +1,12 @@
 /*
- * printer.c  Version 0.8
+ * printer.c  Version 0.11
  *
  * Copyright (c) 1999 Michael Gee      <[EMAIL PROTECTED]>
  * Copyright (c) 1999 Pavel Machek     <[EMAIL PROTECTED]>
  * Copyright (c) 2000 Randy Dunlap     <[EMAIL PROTECTED]>
  * Copyright (c) 2000 Vojtech Pavlik   <[EMAIL PROTECTED]>
+ # Copyright (c) 2001 Pete Zaitcev     <[EMAIL PROTECTED]>
+ # Copyright (c) 2001 David Paschal    <[EMAIL PROTECTED]>
  *
  * USB Printer Device Class driver for USB printers and printer cables
  *
@@ -17,9 +19,11 @@
  *     v0.4 - fixes in unidirectional mode
  *     v0.5 - add DEVICE_ID string support
  *     v0.6 - never time out
- *     v0.7 - fixed bulk-IN read and poll (David Paschal, [EMAIL PROTECTED])
+ *     v0.7 - fixed bulk-IN read and poll (David Paschal)
  *     v0.8 - add devfs support
  *     v0.9 - fix unplug-while-open paths
+ *     v0.10 - add proto_bias option (Pete Zaitcev)
+ *     v0.11 - add hpoj.sf.net ioctls, MODULE_*_USE_COUNT, 2.2 backport (David 
Paschal)
  */
 
 /*
@@ -54,16 +58,36 @@
 /*
  * Version Information
  */
-#define DRIVER_VERSION "v0.8"
-#define DRIVER_AUTHOR "Michael Gee, Pavel Machek, Vojtech Pavlik, Randy 
Dunlap"
+#define DRIVER_VERSION "v0.11"
+#define DRIVER_AUTHOR "Michael Gee, Pavel Machek, Vojtech Pavlik, Randy 
Dunlap, Pete Zaitcev, David Paschal"
 #define DRIVER_DESC "USB Printer Device Class driver"
 
 #define USBLP_BUF_SIZE         8192
 #define DEVICE_ID_SIZE         1024
 
-#define IOCNR_GET_DEVICE_ID    1
-#define LPIOC_GET_DEVICE_ID(len) _IOC(_IOC_READ, 'P', IOCNR_GET_DEVICE_ID, 
len)    /* get device_id string */
+/* ioctls: */
 #define LPGETSTATUS            0x060b          /* same as in drivers/char/lp.c */
+#define IOCNR_GET_DEVICE_ID            1
+#define IOCNR_GET_PROTOCOLS            2
+#define IOCNR_SET_PROTOCOL             3
+#define IOCNR_HP_SET_CHANNEL           4
+#define IOCNR_GET_BUS_ADDRESS          5
+#define IOCNR_GET_VID_PID              6
+/* Get device_id string: */
+#define LPIOC_GET_DEVICE_ID(len) _IOC(_IOC_READ, 'P', IOCNR_GET_DEVICE_ID, 
len)
+/* The following ioctls were added for http://hpoj.sourceforge.net: */
+/* Get two-int array:
+ * [0]=current protocol (1=7/1/1, 2=7/1/2, 3=7/1/3),
+ * [1]=supported protocol mask (mask&(1<<n)!=0 means 7/1/n supported): */
+#define LPIOC_GET_PROTOCOLS(len) _IOC(_IOC_READ, 'P', IOCNR_GET_PROTOCOLS, 
len)
+/* Set protocol (arg: 1=7/1/1, 2=7/1/2, 3=7/1/3): */
+#define LPIOC_SET_PROTOCOL _IOC(_IOC_WRITE, 'P', IOCNR_SET_PROTOCOL, 0)
+/* Set channel number (HP Vendor-specific command): */
+#define LPIOC_HP_SET_CHANNEL _IOC(_IOC_WRITE, 'P', IOCNR_HP_SET_CHANNEL, 0)
+/* Get two-int array: [0]=bus number, [1]=device address: */
+#define LPIOC_GET_BUS_ADDRESS(len) _IOC(_IOC_READ, 'P', 
IOCNR_GET_BUS_ADDRESS, len)
+/* Get two-int array: [0]=vendor ID, [1]=product ID: */
+#define LPIOC_GET_VID_PID(len) _IOC(_IOC_READ, 'P', IOCNR_GET_VID_PID, len)
 
 /*
  * A DEVICE_ID string may include the printer's serial number.
@@ -77,23 +101,37 @@
  * USB Printer Requests
  */
 
-#define USBLP_REQ_GET_ID       0x00
-#define USBLP_REQ_GET_STATUS   0x01
-#define USBLP_REQ_RESET                0x02
+#define USBLP_REQ_GET_ID                       0x00
+#define USBLP_REQ_GET_STATUS                   0x01
+#define USBLP_REQ_RESET                                0x02
+#define USBLP_REQ_HP_CHANNEL_CHANGE_REQUEST    0x00    /* HP Vendor-specific */
 
 #define USBLP_MINORS           16
 #define USBLP_MINOR_BASE       0
 
 #define USBLP_WRITE_TIMEOUT    (5*HZ)                  /* 5 seconds */
 
+#define USBLP_FIRST_PROTOCOL   1
+#define USBLP_LAST_PROTOCOL    3
+#define USBLP_MAX_PROTOCOLS    (USBLP_LAST_PROTOCOL+1)
+
 struct usblp {
        struct usb_device       *dev;                   /* USB device */
        devfs_handle_t          devfs;                  /* devfs device */
        struct semaphore        sem;                    /* locks this struct, 
especially "dev" */
+       char                    *buf;           /* writeurb.transfer_buffer */
        struct urb              readurb, writeurb;      /* The urbs */
        wait_queue_head_t       wait;                   /* Zzzzz ... */
        int                     readcount;              /* Counter for reads */
        int                     ifnum;                  /* Interface number */
+       /* Alternate-setting numbers and endpoints for each protocol
+        * (7/1/{index=1,2,3}) that the device supports: */
+       struct {
+               int                             alt_setting;
+               struct usb_endpoint_descriptor  *epwrite;
+               struct usb_endpoint_descriptor  *epread;
+       }                       protocol[USBLP_MAX_PROTOCOLS];
+       int                     current_protocol;
        int                     minor;                  /* minor number of device */
        unsigned int            quirks;                 /* quirks flags */
        unsigned char           used;                   /* True if open */
@@ -102,6 +140,33 @@
                                                        /* first 2 bytes are 
(big-endian) length */
 };
 
+#ifdef DEBUG
+static void usblp_dump(struct usblp *usblp) {
+       int p;
+
+       dbg("usblp=0x%p", usblp);
+       dbg("dev=0x%p", usblp->dev);
+       dbg("devfs=0x%p", usblp->devfs);
+       dbg("buf=0x%p", usblp->buf);
+       dbg("readcount=%d", usblp->readcount);
+       dbg("ifnum=%d", usblp->ifnum);
+    for (p = USBLP_FIRST_PROTOCOL; p <= USBLP_LAST_PROTOCOL; p++) {
+       dbg("protocol[%d].alt_setting=%d", p, usblp->protocol[p].alt_setting);
+       dbg("protocol[%d].epwrite=%p", p, usblp->protocol[p].epwrite);
+       dbg("protocol[%d].epread=%p", p, usblp->protocol[p].epread);
+    }
+       dbg("current_protocol=%d", usblp->current_protocol);
+       dbg("minor=%d", usblp->minor);
+       dbg("quirks=%d", usblp->quirks);
+       dbg("used=%d", usblp->used);
+       dbg("bidir=%d", usblp->bidir);
+       dbg("device_id_string=\"%s\"",
+               usblp->device_id_string ?
+                       usblp->device_id_string + 2 :
+                       (unsigned char *)"(null)");
+}
+#endif
+
 extern devfs_handle_t usb_devfs_handle;                        /* /dev/usb dir. */
 
 static struct usblp *usblp_table[USBLP_MINORS];
@@ -126,26 +191,41 @@
        { 0, 0 }
 };
 
+static int usblp_select_alts(struct usblp *usblp);
+static int usblp_set_protocol(struct usblp *usblp, int protocol);
+static int usblp_cache_device_id_string(struct usblp *usblp);
+
+
 /*
  * Functions for usblp control messages.
  */
 
-static int usblp_ctrl_msg(struct usblp *usblp, int request, int dir, int 
recip, int value, void *buf, int len)
+static int usblp_ctrl_msg(struct usblp *usblp, int request, int type, int 
dir, int recip, int value, void *buf, int len)
 {
        int retval = usb_control_msg(usblp->dev,
                dir ? usb_rcvctrlpipe(usblp->dev, 0) : usb_sndctrlpipe(usblp->dev, 0),
-               request, USB_TYPE_CLASS | dir | recip, value, usblp->ifnum, buf, len, 
HZ * 
5);
+               request, type | dir | recip, value, usblp->ifnum, buf, len, 
USBLP_WRITE_TIMEOUT);
        dbg("usblp_control_msg: rq: 0x%02x dir: %d recip: %d value: %d len: %#x 
result: %d",
                request, !!dir, recip, value, len, retval);
        return retval < 0 ? retval : 0;
 }
 
 #define usblp_read_status(usblp, status)\
-       usblp_ctrl_msg(usblp, USBLP_REQ_GET_STATUS, USB_DIR_IN, USB_RECIP_INTERFACE, 
0, status, 1)
+       usblp_ctrl_msg(usblp, USBLP_REQ_GET_STATUS, USB_TYPE_CLASS, USB_DIR_IN, 
USB_RECIP_INTERFACE, 0, status, 1)
 #define usblp_get_id(usblp, config, id, maxlen)\
-       usblp_ctrl_msg(usblp, USBLP_REQ_GET_ID, USB_DIR_IN, USB_RECIP_INTERFACE, 
config, id, maxlen)
+       usblp_ctrl_msg(usblp, USBLP_REQ_GET_ID, USB_TYPE_CLASS, USB_DIR_IN, 
USB_RECIP_INTERFACE, config, id, maxlen)
 #define usblp_reset(usblp)\
-       usblp_ctrl_msg(usblp, USBLP_REQ_RESET, USB_DIR_OUT, USB_RECIP_OTHER, 0, 
NULL, 0)
+       usblp_ctrl_msg(usblp, USBLP_REQ_RESET, USB_TYPE_CLASS, USB_DIR_OUT, 
USB_RECIP_OTHER, 0, NULL, 0)
+
+#define usblp_hp_channel_change_request(usblp, channel, buffer) \
+       usblp_ctrl_msg(usblp, USBLP_REQ_HP_CHANNEL_CHANGE_REQUEST, USB_TYPE_VENDOR, 
USB_DIR_IN, USB_RECIP_INTERFACE, channel, buffer, 1)
+
+/*
+ * See the description for usblp_select_alts() below for the usage
+ * explanation.  Look into your /proc/bus/usb/devices and dmesg in
+ * case of any trouble.
+ */
+static int proto_bias = -1;
 
 /*
  * URB callback.
@@ -249,6 +329,7 @@
                }
        }
 out:
+       if (!retval) MOD_INC_USE_COUNT;
        unlock_kernel();
        return retval;
 }
@@ -257,13 +338,20 @@
 {
        devfs_unregister (usblp->devfs);
        usblp_table [usblp->minor] = NULL;
-       info ("usblp%d: removed", usblp->minor);
+       info("usblp%d: removed", usblp->minor);
 
        kfree (usblp->writeurb.transfer_buffer);
        kfree (usblp->device_id_string);
        kfree (usblp);
 }
 
+static void usblp_unlink_urbs(struct usblp *usblp)
+{
+       usb_unlink_urb(&usblp->writeurb);
+       if (usblp->bidir)
+               usb_unlink_urb(&usblp->readurb);
+}
+
 static int usblp_release(struct inode *inode, struct file *file)
 {
        struct usblp *usblp = file->private_data;
@@ -272,12 +360,11 @@
        lock_kernel();
        usblp->used = 0;
        if (usblp->dev) {
-               if (usblp->bidir)
-                       usb_unlink_urb(&usblp->readurb);
-               usb_unlink_urb(&usblp->writeurb);
+               usblp_unlink_urbs(usblp);
                up(&usblp->sem);
        } else          /* finish cleanup from disconnect */
                usblp_cleanup (usblp);
+       MOD_DEC_USE_COUNT;
        unlock_kernel();
        return 0;
 }
@@ -294,8 +381,9 @@
 static int usblp_ioctl(struct inode *inode, struct file *file, unsigned int 
cmd, unsigned long arg)
 {
        struct usblp *usblp = file->private_data;
-       int length, err;
-       unsigned char status;
+       int length, err, i;
+       unsigned char status, newChannel;
+       int twoints[2];
        int retval = 0;
 
        down (&usblp->sem);
@@ -314,32 +402,128 @@
                                        goto done;
                                }
 
-                               err = usblp_get_id(usblp, 0, usblp->device_id_string, 
DEVICE_ID_SIZE - 1);
+                               length = usblp_cache_device_id_string(usblp);
+                               if (length < 0) {
+                                       retval = length;
+                                       goto done;
+                               }
+                               if (length > _IOC_SIZE(cmd))
+                                       length = _IOC_SIZE(cmd); /* truncate */
+
+                               if (copy_to_user((unsigned char *) arg,
+                                               usblp->device_id_string,
+                                               (unsigned long) length)) {
+                                       retval = -EFAULT;
+                                       goto done;
+                               }
+
+                               break;
+
+                       case IOCNR_GET_PROTOCOLS:
+                               if (_IOC_DIR(cmd) != _IOC_READ ||
+                                   _IOC_SIZE(cmd) < sizeof(twoints)) {
+                                       retval = -EINVAL;
+                                       goto done;
+                               }
+
+                               twoints[0] = usblp->current_protocol;
+                               twoints[1] = 0;
+                               for (i = USBLP_FIRST_PROTOCOL;
+                                    i <= USBLP_LAST_PROTOCOL; i++) {
+                                       if (usblp->protocol[i].alt_setting >= 0)
+                                               twoints[1] |= (1<<i);
+                               }
+
+                               if (copy_to_user((unsigned char *)arg,
+                                               (unsigned char *)twoints,
+                                               sizeof(twoints))) {
+                                       retval = -EFAULT;
+                                       goto done;
+                               }
+
+                               break;
+
+                       case IOCNR_SET_PROTOCOL:
+                               if (_IOC_DIR(cmd) != _IOC_WRITE) {
+                                       retval = -EINVAL;
+                                       goto done;
+                               }
+
+#ifdef DEBUG
+                               if (arg == -10) {
+                                       usblp_dump(usblp);
+                                       break;
+                               }
+#endif
+
+                               usblp_unlink_urbs(usblp);
+                               retval = usblp_set_protocol(usblp, arg);
+                               if (retval < 0) {
+                                       usblp_set_protocol(usblp,
+                                               usblp->current_protocol);
+                               }
+                               break;
+
+                       case IOCNR_HP_SET_CHANNEL:
+                               if (_IOC_DIR(cmd) != _IOC_WRITE ||
+                                   usblp->dev->descriptor.idVendor != 0x03F0 ||
+                                   usblp->quirks & USBLP_QUIRK_BIDIR) {
+                                       retval = -EINVAL;
+                                       goto done;
+                               }
+
+                               err = usblp_hp_channel_change_request(usblp,
+                                       arg, &newChannel);
                                if (err < 0) {
-                                       dbg ("usblp%d: error = %d reading IEEE-1284 
Device ID string",
+                                       err("usblp%d: error = %d setting "
+                                               "HP channel",
                                                usblp->minor, err);
-                                       usblp->device_id_string[0] = 
usblp->device_id_string[1] = '\0';
                                        retval = -EIO;
                                        goto done;
                                }
 
-                               length = (usblp->device_id_string[0] << 8) + 
usblp->device_id_string[1]; 
/* big-endian */
-                               if (length < DEVICE_ID_SIZE)
-                                       usblp->device_id_string[length] = '\0';
-                               else
-                                       usblp->device_id_string[DEVICE_ID_SIZE - 1] = 
'\0';
+                               dbg("usblp%d requested/got HP channel %ld/%d",
+                                       usblp->minor, arg, newChannel);
+                               break;
 
-                               dbg ("usblp%d Device ID string [%d/max %d]='%s'",
-                                       usblp->minor, length, _IOC_SIZE(cmd), 
&usblp->device_id_string[2]);
+                       case IOCNR_GET_BUS_ADDRESS:
+                               if (_IOC_DIR(cmd) != _IOC_READ ||
+                                   _IOC_SIZE(cmd) < sizeof(twoints)) {
+                                       retval = -EINVAL;
+                                       goto done;
+                               }
 
-                               if (length > _IOC_SIZE(cmd)) length = _IOC_SIZE(cmd); 
/* truncate */
+                               twoints[0] = usblp->dev->bus->busnum;
+                               twoints[1] = usblp->dev->devnum;
+                               if (copy_to_user((unsigned char *)arg,
+                                               (unsigned char *)twoints,
+                                               sizeof(twoints))) {
+                                       retval = -EFAULT;
+                                       goto done;
+                               }
 
-                               if (copy_to_user((unsigned char *) arg,
-                                               usblp->device_id_string, (unsigned 
long) length)) {
+                               dbg("usblp%d is bus=%d, device=%d",
+                                       usblp->minor, twoints[0], twoints[1]);
+                               break;
+
+                       case IOCNR_GET_VID_PID:
+                               if (_IOC_DIR(cmd) != _IOC_READ ||
+                                   _IOC_SIZE(cmd) < sizeof(twoints)) {
+                                       retval = -EINVAL;
+                                       goto done;
+                               }
+
+                               twoints[0] = usblp->dev->descriptor.idVendor;
+                               twoints[1] = usblp->dev->descriptor.idProduct;
+                               if (copy_to_user((unsigned char *)arg,
+                                               (unsigned char *)twoints,
+                                               sizeof(twoints))) {
                                        retval = -EFAULT;
                                        goto done;
                                }
 
+                               dbg("usblp%d is VID=0x%4.4X, PID=0x%4.4X",
+                                       usblp->minor, twoints[0], twoints[1]);
                                break;
 
                        default:
@@ -533,7 +717,9 @@
 }
 
 static struct file_operations usblp_fops = {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
        owner:          THIS_MODULE,
+#endif
        read:           usblp_read,
        write:          usblp_write,
        poll:           usblp_poll,
@@ -542,140 +728,262 @@
        release:        usblp_release,
 };
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)
+static void *usblp_probe(struct usb_device *dev, unsigned int ifnum)
+#else
 static void *usblp_probe(struct usb_device *dev, unsigned int ifnum,
                         const struct usb_device_id *id)
+#endif
 {
-       struct usb_interface_descriptor *interface;
-       struct usb_endpoint_descriptor *epread, *epwrite;
-       struct usblp *usblp;
-       int minor, i, bidir = 0, quirks;
-       int alts = dev->actconfig->interface[ifnum].act_altsetting;
-       int length, err;
-       char *buf;
+       struct usblp *usblp = 0;
+       int protocol;
        char name[6];
 
-       /* If a bidirectional interface exists, use it. */
-       for (i = 0; i < dev->actconfig->interface[ifnum].num_altsetting; i++) {
-
-               interface = &dev->actconfig->interface[ifnum].altsetting[i];
-
-               if (interface->bInterfaceClass != 7 || interface->bInterfaceSubClass 
!= 1 ||
-                   interface->bInterfaceProtocol < 1 || interface->bInterfaceProtocol 
> 3 
||
-                  (interface->bInterfaceProtocol > 1 && interface->bNumEndpoints < 2))
-                       continue;
-
-               if (interface->bInterfaceProtocol > 1) {
-                       bidir = 1;
-                       alts = i;
-                       break;
-               }
-       }
-
-       interface = &dev->actconfig->interface[ifnum].altsetting[alts];
-       if (usb_set_interface(dev, ifnum, alts))
-               err("can't set desired altsetting %d on interface %d", alts, ifnum);
-
-       epwrite = interface->endpoint + 0;
-       epread = bidir ? interface->endpoint + 1 : NULL;
-
-       if ((epwrite->bEndpointAddress & 0x80) == 0x80) {
-               if (interface->bNumEndpoints == 1)
-                       return NULL;
-               epwrite = interface->endpoint + 1;
-               epread = bidir ? interface->endpoint + 0 : NULL;
-       }
-
-       if ((epwrite->bEndpointAddress & 0x80) == 0x80)
-               return NULL;
-
-       if (bidir && (epread->bEndpointAddress & 0x80) != 0x80)
-               return NULL;
-
-       for (minor = 0; minor < USBLP_MINORS && usblp_table[minor]; minor++);
-       if (usblp_table[minor]) {
-               err("no more free usblp devices");
-               return NULL;
-       }
-
+       /* Malloc and start initializing usblp structure so we can use it
+        * directly. */
        if (!(usblp = kmalloc(sizeof(struct usblp), GFP_KERNEL))) {
-               err("out of memory");
-               return NULL;
+               err("out of memory for usblp");
+               goto abort;
        }
        memset(usblp, 0, sizeof(struct usblp));
-       init_MUTEX (&usblp->sem);
-
-       /* lookup quirks for this printer */
-       quirks = usblp_quirks(dev->descriptor.idVendor, dev->descriptor.idProduct);
-
-       if (bidir && (quirks & USBLP_QUIRK_BIDIR)) {
-               bidir = 0;
-               epread = NULL;
-               info ("Disabling reads from problem bidirectional printer on usblp%d",
-                       minor);
-       }
-
        usblp->dev = dev;
-       usblp->ifnum = ifnum;
-       usblp->minor = minor;
-       usblp->bidir = bidir;
-       usblp->quirks = quirks;
-
+       init_MUTEX (&usblp->sem);
        init_waitqueue_head(&usblp->wait);
+       usblp->ifnum = ifnum;
 
-       if (!(buf = kmalloc(USBLP_BUF_SIZE * (bidir ? 2 : 1), GFP_KERNEL))) {
-               err("out of memory");
-               kfree(usblp);
-               return NULL;
+       /* Look for a free usblp_table entry. */
+       while (usblp_table[usblp->minor]) {
+               usblp->minor++;
+               if (usblp->minor >= USBLP_MINORS) {
+                       err("no more free usblp devices");
+                       goto abort;
+               }
        }
 
+       /* Malloc device ID string buffer to the largest expected length,
+        * since we can re-query it on an ioctl and a dynamic string
+        * could change in length. */
        if (!(usblp->device_id_string = kmalloc(DEVICE_ID_SIZE, GFP_KERNEL))) {
-               err("out of memory");
-               kfree(usblp);
-               kfree(buf);
-               return NULL;
+               err("out of memory for device_id_string");
+               goto abort;
        }
 
-       FILL_BULK_URB(&usblp->writeurb, dev, usb_sndbulkpipe(dev, epwrite->
bEndpointAddress),
-               buf, 0, usblp_bulk, usblp);
+       /* Malloc write/read buffers in one chunk.  We somewhat wastefully
+        * malloc both regardless of bidirectionality, because the
+        * alternate setting can be changed later via an ioctl. */
+       if (!(usblp->buf = kmalloc(2 * USBLP_BUF_SIZE, GFP_KERNEL))) {
+               err("out of memory for buf");
+               goto abort;
+       }
+
+       /* Lookup quirks for this printer. */
+       usblp->quirks = usblp_quirks(
+               dev->descriptor.idVendor,
+               dev->descriptor.idProduct);
+
+       /* Analyze and pick initial alternate settings and endpoints. */
+       protocol = usblp_select_alts(usblp);
+       if (protocol < 0) {
+               dbg("incompatible printer-class device 0x%4.4X/0x%4.4X",
+                       dev->descriptor.idVendor,
+                       dev->descriptor.idProduct);
+               goto abort;
+       }
+
+       /* Setup the selected alternate setting and endpoints. */
+       if (usblp_set_protocol(usblp, protocol) < 0)
+               goto abort;
 
-       if (bidir)
-               FILL_BULK_URB(&usblp->readurb, dev, usb_rcvbulkpipe(dev, epread->
bEndpointAddress),
-                       buf + USBLP_BUF_SIZE, USBLP_BUF_SIZE, usblp_bulk, usblp);
-
-       /* Get the device_id string if possible. FIXME: Could make this 
kmalloc(length). */
-       err = usblp_get_id(usblp, 0, usblp->device_id_string, DEVICE_ID_SIZE - 1);
-       if (err >= 0) {
-               length = (usblp->device_id_string[0] << 8) + 
usblp->device_id_string[1]; /* 
big-endian */
-               if (length < DEVICE_ID_SIZE)
-                       usblp->device_id_string[length] = '\0';
-               else
-                       usblp->device_id_string[DEVICE_ID_SIZE - 1] = '\0';
-               dbg ("usblp%d Device ID string [%d]=%s",
-                       minor, length, &usblp->device_id_string[2]);
-       }
-       else {
-               err ("usblp%d: error = %d reading IEEE-1284 Device ID string",
-                       minor, err);
-               usblp->device_id_string[0] = usblp->device_id_string[1] = '\0';
-       }
+       /* Retrieve and store the device ID string. */
+       usblp_cache_device_id_string(usblp);
 
 #ifdef DEBUG
        usblp_check_status(usblp, 0);
 #endif
 
-       sprintf(name, "lp%d", minor);
-
-       /* if we have devfs, create with perms=660 */
+       /* If we have devfs, create with perms=660. */
+       sprintf(name, "lp%d", usblp->minor);
        usblp->devfs = devfs_register(usb_devfs_handle, name,
                                      DEVFS_FL_DEFAULT, USB_MAJOR,
-                                     USBLP_MINOR_BASE + minor,
+                                     USBLP_MINOR_BASE + usblp->minor,
                                      S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP |
                                      S_IWGRP, &usblp_fops, NULL);
 
-       info("usblp%d: USB %sdirectional printer dev %d if %d alt %d",
-               minor, bidir ? "Bi" : "Uni", dev->devnum, ifnum, alts);
+       info("usblp%d: USB %sdirectional printer dev %d "
+               "if %d alt %d proto %d vid 0x%4.4X pid 0x%4.4X",
+               usblp->minor, usblp->bidir ? "Bi" : "Uni", dev->devnum, ifnum,
+               usblp->protocol[usblp->current_protocol].alt_setting,
+               usblp->current_protocol, usblp->dev->descriptor.idVendor,
+               usblp->dev->descriptor.idProduct);
+
+       return usblp_table[usblp->minor] = usblp;
+
+abort:
+       if (usblp) {
+               if (usblp->buf) kfree(usblp->buf);
+               if (usblp->device_id_string) kfree(usblp->device_id_string);
+               kfree(usblp);
+       }
+       return NULL;
+}
+
+/*
+ * We are a "new" style driver with usb_device_id table,
+ * but our requirements are too intricate for simple match to handle.
+ *
+ * The "proto_bias" option may be used to specify the preferred protocol
+ * for all USB printers (1=7/1/1, 2=7/1/2, 3=7/1/3).  If the device
+ * supports the preferred protocol, then we bind to it.
+ *
+ * The best interface for us is 7/1/2, because it is compatible
+ * with a stream of characters. If we find it, we bind to it.
+ *
+ * Note that the people from hpoj.sourceforge.net need to be able to
+ * bind to 7/1/3 (MLC/1284.4), so we provide them ioctls for this purpose.
+ *
+ * Failing 7/1/2, we look for 7/1/3, even though it's probably not
+ * stream-compatible, because this matches the behaviour of the old code.
+ *
+ * If nothing else, we bind to 7/1/1 - the unidirectional interface.
+ */
+static int usblp_select_alts(struct usblp *usblp)
+{
+       struct usb_interface *if_alt;
+       struct usb_interface_descriptor *ifd;
+       struct usb_endpoint_descriptor *epd, *epwrite, *epread;
+       int p, i, e;
+
+       if_alt = &usblp->dev->actconfig->interface[usblp->ifnum];
+
+       for (p = 0; p < USBLP_MAX_PROTOCOLS; p++)
+               usblp->protocol[p].alt_setting = -1;
+
+       /* Find out what we have. */
+       for (i = 0; i < if_alt->num_altsetting; i++) {
+               ifd = &if_alt->altsetting[i];
+
+               if (ifd->bInterfaceClass != 7 || ifd->bInterfaceSubClass != 1)
+                       continue;
+
+               if (ifd->bInterfaceProtocol < USBLP_FIRST_PROTOCOL ||
+                   ifd->bInterfaceProtocol > USBLP_LAST_PROTOCOL)
+                       continue;
+
+               /* Look for bulk OUT and IN endpoints. */
+               epwrite = epread = 0;
+               for (e = 0; e < ifd->bNumEndpoints; e++) {
+                       epd = &ifd->endpoint[e];
+
+                       if ((epd->bmAttributes&USB_ENDPOINT_XFERTYPE_MASK)!=
+                           USB_ENDPOINT_XFER_BULK)
+                               continue;
+
+                       if (!(epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK)) {
+                               if (!epwrite) epwrite=epd;
+
+                       } else {
+                               if (!epread) epread=epd;
+                       }
+               }
+
+               /* Ignore buggy hardware without the right endpoints. */
+               if (!epwrite || (ifd->bInterfaceProtocol > 1 && !epread))
+                       continue;
+
+               /* Turn off reads for 7/1/1 (unidirectional) interfaces
+                * and buggy bidirectional printers. */
+               if (ifd->bInterfaceProtocol == 1) {
+                       epread = NULL;
+               } else if (usblp->quirks & USBLP_QUIRK_BIDIR) {
+                       info("Disabling reads from problem bidirectional "
+                               "printer on usblp%d", usblp->minor);
+                       epread = NULL;
+               }
+
+               usblp->protocol[ifd->bInterfaceProtocol].alt_setting = i;
+               usblp->protocol[ifd->bInterfaceProtocol].epwrite = epwrite;
+               usblp->protocol[ifd->bInterfaceProtocol].epread = epread;
+       }
+
+       /* If our requested protocol is supported, then use it. */
+       if (proto_bias >= USBLP_FIRST_PROTOCOL &&
+           proto_bias <= USBLP_LAST_PROTOCOL &&
+           usblp->protocol[proto_bias].alt_setting != -1)
+               return proto_bias;
+
+       /* Ordering is important here. */
+       if (usblp->protocol[2].alt_setting != -1) return 2;
+       if (usblp->protocol[3].alt_setting != -1) return 3;
+       if (usblp->protocol[1].alt_setting != -1) return 1;
 
-       return usblp_table[minor] = usblp;
+       /* If nothing is available, then don't bind to this device. */
+       return -1;
+}
+
+static int usblp_set_protocol(struct usblp *usblp, int protocol)
+{
+       int r, alts;
+
+       if (protocol < USBLP_FIRST_PROTOCOL || protocol > USBLP_LAST_PROTOCOL)
+               return -EINVAL;
+
+       alts = usblp->protocol[protocol].alt_setting;
+       if (alts < 0) return -EINVAL;
+       r = usb_set_interface(usblp->dev, usblp->ifnum, alts);
+       if (r < 0) {
+               err("can't set desired altsetting %d on interface %d",
+                       alts, usblp->ifnum);
+               return r;
+       }
+
+       FILL_BULK_URB(&usblp->writeurb, usblp->dev,
+               usb_sndbulkpipe(usblp->dev,
+                usblp->protocol[protocol].epwrite->bEndpointAddress),
+               usblp->buf, 0,
+               usblp_bulk, usblp);
+
+       usblp->bidir = (usblp->protocol[protocol].epread != 0);
+       if (usblp->bidir)
+               FILL_BULK_URB(&usblp->readurb, usblp->dev,
+                       usb_rcvbulkpipe(usblp->dev,
+                        usblp->protocol[protocol].epread->bEndpointAddress),
+                       usblp->buf + USBLP_BUF_SIZE, USBLP_BUF_SIZE,
+                       usblp_bulk, usblp);
+
+       usblp->current_protocol = protocol;
+       dbg("usblp%d set protocol %d", usblp->minor, protocol);
+       return 0;
+}
+
+/* Retrieves and caches device ID string.
+ * Returns length, including length bytes but not null terminator.
+ * On error, returns a negative errno value. */
+static int usblp_cache_device_id_string(struct usblp *usblp)
+{
+       int err, length;
+
+       err = usblp_get_id(usblp, 0, usblp->device_id_string, DEVICE_ID_SIZE - 1);
+       if (err < 0) {
+               dbg("usblp%d: error = %d reading IEEE-1284 Device ID string",
+                       usblp->minor, err);
+               usblp->device_id_string[0] = usblp->device_id_string[1] = '\0';
+               return -EIO;
+       }
+
+       /* First two bytes are length in big-endian.
+        * They count themselves, and we copy them into
+        * the user's buffer. */
+       length = (usblp->device_id_string[0] << 8) + usblp->device_id_string[1];
+       if (length < 2)
+               length = 2;
+       else if (length >= DEVICE_ID_SIZE)
+               length = DEVICE_ID_SIZE - 1;
+       usblp->device_id_string[length] = '\0';
+
+       dbg("usblp%d Device ID string [len=%d]=\"%s\"",
+               usblp->minor, length, &usblp->device_id_string[2]);
+
+       return length;
 }
 
 static void usblp_disconnect(struct usb_device *dev, void *ptr)
@@ -691,9 +999,7 @@
        lock_kernel();
        usblp->dev = NULL;
 
-       usb_unlink_urb(&usblp->writeurb);
-       if (usblp->bidir)
-               usb_unlink_urb(&usblp->readurb);
+       usblp_unlink_urbs(usblp);
 
        if (!usblp->used)
                usblp_cleanup (usblp);
@@ -702,6 +1008,7 @@
        unlock_kernel();
 }
 
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
 static struct usb_device_id usblp_ids [] = {
        { USB_DEVICE_INFO(7, 1, 1) },
        { USB_DEVICE_INFO(7, 1, 2) },
@@ -713,6 +1020,7 @@
 };
 
 MODULE_DEVICE_TABLE (usb, usblp_ids);
+#endif
 
 static struct usb_driver usblp_driver = {
        name:           "usblp",
@@ -720,14 +1028,16 @@
        disconnect:     usblp_disconnect,
        fops:           &usblp_fops,
        minor:          USBLP_MINOR_BASE,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
        id_table:       usblp_ids,
+#endif
 };
 
 static int __init usblp_init(void)
 {
        if (usb_register(&usblp_driver))
                return -1;
-       info(DRIVER_VERSION ":" DRIVER_DESC);
+       info(DRIVER_VERSION ": " DRIVER_DESC);
        return 0;
 }
 
@@ -741,5 +1051,8 @@
 
 MODULE_AUTHOR( DRIVER_AUTHOR );
 MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_PARM(proto_bias, "i");
+MODULE_PARM_DESC(proto_bias, "Favourite protocol number");
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,12)
 MODULE_LICENSE("GPL");
-
+#endif



_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to