| /* |
| * printer.c Version 0.8 |
| * |
| * Copyright (c) 1999 Michael Gee <michael@linuxspecific.com> |
| * Copyright (c) 1999 Pavel Machek <pavel@suse.cz> |
| * Copyright (c) 2000 Randy Dunlap <randy.dunlap@intel.com> |
| * Copyright (c) 2000 Vojtech Pavlik <vojtech@suse.cz> |
| * |
| * USB Printer Device Class driver for USB printers and printer cables |
| * |
| * Sponsored by SuSE |
| * |
| * ChangeLog: |
| * v0.1 - thorough cleaning, URBification, almost a rewrite |
| * v0.2 - some more cleanups |
| * v0.3 - cleaner again, waitqueue fixes |
| * 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, paschal@rcsis.com) |
| * v0.8 - add devfs support |
| * v0.9 - fix unplug-while-open paths |
| * v0.10- remove sleep_on, fix error on oom (oliver@neukum.org) |
| */ |
| |
| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/smp_lock.h> |
| #include <linux/signal.h> |
| #include <linux/poll.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/lp.h> |
| #include <linux/devfs_fs_kernel.h> |
| #undef DEBUG |
| #include <linux/usb.h> |
| |
| /* |
| * Version Information |
| */ |
| #define DRIVER_VERSION "v0.10" |
| #define DRIVER_AUTHOR "Michael Gee, Pavel Machek, Vojtech Pavlik, Randy Dunlap" |
| #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 */ |
| #define LPGETSTATUS 0x060b /* same as in drivers/char/lp.c */ |
| |
| /* |
| * A DEVICE_ID string may include the printer's serial number. |
| * It should end with a semi-colon (';'). |
| * An example from an HP 970C DeskJet printer is (this is one long string, |
| * with the serial number changed): |
| MFG:HEWLETT-PACKARD;MDL:DESKJET 970C;CMD:MLC,PCL,PML;CLASS:PRINTER;DESCRIPTION:Hewlett-Packard DeskJet 970C;SERN:US970CSEPROF;VSTATUS:$HB0$NC0,ff,DN,IDLE,CUT,K1,C0,DP,NR,KP000,CP027;VP:0800,FL,B0;VJ: ; |
| */ |
| |
| /* |
| * USB Printer Requests |
| */ |
| |
| #define USBLP_REQ_GET_ID 0x00 |
| #define USBLP_REQ_GET_STATUS 0x01 |
| #define USBLP_REQ_RESET 0x02 |
| |
| #define USBLP_MINORS 16 |
| #define USBLP_MINOR_BASE 0 |
| |
| #define USBLP_WRITE_TIMEOUT (5*HZ) /* 5 seconds */ |
| |
| struct usblp { |
| struct usb_device *dev; /* USB device */ |
| devfs_handle_t devfs; /* devfs device */ |
| struct semaphore sem; /* locks this struct, especially "dev" */ |
| struct urb readurb, writeurb; /* The urbs */ |
| wait_queue_head_t wait; /* Zzzzz ... */ |
| int readcount; /* Counter for reads */ |
| int ifnum; /* Interface number */ |
| int minor; /* minor number of device */ |
| int wcomplete; /* writing is completed */ |
| int rcomplete; /* reading is completed */ |
| unsigned int quirks; /* quirks flags */ |
| unsigned char used; /* True if open */ |
| unsigned char bidir; /* interface is bidirectional */ |
| unsigned char *device_id_string; /* IEEE 1284 DEVICE ID string (ptr) */ |
| /* first 2 bytes are (big-endian) length */ |
| }; |
| |
| extern devfs_handle_t usb_devfs_handle; /* /dev/usb dir. */ |
| |
| static struct usblp *usblp_table[USBLP_MINORS]; |
| |
| /* Quirks: various printer quirks are handled by this table & its flags. */ |
| |
| struct quirk_printer_struct { |
| __u16 vendorId; |
| __u16 productId; |
| unsigned int quirks; |
| }; |
| |
| #define USBLP_QUIRK_BIDIR 0x1 /* reports bidir but requires unidirectional mode (no INs/reads) */ |
| #define USBLP_QUIRK_USB_INIT 0x2 /* needs vendor USB init string */ |
| |
| static struct quirk_printer_struct quirk_printers[] = { |
| { 0x03f0, 0x0004, USBLP_QUIRK_BIDIR }, /* HP DeskJet 895C */ |
| { 0x03f0, 0x0104, USBLP_QUIRK_BIDIR }, /* HP DeskJet 880C */ |
| { 0x03f0, 0x0204, USBLP_QUIRK_BIDIR }, /* HP DeskJet 815C */ |
| { 0x03f0, 0x0304, USBLP_QUIRK_BIDIR }, /* HP DeskJet 810C/812C */ |
| { 0x03f0, 0x0404, USBLP_QUIRK_BIDIR }, /* HP DeskJet 830C */ |
| { 0, 0 } |
| }; |
| |
| /* |
| * 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) |
| { |
| 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); |
| 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) |
| #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) |
| #define usblp_reset(usblp)\ |
| usblp_ctrl_msg(usblp, USBLP_REQ_RESET, USB_DIR_OUT, USB_RECIP_OTHER, 0, NULL, 0) |
| |
| /* |
| * URB callback. |
| */ |
| |
| static void usblp_bulk_read(struct urb *urb) |
| { |
| struct usblp *usblp = urb->context; |
| |
| if (!usblp || !usblp->dev || !usblp->used) |
| return; |
| |
| if (unlikely(urb->status)) |
| warn("usblp%d: nonzero read/write bulk status received: %d", |
| usblp->minor, urb->status); |
| usblp->rcomplete = 1; |
| wake_up_interruptible(&usblp->wait); |
| } |
| |
| static void usblp_bulk_write(struct urb *urb) |
| { |
| struct usblp *usblp = urb->context; |
| |
| if (!usblp || !usblp->dev || !usblp->used) |
| return; |
| |
| if (unlikely(urb->status)) |
| warn("usblp%d: nonzero read/write bulk status received: %d", |
| usblp->minor, urb->status); |
| usblp->wcomplete = 1; |
| wake_up_interruptible(&usblp->wait); |
| } |
| |
| /* |
| * Get and print printer errors. |
| */ |
| |
| static char *usblp_messages[] = { "ok", "out of paper", "off-line", "on fire" }; |
| |
| static int usblp_check_status(struct usblp *usblp, int err) |
| { |
| unsigned char status, newerr = 0; |
| int error; |
| |
| error = usblp_read_status (usblp, &status); |
| if (error < 0) { |
| err("usblp%d: error %d reading printer status", |
| usblp->minor, error); |
| return 0; |
| } |
| |
| if (~status & LP_PERRORP) { |
| newerr = 3; |
| if (status & LP_POUTPA) newerr = 1; |
| if (~status & LP_PSELECD) newerr = 2; |
| } |
| |
| if (newerr != err) |
| info("usblp%d: %s", usblp->minor, usblp_messages[newerr]); |
| |
| return newerr; |
| } |
| |
| /* |
| * File op functions. |
| */ |
| |
| static int usblp_open(struct inode *inode, struct file *file) |
| { |
| int minor = minor(inode->i_rdev) - USBLP_MINOR_BASE; |
| struct usblp *usblp; |
| int retval; |
| |
| if (minor < 0 || minor >= USBLP_MINORS) |
| return -ENODEV; |
| |
| lock_kernel(); |
| usblp = usblp_table[minor]; |
| |
| retval = -ENODEV; |
| if (!usblp || !usblp->dev) |
| goto out; |
| |
| retval = -EBUSY; |
| if (usblp->used) |
| goto out; |
| |
| /* |
| * TODO: need to implement LP_ABORTOPEN + O_NONBLOCK as in drivers/char/lp.c ??? |
| * This is #if 0-ed because we *don't* want to fail an open |
| * just because the printer is off-line. |
| */ |
| #if 0 |
| if ((retval = usblp_check_status(usblp, 0))) { |
| retval = retval > 1 ? -EIO : -ENOSPC; |
| goto out; |
| } |
| #else |
| retval = 0; |
| #endif |
| |
| usblp->used = 1; |
| file->private_data = usblp; |
| |
| usblp->writeurb.transfer_buffer_length = 0; |
| usblp->writeurb.status = 0; |
| usblp->wcomplete = 1; /* we begin writeable */ |
| usblp->rcomplete = 0; |
| |
| if (usblp->bidir) { |
| usblp->readcount = 0; |
| usblp->readurb.dev = usblp->dev; |
| if (usb_submit_urb(&usblp->readurb, GFP_KERNEL) < 0) { |
| retval = -EIO; |
| usblp->used = 0; |
| file->private_data = NULL; |
| } |
| } |
| out: |
| unlock_kernel(); |
| return retval; |
| } |
| |
| static void usblp_cleanup (struct usblp *usblp) |
| { |
| devfs_unregister (usblp->devfs); |
| usblp_table [usblp->minor] = NULL; |
| info ("usblp%d: removed", usblp->minor); |
| |
| kfree (usblp->writeurb.transfer_buffer); |
| kfree (usblp->device_id_string); |
| kfree (usblp); |
| } |
| |
| static int usblp_release(struct inode *inode, struct file *file) |
| { |
| struct usblp *usblp = file->private_data; |
| |
| down (&usblp->sem); |
| lock_kernel(); |
| usblp->used = 0; |
| if (usblp->dev) { |
| if (usblp->bidir) |
| usb_unlink_urb(&usblp->readurb); |
| usb_unlink_urb(&usblp->writeurb); |
| up(&usblp->sem); |
| } else /* finish cleanup from disconnect */ |
| usblp_cleanup (usblp); |
| unlock_kernel(); |
| return 0; |
| } |
| |
| /* No kernel lock - fine */ |
| static unsigned int usblp_poll(struct file *file, struct poll_table_struct *wait) |
| { |
| struct usblp *usblp = file->private_data; |
| poll_wait(file, &usblp->wait, wait); |
| return ((!usblp->bidir || usblp->readurb.status == -EINPROGRESS) ? 0 : POLLIN | POLLRDNORM) |
| | (usblp->writeurb.status == -EINPROGRESS ? 0 : POLLOUT | POLLWRNORM); |
| } |
| |
| 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 retval = 0; |
| |
| down (&usblp->sem); |
| if (!usblp->dev) { |
| retval = -ENODEV; |
| goto done; |
| } |
| |
| if (_IOC_TYPE(cmd) == 'P') /* new-style ioctl number */ |
| |
| switch (_IOC_NR(cmd)) { |
| |
| case IOCNR_GET_DEVICE_ID: /* get the DEVICE_ID string */ |
| if (_IOC_DIR(cmd) != _IOC_READ) { |
| retval = -EINVAL; |
| goto done; |
| } |
| |
| 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'; |
| 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 Device ID string [%d/max %d]='%s'", |
| usblp->minor, length, _IOC_SIZE(cmd), &usblp->device_id_string[2]); |
| |
| 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; |
| |
| default: |
| retval = -EINVAL; |
| } |
| else /* old-style ioctl value */ |
| switch (cmd) { |
| |
| case LPGETSTATUS: |
| if (usblp_read_status(usblp, &status)) { |
| err("usblp%d: failed reading printer status", usblp->minor); |
| retval = -EIO; |
| goto done; |
| } |
| if (copy_to_user ((unsigned char *)arg, &status, 1)) |
| retval = -EFAULT; |
| break; |
| |
| default: |
| retval = -EINVAL; |
| } |
| |
| done: |
| up (&usblp->sem); |
| return retval; |
| } |
| |
| static ssize_t usblp_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) |
| { |
| DECLARE_WAITQUEUE(wait, current); |
| struct usblp *usblp = file->private_data; |
| int timeout, err = 0, writecount = 0; |
| |
| while (writecount < count) { |
| if (!usblp->wcomplete) { |
| barrier(); |
| if (file->f_flags & O_NONBLOCK) |
| return -EAGAIN; |
| |
| timeout = USBLP_WRITE_TIMEOUT; |
| add_wait_queue(&usblp->wait, &wait); |
| while ( 1==1 ) { |
| |
| if (signal_pending(current)) { |
| remove_wait_queue(&usblp->wait, &wait); |
| return writecount ? writecount : -EINTR; |
| } |
| set_current_state(TASK_INTERRUPTIBLE); |
| if (timeout && !usblp->wcomplete) { |
| timeout = schedule_timeout(timeout); |
| } else { |
| set_current_state(TASK_RUNNING); |
| break; |
| } |
| } |
| remove_wait_queue(&usblp->wait, &wait); |
| } |
| |
| down (&usblp->sem); |
| if (!usblp->dev) { |
| up (&usblp->sem); |
| return -ENODEV; |
| } |
| |
| if (usblp->writeurb.status != 0) { |
| if (usblp->quirks & USBLP_QUIRK_BIDIR) { |
| if (!usblp->wcomplete) |
| err("usblp%d: error %d writing to printer", |
| usblp->minor, usblp->writeurb.status); |
| err = usblp->writeurb.status; |
| } else |
| err = usblp_check_status(usblp, err); |
| up (&usblp->sem); |
| |
| /* if the fault was due to disconnect, let khubd's |
| * call to usblp_disconnect() grab usblp->sem ... |
| */ |
| schedule (); |
| continue; |
| } |
| |
| writecount += usblp->writeurb.transfer_buffer_length; |
| usblp->writeurb.transfer_buffer_length = 0; |
| |
| if (writecount == count) { |
| up (&usblp->sem); |
| break; |
| } |
| |
| usblp->writeurb.transfer_buffer_length = (count - writecount) < USBLP_BUF_SIZE ? |
| (count - writecount) : USBLP_BUF_SIZE; |
| |
| if (copy_from_user(usblp->writeurb.transfer_buffer, buffer + writecount, |
| usblp->writeurb.transfer_buffer_length)) return -EFAULT; |
| |
| usblp->writeurb.dev = usblp->dev; |
| usblp->wcomplete = 0; |
| if (usb_submit_urb(&usblp->writeurb, GFP_KERNEL)) { |
| count = -EIO; |
| up (&usblp->sem); |
| break; |
| } |
| up (&usblp->sem); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t usblp_read(struct file *file, char *buffer, size_t count, loff_t *ppos) |
| { |
| struct usblp *usblp = file->private_data; |
| DECLARE_WAITQUEUE(wait, current); |
| |
| if (!usblp->bidir) |
| return -EINVAL; |
| |
| down (&usblp->sem); |
| if (!usblp->dev) { |
| count = -ENODEV; |
| goto done; |
| } |
| |
| if (!usblp->rcomplete) { |
| barrier(); |
| |
| if (file->f_flags & O_NONBLOCK) { |
| count = -EAGAIN; |
| goto done; |
| } |
| |
| // FIXME: only use urb->status inside completion |
| // callbacks; this way is racey... |
| add_wait_queue(&usblp->wait, &wait); |
| while (1==1) { |
| if (signal_pending(current)) { |
| count = -EINTR; |
| remove_wait_queue(&usblp->wait, &wait); |
| goto done; |
| } |
| up (&usblp->sem); |
| set_current_state(TASK_INTERRUPTIBLE); |
| if (!usblp->rcomplete) { |
| schedule(); |
| } else { |
| set_current_state(TASK_RUNNING); |
| break; |
| } |
| down (&usblp->sem); |
| } |
| remove_wait_queue(&usblp->wait, &wait); |
| } |
| |
| if (!usblp->dev) { |
| count = -ENODEV; |
| goto done; |
| } |
| |
| if (usblp->readurb.status) { |
| err("usblp%d: error %d reading from printer", |
| usblp->minor, usblp->readurb.status); |
| usblp->readurb.dev = usblp->dev; |
| usblp->readcount = 0; |
| usb_submit_urb(&usblp->readurb, GFP_KERNEL); |
| count = -EIO; |
| goto done; |
| } |
| |
| count = count < usblp->readurb.actual_length - usblp->readcount ? |
| count : usblp->readurb.actual_length - usblp->readcount; |
| |
| if (copy_to_user(buffer, usblp->readurb.transfer_buffer + usblp->readcount, count)) { |
| count = -EFAULT; |
| goto done; |
| } |
| |
| if ((usblp->readcount += count) == usblp->readurb.actual_length) { |
| usblp->readcount = 0; |
| usblp->readurb.dev = usblp->dev; |
| usblp->rcomplete = 0; |
| if (usb_submit_urb(&usblp->readurb, GFP_KERNEL)) { |
| count = -EIO; |
| goto done; |
| } |
| } |
| |
| done: |
| up (&usblp->sem); |
| return count; |
| } |
| |
| /* |
| * Checks for printers that have quirks, such as requiring unidirectional |
| * communication but reporting bidirectional; currently some HP printers |
| * have this flaw (HP 810, 880, 895, etc.), or needing an init string |
| * sent at each open (like some Epsons). |
| * Returns 1 if found, 0 if not found. |
| * |
| * HP recommended that we use the bidirectional interface but |
| * don't attempt any bulk IN transfers from the IN endpoint. |
| * Here's some more detail on the problem: |
| * The problem is not that it isn't bidirectional though. The problem |
| * is that if you request a device ID, or status information, while |
| * the buffers are full, the return data will end up in the print data |
| * buffer. For example if you make sure you never request the device ID |
| * while you are sending print data, and you don't try to query the |
| * printer status every couple of milliseconds, you will probably be OK. |
| */ |
| static unsigned int usblp_quirks (__u16 vendor, __u16 product) |
| { |
| int i; |
| |
| for (i = 0; quirk_printers[i].vendorId; i++) { |
| if (vendor == quirk_printers[i].vendorId && |
| product == quirk_printers[i].productId) |
| return quirk_printers[i].quirks; |
| } |
| return 0; |
| } |
| |
| static struct file_operations usblp_fops = { |
| owner: THIS_MODULE, |
| read: usblp_read, |
| write: usblp_write, |
| poll: usblp_poll, |
| ioctl: usblp_ioctl, |
| open: usblp_open, |
| release: usblp_release, |
| }; |
| |
| static void *usblp_probe(struct usb_device *dev, unsigned int ifnum, |
| const struct usb_device_id *id) |
| { |
| 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; |
| 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; |
| } |
| |
| if (!(usblp = kmalloc(sizeof(struct usblp), GFP_KERNEL))) { |
| err("out of memory"); |
| return NULL; |
| } |
| 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_waitqueue_head(&usblp->wait); |
| |
| if (!(buf = kmalloc(USBLP_BUF_SIZE * (bidir ? 2 : 1), GFP_KERNEL))) { |
| err("out of memory"); |
| kfree(usblp); |
| return NULL; |
| } |
| |
| if (!(usblp->device_id_string = kmalloc(DEVICE_ID_SIZE, GFP_KERNEL))) { |
| err("out of memory"); |
| kfree(usblp); |
| kfree(buf); |
| return NULL; |
| } |
| |
| FILL_BULK_URB(&usblp->writeurb, dev, usb_sndbulkpipe(dev, epwrite->bEndpointAddress), |
| buf, 0, usblp_bulk_write, usblp); |
| |
| if (bidir) |
| FILL_BULK_URB(&usblp->readurb, dev, usb_rcvbulkpipe(dev, epread->bEndpointAddress), |
| buf + USBLP_BUF_SIZE, USBLP_BUF_SIZE, usblp_bulk_read, 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'; |
| } |
| |
| #ifdef DEBUG |
| usblp_check_status(usblp, 0); |
| #endif |
| |
| sprintf(name, "lp%d", minor); |
| |
| /* if we have devfs, create with perms=660 */ |
| usblp->devfs = devfs_register(usb_devfs_handle, name, |
| DEVFS_FL_DEFAULT, USB_MAJOR, |
| USBLP_MINOR_BASE + 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); |
| |
| return usblp_table[minor] = usblp; |
| } |
| |
| static void usblp_disconnect(struct usb_device *dev, void *ptr) |
| { |
| struct usblp *usblp = ptr; |
| |
| if (!usblp || !usblp->dev) { |
| err("bogus disconnect"); |
| BUG (); |
| } |
| |
| down (&usblp->sem); |
| lock_kernel(); |
| usblp->dev = NULL; |
| |
| usb_unlink_urb(&usblp->writeurb); |
| if (usblp->bidir) |
| usb_unlink_urb(&usblp->readurb); |
| |
| if (!usblp->used) |
| usblp_cleanup (usblp); |
| else /* cleanup later, on close */ |
| up (&usblp->sem); |
| unlock_kernel(); |
| } |
| |
| static struct usb_device_id usblp_ids [] = { |
| { USB_DEVICE_INFO(7, 1, 1) }, |
| { USB_DEVICE_INFO(7, 1, 2) }, |
| { USB_DEVICE_INFO(7, 1, 3) }, |
| { USB_INTERFACE_INFO(7, 1, 1) }, |
| { USB_INTERFACE_INFO(7, 1, 2) }, |
| { USB_INTERFACE_INFO(7, 1, 3) }, |
| { } /* Terminating entry */ |
| }; |
| |
| MODULE_DEVICE_TABLE (usb, usblp_ids); |
| |
| static struct usb_driver usblp_driver = { |
| owner: THIS_MODULE, |
| name: "usblp", |
| probe: usblp_probe, |
| disconnect: usblp_disconnect, |
| fops: &usblp_fops, |
| minor: USBLP_MINOR_BASE, |
| id_table: usblp_ids, |
| }; |
| |
| static int __init usblp_init(void) |
| { |
| if (usb_register(&usblp_driver)) |
| return -1; |
| info(DRIVER_VERSION ":" DRIVER_DESC); |
| return 0; |
| } |
| |
| static void __exit usblp_exit(void) |
| { |
| usb_deregister(&usblp_driver); |
| } |
| |
| module_init(usblp_init); |
| module_exit(usblp_exit); |
| |
| MODULE_AUTHOR( DRIVER_AUTHOR ); |
| MODULE_DESCRIPTION( DRIVER_DESC ); |
| MODULE_LICENSE("GPL"); |
| |