aboutsummaryrefslogtreecommitdiff
path: root/usbdfu.c
diff options
context:
space:
mode:
Diffstat (limited to 'usbdfu.c')
-rw-r--r--usbdfu.c500
1 files changed, 16 insertions, 484 deletions
diff --git a/usbdfu.c b/usbdfu.c
index 12bbe4b..5a2754c 100644
--- a/usbdfu.c
+++ b/usbdfu.c
@@ -24,7 +24,7 @@
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>
-#include <linux/tqueue.h>
+#include <linux/version.h>
#include <linux/init.h>
#include "usbdfu.h"
@@ -36,7 +36,10 @@ static int debug;
/* Use our own dbg macro */
#undef dbg
-#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg); } while (0)
+#define dbg(format, arg...) \
+ do { if (debug) \
+ printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg);\
+ } while (0)
#ifdef DEBUG_SEM
#define dfu_down(sem) do { dbg("sem %s down", #sem); down(sem); } while (0)
@@ -53,12 +56,7 @@ static int debug;
/* Module paramaters */
MODULE_PARM(debug, "i");
-MODULE_PARM_DESC(debug, "Debug enabled or not");
-
-/* USB class/subclass for DFU devices/interfaces */
-
-#define DFU_USB_CLASS 0xfe
-#define DFU_USB_SUBCLASS 0x01
+MODULE_PARM_DESC(debug, "debug enabled (=1)");
/* DFU states */
@@ -90,10 +88,6 @@ struct dfu_status {
unsigned char iString;
} __attribute__ ((packed));
-struct usbdfu_infolist {
- struct list_head list;
- struct usbdfu_info *info;
-};
/* driver independent download context */
struct dfu_ctx {
@@ -103,77 +97,15 @@ struct dfu_ctx {
u8 *buf;
};
-#define KEVENT_FLAG_SCHEDRESET 1
-#define KEVENT_FLAG_RESET 2
-
-/* Structure to hold all of our device specific stuff */
-struct usbdfu {
- struct usb_device * udev; /* save off the usb device pointer */
-
- struct timer_list timer;
-
- struct tq_struct kevent;
- u32 kevent_flags;
-
- struct semaphore sem; /* locks this structure */
-
- struct usbdfu_info *info;
- u8 op_mode;
-};
-
-LIST_HEAD(usbdfu_infolist_head);
-struct semaphore usbdfu_lock;
-
-/* local function prototypes */
-static void * usbdfu_probe(struct usb_device *dev,
- unsigned int ifnum, const struct usb_device_id *id);
-static void usbdfu_disconnect(struct usb_device *dev, void *ptr);
-
-static struct usb_device_id dev_table[] = {
- { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
- .bInterfaceClass = DFU_USB_CLASS, .bInterfaceSubClass = DFU_USB_SUBCLASS},
- { }
-};
-
-MODULE_DEVICE_TABLE (usb, dev_table);
-
-/* usb specific object needed to register this driver with the usb subsystem */
-static struct usb_driver usbdfu_driver = {
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)
- owner: THIS_MODULE,
-#endif
- name: "usbdfu",
- probe: usbdfu_probe,
- disconnect: usbdfu_disconnect,
- id_table: dev_table,
-};
-
-/**
- * usbdfu_debug_data
- */
-static inline void usbdfu_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");
-}
-
-
-#define USB_SUCCESS(a) (a >= 0)
+#define USB_SUCCESS(a) ((a) >= 0)
#define DFU_PACKETSIZE 1024
#define INTERFACE_VENDOR_REQUEST_OUT 0x41
#define INTERFACE_VENDOR_REQUEST_IN 0xc1
+/* needed ??? */
+static int dfu_detach(struct usb_device *udev) __attribute__ ((unused));
static
int dfu_detach(struct usb_device *udev)
{
@@ -283,8 +215,7 @@ struct dfu_ctx *dfu_alloc_ctx(struct usb_device *udev)
return ctx;
}
-int do_dfu_download(struct usb_device *udev, unsigned char *dfu_buffer,
- unsigned int dfu_len)
+int usbdfu_download(struct usb_device *udev, u8 *dfu_buffer, u32 dfu_len)
{
struct dfu_ctx *ctx;
struct dfu_status *dfu_stat_buf;
@@ -321,12 +252,12 @@ int do_dfu_download(struct usb_device *udev, unsigned char *dfu_buffer,
switch (dfu_state) {
case STATE_DFU_DOWNLOAD_SYNC:
dbg("STATE_DFU_DOWNLOAD_SYNC");
- if (USB_SUCCESS
- (status = dfu_get_status(ctx, dfu_stat_buf))) {
+ status = dfu_get_status(ctx, dfu_stat_buf);
+ if (USB_SUCCESS(status)) {
dfu_state = dfu_stat_buf->bState;
dfu_timeout = __get_timeout(dfu_stat_buf);
need_dfu_state = 0;
- }else
+ } else
err("dfu_get_status failed with %d", status);
break;
@@ -424,404 +355,9 @@ int do_dfu_download(struct usb_device *udev, unsigned char *dfu_buffer,
return 0;
}
-static
-int usbdfu_download(struct usbdfu *dev, u8 *fw_buf, u32 fw_len)
-{
- int ret = 0;
-
- if (dev->info->pre_download_hook) {
- ret = dev->info->pre_download_hook(dev->udev);
- }
-
- if (ret)
- return ret;
-
- info("Downloading firmware for USB device %d...", dev->udev->devnum);
-
- ret = do_dfu_download(dev->udev, fw_buf, fw_len);
-
- if (ret)
- return ret;
-
- if (dev->info->post_download_hook) {
- ret = dev->info->post_download_hook(dev->udev);
- }
-
- return ret;
-}
-
-static inline void usbdfu_delete (struct usbdfu *dev)
-{
- kfree (dev);
-}
-
-/* shamelessly copied from usbnet.c (oku) */
-static void defer_kevent (struct usbdfu *dev, int flag)
-{
- set_bit (flag, &dev->kevent_flags);
- if (!schedule_task (&dev->kevent))
- err ("kevent %d may have been dropped", flag);
- else
- dbg ("kevent %d scheduled", flag);
-}
-
-static void kevent_timer(unsigned long data)
-{
- struct usbdfu *dev = (struct usbdfu *)data;
-
- defer_kevent(dev, KEVENT_FLAG_RESET);
-
-/* jal: this hangs SMP systems. no need to stop the timer, as
- it is non-periodic */
-// del_timer_sync(&dev->timer);
-}
-
-/* TODO: how do we make sure the device hasn't been
- plugged out in the meantime? */
-/* We don't really need to (trying to reset a disconnected device shouldn't
- * cause a problem), we just need to make sure that disconnect hasn't freed the
- * dev structure already. We do this by not freeing dev as long as
- * "kevent_flags" has something set (indicating a kevent is pending) --alex */
-
-static void
-kevent(void *data)
-{
- struct usbdfu *dev = data;
- struct usb_device *udev;
- struct usbdfu_info *info;
- struct usb_interface *interface;
-
- dbg("kevent entered");
-
- /* some paranoid checks: */
- if(!dev){
- err("kevent: no dev!");
- return;
- }
-
- dfu_down(&dev->sem);
-
- info = dev->info;
- if(!info){
- err("kevent: no dev->info!");
- goto exit;
- }
- udev = dev->udev;
- if(!udev){
- err("kevent: no device!");
- goto exit;
- }
-
- if (test_bit(KEVENT_FLAG_SCHEDRESET, &dev->kevent_flags)) {
- clear_bit(KEVENT_FLAG_SCHEDRESET, &dev->kevent_flags);
- defer_kevent (dev, KEVENT_FLAG_RESET);
- } else if (test_bit(KEVENT_FLAG_RESET, &dev->kevent_flags)) {
- clear_bit(KEVENT_FLAG_RESET, &dev->kevent_flags);
-
- /* releasing interface, so it can be claimed by our
- fellow driver */
- interface = &udev->actconfig->interface[0];
- usb_driver_release_interface(&usbdfu_driver, interface);
-
- /* Once we release the interface, the USB system won't call
- * usbdfu_disconnect for us, so we need to do that ourselves.
- * Note: we cannot use dev after this point. */
- dfu_up(&dev->sem);
- usbdfu_disconnect(udev, dev);
-
- dbg("resetting device");
- usb_reset_device(udev);
-
- dbg("scanning unclaimed devices");
- usb_scan_devices();
-
- return;
- }
-
- exit:
- dfu_up(&dev->sem);
- return;
-}
-
-int usbdfu_register(struct usbdfu_info *info)
-{
- struct usbdfu_infolist *infolist = kmalloc(sizeof(struct usbdfu_infolist), GFP_KERNEL);
-
- if(!infolist)
- return -ENOMEM;
-
- infolist->info = info;
-
- dfu_down(&usbdfu_lock);
- list_add_tail(&infolist->list, &usbdfu_infolist_head);
- dfu_up(&usbdfu_lock); /* before scan, because that calls probe() */
-
- dbg("registered new driver %s", info->name);
-
- /* if the device is not yet plugged in, we are settled. If it already
- is (and it's already in DFU state), we have to scan for unclaimed
- devices. This will call our probe function again. */
- usb_scan_devices();
-
- return 0;
-}
-
-void usbdfu_deregister(struct usbdfu_info *info)
-{
- struct list_head *tmp;
- struct usbdfu_infolist *infolist = NULL;
-
- dbg("deregistering driver %s", info->name);
- dfu_down(&usbdfu_lock);
-
- for(tmp = usbdfu_infolist_head.next;
- tmp != &usbdfu_infolist_head;
- tmp = tmp->next) {
-
- infolist = list_entry(tmp, struct usbdfu_infolist,
- list);
-
- if(infolist->info == info)
- break;
- }
- if(tmp != &usbdfu_infolist_head){
- list_del(tmp);
- kfree(infolist);
- } else {
- err("unregistering %s: driver was not previously registered!",
- info->name);
- }
- dfu_up(&usbdfu_lock);
-}
-
-int usbdfu_in_use(struct usb_device *udev, unsigned int ifnum)
-{
- int result;
- u8 state;
- struct usb_interface *interface;
- struct usb_interface_descriptor *idesc;
-
- if (ifnum != 0) {
- /* DFU-mode devices only have one interface */
- return 0;
- }
-
- /* Check to see whether the interface's class is a DFU device.
- * We need to check this first to make sure the DFU_GETSTATE command
- * isn't misinterpreted as something else. */
- interface = &udev->actconfig->interface[ifnum];
- idesc = &interface->altsetting[interface->act_altsetting];
- if ((idesc->bInterfaceClass != DFU_USB_CLASS) ||
- (idesc->bInterfaceSubClass != DFU_USB_SUBCLASS)) {
- dbg("interface class is not DFU");
- return 0;
- }
-
- result = dfu_get_state(udev, &state);
- if (result < 0) {
- return result;
- } else if (result != 1) {
- /* This should be an error. The device reported this interface
- * as a DFU-class interface, but it's not responding correctly
- * to DFU-class commands on this interface. However, there
- * appear to be some broken devices out there where this is
- * normal behavior in some cases (at76c503 immediately after
- * fw-load-reset), so just continue on (and hope we didn't
- * screw anything up with that DFU command).. */
- dbg("DFU state query returned %d-byte response",
- result);
- return 0;
- }
-
- switch (state) {
- case STATE_IDLE:
- case STATE_DETACH:
- /* Device is in an application mode, it's up to other drivers
- * to deal with it */
- dbg("DFU state=App (%d)", state);
- return 0;
- case STATE_DFU_IDLE:
- case STATE_DFU_DOWNLOAD_SYNC:
- case STATE_DFU_DOWNLOAD_BUSY:
- case STATE_DFU_DOWNLOAD_IDLE:
- case STATE_DFU_MANIFEST_SYNC:
- case STATE_DFU_MANIFEST:
- case STATE_DFU_MANIFEST_WAIT_RESET:
- case STATE_DFU_UPLOAD_IDLE:
- case STATE_DFU_ERROR:
- /* This is what we're looking for. We're in the middle
- * of dealing with this device */
- dbg("DFU state=DFU (%d)", state);
- return 1;
- default:
- /* We got something that shouldn't be a valid response to a DFU
- * state query. Again, this sometimes happens on broken
- * devices (at76c503 immediately after fw-load-reset) which
- * report DFU class but aren't really DFU-capable. */
- dbg("DFU state query returned bizarre response (%d)", state);
- return 0;
- }
-}
-
-static struct usbdfu_info *find_info(struct usb_device *udev)
-{
- struct usb_interface *interface;
- struct list_head *tmp;
- struct usbdfu_infolist *infolist;
-
- dbg("searching for driver");
-
- interface = &udev->actconfig->interface[0];
-
- for(tmp = usbdfu_infolist_head.next;
- tmp != &usbdfu_infolist_head;
- tmp = tmp->next) {
- infolist = list_entry(tmp, struct usbdfu_infolist,
- list);
- if(usb_match_id(udev, interface, infolist->info->id_table))
- return infolist->info;
- }
-
- return NULL;
-}
-
-int usbdfu_initiate_download(struct usb_device *udev)
-{
- int result;
-
- if (!find_info(udev)) {
- return -ENOENT;
- }
-
- result = dfu_detach(udev);
- if (!result) {
- dbg("dfu_detach failed (%d)", result);
- return result;
- }
-
- usb_reset_device(udev);
- usb_scan_devices();
-
- return 0;
-}
-
-static void * usbdfu_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id)
-{
- struct usbdfu *dev = NULL;
- struct usbdfu_info *info = NULL;
- int ret;
-
- dbg("usbdfu_probe entered");
-
- if (ifnum != 0) {
- dbg("more than one interface, cannot be DFU mode");
- return NULL;
- }
-
- dfu_down(&usbdfu_lock);
-
- info = find_info(udev);
- if (!info)
- goto exit; /* not for us */
-
- dbg("device is registered (%s)", info->name);
-
- if (usbdfu_in_use(udev, ifnum) != 1) {
- dbg("device not in DFU-idle mode");
- goto exit;
- }
- dbg("device is in DFU mode");
-
- /* allocate memory for our device state and intialize it */
- dev = kmalloc (sizeof(struct usbdfu), GFP_KERNEL);
- if (dev == NULL) {
- err ("out of memory");
- goto exit;
- }
- memset (dev, 0, sizeof (*dev));
-
- init_MUTEX (&dev->sem);
-
- dfu_down(&dev->sem);
-
- INIT_TQUEUE (&dev->kevent, kevent, dev);
- dev->udev = udev;
- dev->info = info;
-
- dbg("going for download");
- /* here our main action takes place: */
- ret = usbdfu_download(dev, info->fw_buf, info->fw_buf_len);
- if(ret < 0){
- err("Firmware download failed for USB device %d", udev->devnum);
- goto error;
- }
-
- init_timer(&dev->timer);
-
- if(info->reset_delay){
- dev->timer.data = (long) dev;
- dev->timer.function = kevent_timer;
-
- mod_timer(&dev->timer, jiffies + info->reset_delay);
- }else{
- defer_kevent (dev, KEVENT_FLAG_SCHEDRESET);
- }
-
- dfu_up(&dev->sem);
- goto exit;
-
-error:
- dfu_up(&dev->sem);
- usbdfu_delete (dev);
- dev = NULL;
-
-exit:
- dfu_up(&usbdfu_lock);
-
- dbg("usbdfu_probe() exiting");
- return dev;
-}
-
-
-static void usbdfu_disconnect(struct usb_device *udev, void *ptr)
-{
- struct usbdfu *dev = (struct usbdfu *)ptr;
- int kevent_pending;
-
- dbg("usbdfu_disconnect called");
-
- while (1) {
- dfu_down(&dev->sem);
- kevent_pending = dev->kevent_flags;
- dfu_up(&dev->sem);
- if (!kevent_pending) break;
- dbg("usbdfu_disconnect: waiting for kevent to complete (%d pending)...", kevent_pending);
- schedule();
- }
-
- del_timer_sync(&dev->timer);
-
- usbdfu_delete(dev);
-
- dbg("USB DFU now disconnected");
-}
-
static int __init usbdfu_init(void)
{
- int result;
-
- info(DRIVER_DESC " " DRIVER_VERSION);
-
- init_MUTEX(&usbdfu_lock);
-
- /* register this driver with the USB subsystem */
- result = usb_register(&usbdfu_driver);
- if (result < 0) {
- err("usb_register failed for the "__FILE__" driver. Error number %d",
- result);
- return -1;
- }
-
+ info(DRIVER_DESC " " DRIVER_VERSION " loading");
return 0;
}
@@ -830,17 +366,13 @@ static int __init usbdfu_init(void)
*/
static void __exit usbdfu_exit(void)
{
- /* deregister this driver with the USB subsystem */
- usb_deregister(&usbdfu_driver);
+ info(DRIVER_DESC " " DRIVER_VERSION " unloading");
}
module_init (usbdfu_init);
module_exit (usbdfu_exit);
-EXPORT_SYMBOL(usbdfu_register);
-EXPORT_SYMBOL(usbdfu_deregister);
-EXPORT_SYMBOL(usbdfu_in_use);
-EXPORT_SYMBOL(usbdfu_initiate_download);
+EXPORT_SYMBOL(usbdfu_download);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);