Working now with some sort of sensible device registration structure
authorMichael Brown <mbrown@fensystems.co.uk>
Sun, 12 Mar 2006 17:30:07 +0000 (17:30 +0000)
committerMichael Brown <mbrown@fensystems.co.uk>
Sun, 12 Mar 2006 17:30:07 +0000 (17:30 +0000)
kernel/quickusb.c
kernel/quickusb.h [new file with mode: 0644]

index 377604d..8d2035f 100644 (file)
 #include <linux/kref.h>
 #include <linux/usb.h>
 #include <asm/uaccess.h>
+#include "quickusb.h"
 
 #define QUICKUSB_VENDOR_ID 0x0fbb
 #define QUICKUSB_DEVICE_ID 0x0001
 
-#define QUICKUSB_BREQUEST 0xb3
-#define QUICKUSB_BREQUESTTYPE_READ 0xc0
-#define QUICKUSB_BREQUESTTYPE_WRITE 0x40
-#define QUICKUSB_MAX_DATA_LEN 64
-#define QUICKUSB_TIMEOUT ( 1 * HZ )
-
-#define QUICKUSB_MAX_BOARDS 256
-#define QUICKUSB_MAX_SUBDEV 16
-#define QUICKUSB_SUBDEV_MASK ( QUICKUSB_MAX_SUBDEV - 1 )
-#define QUICKUSB_BOARD( dev_minor ) ( (dev_minor) / QUICKUSB_MAX_SUBDEV )
-#define QUICKUSB_SUBDEV( dev_minor ) ( (dev_minor) & QUICKUSB_SUBDEV_MASK )
+#define QUICKUSB_MAX_SUBDEVS 8
+#define QUICKUSB_SUBDEV_MASK ( QUICKUSB_MAX_SUBDEVS - 1 )
+#define QUICKUSB_MINOR_BOARD( dev_minor ) \
+       ( (dev_minor) / QUICKUSB_MAX_SUBDEVS )
+#define QUICKUSB_MINOR_SUBDEV( dev_minor ) \
+       ( (dev_minor) & QUICKUSB_SUBDEV_MASK )
 #define QUICKUSB_MINOR( board, subdev ) \
-       ( (board) * QUICKUSB_MAX_SUBDEV + (subdev) )
+       ( (board) * QUICKUSB_MAX_SUBDEVS + (subdev) )
 
-#define QUICKUSB_SUBDEV_GPPIO_A 0
-
-
-static const char *quickusb_subdev_name ( unsigned int subdev ) {
-       switch ( subdev ) {
-       case QUICKUSB_SUBDEV_GPPIO_A:
-               return "gppio_a";
-       default:
-               return NULL;
-       }
-}
+#define QUICKUSB_MAX_GPPIO 5
 
 static int debug = 0;
 static int dev_major = 0;
 
-static DECLARE_MUTEX ( quickusb_lock );
+struct quickusb_gppio {
+       struct quickusb_device *quickusb;
+       unsigned int port;
+};
 
-struct quickusb_board {
-       struct usb_device *usbdev;
+struct quickusb_subdev {
+       struct file_operations *f_op;
+       void *private_data;
+       unsigned char name[32];
+};
+
+struct quickusb_device {
+       struct usb_device *usb;
        struct usb_interface *interface;
        struct kref kref;
-       int board;
-       unsigned long subdevs;
+       struct list_head list;
+       unsigned int board;
+       struct quickusb_gppio gppio[QUICKUSB_MAX_GPPIO];
+       struct quickusb_subdev subdev[QUICKUSB_MAX_SUBDEVS];
 };
 
-static struct quickusb_board *quickusb_boards[QUICKUSB_MAX_BOARDS];
-
 static void quickusb_delete ( struct kref *kref ) {
-       struct quickusb_board *quickusb;
+       struct quickusb_device *quickusb;
 
-       quickusb = container_of ( kref, struct quickusb_board, kref );
-       usb_put_dev ( quickusb->usbdev );
+       quickusb = container_of ( kref, struct quickusb_device, kref );
+       usb_put_dev ( quickusb->usb );
        kfree ( quickusb );
 }
 
+static LIST_HEAD ( quickusb_list );
+
+static DECLARE_MUTEX ( quickusb_lock );
+
+/****************************************************************************
+ *
+ * GPPIO char device operations
+ *
+ */
+
 static ssize_t quickusb_gppio_read ( struct file *file, char __user *user_data,
                                     size_t len, loff_t *ppos ) {
-       struct quickusb_board *quickusb = file->private_data;
-       struct usb_device *usbdev = quickusb->usbdev;
+       struct quickusb_gppio *gppio = file->private_data;
+       struct usb_device *usb = gppio->quickusb->usb;
        unsigned char data[QUICKUSB_MAX_DATA_LEN];
        int rc;
 
        if ( len > sizeof ( data ) )
                len = sizeof ( data );
 
-       if ( ( rc = usb_control_msg ( usbdev, usb_rcvctrlpipe ( usbdev, 0 ),
-                                     QUICKUSB_BREQUEST,
-                                     QUICKUSB_BREQUESTTYPE_READ, 0, 1, data,
-                                     len, QUICKUSB_TIMEOUT ) ) < 0 )
+       if ( ( rc = quickusb_read_port ( usb, gppio->port,
+                                        data, &len ) ) != 0 )
                return rc;
 
        if ( ( rc = copy_to_user ( user_data, data, len ) ) != 0 )
@@ -95,8 +98,8 @@ static ssize_t quickusb_gppio_read ( struct file *file, char __user *user_data,
 static ssize_t quickusb_gppio_write ( struct file *file,
                                      const char __user *user_data,
                                      size_t len, loff_t *ppos ) {
-       struct quickusb_board *quickusb = file->private_data;
-       struct usb_device *usbdev = quickusb->usbdev;
+       struct quickusb_gppio *gppio = file->private_data;
+       struct usb_device *usb = gppio->quickusb->usb;
        unsigned char data[QUICKUSB_MAX_DATA_LEN];
        int rc;
 
@@ -106,114 +109,184 @@ static ssize_t quickusb_gppio_write ( struct file *file,
        if ( ( rc = copy_from_user ( data, user_data, len ) ) != 0 )
                return rc;
 
-       if ( ( rc = usb_control_msg ( usbdev, usb_sndctrlpipe ( usbdev, 0 ),
-                                     QUICKUSB_BREQUEST,
-                                     QUICKUSB_BREQUESTTYPE_WRITE, 0, 1, data,
-                                     len, QUICKUSB_TIMEOUT ) ) < 0 )
+       if ( ( rc = quickusb_write_port ( usb, gppio->port,
+                                         data, &len ) ) != 0 )
                return rc;
 
        *ppos += len;
        return len;
 }
 
+static int quickusb_gppio_release ( struct inode *inode, struct file *file ) {
+       struct quickusb_gppio *gppio = file->private_data;
+       
+       kref_put ( &gppio->quickusb->kref, quickusb_delete );
+       return 0;
+}
+
+static struct file_operations quickusb_gppio_fops = {
+       .owner          = THIS_MODULE,
+       .read           = quickusb_gppio_read,
+       .write          = quickusb_gppio_write,
+       .release        = quickusb_gppio_release,
+};
+
+/****************************************************************************
+ *
+ * Char device (subdev) operations
+ *
+ */
+
 static int quickusb_open ( struct inode *inode, struct file *file ) {
-       unsigned int board = QUICKUSB_BOARD ( iminor ( inode ) );
-       unsigned int subdev = QUICKUSB_SUBDEV ( iminor ( inode ) );
-       struct quickusb_board *quickusb;
+       unsigned int board = QUICKUSB_MINOR_BOARD ( iminor ( inode ) );
+       unsigned int subdev = QUICKUSB_MINOR_SUBDEV ( iminor ( inode ) );
+       struct quickusb_device *quickusb;
+       int found = 0;
        int rc = 0;
 
+       /* Locate board and increase refcount */
        down ( &quickusb_lock );
-
-       /* Increase refcount on the board */
-       quickusb = quickusb_boards[board];
-       if ( ! quickusb ) {
+       list_for_each_entry ( quickusb, &quickusb_list, list ) {
+               if ( quickusb->board == board ) {
+                       kref_get ( &quickusb->kref );
+                       found = 1;
+                       break;
+               }
+       }
+       up ( &quickusb_lock );
+       if ( ! found ) {
+               quickusb = NULL;
                rc = -ENODEV;
                goto out;
        }
-       kref_get ( &quickusb->kref );
 
-       /* Set private data pointer */
-       file->private_data = quickusb;
+       /* Set up per-subdevice file operations and private data */
+       file->f_op = quickusb->subdev[subdev].f_op;
+       file->private_data = quickusb->subdev[subdev].private_data;
+       if ( ! file->f_op ) {
+               rc = -ENODEV;
+               goto out;
+       }
+       
+       /* Perform any subdev-specific open operation */
+       if ( file->f_op->open )
+               rc = file->f_op->open ( inode, file );
 
  out:
-       up ( &quickusb_lock );
-       return 0;
-}
-
-static int quickusb_release ( struct inode *inode, struct file *file ) {
-       struct quickusb_board *quickusb = file->private_data;
-
-       kref_put ( &quickusb->kref, quickusb_delete );
-       return 0;
+       if ( ( rc != 0 ) && quickusb )
+               kref_put ( &quickusb->kref, quickusb_delete );
+       return rc;
 }
 
 static struct file_operations quickusb_fops = {
        .owner          = THIS_MODULE,
        .open           = quickusb_open,
-       .read           = quickusb_gppio_read,
-       .write          = quickusb_gppio_write,
-       .release        = quickusb_release,
 };
 
-static int quickusb_register_dev ( struct quickusb_board *quickusb ) {
-       unsigned int board;
-       unsigned int subdev;
+/****************************************************************************
+ *
+ * Char device (subdev) registration/deregistration
+ *
+ */
+
+static int quickusb_register_subdev ( struct quickusb_device *quickusb,
+                                     unsigned int subdev_idx,
+                                     struct file_operations *f_op,
+                                     void *private_data,
+                                     const char *subdev_fmt, ... ) {
+       struct quickusb_subdev *subdev = &quickusb->subdev[subdev_idx];
        unsigned int dev_minor;
-       const char *name;
        dev_t dev;
+       va_list ap;
        int rc;
 
-       /* Allocate a board number */
-       for ( board = 0 ; board < QUICKUSB_MAX_BOARDS ; board++ ) {
-               if ( ! quickusb_boards[board] ) {
-                       quickusb_boards[board] = quickusb;
-                       quickusb->board = board;
-                       break;
-               }
-       }
-       if ( quickusb->board < 0 )
-               return -ENOMEM;
+       /* Construct device number */
+       dev_minor = QUICKUSB_MINOR ( quickusb->board, subdev_idx );
+       dev = MKDEV ( dev_major, dev_minor );
+
+       /* Construct device name */
+       va_start ( ap, subdev_fmt );
+       vsnprintf ( subdev->name, sizeof ( subdev->name ), subdev_fmt, ap );
+       va_end ( ap );
+
+       /* Create devfs device */
+       if ( ( rc = devfs_mk_cdev ( dev,
+                                   ( S_IFCHR | S_IRUSR | S_IWUSR |
+                                     S_IRGRP | S_IWGRP ),
+                                   subdev->name ) ) != 0 )
+               return rc;
+
+       /* Fill subdev structure */
+       subdev->f_op = f_op;
+       subdev->private_data = private_data;
+
+       return 0;
+}
+                                     
+static void quickusb_deregister_subdev ( struct quickusb_device *quickusb,
+                                        unsigned int subdev_idx ) {
+       struct quickusb_subdev *subdev = &quickusb->subdev[subdev_idx];
+
+       if ( ! subdev->f_op )
+               return;
+
+       /* Clear subdev structure */
+       memset ( subdev, 0, sizeof ( *subdev ) );
        
-       /* Create device nodes */
-       for ( subdev = 0 ; subdev < QUICKUSB_MAX_SUBDEV ; subdev++ ) {
-               dev_minor = QUICKUSB_MINOR ( quickusb->board, subdev );
-               dev = MKDEV ( dev_major, dev_minor );
-               name = quickusb_subdev_name ( subdev );
-               if ( ! name )
-                       continue;
-               if ( ( rc = devfs_mk_cdev ( dev,
-                                           ( S_IFCHR | S_IRUSR | S_IWUSR |
-                                             S_IRGRP | S_IWGRP ),
-                                           "quickusb/%d/%s", quickusb->board,
-                                           name ) ) != 0 )
+       /* Remove devfs device */
+       devfs_remove ( subdev->name );
+}
+
+/****************************************************************************
+ *
+ * Device creation / destruction
+ *
+ */
+
+static int quickusb_register_devices ( struct quickusb_device *quickusb ) {
+       unsigned int subdev_idx = 0;
+       struct quickusb_gppio *gppio;
+       unsigned char gppio_char;
+       int i;
+       int rc;
+
+       /* Register GPPIO ports as subdevs */
+       for ( i = 0 ; i < QUICKUSB_MAX_GPPIO ; i++ ) {
+               gppio = &quickusb->gppio[i];
+               gppio_char = ( 'a' + gppio->port );
+               if ( ( rc = quickusb_register_subdev ( quickusb, subdev_idx++,
+                                                      &quickusb_gppio_fops,
+                                                      gppio,
+                                                      "quickusb%d/gppio_%c",
+                                                      quickusb->board,
+                                                      gppio_char ) ) != 0 )
                        return rc;
-               quickusb->subdevs |= ( 1 << subdev );
        }
+       
        return 0;
 }
 
-static void quickusb_deregister_dev ( struct quickusb_board *quickusb ) {
-       unsigned int subdev;
-       
-       if ( quickusb->board < 0 )
-               return;
+static void quickusb_deregister_devices ( struct quickusb_device *quickusb ) {
+       int i;
 
-       /* Remove device nodes */
-       for ( subdev = 0 ; subdev < QUICKUSB_MAX_SUBDEV ; subdev++ ) {
-               if ( ! ( quickusb->subdevs & ( 1 << subdev ) ) )
-                       continue;
-               devfs_remove ( "quickusb/%d/%s", quickusb->board,
-                              quickusb_subdev_name ( subdev ) );
+       /* Deregister all subdevs */
+       for ( i = 0 ; i < QUICKUSB_MAX_SUBDEVS ; i++ ) {
+               quickusb_deregister_subdev ( quickusb, i );
        }
-
-       /* Free up board number */
-       quickusb_boards[quickusb->board] = NULL;
-       quickusb->board = -1;
 }
 
+/****************************************************************************
+ *
+ * USB hotplug add/remove
+ *
+ */
+
 static int quickusb_probe ( struct usb_interface *interface,
                            const struct usb_device_id *id ) {
-       struct quickusb_board *quickusb = NULL;
+       struct quickusb_device *quickusb = NULL;
+       struct quickusb_device *pre_existing_quickusb;
+       unsigned int board = 0;
+       int i;
        int rc = 0;
 
        down ( &quickusb_lock );
@@ -226,15 +299,28 @@ static int quickusb_probe ( struct usb_interface *interface,
        }
        memset ( quickusb, 0, sizeof ( *quickusb ) );
        kref_init ( &quickusb->kref );
-       quickusb->board = -1;
-       quickusb->usbdev = usb_get_dev ( interface_to_usbdev ( interface ) );
+       INIT_LIST_HEAD ( &quickusb_list );
+       quickusb->usb = usb_get_dev ( interface_to_usbdev ( interface ) );
        quickusb->interface = interface;
+       for ( i = 0 ; i < QUICKUSB_MAX_GPPIO ; i++ ) {
+               quickusb->gppio[i].quickusb = quickusb;
+               quickusb->gppio[i].port = i;
+       }
+       
+       /* Obtain a free board board and link into list */
+       list_for_each_entry ( pre_existing_quickusb, &quickusb_list, list ) {
+               if ( pre_existing_quickusb->board != board )
+                       break;
+               board++;
+       }
+       quickusb->board = board;
+       list_add_tail ( &quickusb->list, &pre_existing_quickusb->list );
 
        /* Record driver private data */
        usb_set_intfdata ( interface, quickusb );
 
        /* Register devices */
-       if ( ( rc = quickusb_register_dev ( quickusb ) ) != 0 ) {
+       if ( ( rc = quickusb_register_devices ( quickusb ) ) != 0 ) {
                printk ( KERN_ERR "quickusb unable to register devices\n" );
                goto err;
        }
@@ -245,7 +331,8 @@ static int quickusb_probe ( struct usb_interface *interface,
  err:
        usb_set_intfdata ( interface, NULL );
        if ( quickusb ) {
-               quickusb_deregister_dev ( quickusb );
+               quickusb_deregister_devices ( quickusb );
+               list_del ( &quickusb->list );
                kref_put ( &quickusb->kref, quickusb_delete );
        }
  out:
@@ -254,13 +341,14 @@ static int quickusb_probe ( struct usb_interface *interface,
 }
 
 static void quickusb_disconnect ( struct usb_interface *interface ) {
-       struct quickusb_board *quickusb = usb_get_intfdata ( interface );
+       struct quickusb_device *quickusb = usb_get_intfdata ( interface );
 
        printk ( KERN_INFO "quickusb%d disconnected\n", quickusb->board );
 
        down ( &quickusb_lock );
-       quickusb_deregister_dev ( quickusb );
        usb_set_intfdata ( interface, NULL );
+       quickusb_deregister_devices ( quickusb );
+       list_del ( &quickusb->list );
        up ( &quickusb_lock );
 
        kref_put ( &quickusb->kref, quickusb_delete );
@@ -279,6 +367,12 @@ static struct usb_driver quickusb_driver = {
        .id_table       = quickusb_ids,
 };
 
+/****************************************************************************
+ *
+ * Kernel module interface
+ *
+ */
+
 static int quickusb_init ( void ) {
        int rc;
 
diff --git a/kernel/quickusb.h b/kernel/quickusb.h
new file mode 100644 (file)
index 0000000..7c279f8
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef QUICKUSB_H
+#define QUICKUSB_H
+
+#define QUICKUSB_BREQUEST 0xb3
+#define QUICKUSB_BREQUESTTYPE_READ 0xc0
+#define QUICKUSB_BREQUESTTYPE_WRITE 0x40
+#define QUICKUSB_MAX_DATA_LEN 64
+#define QUICKUSB_TIMEOUT ( 1 * HZ )
+
+/**
+ * QuickUsbReadPort - read data from GPPIO port
+ *
+ * @usb: USB device
+ * @address: Port number
+ * @data: Data buffer
+ * @len: Length of data to read (max QUICKUSB_MAX_DATA_LEN)
+ *
+ * Returns 0 for success, or negative error number
+ */
+static inline int quickusb_read_port ( struct usb_device *usb,
+                                      unsigned char address,
+                                      unsigned char *data,
+                                      size_t *len ) {
+       int ret;
+       
+       ret =  usb_control_msg ( usb, usb_rcvctrlpipe ( usb, 0 ),
+                                QUICKUSB_BREQUEST,
+                                QUICKUSB_BREQUESTTYPE_READ, 0, 1, data, *len,
+                                QUICKUSB_TIMEOUT );
+       if ( ret > 0 ) {
+               *len = ret;
+               ret = 0;
+       }
+
+       return ret;
+}
+
+/**
+ * QuickUsbWritePort - write data to GPPIO port
+ *
+ * @usb: USB device
+ * @address: Port number
+ * @data: Data to be written
+ * @len: Length of data to write (max QUICKUSB_MAX_DATA_LEN)
+ *
+ * Returns 0 for success, or negative error number
+ */
+static inline int quickusb_write_port ( struct usb_device *usb,
+                                       unsigned char address,
+                                       unsigned char *data,
+                                       size_t *len ) {
+       int ret;
+
+       ret =  usb_control_msg ( usb, usb_sndctrlpipe ( usb, 0 ),
+                                QUICKUSB_BREQUEST,
+                                QUICKUSB_BREQUESTTYPE_WRITE, 0, 1, data, *len,
+                                QUICKUSB_TIMEOUT );
+
+       if ( ret > 0 ) {
+               *len = ret;
+               ret = 0;
+       }
+
+       return ret;
+}
+
+#endif /* QUICKUSB_H */