Hi, since there is no way to do interrupt transfers from user space I wrote a kernel driver for the LEGO USB Tower last weekend and am now trying to debug it, currently with mixed success. Loading/unloading, probing, and open/release seem to work ok, but read and write don't. To be more precise, it sends data left in an URB over and over even though I set available_length to 0, and receiving data doesn't work either. I guess I don't understand the way to handle URBs in callbacks well enough.
The minor device number and the ioctl for configuration changes are also currently not officially allocated. I'm going off for a week tomorrow and can't work on it in the meantime (maybe I can at least read a little email), so feel free to hack away at it. The patch follows, it is against linux-2.4.10-ac12 but should patch easily into newer kernels. Happy hacking Jürgen -- Jürgen Stuber <[EMAIL PROTECTED]> http://www.loria.fr/~stuber/ diff -urN -X dontdiff linux-2.4.10-ac12-pcifix/Documentation/Configure.help linux-2.4.10-ac12-legousbtower-pcifix/Documentation/Configure.help --- linux-2.4.10-ac12-pcifix/Documentation/Configure.help Sun Oct 14 19:59:55 2001 +++ linux-2.4.10-ac12-legousbtower-pcifix/Documentation/Configure.help Tue Oct 16 +22:10:51 2001 @@ -13076,6 +13076,17 @@ The module will be called rio500.o. If you want to compile it as a module, say M here and read <file:Documentation/modules.txt>. +LEGO USB Tower support +CONFIG_USB_LEGOUSBTOWER + Say Y here if you want to connect a LEGO USB Tower to your + computer's USB port. The tower uses IR to communicate with + various devices from the LEGO Mindstorms range. + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called legousbtower.o. If you want to compile it + as a module, say M here and read <file:Documentation/modules.txt>. + D-Link DSB-R100 FM radio support CONFIG_USB_DSBR Say Y here if you want to connect this type of radio to your diff -urN -X dontdiff linux-2.4.10-ac12-pcifix/drivers/usb/Config.in linux-2.4.10-ac12-legousbtower-pcifix/drivers/usb/Config.in --- linux-2.4.10-ac12-pcifix/drivers/usb/Config.in Sun Oct 14 19:54:41 2001 +++ linux-2.4.10-ac12-legousbtower-pcifix/drivers/usb/Config.in Sun Oct 14 16:06:54 +2001 @@ -98,4 +98,5 @@ comment 'Miscellaneous USB drivers' dep_tristate ' USB Diamond Rio500 support (EXPERIMENTAL)' CONFIG_USB_RIO500 $CONFIG_USB $CONFIG_EXPERIMENTAL dep_tristate ' USB MassWorks ID-75 (EXPERIMENTAL)' CONFIG_USB_ID75 $CONFIG_USB $CONFIG_EXPERIMENTAL +dep_tristate ' LEGO USB Tower (EXPERIMENTAL)' CONFIG_USB_LEGOUSBTOWER $CONFIG_USB +$CONFIG_EXPERIMENTAL endmenu diff -urN -X dontdiff linux-2.4.10-ac12-pcifix/drivers/usb/Makefile linux-2.4.10-ac12-legousbtower-pcifix/drivers/usb/Makefile --- linux-2.4.10-ac12-pcifix/drivers/usb/Makefile Sun Oct 14 19:54:41 2001 +++ linux-2.4.10-ac12-legousbtower-pcifix/drivers/usb/Makefile Sun Oct 14 16:05:04 +2001 @@ -73,6 +73,7 @@ obj-$(CONFIG_USB_HPUSBSCSI) += hpusbscsi.o obj-$(CONFIG_USB_BLUETOOTH) += bluetooth.o obj-$(CONFIG_USB_ID75) += id75.o +obj-$(CONFIG_USB_LEGOUSBTOWER) += legousbtower.o # network drivers diff -urN -X dontdiff linux-2.4.10-ac12-pcifix/drivers/usb/legousbtower.c linux-2.4.10-ac12-legousbtower-pcifix/drivers/usb/legousbtower.c --- linux-2.4.10-ac12-pcifix/drivers/usb/legousbtower.c Thu Jan 1 01:00:00 1970 +++ linux-2.4.10-ac12-legousbtower-pcifix/drivers/usb/legousbtower.c Fri Oct 19 +20:21:34 2001 @@ -0,0 +1,1237 @@ +/* + * LEGO USB Tower driver - 0.1 + * + * Copyright (c) 2001 Juergen Stuber <[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. + * + * derived from USB Skeleton driver - 0.5 + * Copyright (c) 2001 Greg Kroah-Hartman ([EMAIL PROTECTED]) + * + * TODO: + * - move minor_table to a dynamic list. + * + * History: + * + * 2001-10-13 - 0.1 - first version + * + */ + +#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> + +#ifndef USB_DIR_MASK +#define USB_DIR_MASK 0x80 /* should be in usb.h */ +#endif + +#include "legousbtower.h" + +#ifdef CONFIG_USB_DEBUG + static int debug = 4; +#else + static int debug; +#endif + +/* Use our own dbg macro */ +#undef dbg +#define dbg(lvl, format, arg...) do { if (debug>=lvl) printk(KERN_DEBUG __FILE__ ": " +format "\n" , ## arg); } while (0) + + +/* Version Information */ +#define DRIVER_VERSION "v0.1" +#define DRIVER_AUTHOR "Juergen Stuber, [EMAIL PROTECTED]" +#define DRIVER_DESC "LEGO USB Tower Driver" + +/* Module paramaters */ +MODULE_PARM(debug, "i"); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + + +/* Define these values to match your device */ +#define LEGO_USB_TOWER_VENDOR_ID 0x0694 +#define LEGO_USB_TOWER_PRODUCT_ID 0x0001 + +/* table of devices that work with this driver */ +static struct usb_device_id tower_table [] = { + { USB_DEVICE(LEGO_USB_TOWER_VENDOR_ID, LEGO_USB_TOWER_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, tower_table); + + +/* FIXME: Get a minor range for your devices from the usb maintainer */ +#define LEGO_USB_TOWER_MINOR_BASE 0xf0 + +/* we can have up to this number of device plugged in at once */ +#define MAX_DEVICES 16 + +/* number of configurations */ +#define MAX_CONFIGURATION 4 + + +/************************************************************* + * buffering */ + +struct buffer; + +static struct buffer* buffer_alloc (int size); +static void buffer_free (struct buffer *buf); +static inline void buffer_clear ( struct buffer *buf); +static inline void buffer_remove (struct buffer *buf, int len); +static inline void buffer_takeup (struct buffer *buf, int len); + +struct dbuffer; + +static struct dbuffer* dbuffer_alloc (int size); +static void dbuffer_free (struct dbuffer *dbuf); +static void dbuffer_clear ( struct dbuffer *dbuf); +static inline int dbuffer_read (struct dbuffer *dbuf, unsigned char *ubuf, int count); +static inline int dbuffer_write (struct dbuffer *dbuf, const unsigned char *ubuf, int +count); +static inline int dbuffer_available_for_read (struct dbuffer *dbuf); +static inline unsigned char* dbuffer_address_for_read (struct dbuffer *dbuf); +static inline void dbuffer_read_done (struct dbuffer *dbuf, int len); +static inline int dbuffer_available_for_write (struct dbuffer *dbuf); +static inline unsigned char* dbuffer_address_for_write (struct dbuffer *dbuf); +static inline void dbuffer_write_done (struct dbuffer *dbuf, int len); +static inline int dbuffer_to_user (struct dbuffer *dbuf, unsigned char *ubuf, int +len); +static inline int dbuffer_from_user (struct dbuffer *dbuf, const unsigned char *ubuf, +int len); +static inline void dbuffer_try_swap (struct dbuffer *dbuf); + + + +struct buffer { + int fill; + unsigned char* contents; +}; + + +static struct buffer* buffer_alloc (int size) +{ + struct buffer* buf; + + buf = kmalloc (sizeof(struct buffer), GFP_KERNEL); + if (buf == NULL) { + goto error; + } + buf->fill = 0; + buf->contents = kmalloc (size, GFP_KERNEL); + if (buf->contents == NULL) { + goto error; + } + goto exit; + + error: + buffer_free (buf); + buf = NULL; + + exit: + return buf; +} + + +static void buffer_free ( struct buffer *buf) +{ + if (buf != NULL) { + if (buf->contents != NULL) { + kfree (buf->contents); + } + kfree (buf); + } +} + + +static inline void buffer_clear ( struct buffer *buf) +{ + buf->fill = 0; +} + + +static inline void buffer_remove (struct buffer *buf, int len) +{ + int i; + + buf->fill -= len; + + /* shift the buffer down */ + for ( i=0; i < buf->fill; i++) { + buf[i] = buf[len+i]; + } +} + + +static inline void buffer_takeup (struct buffer *buf, int len) +{ + buf->fill += len; +} + +/*******************************/ + +struct dbuffer { + int size; + struct buffer* in; + struct buffer* out; + spinlock_t lock; + wait_queue_head_t wait; +}; + + +static struct dbuffer* dbuffer_alloc (int size) +{ + struct dbuffer* dbuf; + + dbuf = kmalloc (sizeof(struct dbuffer), GFP_KERNEL); + if (dbuf == NULL) { + goto error; + } + dbuf->size = size; + dbuf->in = buffer_alloc (size); + if (dbuf->in == NULL) { + goto error; + } + dbuf->out = buffer_alloc (size); + if (dbuf->out == NULL) { + goto error; + } + spin_lock_init (&dbuf->lock); + init_waitqueue_head (&dbuf->wait); + goto exit; + + error: + dbuffer_free (dbuf); + dbuf = NULL; + + exit: + return dbuf; +} + + +static void dbuffer_free ( struct dbuffer *dbuf) +{ + if (dbuf != NULL) { + buffer_free (dbuf->in); + buffer_free (dbuf->out); + kfree (dbuf); + dbuf = NULL; + } +} + + +static void dbuffer_clear ( struct dbuffer *dbuf) +{ + if (dbuf != NULL) { + buffer_clear (dbuf->in); + buffer_clear (dbuf->out); + } +} + + +static inline int dbuffer_read (struct dbuffer *dbuf, unsigned char *ubuf, int count) +{ + int available; + int bytes_to_read; + unsigned int flags; + int retval = 0; + + spin_lock_irqsave (&dbuf->lock, flags); + + available = dbuffer_available_for_read (dbuf); + bytes_to_read = count > available ? available : count; + + if (dbuffer_to_user (dbuf, ubuf, bytes_to_read) != 0) { + retval = -EFAULT; + goto unlock_exit; + } + dbuffer_read_done (dbuf, bytes_to_read); + retval = bytes_to_read; + + unlock_exit: + spin_unlock_irqrestore (&dbuf->lock, flags); + return retval; +} + + +static inline int dbuffer_write (struct dbuffer *dbuf, const unsigned char *ubuf, int +count) +{ + int available; + int bytes_to_write; + unsigned int flags; + int retval = 0; + + spin_lock_irqsave (&dbuf->lock, flags); + + available = dbuffer_available_for_write (dbuf); + bytes_to_write = count > available ? available : count; + + if (dbuffer_from_user (dbuf, ubuf, bytes_to_write) != 0) { + retval = -EFAULT; + goto unlock_exit; + } + dbuffer_write_done (dbuf, bytes_to_write); + retval = bytes_to_write; + + unlock_exit: + spin_unlock_irqrestore (&dbuf->lock, flags); + return retval; +} + + +static inline int dbuffer_available_for_read (struct dbuffer *dbuf) +{ + return dbuf->out->fill; +} + + +static inline unsigned char* dbuffer_address_for_read (struct dbuffer *dbuf) +{ + return dbuf->out->contents; +} + + +static inline void dbuffer_read_done (struct dbuffer *dbuf, int len) +{ + buffer_remove (dbuf->out, len); +} + + +static inline int dbuffer_available_for_write (struct dbuffer *dbuf) +{ + return dbuf->size - dbuf->in->fill; +} + + +static inline unsigned char* dbuffer_address_for_write (struct dbuffer *dbuf) +{ + return dbuf->in->contents + dbuf->in->fill; +} + + +static inline void dbuffer_write_done (struct dbuffer *dbuf, int len) +{ + buffer_takeup (dbuf->in, len); +} + + +static inline int dbuffer_to_user (struct dbuffer *dbuf, unsigned char *ubuf, int len) +{ + int retval = 0; + + if (copy_to_user (ubuf, dbuffer_address_for_read (dbuf), len) != 0) { + retval = -EFAULT; + goto exit; + } + exit: + return retval; +} + + +static inline int dbuffer_from_user (struct dbuffer *dbuf, const unsigned char *ubuf, +int len) +{ + int retval = 0; + + if (copy_from_user (dbuffer_address_for_write (dbuf), ubuf, len) != 0) { + retval = -EFAULT; + goto exit; + } + exit: + return retval; +} + + +static inline void dbuffer_try_swap (struct dbuffer *dbuf) +{ + unsigned int flags; + + spin_lock_irqsave (&dbuf->lock, flags); + if (dbuf->out->fill == 0 && dbuf->in->fill != 0) { + struct buffer* tmp = dbuf->out; + dbuf->out = dbuf->in; + dbuf->in = tmp; + } + spin_unlock_irqrestore (&dbuf->lock, flags); + wake_up (&dbuf->wait); +} + +/*************************************************************/ + +/* Structure to hold all of our device specific stuff */ +struct lego_usb_tower { + /* initialized by tower_probe */ + struct usb_device* udev; /* save off the usb device +pointer */ + devfs_handle_t devfs; /* devfs device node */ + unsigned char minor; /* the starting minor number +for this device */ + int open_count; /* number of times this port +has been opened */ + struct semaphore sem; /* locks this structure */ + struct dbuffer* in_buffer; + struct usb_endpoint_descriptor* interrupt_in_endpoint; + struct urb* interrupt_in_urb; + + struct dbuffer* out_buffer; + struct usb_endpoint_descriptor* interrupt_out_endpoint; + struct urb* interrupt_out_urb; + int out_urb_pending_length; +}; + + +/* the global usb devfs handle */ +extern devfs_handle_t usb_devfs_handle; + + +/* local function prototypes */ +static ssize_t tower_read (struct file *file, char *buffer, size_t count, loff_t +*ppos); +static ssize_t tower_write (struct file *file, const char *buffer, size_t count, +loff_t *ppos); +static int tower_ioctl (struct inode *inode, struct file *file, unsigned int +cmd, unsigned long arg); +static inline void tower_delete (struct lego_usb_tower *dev); +static int tower_open (struct inode *inode, struct file *file); +static int tower_release (struct inode *inode, struct file *file); +static int tower_release_internal (struct lego_usb_tower *dev); +static void tower_abort_transfers (struct lego_usb_tower *dev); +static void tower_interrupt_in_callback (struct urb *urb); +static void tower_interrupt_out_callback (struct urb *urb); + +static void* tower_probe (struct usb_device *dev, unsigned int ifnum, const +struct usb_device_id *id); +static void tower_disconnect (struct usb_device *dev, void *ptr); + + +/* array of pointers to our devices that are currently connected */ +static struct lego_usb_tower *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 tower_fops = { + owner: THIS_MODULE, + read: tower_read, + write: tower_write, + ioctl: tower_ioctl, + open: tower_open, + release: tower_release, +}; + + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver tower_driver = { + name: "legousbtower", + probe: tower_probe, + disconnect: tower_disconnect, + fops: &tower_fops, + minor: LEGO_USB_TOWER_MINOR_BASE, + id_table: tower_table, +}; + + +/** + * lego_usb_tower_debug_data + */ +static inline void lego_usb_tower_debug_data (int level, const char *function, int +size, const unsigned char *data) +{ + int i; + + if (debug < level) + return; + + printk (KERN_DEBUG __FILE__": %s - length = %d, data = ", + function, size); + for (i = 0; i < size; ++i) { + printk ("%.2x ", data[i]); + } + printk ("\n"); +} + + +/** + * tower_delete + */ +static inline void tower_delete (struct lego_usb_tower *dev) +{ + dbg(2,__FUNCTION__ " enter"); + + minor_table[dev->minor] = NULL; + tower_abort_transfers (dev); + + /* free data structures */ + if (dev->interrupt_in_urb != NULL) { + dbg(2, __FUNCTION__ "in urb is nonnull"); + usb_free_urb (dev->interrupt_in_urb); + dev->interrupt_in_urb = NULL; + dbg(2,__FUNCTION__ " freed in urb"); + } + if (dev->interrupt_out_urb != NULL) { + dbg(2, __FUNCTION__ "out urb is nonnull"); + usb_free_urb (dev->interrupt_out_urb); + dev->interrupt_out_urb = NULL; + dbg(2,__FUNCTION__ " freed out urb"); + } + if (dev->in_buffer != NULL) { + dbg(2, __FUNCTION__ "in_buffer is nonnull"); + dbuffer_free (dev->in_buffer); + dev->in_buffer = NULL; + dbg(2,__FUNCTION__ " freed in_buffer"); + } + if (dev->out_buffer != NULL) { + dbg(2, __FUNCTION__ "out_buffer is nonnull"); + dbuffer_free (dev->out_buffer); + dev->out_buffer = NULL; + dbg(2,__FUNCTION__ " freed out_buffer"); + } + + kfree (dev); + + dbg(2,__FUNCTION__ " leave"); +} + + +/** + * tower_open + */ +static int tower_open (struct inode *inode, struct file *file) +{ + struct lego_usb_tower *dev = NULL; + int subminor; + int retval = 0; + + dbg(2,__FUNCTION__ " enter"); + + subminor = MINOR (inode->i_rdev) - LEGO_USB_TOWER_MINOR_BASE; + if ((subminor < 0) || + (subminor >= MAX_DEVICES)) { + retval = -ENODEV; + goto exit; + } + + /* increment our usage count for the module */ + MOD_INC_USE_COUNT; + + /* lock our minor table and get our local data for this minor */ + down (&minor_table_mutex); + dev = minor_table[subminor]; + if (dev == NULL) { + retval = -ENODEV; + goto unlock_minor_table_exit; + } + + /* lock this device */ + down (&dev->sem); + + /* increment our usage count for the device */ + ++dev->open_count; + + /* check that nobody else is using the device */ + /* NOTE: the cleanup code will decrement the count to 1 */ + if (dev->open_count > 1) { + retval = -EBUSY; + goto error; + } + /* save our object in the file's private structure */ + file->private_data = dev; + + /* initialize in direction */ + dbuffer_clear (dev->in_buffer); + FILL_INT_URB( + dev->interrupt_in_urb, + dev->udev, + usb_rcvintpipe(dev->udev, +dev->interrupt_in_endpoint->bEndpointAddress), + dbuffer_address_for_write (dev->in_buffer), + dev->interrupt_in_endpoint->wMaxPacketSize, + tower_interrupt_in_callback, + dev, + dev->interrupt_in_endpoint->bInterval); + retval = usb_submit_urb (dev->interrupt_in_urb); + if (retval != 0) { + err("Couldn't submit interrupt_in_urb"); + goto error; + } + + /* initialize out direction */ + dbuffer_clear (dev->out_buffer); + dev->out_urb_pending_length = 0; + FILL_INT_URB( + dev->interrupt_out_urb, + dev->udev, + usb_sndintpipe(dev->udev, +dev->interrupt_out_endpoint->bEndpointAddress), + dbuffer_address_for_read (dev->out_buffer), + dev->interrupt_out_endpoint->wMaxPacketSize, + tower_interrupt_out_callback, + dev, + dev->interrupt_out_endpoint->bInterval); + retval = usb_submit_urb (dev->interrupt_out_urb); + if (retval != 0) { + err("Couldn't submit interrupt_out_urb"); + goto error; + } + + goto unlock_exit; + + error: + tower_release_internal (dev); + + unlock_exit: + /* unlock this device */ + up (&dev->sem); + + unlock_minor_table_exit: + /* unlock the minor table */ + up (&minor_table_mutex); + if (retval != 0) { + MOD_DEC_USE_COUNT; + } + + exit: + dbg(2,__FUNCTION__ " leave, return value %d", retval); + + return retval; +} + + +/** + * tower_release + */ +static int tower_release (struct inode *inode, struct file *file) +{ + struct lego_usb_tower *dev; + int retval = 0; + + dbg(2,__FUNCTION__ " enter"); + + dev = (struct lego_usb_tower *)file->private_data; + if (dev == NULL) { + dbg(1,__FUNCTION__ " - object is NULL"); + retval = -ENODEV; + goto exit; + } + + /* lock our minor table */ + down (&minor_table_mutex); + + /* lock our device */ + down (&dev->sem); + + if (dev->open_count <= 0) { + dbg(1,__FUNCTION__ " - device not opened"); + up (&dev->sem); + up (&minor_table_mutex); + retval = -ENODEV; + goto exit; + } + + /* do the work */ + retval = tower_release_internal (dev); + + up (&dev->sem); + up (&minor_table_mutex); + + /* decrement our usage count for the module */ + MOD_DEC_USE_COUNT; + + exit: + dbg(2,__FUNCTION__ " leave, return value %d", retval); + + return retval; +} + + +/** + * tower_release_internal + * assumes minor_table and device are locked + */ +static int tower_release_internal (struct lego_usb_tower *dev) +{ + int retval = 0; + + dbg(2,__FUNCTION__ " enter"); + + if (dev->udev == NULL) { + /* the device was unplugged before the file was released */ + tower_delete (dev); + goto exit; + } + + /* decrement our usage count for the device */ + --dev->open_count; + if (dev->open_count <= 0) { + tower_abort_transfers (dev); + dev->open_count = 0; + } + + exit: + dbg(2,__FUNCTION__ " leave"); + + return retval; +} + + +/** + * tower_abort_transfers + * aborts transfers and frees associated data structures + */ +static void tower_abort_transfers (struct lego_usb_tower *dev) +{ + dbg(2,__FUNCTION__ " enter"); + + if (dev == NULL) { + dbg(1, __FUNCTION__ "dev is null"); + goto exit; + } + dbg(3, __FUNCTION__ "dev is nonnull"); + + /* shutdown transfer */ + if (dev->interrupt_in_urb != NULL) { + dbg(3, __FUNCTION__ " in urb is nonnull"); + usb_unlink_urb (dev->interrupt_in_urb); + dbg(2,__FUNCTION__ " unlinked in urb"); + } + if (dev->interrupt_out_urb != NULL) { + dbg(3, __FUNCTION__ " out urb is nonnull"); + usb_unlink_urb (dev->interrupt_out_urb); + dbg(2,__FUNCTION__ " unlinked out urb"); + } + + exit: + dbg(2,__FUNCTION__ " leave"); +} + + +/** + * tower_read + */ +static ssize_t tower_read (struct file *file, char *buffer, size_t count, loff_t +*ppos) +{ + struct lego_usb_tower *dev; + size_t bytes_read = 0; + size_t bytes_read1; + int retval = 0; + + dbg(2,__FUNCTION__ " enter, count = %d", count); + + dev = (struct lego_usb_tower *)file->private_data; + + /* lock this object */ + down (&dev->sem); + + dbg(3,__FUNCTION__ " locked device"); + + /* verify that the device wasn't unplugged */ + if (dev->udev == NULL) { + retval = -ENODEV; + goto exit; + } + + /* verify that we actually have some data to read */ + if (count == 0) { + dbg(1,__FUNCTION__ " - read request of 0 bytes"); + goto exit; + } + + while (count > 0) { + dbg(3,__FUNCTION__ " begin iteration"); + + if (dbuffer_available_for_read (dev->in_buffer) == 0) { + if (bytes_read > 0) { + retval = bytes_read; + goto exit; + } + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto exit; + } + dbg(3,__FUNCTION__ " before wait"); + retval = wait_event_interruptible (dev->in_buffer->wait, + dbuffer_available_for_read +(dev->in_buffer) > 0); + if (retval == -ERESTARTSYS) { + goto exit; + } + dbg(3,__FUNCTION__ " after wait"); + } else { + dbg(3,__FUNCTION__ " data to read"); + + /* read the data from in_buffer into userspace */ + bytes_read1 = dbuffer_read (dev->in_buffer, buffer, count); + if (bytes_read1 < 0) { + retval = -EFAULT; + goto exit; + } + buffer += bytes_read1; + count -= bytes_read1; + + bytes_read += bytes_read1; + } + } + dbg(3,__FUNCTION__ " after loop"); + + retval = bytes_read; + + exit: + /* unlock the device */ + up (&dev->sem); + + dbg(2,__FUNCTION__ " leave, return value %d", retval); + + return retval; +} + + +/** + * tower_write + */ +static ssize_t tower_write (struct file *file, const char *buffer, size_t count, +loff_t *ppos) +{ + struct lego_usb_tower *dev; + size_t bytes_written = 0; + size_t bytes_written1; + int retval = 0; + + dbg(2,__FUNCTION__ " enter, count = %d", count); + + dev = (struct lego_usb_tower *)file->private_data; + + /* lock this object */ + down (&dev->sem); + + /* verify that the device wasn't unplugged */ + if (dev->udev == NULL) { + retval = -ENODEV; + goto exit; + } + + /* verify that we actually have some data to write */ + if (count == 0) { + dbg(1,__FUNCTION__ " - write request of 0 bytes"); + goto exit; + } + + while (count > 0) { + if (dbuffer_available_for_write (dev->out_buffer) == 0) { + if (bytes_written > 0) { + retval = bytes_written; + goto exit; + } + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto exit; + } + retval = wait_event_interruptible (dev->out_buffer->wait, + +dbuffer_available_for_write (dev->out_buffer) > 0); + if (retval == -ERESTARTSYS) { + goto exit; + } + } else { + /* write the data into out_buffer from userspace */ + bytes_written1 = dbuffer_write (dev->out_buffer, buffer, +count); + if (bytes_written1 < 0) { + retval = -EFAULT; + goto exit; + } + buffer += bytes_written1; + count -= bytes_written1; + + bytes_written += bytes_written1; + } + } + retval = bytes_written; + + exit: + /* unlock the device */ + up (&dev->sem); + + dbg(2,__FUNCTION__ " leave, return value %d", retval); + + return retval; +} + + +/** + * tower_ioctl + */ +static int tower_ioctl (struct inode *inode, struct file *file, unsigned int cmd, +unsigned long arg) +{ + struct lego_usb_tower *dev; + int num_configurations; + int configuration; + __u8 configuration_value; + int retval = -ENOTTY; /* we don't understand ioctl +*/ + + dbg(2,__FUNCTION__ " enter, cmd 0x%.4x, arg %ld", cmd, arg); + + dev = (struct lego_usb_tower *)file->private_data; + + /* lock this object */ + down (&dev->sem); + + /* verify that the device wasn't unplugged */ + if (dev->udev == NULL) { + retval = -ENODEV; + goto unlock_exit; + } + + switch (cmd) { + case IOCTL_SET_CONFIGURATION: + /* allow configuration change only if device not open */ + if (dev->open_count > 0) { + retval = -EBUSY; + goto unlock_exit; + } + if (get_user( configuration, (int *) arg) != 0) { + retval = -EFAULT; + goto unlock_exit; + } + break; + num_configurations = dev->udev->descriptor.bNumConfigurations; + if (configuration < 0 || configuration > num_configurations) { + retval = -EINVAL; + goto unlock_exit; + } + configuration_value = +dev->udev->config[configuration].bConfigurationValue; + retval = usb_set_configuration (dev->udev, configuration_value); + } + + unlock_exit: + /* unlock the device */ + up (&dev->sem); + + dbg(2,__FUNCTION__ " leave, return value %d", retval); + + return retval; +} + + +/** + * tower_interrupt_in_callback + */ +static void tower_interrupt_in_callback (struct urb *urb) +{ + struct lego_usb_tower *dev = (struct lego_usb_tower *)urb->context; + int available; + + dbg(4,__FUNCTION__ " enter, status %d", urb->status); + lego_usb_tower_debug_data(4,__FUNCTION__, urb->actual_length, +urb->transfer_buffer); + + if (urb->status != 0) { + if ((urb->status != -ENOENT) && + (urb->status != -ECONNRESET)) { + dbg(1,__FUNCTION__ " - nonzero status received: %d", + urb->status); + } + goto exit; + } + dbuffer_write_done (dev->in_buffer, urb->actual_length); + dbuffer_try_swap (dev->in_buffer); /* swap only between urbs! */ + urb->actual_length = 0; /* needed? */ + + available = dbuffer_available_for_write (dev->in_buffer); + if (available >= urb->transfer_buffer_length) { + urb->transfer_buffer = dbuffer_address_for_write (dev->in_buffer); + urb->actual_length = urb->transfer_buffer_length; + } else { + dbg(1,__FUNCTION__ " - read buffer overflow"); + } + + exit: + lego_usb_tower_debug_data(4,__FUNCTION__, urb->actual_length, +urb->transfer_buffer); + dbg(4,__FUNCTION__ " leave, status %d", urb->status); +} + + +/** + * tower_interrupt_out_callback + */ +static void tower_interrupt_out_callback (struct urb *urb) +{ + struct lego_usb_tower *dev = (struct lego_usb_tower *)urb->context; + int available; + int out_length; + + dbg(4,__FUNCTION__ " enter, status %d", urb->status); + lego_usb_tower_debug_data(4,__FUNCTION__, urb->actual_length, +urb->transfer_buffer); + + if (urb->status != 0) { + if ((urb->status != -ENOENT) && + (urb->status != -ECONNRESET)) { + dbg(1,__FUNCTION__ " - nonzero status received: %d", + urb->status); + } + goto exit; + } + + /* remove data successfully sent */ + dbuffer_read_done (dev->out_buffer, dev->out_urb_pending_length); + dbuffer_try_swap (dev->out_buffer); /* swap only between urbs! */ + + /* fill urb with next chunk */ + available = dbuffer_available_for_read (dev->out_buffer); + if ( available > 0) { + out_length = available > urb->transfer_buffer_length ? + urb->transfer_buffer_length : available; + urb->transfer_buffer = dbuffer_address_for_read (dev->out_buffer); + } else { + out_length = 0; + urb->status = USB_ST_BUFFERUNDERRUN; /* FIXME? */ + } + urb->actual_length = out_length; + dev->out_urb_pending_length = out_length; + + exit: + lego_usb_tower_debug_data(4,__FUNCTION__, urb->actual_length, +urb->transfer_buffer); + dbg(4,__FUNCTION__ " leave, status %d", urb->status); +} + + +/** + * tower_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static void * tower_probe (struct usb_device *udev, unsigned int ifnum, const struct +usb_device_id *id) +{ + struct lego_usb_tower *dev = NULL; + int minor; + struct usb_interface* interface; + struct usb_interface_descriptor *iface_desc; + struct usb_endpoint_descriptor* endpoint; + int i; + char name[10]; + void *retval = NULL; + + dbg(2,__FUNCTION__ " enter"); + + if (udev == NULL) { + info ("udev is NULL."); + } + + /* See if the device offered us matches what we can accept */ + if ((udev->descriptor.idVendor != LEGO_USB_TOWER_VENDOR_ID) || + (udev->descriptor.idProduct != LEGO_USB_TOWER_PRODUCT_ID)) { + goto exit; + } + + if( ifnum != 0 ) { + info ("Strange interface number %d.", ifnum); + goto exit; + } + + /* select a "subminor" number (part of a minor number) */ + down (&minor_table_mutex); + for (minor = 0; minor < MAX_DEVICES; ++minor) { + if (minor_table[minor] == NULL) + break; + } + if (minor >= MAX_DEVICES) { + info ("Too many devices plugged in, can not handle this device."); + goto unlock_exit; + } + + /* allocate memory for our device state and intialize it */ + dev = kmalloc (sizeof(struct lego_usb_tower), GFP_KERNEL); + if (dev == NULL) { + err ("Out of memory"); + goto unlock_minor_exit; + } + init_MUTEX (&dev->sem); + down (&dev->sem); + dev->udev = udev; + dev->minor = minor; + dev->open_count = 0; + + /* look for the endpoints */ + /* It seems slightly dubious to set up endpoints here, + as we may change the configuration before calling open. + But the endpoints should be the same in all configurations. */ + interface = &dev->udev->actconfig->interface[0]; + iface_desc = &interface->altsetting[0]; + + /* set up the endpoint information */ + dev->interrupt_in_endpoint = NULL; + dev->interrupt_out_endpoint = NULL; + + for (i = 0; i < iface_desc->bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i]; + + if (((endpoint->bEndpointAddress & USB_DIR_MASK) == USB_DIR_IN) && + ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == +USB_ENDPOINT_XFER_INT)) { + /* we found an interrupt in endpoint */ + dev->interrupt_in_endpoint = endpoint; + } + + if (((endpoint->bEndpointAddress & USB_DIR_MASK) == USB_DIR_OUT) && + ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == +USB_ENDPOINT_XFER_INT)) { + /* we found an interrupt out endpoint */ + dev->interrupt_out_endpoint = endpoint; + } + } + if(dev->interrupt_in_endpoint == NULL) { + err("interrupt in endpoint not found"); + retval = NULL; + goto unlock_exit; + } + if (dev->interrupt_out_endpoint == NULL) { + err("interrupt out endpoint not found"); + retval = NULL; + goto unlock_exit; + } + + dev->interrupt_in_urb = NULL; + dev->in_buffer = NULL; + dev->interrupt_out_urb = NULL; + dev->out_buffer = NULL; + + dev->in_buffer = dbuffer_alloc ( 2 * +dev->interrupt_in_endpoint->wMaxPacketSize); + if (!dev->in_buffer) { + err("Couldn't allocate in_buffer"); + retval = NULL; + goto unlock_exit; + } + dev->interrupt_in_urb = usb_alloc_urb(0); + if (!dev->interrupt_in_urb) { + err("Couldn't allocate interrupt_in_urb"); + retval = NULL; + goto unlock_exit; + } + dev->out_buffer = dbuffer_alloc (dev->interrupt_out_endpoint->wMaxPacketSize); + if (!dev->out_buffer) { + err("Couldn't allocate out_buffer"); + retval = NULL; + goto unlock_exit; + } + dev->interrupt_out_urb = usb_alloc_urb(0); + if (!dev->interrupt_out_urb) { + err("Couldn't allocate interrupt_out_urb"); + retval = NULL; + goto unlock_exit; + } + + /* put dev in interface->private_data for disconnect */ + udev->actconfig->interface[0].private_data = dev; + + /* publish it */ + minor_table[minor] = dev; + + /* initialize the devfs node for this device and register it */ + sprintf(name, "tower%d", dev->minor); + + dev->devfs = devfs_register (usb_devfs_handle, name, + DEVFS_FL_DEFAULT, USB_MAJOR, + LEGO_USB_TOWER_MINOR_BASE + dev->minor, + S_IFCHR | S_IRUSR | S_IWUSR | + S_IRGRP | S_IWGRP | S_IROTH, + &tower_fops, NULL); + + /* let the user know what node this device is now attached to */ + info ("LEGO USB Tower device now attached to tower%d", dev->minor); + + retval = dev; + + unlock_exit: + /* unlock device */ + up (&dev->sem); + + unlock_minor_exit: + up (&minor_table_mutex); + + exit: + dbg(2,__FUNCTION__ " leave, return value 0x%.8lx (dev)", (long)dev); + + return retval; +} + + +/** + * tower_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void tower_disconnect (struct usb_device *udev, void *ptr) +{ + struct lego_usb_tower *dev; + int minor; + + dbg(2,__FUNCTION__ " enter"); + + dev = (struct lego_usb_tower *)ptr; + + down (&minor_table_mutex); + down (&dev->sem); + + minor = dev->minor; + + /* remove our devfs node */ + devfs_unregister(dev->devfs); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + tower_delete (dev); + } else { + dev->udev = NULL; + up (&dev->sem); + } + + info("LEGO USB Tower #%d now disconnected", minor); + up (&minor_table_mutex); + + dbg(2,__FUNCTION__ " leave"); +} + + + +/** + * lego_usb_tower_init + */ +static int __init lego_usb_tower_init(void) +{ + int result; + int retval = 0; + + dbg(2,__FUNCTION__ " enter"); + + /* register this driver with the USB subsystem */ + result = usb_register(&tower_driver); + if (result < 0) { + err("usb_register failed for the "__FILE__" driver. Error number %d", +result); + retval = -1; + goto exit; + } + + info(DRIVER_DESC " " DRIVER_VERSION); + + exit: + dbg(2,__FUNCTION__ " leave, return value %d", retval); + + return retval; +} + + +/** + * lego_usb_tower_exit + */ +static void __exit lego_usb_tower_exit(void) +{ + dbg(2,__FUNCTION__ " enter"); + + /* deregister this driver with the USB subsystem */ + usb_deregister(&tower_driver); + + dbg(2,__FUNCTION__ " leave"); +} + +module_init (lego_usb_tower_init); +module_exit (lego_usb_tower_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff -urN -X dontdiff linux-2.4.10-ac12-pcifix/drivers/usb/legousbtower.h linux-2.4.10-ac12-legousbtower-pcifix/drivers/usb/legousbtower.h --- linux-2.4.10-ac12-pcifix/drivers/usb/legousbtower.h Thu Jan 1 01:00:00 1970 +++ linux-2.4.10-ac12-legousbtower-pcifix/drivers/usb/legousbtower.h Sun Oct 14 +15:25:34 2001 @@ -0,0 +1,7 @@ +/* + * LEGO USB Tower driver - 0.1 + */ + +/* set configuration */ +/* FIXME: obtain unique IOCTL number */ +#define IOCTL_SET_CONFIGURATION _IOW('U', 200, int) _______________________________________________ [EMAIL PROTECTED] To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel