Hello folks,
I've incorporated the suggestions mentioned before, and a few other
cleanups. Anecdotally, the module seems to be performing very well on my
system (x86-64). Not withstanding recent development work to
usbfs/libusb, I think that this makes a great deal of sense as a kernel
module as it provides a fairly generic and useful capability that could
serve many USB gadgets. I'm hoping that you (mostly Greg) will be
amenable to it in preference to a libusb solution!
With any luck, if this code looks in decent shape, my next submission
will be in the form of a patch against 2.6.20-gitXX.
I've reworked the read() and read_more() methods and would appreciate
attention to locking (et cetera) there. The write() and poll() methods
are similar to the previous versions. I'm open to suggestions regarding
the parameters (fifo size/max number of outstanding requests/etc).
Lastly, the usb-skel code from which I based my efforts suggests that I
should obtain a USB_SUSI_MINOR_BASE from the usb maintainer. Is this
irrelevant in the days of dynamically generated minors?
Again, if you prefer, you can grab the source from my repository:
svn co svn://edo.csail.mit.edu/home/svn/susi
Thanks again!
-Ed
/*
* Simple USB Stream Interface (SUSI)
*
* This module is intended for a wide range of USB devices that
* communicate over an ordered/reliable stream constructed from a pair
* of bulk endpoints (in a manner identical to the CDC communications
* class, though without the line state management.) This driver
* is intended for higher performance, in that it attempts to send
* more data and read more data from the device in each USB service
* period of 1ms. It does this through larger buffers and multiple
* URBs. This driver also dispenses with the TTY layer.
*
* We use multiple read URBs to try to pull more data from the USB device,
* even if the USB device is sending short packets. On some UHCI hosts,
* this is only marginally effective; it is advantageous for the USB device
* to explicitly merge writes into full-length packets in order to make
* better use of the available USB bandwidth.
*
* Possible future improvements: each write() call results in a single
* URB, and we have a fixed maximum number of outstanding write URBs;
* this artificially limits write bandwidth when the writes are of
* very small size. Merging multiple writes into a single URB would
* seem to be a better solution than further increasing the maximum number
* of in-flight write URBs.
*
* It could also be nice to instantiate a separate device node for
* *each* pair of bulk endpoints; the present driver ignores all but
* the first pair of bulk endpoints.
*
* Copyright (C) 2007 Edwin Olson ([EMAIL PROTECTED])
* Copyright (C) 2001-2004 Greg Kroah-Hartman ([EMAIL PROTECTED])
*
* 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, version 2.
*
* This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c
* but has been rewritten to be easier to read and use.
*
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kref.h>
#include <linux/kfifo.h>
#include <asm/uaccess.h>
#include <linux/usb.h>
#include <linux/mutex.h>
#include <linux/poll.h>
/* Define these values to match your devices */
#define USB_SUSI_VENDOR_ID 0x00ed
#define USB_SUSI_PRODUCT_ID 0x0001
/* table of devices that work with this driver */
static struct usb_device_id susi_table [] = {
{ USB_DEVICE(USB_SUSI_VENDOR_ID, USB_SUSI_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, susi_table);
/* Get a minor range for your devices from the usb maintainer */
#define USB_SUSI_MINOR_BASE 192
/* our private defines. if this grows any larger, use your own .h file */
#define WRITE_SIZE (PAGE_SIZE - 512)
#define WRITES_IN_FLIGHT 8
#define READS_IN_FLIGHT 4
#define READ_SIZE 256
#define READ_FIFO_SIZE 4096
/* When copying out of the read fifo into userspace, we must use a
temporary buffer. This is the size of that buffer. */
#define TBUF_LEN 256
/* Structure to hold all of our device specific stuff */
struct usb_susi {
struct usb_device *udev; /* the usb device for this device */
struct usb_interface *interface; /* the interface for this device */
struct semaphore write_sem; /* limiting the number of writes in progress */
__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
struct kref kref;
struct mutex io_mutex; /* synchronize I/O with disconnect */
int reads_pending; /* how many URBs outstanding? */
struct kfifo *read_fifo;
spinlock_t spinlock;
wait_queue_head_t readq, writeq; /* read and write queues */
};
#define to_susi_dev(d) container_of(d, struct usb_susi, kref)
static struct usb_driver susi_driver;
static void susi_delete(struct kref *kref)
{
struct usb_susi *dev = to_susi_dev(kref);
usb_put_dev(dev->udev);
kfifo_free(dev->read_fifo);
kfree(dev);
}
static int susi_open(struct inode *inode, struct file *file)
{
struct usb_susi *dev;
struct usb_interface *interface;
int subminor;
int retval = 0;
subminor = iminor(inode);
interface = usb_find_interface(&susi_driver, subminor);
if (!interface) {
err ("%s - error, can't find device for minor %d",
__FUNCTION__, subminor);
retval = -ENODEV;
goto exit;
}
dev = usb_get_intfdata(interface);
if (!dev) {
retval = -ENODEV;
goto exit;
}
/* increment our usage count for the device */
kref_get(&dev->kref);
/* prevent the device from being autosuspended */
retval = usb_autopm_get_interface(interface);
if (retval) {
kref_put(&dev->kref, susi_delete);
goto exit;
}
/* save our object in the file's private structure */
file->private_data = dev;
exit:
return retval;
}
static int susi_release(struct inode *inode, struct file *file)
{
struct usb_susi *dev;
dev = (struct usb_susi *)file->private_data;
if (dev == NULL)
return -ENODEV;
/* allow the device to be autosuspended */
mutex_lock(&dev->io_mutex);
if (dev->interface)
usb_autopm_put_interface(dev->interface);
mutex_unlock(&dev->io_mutex);
/* decrement the count on our device */
kref_put(&dev->kref, susi_delete);
return 0;
}
static int susi_read_more(struct usb_susi *dev);
static void susi_read_bulk_callback (struct urb *urb)
{
struct usb_susi *dev;
unsigned long flags;
dev = (struct usb_susi *)urb->context;
/* sync/async unlink faults aren't errors */
if (urb->status &&
!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN)) {
err("%s - nonzero read bulk status received: %d",
__FUNCTION__, urb->status);
}
kfifo_put(dev->read_fifo, urb->transfer_buffer, urb->actual_length);
wake_up_interruptible(&dev->readq);
usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
spin_lock_irqsave(&dev->spinlock, flags);
dev->reads_pending--;
spin_unlock_irqrestore(&dev->spinlock, flags);
susi_read_more(dev);
}
/** Issue more read URBs until all of the space in the kfifo is allocated **/
static int susi_read_more(struct usb_susi *dev)
{
int retval = 0;
int fifo_space;
unsigned long flags;
char *readbuf;
struct urb *urb;
spin_lock_irqsave(&dev->spinlock, flags);
fifo_space = dev->read_fifo->size - __kfifo_len(dev->read_fifo);
/* If the fifo is big enough for us to add another pending read,
create a new read URB */
while ((dev->reads_pending + 1) * READ_SIZE < fifo_space &&
dev->reads_pending < READS_IN_FLIGHT) {
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (!urb) {
retval = -ENOMEM;
goto exit;
}
readbuf = usb_buffer_alloc(dev->udev, READ_SIZE, GFP_ATOMIC, &urb->transfer_dma);
if (!readbuf) {
usb_free_urb(urb);
retval = -ENOMEM;
goto exit;
}
usb_fill_bulk_urb(urb, dev->udev,
usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
readbuf, READ_SIZE,
susi_read_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval)
err("%s - failed submitting read urb, error %d", __FUNCTION__, retval);
/* release our reference to this urb, the USB core will eventually free it entirely */
usb_free_urb(urb);
dev->reads_pending++;
}
exit:
spin_unlock_irqrestore(&dev->spinlock, flags);
return retval;
}
static ssize_t susi_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
struct usb_susi *dev;
char tbuf[TBUF_LEN];
int retval;
int xfersize;
dev = (struct usb_susi *)file->private_data;
mutex_lock(&dev->io_mutex);
while (dev->interface && kfifo_len(dev->read_fifo) == 0) {
mutex_unlock(&dev->io_mutex);
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
if (wait_event_interruptible(dev->readq, kfifo_len(dev->read_fifo)!=0 || !dev->interface))
return -ERESTARTSYS;
mutex_lock(&dev->io_mutex);
}
if (!dev->interface) { /* disconnect() was called */
retval = -ENODEV;
goto exit;
}
xfersize = count < TBUF_LEN ? count : TBUF_LEN; // min(count, TBUF_LEN);
xfersize = kfifo_get(dev->read_fifo, tbuf, TBUF_LEN);
if (copy_to_user(buffer, tbuf, xfersize))
retval = -EFAULT;
else
retval = xfersize;
/* read_bulk_callback generally reschedules another read
automatically, but it stops if the fifo ever gets full. Now
that we've read data, there may be enough room in the fifo
to issue another read. */
susi_read_more(dev);
exit:
mutex_unlock(&dev->io_mutex);
return retval;
}
static void susi_write_bulk_callback(struct urb *urb)
{
struct usb_susi *dev;
dev = (struct usb_susi *)urb->context;
/* sync/async unlink faults aren't errors */
if (urb->status &&
!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN)) {
err("%s - nonzero write bulk status received: %d",
__FUNCTION__, urb->status);
}
/* free up our allocated buffer */
usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
up(&dev->write_sem);
/* we can write more, so wake up the queue. */
wake_up_interruptible(&dev->writeq);
}
static ssize_t susi_write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos)
{
struct usb_susi *dev;
int retval = 0;
struct urb *urb = NULL;
char *buf = NULL;
size_t writesize = min(count, (size_t) WRITE_SIZE);
dev = (struct usb_susi *)file->private_data;
/* verify that we actually have some data to write */
if (count == 0)
goto exit;
/* limit the number of URBs in flight to stop a user from using up all RAM */
if (down_interruptible(&dev->write_sem)) {
retval = -ERESTARTSYS;
goto exit;
}
mutex_lock(&dev->io_mutex);
if (!dev->interface) { /* disconnect() was called */
retval = -ENODEV;
goto error;
}
/* create a urb, and a buffer for it, and copy the data to the urb */
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}
buf = usb_buffer_alloc(dev->udev, writesize, GFP_KERNEL, &urb->transfer_dma);
if (!buf) {
retval = -ENOMEM;
goto error;
}
if (copy_from_user(buf, user_buffer, writesize)) {
retval = -EFAULT;
goto error;
}
/* initialize the urb properly */
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, writesize, susi_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* send the data out the bulk port */
retval = usb_submit_urb(urb, GFP_KERNEL);
if (retval) {
err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
goto error;
}
/* release our reference to this urb, the USB core will eventually free it entirely */
usb_free_urb(urb);
mutex_unlock(&dev->io_mutex);
return writesize;
error:
if (buf)
usb_buffer_free(dev->udev, writesize, buf, urb->transfer_dma);
if (urb)
usb_free_urb(urb);
mutex_unlock(&dev->io_mutex);
up(&dev->write_sem);
exit:
return retval;
}
static unsigned int susi_poll(struct file *file, poll_table *wait)
{
struct usb_susi *dev;
unsigned int mask = 0;
dev = (struct usb_susi *)file->private_data;
mutex_lock(&dev->io_mutex);
poll_wait(file, &dev->readq, wait);
poll_wait(file, &dev->writeq, wait);
if (kfifo_len(dev->read_fifo))
mask |= POLLIN | POLLRDNORM;
if (down_trylock(&dev->write_sem) == 0) {
// this would be prettier if there was a semaphore_value() call
mask |= POLLOUT | POLLWRNORM;
up(&dev->write_sem);
}
mutex_unlock(&dev->io_mutex);
return mask;
}
static const struct file_operations susi_fops = {
.owner = THIS_MODULE,
.read = susi_read,
.write = susi_write,
.open = susi_open,
.poll = susi_poll,
.release = susi_release,
};
/*
* usb class driver info in order to get a minor number from the usb core,
* and to have the device registered with the driver core
*/
static struct usb_class_driver susi_class = {
.name = "susi%d",
.fops = &susi_fops,
.minor_base = USB_SUSI_MINOR_BASE,
};
static int susi_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct usb_susi *dev;
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
int i;
int retval = -ENOMEM;
/* allocate memory for our device state and initialize it */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
err("Out of memory");
goto error;
}
kref_init(&dev->kref);
sema_init(&dev->write_sem, WRITES_IN_FLIGHT);
mutex_init(&dev->io_mutex);
dev->udev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = interface;
/* set up the endpoint information */
/* use only the first bulk-in and bulk-out endpoints */
iface_desc = interface->cur_altsetting;
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
if (!dev->bulk_in_endpointAddr &&
usb_endpoint_is_bulk_in(endpoint)) {
/* we found a bulk in endpoint */
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
}
if (!dev->bulk_out_endpointAddr &&
usb_endpoint_is_bulk_out(endpoint)) {
/* we found a bulk out endpoint */
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
}
}
if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
err("Could not find both bulk-in and bulk-out endpoints");
goto error;
}
/* save our data pointer in this interface device */
usb_set_intfdata(interface, dev);
/* we can register the device now, as it is ready */
retval = usb_register_dev(interface, &susi_class);
if (retval) {
/* something prevented us from registering this driver */
err("Not able to get a minor for this device.");
usb_set_intfdata(interface, NULL);
goto error;
}
dev->spinlock = SPIN_LOCK_UNLOCKED;
dev->read_fifo = kfifo_alloc(READ_FIFO_SIZE, GFP_KERNEL, &dev->spinlock);
if (!dev->read_fifo) {
retval = -ENOMEM;
goto error;
}
init_waitqueue_head(&dev->readq);
init_waitqueue_head(&dev->writeq);
/* start reading... */
susi_read_more(dev);
/* let the user know what node this device is now attached to */
info("susi device now attached to susi-%d", interface->minor);
return 0;
error:
if (dev)
kref_put(&dev->kref, susi_delete);
return retval;
}
static void susi_disconnect(struct usb_interface *interface)
{
struct usb_susi *dev;
int minor = interface->minor;
/* prevent susi_open() from racing susi_disconnect() */
lock_kernel();
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);
/* give back our minor */
usb_deregister_dev(interface, &susi_class);
/* prevent more I/O from starting */
mutex_lock(&dev->io_mutex);
dev->interface = NULL;
mutex_unlock(&dev->io_mutex);
unlock_kernel();
/* wake up a reader so that we will notice the disconnect */
wake_up_interruptible(&dev->readq);
wake_up_interruptible(&dev->writeq);
/* decrement our usage count */
kref_put(&dev->kref, susi_delete);
info("susi #%d now disconnected", minor);
}
static struct usb_driver susi_driver = {
.name = "susi",
.probe = susi_probe,
.disconnect = susi_disconnect,
.id_table = susi_table,
};
static int __init usb_susi_init(void)
{
int result;
/* register this driver with the USB subsystem */
result = usb_register(&susi_driver);
if (result)
err("usb_register failed. Error number %d", result);
return result;
}
static void __exit usb_susi_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&susi_driver);
}
module_init(usb_susi_init);
module_exit(usb_susi_exit);
MODULE_LICENSE("GPL");
-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier.
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel