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);
}

Reply via email to