Hi, Attached is a usb-skeleton.c driver that I whipped up to try to provide help to beginning usb driver authors (much like the pci-skeleton.c driver does.) I haven't really tested it well, but I thought I would like to get comments from the list at this time. Please let me know if you think the driver should have anything else in it, further comments in places, or whatever. It only handles bulk transfers, as I wanted to keep it simple (I might create a separate driver as an isochronous example if people think it would be useful.) I used the locking model that the dc2xx driver has, as it seems to be the cleanest and most reliable at this time (yes the SMP locking in the usb-serial drivers is messed up in places, it's on my todo list...) thanks, greg k-h
/* * USB Skeleton driver * * Copyright (c) 2001 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; either version 2 of * the License, or (at your option) any later version. * * * This driver is to be used as a skeleton driver to be able to create a * USB driver quickly. The design of it is based on the usb-serial and * dc2xx drivers. * * History: * * 2001_05_01 - 0.1 - first cut. * */ #include <linux/config.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/signal.h> #include <linux/errno.h> #include <linux/poll.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/fcntl.h> #include <linux/module.h> #include <linux/spinlock.h> #include <linux/list.h> #include <linux/smp_lock.h> #include <linux/devfs_fs_kernel.h> #include <linux/usb.h> #ifdef CONFIG_USB_DEBUG static int debug = 1; #else static int debug; #endif /* Use our own dbg macro */ #undef dbg #define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg); } while (0) /* Version Information */ #define DRIVER_VERSION "v0.1" #define DRIVER_AUTHOR "Greg Kroah-Hartman, [EMAIL PROTECTED]" #define DRIVER_DESC "USB Skeleton Driver" /* Module paramaters */ MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Debug enabled or not"); /* Define these values to match your device */ #define USB_SKEL_VENDOR_ID 0xffff #define USB_SKEL_PRODUCT_ID 0xffff /* table of devices that work with this driver */ static struct usb_device_id skel_table [] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, skel_table); /* Get a minor range for your devices from the usb maintainer */ #define USB_SKEL_MINOR_BASE 200 /* we can have up to this number of device plugged in at once */ #define MAX_DEVICES 16 /* Structure to hold all of our device specific stuff */ struct usb_skel { struct usb_device * dev; /* save off the usb device pointer */ struct usb_interface * interface; /* the interface for this device */ devfs_handle_t devfs; /* devfs device node */ unsigned char minor; /* the starting minor number for this device */ unsigned char num_ports; /* the number of ports this device has */ char num_interrupt_in; /* number of interrupt in endpoints we have */ char num_bulk_in; /* number of bulk in endpoints we have */ char num_bulk_out; /* number of bulk out endpoints we have */ unsigned char * bulk_in_buffer; /* the buffer to receive data */ int bulk_in_size; /* the size of the receive buffer */ __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ unsigned char * bulk_out_buffer; /* the buffer to send data */ int bulk_out_size; /* the size of the send buffer */ struct urb * write_urb; /* the urb used to send data */ __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */ struct tq_struct tqueue; /* task queue for line discipline waking up */ int open_count; /* number of times this port has been opened */ struct semaphore sem; /* locks this structure */ }; /* the global usb devfs handle */ extern devfs_handle_t usb_devfs_handle; /* local function prototypes */ static ssize_t skel_read (struct file *file, char *buffer, size_t count, loff_t *ppos); static ssize_t skel_write (struct file *file, const char *buffer, size_t count, loff_t *ppos); static int skel_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); static int skel_open (struct inode *inode, struct file *file); static int skel_release (struct inode *inode, struct file *file); static void * skel_probe (struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id); static void skel_disconnect (struct usb_device *dev, void *ptr); static void skel_write_bulk_callback (struct urb *urb); /* array of pointers to our devices that are currently connected */ static struct usb_skel *minor_table[MAX_DEVICES]; /* lock to protect the minor_table structure */ static DECLARE_MUTEX (minor_table_mutex); /* file operations needed when we register this driver */ static struct file_operations skel_fops = { owner: THIS_MODULE, read: skel_read, write: skel_write, ioctl: skel_ioctl, open: skel_open, release: skel_release, }; /* usb specific object needed to register this driver with the usb subsystem */ static struct usb_driver skel_driver = { name: "skeleton", probe: skel_probe, disconnect: skel_disconnect, fops: &skel_fops, minor: USB_SKEL_MINOR_BASE, id_table: skel_table, }; static inline struct usb_skel *get_serial_by_minor (int minor) { return minor_table[minor-USB_SKEL_MINOR_BASE]; } /** * usb_skel_debug_data */ static inline void usb_skel_debug_data (const char *function, int size, const unsigned char *data) { int i; if (!debug) return; printk (KERN_DEBUG __FILE__": %s - length = %d, data = ", function, size); for (i = 0; i < size; ++i) { printk ("%.2x ", data[i]); } printk ("\n"); } /** * skel_open */ static int skel_open (struct inode *inode, struct file *file) { struct usb_skel *skel = NULL; int subminor; int retval = 0; dbg(__FUNCTION__); /* lock our minor table */ down (&minor_table_mutex); subminor = MINOR (inode->i_rdev) - USB_SKEL_MINOR_BASE; if ((subminor < 0) || (subminor >= MAX_DEVICES)) { skel = minor_table[subminor]; if (skel == NULL) { up (&minor_table_mutex); return -ENODEV; } } /* lock this device */ down (&skel->sem); /* unlock the minor table */ up (&minor_table_mutex); /* increment our usage count for the module */ MOD_INC_USE_COUNT; ++skel->open_count; /* save our object in the file's private structure */ file->private_data = skel; /* unlock this device */ up (&skel->sem); return retval; } /** * skel_release */ static int skel_release (struct inode *inode, struct file *file) { struct usb_skel *skel; int retval = 0; skel = (struct usb_skel *)file->private_data; if (skel == NULL) { dbg (__FUNCTION__ " - object is NULL"); return -ENODEV; } dbg(__FUNCTION__ " - minor %d", skel->minor); /* lock our minor table */ down (&minor_table_mutex); /* lock our device */ down (&skel->sem); if (skel->open_count <= 0) { dbg (__FUNCTION__ " - device not opened"); retval = -ENODEV; goto exit; } if (skel->dev == NULL) { /* the device was unplugged before the file was released */ minor_table[skel->minor] = NULL; if (skel->bulk_in_buffer != NULL) kfree (skel->bulk_in_buffer); if (skel->bulk_out_buffer != NULL) kfree (skel->bulk_out_buffer); if (skel->write_urb != NULL) usb_free_urb (skel->write_urb); kfree (skel); goto exit; } /* decrement our usage count for the device */ --skel->open_count; if (skel->open_count <= 0) { /* shutdown any bulk writes that might be going on */ usb_unlink_urb (skel->write_urb); skel->open_count = 0; } /* decrement our usage count for the module */ MOD_DEC_USE_COUNT; up (&skel->sem); exit: up (&minor_table_mutex); return retval; } /** * skel_read */ static ssize_t skel_read (struct file *file, char *buffer, size_t count, loff_t *ppos) { struct usb_skel *skel; int retval = 0; skel = (struct usb_skel *)file->private_data; dbg(__FUNCTION__ " - minor %d, count = %d", skel->minor, count); /* lock this object */ down (&skel->sem); /* verify that the device wasn't unplugged */ if (skel->dev == NULL) { up (&skel->sem); return -ENODEV; } /* do an immediate bulk read to get data from the device */ retval = usb_bulk_msg (skel->dev, usb_rcvbulkpipe (skel->dev, skel->bulk_in_endpointAddr), skel->bulk_in_buffer, skel->bulk_in_size, &count, HZ*10); /* if the read was successful, copy the data to userspace */ if (!retval) { if (copy_to_user (buffer, skel->bulk_in_buffer, count)) retval = -EFAULT; else retval = count; } /* unlock the device */ up (&skel->sem); return retval; } /** * skel_write */ static ssize_t skel_write (struct file *file, const char *buffer, size_t count, loff_t *ppos) { struct usb_skel *skel; ssize_t bytes_written = 0; int result; skel = (struct usb_skel *)file->private_data; dbg(__FUNCTION__ " - minor %d, count = %d", skel->minor, count); /* lock this object */ down (&skel->sem); /* verify that the device wasn't unplugged */ if (skel->dev == NULL) { up (&skel->sem); return -ENODEV; } /* verify that we actually have some data to write */ if (count == 0) { dbg(__FUNCTION__ " - write request of 0 bytes"); goto exit; } /* see if we are already in the middle of a write */ if (skel->write_urb->status == -EINPROGRESS) { dbg (__FUNCTION__ " - already writing"); goto exit; } /* we can only write as much as 1 urb will hold */ bytes_written = (count > skel->bulk_out_size) ? skel->bulk_out_size : count; /* copy the data from userspace into our urb */ copy_from_user(skel->write_urb->transfer_buffer, buffer, bytes_written); usb_skel_debug_data (__FUNCTION__, bytes_written, skel->write_urb->transfer_buffer); /* set up our urb */ FILL_BULK_URB(skel->write_urb, skel->dev, usb_sndbulkpipe(skel->dev, skel->bulk_out_endpointAddr), skel->write_urb->transfer_buffer, bytes_written, skel_write_bulk_callback, skel); /* send the data out the bulk port */ result = usb_submit_urb(skel->write_urb); if (result) { err(__FUNCTION__ " - failed submitting write urb, error %d", result); } exit: /* unlock the device */ up (&skel->sem); return (bytes_written); } /** * skel_ioctl */ static int skel_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { struct usb_skel *skel; skel = (struct usb_skel *)file->private_data; /* lock this object */ down (&skel->sem); /* verify that the device wasn't unplugged */ if (skel->dev == NULL) { up (&skel->sem); return -ENODEV; } dbg(__FUNCTION__ " - minor %d, cmd 0x%.4x, arg %ld", skel->minor, cmd, arg); /* fill in your device specific stuff here */ /* unlock the device */ up (&skel->sem); /* return that we did not understand this ioctl call */ return -ENOIOCTLCMD; } /** * skel_write_bulk_callback */ static void skel_write_bulk_callback (struct urb *urb) { struct usb_skel *skel = (struct usb_skel *)urb->context; dbg(__FUNCTION__ " - minor %d", skel->minor); if (urb->status) { dbg(__FUNCTION__ " - nonzero write bulk status received: %d", urb->status); return; } return; } static void * skel_probe(struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id) { struct usb_skel *skel = NULL; struct usb_interface *interface; struct usb_interface_descriptor *iface_desc; struct usb_endpoint_descriptor *endpoint; int minor; int buffer_size; int i; char name[10]; /* See if the device offered us matches what we can accept */ if ((dev->descriptor.idVendor != USB_SKEL_VENDOR_ID) || (dev->descriptor.idProduct != USB_SKEL_PRODUCT_ID)) { return NULL; } /* select a "subminor" number (part of a minor number) */ down (&minor_table_mutex); for (minor = 0; minor < MAX_DEVICES; i++) { if (minor_table[minor] != NULL) break; } if (minor >= MAX_DEVICES) { info ("Too many devices plugged in, can not handle this device."); goto exit; } /* allocate memory for our device state and intialize it */ skel = kmalloc (sizeof(struct usb_skel), GFP_KERNEL); if (skel == NULL) { err ("Out of memory"); goto exit; } minor_table[minor] = skel; interface = &dev->actconfig->interface[ifnum]; init_MUTEX (&skel->sem); skel->dev = dev; skel->interface = interface; skel->minor = minor; /* set up the endpoint information */ /* check out the endpoints */ iface_desc = &interface->altsetting[0]; for (i = 0; i < iface_desc->bNumEndpoints; ++i) { endpoint = &iface_desc->endpoint[i]; if ((endpoint->bEndpointAddress & 0x80) && ((endpoint->bmAttributes & 3) == 0x02)) { /* we found a bulk in endpoint */ buffer_size = endpoint->wMaxPacketSize; skel->bulk_in_size = buffer_size; skel->bulk_in_endpointAddr = endpoint->bEndpointAddress; skel->bulk_in_buffer = kmalloc (buffer_size, GFP_KERNEL); if (!skel->bulk_in_buffer) { err("Couldn't allocate bulk_in_buffer"); goto error; } } if (((endpoint->bEndpointAddress & 0x80) == 0x00) && ((endpoint->bmAttributes & 3) == 0x02)) { /* we found a bulk out endpoint */ skel->write_urb = usb_alloc_urb(0); if (!skel->write_urb) { err("No free urbs available"); goto error; } buffer_size = endpoint->wMaxPacketSize; skel->bulk_out_size = buffer_size; skel->bulk_out_endpointAddr = endpoint->bEndpointAddress; skel->bulk_out_buffer = kmalloc (buffer_size, GFP_KERNEL); if (!skel->bulk_out_buffer) { err("Couldn't allocate bulk_out_buffer"); goto error; } FILL_BULK_URB(skel->write_urb, dev, usb_sndbulkpipe(dev, endpoint->bEndpointAddress), skel->bulk_out_buffer, buffer_size, skel_write_bulk_callback, skel); } } /* initialize the devfs node for this device and register it */ sprintf(name, "skel%d", skel->minor); skel->devfs = devfs_register (usb_devfs_handle, name, DEVFS_FL_DEFAULT, USB_MAJOR, USB_SKEL_MINOR_BASE + skel->minor, S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, &skel_fops, NULL); /* let the user know what node this device is now attached to */ info ("USB Skeleton device now attached to USBSkel%d", skel->minor); goto exit; error: minor_table [skel->minor] = NULL; if (skel->bulk_in_buffer != NULL) kfree (skel->bulk_in_buffer); if (skel->bulk_out_buffer != NULL) kfree (skel->bulk_out_buffer); if (skel->write_urb != NULL) usb_free_urb (skel->write_urb); kfree (skel); skel = NULL; exit: up (&minor_table_mutex); return skel; } /** * skel_disconnect */ static void skel_disconnect(struct usb_device *dev, void *ptr) { struct usb_skel *skel; int minor; skel = (struct usb_skel *)ptr; down (&minor_table_mutex); down (&skel->sem); minor = skel->minor; /* if the device is not opened, then we clean up right now */ if (skel->open_count) { minor_table[skel->minor] = NULL; if (skel->bulk_in_buffer != NULL) kfree (skel->bulk_in_buffer); if (skel->bulk_out_buffer != NULL) kfree (skel->bulk_out_buffer); if (skel->write_urb != NULL) usb_free_urb (skel->write_urb); kfree (skel); } else { skel->dev = NULL; up (&skel->sem); } /* remove our devfs node */ devfs_unregister(skel->devfs); info("USB Skeleton #%d now disconnected", minor); up (&minor_table_mutex); } /** * usb_skel_init */ static int __init usb_skel_init(void) { int result; /* register this driver with the USB subsystem */ result = usb_register(&skel_driver); if (result < 0) { err("usb_register failed for the "__FILE__" driver. Error number %d", result); return -1; } info(DRIVER_VERSION " " DRIVER_AUTHOR); info(DRIVER_DESC); return 0; } /** * usb_skel_exit */ static void __exit usb_skel_exit(void) { /* deregister this driver with the USB subsystem */ usb_deregister(&skel_driver); } module_init (usb_skel_init); module_exit (usb_skel_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC);