Like many, I am trying to develop my first USB driver and having problems. This driver is for a CCD camera which will fly on a sounding rocket in late fall 2006, designed to observe the Sun.( The CCD cameras are going to driven by PC104 / pentiums running RTLinux). I am now the main stumbling block in the software development road since without a camera taking images, it's hard to test the software functionality that needs to be developed. I have taken the usb-skeleton driver and adapted it for this camera. I initially started writing a kernel driver because the IN endpoint for this device was originally isochronous. On the flight model of the camera which we recently received, this endpoint is now bulk. I'm current developing this software on on a laptop running a 2.6.11-6mdksmp kernel.
Here is the output of lsusb -v for the device. Bus 004 Device 002: ID ffff:0002 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 255 Vendor Specific Class bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0xffff idProduct 0x0002 bcdDevice 1.10 iManufacturer 1 iProduct 2 iSerial 3 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 32 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x40 Self Powered MaxPower 0mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0010 1x 16 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Device Qualifier (for other device speed): bLength 10 bDescriptorType 6 bcdUSB 2.00 bDeviceClass 255 Vendor Specific Class bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 bNumConfigurations 1 Currently all I need to do is make sure I can get out a single image we can look at. A single image consists of 2097152 bytes. If I run the userspace program with the machine in single user mode I seem to be able to do this [I've yet to confirm what I'm pulling off is an image as opposed to empty bytes!]. However when I boot it to multiuser mode, the system dies. Primarily it starts by hanging all the X-window stuff but eventually everythig grinds to a halt. There doesn't seem to be anything in the kernel log though. I've traced the problem to the raise_read subroutine at the point at which I use usb_bulk_msg to get the data. I'm obviously missing something and I frankly don't know what. Down, the line one of the constraints I will have is that I'll need to be able to grab frames on 1/10th second cadence. I'm not sure that the approach here is the correct one but until I start successfully pulling images I won't know. I'm not sure I even need a kernel driver if all I'm using is bulk endpoints. I'd also be delighted for suggestions on how to make it as bullet proof as possible given that rocket flight will last 5 minutes and _has_ to work. I've included the description of the USB interface for the camera (below) and the driver and user space programs (attached) as they stand. If anyone has the time to look at the driver and point out my stupidity, I'd really appreciate the help. Even if it's to suggest how to better debug the situation, that would be good too. I'm sure this would make a great learning project if only I had plenty of time to work things out on my own, but I don't. Cheers Alisdair ----------------------------------------------------------------------- USB Interface description This is the USB description interface I am working from: The APS detector module will be controlled by using an USB 2.0 high speed interface. The USB I/F uses 2 Endpoints, one for command issuing and one for data receiving. Command Interface The command interface is performed by BULK transfer on Endpoint 1. The command consist of 16 bytes. For the camera are used 6 bytes only. C3 xx xx xx xx xx xx xx xx lih lil xx is xx g ih il lih long integration(15...8) lil long integration(7...0) is switch integration(7...0) g gain(7...0) ih integration(15...8) il integration(7...0) long integration values: 0 - 65535 (ih-FFh/il-FFh ... ih-00h/il-00h) switch integration : xxxxxxx0 - integration, xxxxxxx1 - long integration gain values: 0 - 3 (xxxxxx11, xxxxxx10, xxxxxx01, xxxxxx00) integration values: 0 - 1023 (ih-xxxxxx11/il-11111111 ... ih-xxxxxx00/il-00000000) long integration step - 1ms (1ms - 65535ms) integration step - 89 us (89us - 91047us). Data Interface The data interface is performed by bulk high speed transfer. The transfer size is 512(byte). After the first IN token to the Endpoint 2 the internal fifo gets ready. The data will be valid by the rising edge of the internal image frame (from sensor). That means, the first IN tokens will be answered with Not Acknowledge (just the same if there are not enough data in the fifo). -- Dr Alisdair Davey [EMAIL PROTECTED] Pergamentum Solutions Tel: 1-303-981-9838 2066 Dailey Lane Superior, CO 80027
// $Id: raise.c,v 1.1 2006/03/07 20:41:27 ard Exp $ // Adapted from drivers/usb/usb-skeleton.c in the kernel tree. // // Load module: insmod ./raise.ko [debug=1] // module maybe loaded with debug argument to switch on debugging. // // 030705 This version is adapted from the original ARD code with massive // input from ATP. ARD approach was not suitable to grabbing frames from // camera as quickly as likely to be needed. Even this approach (Ring buffers // + /dev entry) may not be fast enough for the job and will need to be // verified. // // 031506 Camera spec has changed from using an isochronous Endpoint 0x02 to // a bulk endpoint. Whilst this simplifies things in the long run, it means // a large number of routines need to be rewritten. // // 031606 Completed rewrite to use Bulk in interface. RAISE_PRODUCT_ID has // changed to 0x0002 for this version of the cameras. // #include <linux/config.h> #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 <asm/uaccess.h> #include <linux/usb.h> #include <linux/smp_lock.h> #include <linux/time.h> #include <linux/vmalloc.h> #define DRIVER_AUTHOR "Alisdair Davey <[EMAIL PROTECTED]>, Andrew Phillips <[EMAIL PROTECTED]>" #define DRIVER_DESC "RAISE Camera Driver" // RAISE does not use true vendor and product Ids #define RAISE_VENDOR_ID 0xffff #define RAISE_PRODUCT_ID 0x0002 // #define RAISE_IMAGE_SIZE 2097152 #define RAISE_IMAGE_SIZE 524288 #define DATA_BUF_SIZE 512 // Define module arguments static int raise_debug = 0; // raise_debug controls printing of debug information. May be set at module // load time with insmod ./raise.ko raise_debug=1 or by // echo 1 > /sys/module/raise/raise_debug. module_param(raise_debug, int, S_IRWXUGO); MODULE_PARM_DESC(raise_debug, "Debug parameter"); // Table of devices that work with this driver static struct usb_device_id raise_table [] = { { USB_DEVICE(RAISE_VENDOR_ID, RAISE_PRODUCT_ID) }, { } }; MODULE_DEVICE_TABLE (usb, raise_table); // Use standard definition from USB skeleton file. #define RAISE_MINOR_BASE 192 // Timeout for Bulk reads - 0 is blocking. #define BULK_TIMEOUT HZ * 10 // Structure to hold all of our device specific stuff struct usb_raise { struct usb_device * udev; // the usb device for this device struct usb_interface * interface; // the interface for this device __u8 bulk_in_endpointAddr; // the address of the bulk in endpoint __u8 bulk_out_endpointAddr; // the address of the bulk out endpoint void * databuf; struct kref kref; }; static struct usb_driver raise_driver; static int raise_open(struct inode *inode, struct file *file) { struct usb_raise *dev; struct usb_interface *interface; int subminor; int retval = 0; if (raise_debug) info ("raise_open called"); subminor = iminor(inode); interface = usb_find_interface(&raise_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); // Save our object in the file's private structure file->private_data = dev; exit: return retval; } static void raise_write_bulk_callback(struct urb *urb, struct pt_regs *regs) { // This routine is pretty much verbatim from the skeleton driver. Bulk URBs // are easy to deal with. struct usb_raise *dev; dev = (struct usb_raise *) urb->context; if (raise_debug) info ("raise_open called"); // sync/async unlink faults aren't errors if (urb->status && !(urb->status == -ENOENT || urb->status == -ECONNRESET || urb->status == -ESHUTDOWN)) { dbg("%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); } static ssize_t raise_write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos) { // This routine is pretty much verbatim from the skeleton driver. struct usb_raise *dev; int retval = 0; struct urb *urb = NULL; char *buf = NULL; if (raise_debug) info("raise_write called."); dev = (struct usb_raise *)file->private_data; /* verify that we actually have some data to write */ if (raise_debug) info("raise-write: %d",count); if (count == 0) goto exit; /* 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, count, GFP_KERNEL, &urb->transfer_dma); if (!buf) { retval = -ENOMEM; goto error; } if (copy_from_user(buf, user_buffer, count)) { retval = -EFAULT; goto error; } /* initialize the urb properly */ usb_fill_bulk_urb(urb, dev->udev, usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), buf, count, raise_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_buffer_free(dev->udev, count, buf, urb->transfer_dma); usb_free_urb(urb); exit: return count; error: usb_buffer_free(dev->udev, count, buf, urb->transfer_dma); usb_free_urb(urb); return retval; } #define to_raise_dev(d) container_of(d, struct usb_raise, kref) static void raise_delete(struct kref *kref) { struct usb_raise *dev = to_raise_dev(kref); usb_put_dev(dev->udev); kfree (dev); } static int raise_release(struct inode *inode, struct file *file) { struct usb_raise *dev; dev = (struct usb_raise *)file->private_data; if (dev == NULL) return -ENODEV; // decrement the count on our device */ kref_put(&dev->kref, raise_delete); return 0; } static ssize_t raise_read(struct file *file, char *buffer, size_t count, loff_t *ppos) { struct usb_raise *dev; int retval = 0; if (raise_debug) info("raise_read called."); dev = (struct usb_raise *)file->private_data; retval = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), dev->databuf, RAISE_IMAGE_SIZE, &count, BULK_TIMEOUT); if (!retval) { if (copy_to_user(buffer, dev->databuf, count)) retval = -EFAULT; else retval = count; } info("retval %d", retval); return retval; } static struct file_operations raise_fops = { .owner = THIS_MODULE, .read = raise_read, .write = raise_write, .open = raise_open, .release = raise_release, }; // USB class driver info in order to get a minor number from the usb core, // and to have the device registered with devfs and the driver core static struct usb_class_driver raise_class = { .name = "usb/raise%d", .fops = &raise_fops, .mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, .minor_base = RAISE_MINOR_BASE, }; static int raise_probe (struct usb_interface *interface, const struct usb_device_id *id) { struct usb_raise *dev = NULL; struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; int retval = -ENOMEM; if (raise_debug) info ("raise_probe called."); // Allocate memory for our device state and initialize it dev = kmalloc(sizeof(*dev), GFP_KERNEL); if (dev == NULL) { err("Out of memory"); goto error; } memset(dev, 0x00, sizeof(*dev)); kref_init(&dev->kref); dev->udev = usb_get_dev(interface_to_usbdev(interface)); dev->interface = interface; iface_desc = interface->cur_altsetting; // Allocate bulk out interface endpoint = &iface_desc->endpoint[0].desc; dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; // Allocate bulk in interface endpoint = &iface_desc->endpoint[1].desc; dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; // Allocate data buffer dev->databuf = (void *) vmalloc(RAISE_IMAGE_SIZE); if (dev->databuf == NULL) { err("Could not allocate image space buffer"); goto error; } memset(dev->databuf, 0, RAISE_IMAGE_SIZE); // Save data pointer in this interface device usb_set_intfdata(interface, dev); // Now register the device retval = usb_register_dev(interface, &raise_class); if (retval) // Could not register device { err("Not able to register this device."); usb_set_intfdata(interface, NULL); goto error; } if (raise_debug) info("RAISE camera now attached to raise-%d", interface->minor); return 0; error: if (dev) { kref_put(&dev->kref, raise_delete); kfree(dev); } return retval; } static void raise_disconnect(struct usb_interface *interface) { struct usb_raise *dev; int minor = interface->minor; // Note the skeleton driver uses a kernel lock here. We don't have because // we will only ever have one camera on one computer. dev = usb_get_intfdata(interface); usb_set_intfdata(interface, NULL); // give back our minor device number usb_deregister_dev(interface, &raise_class); // decrement our usage count */ kref_put(&dev->kref, raise_delete); if (raise_debug) info("Raise camera #%d now disconnected", minor); } static struct usb_driver raise_driver = { .owner = THIS_MODULE, .name = "RAISE", .probe = raise_probe, .disconnect = raise_disconnect, .id_table = raise_table, }; static int __init raise_init(void) { int result; // register this driver with the USB subsystem */ result = usb_register(&raise_driver); if (raise_debug) info("Registered RAISE driver"); if (result) err("usb_register failed. Error number %d", result); return result; } static void __exit raise_exit(void) { // deregister this driver with the USB subsystem */ usb_deregister(&raise_driver); if (raise_debug) info("RAISE driver deregistered"); } module_init (raise_init); module_exit (raise_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC);
// $Id:$ // usdriver - sends command to camera and reads images from camers // // 031606 ARD Version that generates commands for the V2 RAISE cameras // #include <dirent.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <linux/types.h> #include <linux/unistd.h> #include <errno.h> #include <fcntl.h> #include <error.h> #define RAISE_IMAGE_SIZE 2097152 #define RAISE_COMMAND_BUFFER 16 #define DATA_BUF_SIZE 512 unsigned char *gen_command_buffer(short int integration, short int gain, unsigned short int iswitch) { unsigned char *cbuf; int i; cbuf = (char *) calloc(RAISE_COMMAND_BUFFER, 1); if (!cbuf) { printf("Could not allocate command buffer\n"); exit(-1); } // Populate command buffer cbuf[11] = iswitch; // use long integration flag cbuf[13] = gain; if (iswitch) { cbuf[8] = (integration & 0xff000000) >> 24; cbuf[9] = (integration & 0x00ff0000) >> 16; } cbuf[14] = (integration & 0x0000ff00) >> 8; cbuf[15] = (integration & 0x000000ff) >> 0; return cbuf; } int main () { char *path = "/dev/raise0"; int retval; void *nbuf, *ptr; int raise_fd; int i; short int gain = 1; short int integration = 1000; int emerg = 0; unsigned char *cbuf; int iswitch = 0; int count = 0; cbuf = gen_command_buffer(integration, gain, iswitch); nbuf = (void *) malloc(RAISE_IMAGE_SIZE); memset(nbuf, 0, RAISE_IMAGE_SIZE); if (!nbuf) { printf("Could not allocate RAISE image buffer\n"); exit(-1); } if (!(raise_fd = open(path, O_RDWR))) { printf("Could not open raise device.\n"); exit(-1); } retval = write(raise_fd, cbuf, 16); printf("Wrote %d bytes to raise device: \n",retval); retval = read(raise_fd, nbuf, RAISE_IMAGE_SIZE); if (retval == -1) { perror("read error"); } else { printf("Read %d bytes from raise device:\n",retval); } close(raise_fd); if (retval != RAISE_IMAGE_SIZE) { printf("Not enough bytes for image - exiting\n"); exit(-1); } if (!(raise_fd = open("/tmp/raise_image", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO))) { printf("Could not open raise image file.\n"); exit(-1); } retval = write(raise_fd, nbuf, RAISE_IMAGE_SIZE); if (retval == -1) { perror("read error"); } printf("Wrote %d bytes to raise image file: \n",retval); close(raise_fd); }