📄 usbdfu.c
字号:
/* -*- linux-c -*- *//* * USB Device Firmware Upgrade (DFU) handler * * Copyright (c) 2003 Oliver Kurth <oku@masqmail.cx> * * 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. * * 2003_01_19 0.1: * - initial release * * TODO: * (someday) * - make a way for drivers to feed firmware data at download time (instead of * providing it all at once during register) * - procfs support for userland firmware downloaders * - Firmware upload (device-to-host) support */#include <linux/config.h>#include <linux/slab.h>#include <linux/module.h>#include <linux/usb.h>#include <linux/tqueue.h>#include <linux/init.h>#include "usbdfu.h"#ifdef CONFIG_USB_DEBUGstatic int debug = 1;#elsestatic int debug;#endif/* Use our own dbg macro */#undef dbg#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) #define dfu_up(sem) do { dbg("sem %s up", #sem); up(sem); } while (0)#else #define dfu_down(sem) down(sem) #define dfu_up(sem) up(sem)#endif/* Version Information */#define DRIVER_AUTHOR \"Oliver Kurth <oku@masqmail.cx>, Joerg Albert <joerg.albert@gmx.de>, Alex <alex@foogod.com>"#define DRIVER_DESC "USB Device Firmware Upgrade (DFU) handler"/* 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/* DFU states */#define STATE_IDLE 0x00#define STATE_DETACH 0x01#define STATE_DFU_IDLE 0x02#define STATE_DFU_DOWNLOAD_SYNC 0x03#define STATE_DFU_DOWNLOAD_BUSY 0x04#define STATE_DFU_DOWNLOAD_IDLE 0x05#define STATE_DFU_MANIFEST_SYNC 0x06#define STATE_DFU_MANIFEST 0x07#define STATE_DFU_MANIFEST_WAIT_RESET 0x08#define STATE_DFU_UPLOAD_IDLE 0x09#define STATE_DFU_ERROR 0x0a/* DFU commands */#define DFU_DETACH 0#define DFU_DNLOAD 1#define DFU_UPLOAD 2#define DFU_GETSTATUS 3#define DFU_CLRSTATUS 4#define DFU_GETSTATE 5#define DFU_ABORT 6struct dfu_status { unsigned char bStatus; unsigned char bwPollTimeout[3]; unsigned char bState; unsigned char iString;} __attribute__ ((packed));struct usbdfu_infolist { struct list_head list; struct usbdfu_info *info;};/* driver independent download context */struct dfu_ctx { struct usb_device *udev; u8 dfu_state; struct dfu_status dfu_status; 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 DFU_PACKETSIZE 1024#define INTERFACE_VENDOR_REQUEST_OUT 0x41#define INTERFACE_VENDOR_REQUEST_IN 0xc1staticint dfu_detach(struct usb_device *udev){ int result; dbg("dfu_detach"); result = usb_control_msg(udev, usb_sndctrlpipe(udev,0), DFU_DETACH, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 1000, /* Value */ 0, /* Index */ NULL, /* Buffer */ 0, /* Size */ HZ); return result;}staticint dfu_download_block(struct dfu_ctx *ctx, u8 *buffer, int bytes, int block){ int result; u8 *tmpbuf = ctx->buf; struct usb_device *udev = ctx->udev; dbg("dfu_download_block(): buffer=%p, bytes=%d, block=%d", buffer, bytes, block); if(tmpbuf == NULL) return -ENOMEM; memcpy(tmpbuf, buffer, bytes); result = usb_control_msg(udev, usb_sndctrlpipe(udev,0), DFU_DNLOAD, USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE, block, /* Value */ 0, /* Index */ tmpbuf, /* Buffer */ bytes, /* Size */ HZ); return result;}staticint dfu_get_status(struct dfu_ctx *ctx, struct dfu_status *status){ int result; struct usb_device *udev = ctx->udev;// dbg("dfu_get_status()"); result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), DFU_GETSTATUS, USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE, 0, /* Value */ 0, /* Index */ status, /* Buffer */ sizeof(struct dfu_status), /* Size */ HZ); return result;}staticu8 dfu_get_state(struct usb_device *udev, u8 *state){ int result;// dbg("dfu_get_state()"); result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), DFU_GETSTATE, /* Request */ USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE, 0, /* Value */ 0, /* Index */ state, /* Buffer */ 1, /* Size */ HZ); return result;}static inlineu32 __get_timeout(struct dfu_status *s){ unsigned long ret = 0; ret = (unsigned long) (s->bwPollTimeout[2] << 16); ret |= (unsigned long) (s->bwPollTimeout[1] << 8); ret |= (unsigned long) (s->bwPollTimeout[0]); return ret;}staticstruct dfu_ctx *dfu_alloc_ctx(struct usb_device *udev){ struct dfu_ctx *ctx; ctx = kmalloc(sizeof(struct dfu_ctx) + DFU_PACKETSIZE, GFP_KERNEL|GFP_DMA); if(ctx){ ctx->udev = udev; ctx->buf = (u8 *)&(ctx[1]); } return ctx;}int do_dfu_download(struct usb_device *udev, unsigned char *dfu_buffer, unsigned int dfu_len){ struct dfu_ctx *ctx; struct dfu_status *dfu_stat_buf; int status = 0; int need_dfu_state = 1; int is_done = 0; u8 dfu_state = 0; u32 dfu_timeout = 0; int dfu_block_bytes = 0, dfu_bytes_left = dfu_len, dfu_buffer_offset = 0; int dfu_block_cnt = 0; if (dfu_len == 0) { err("FW Buffer length invalid!"); return -EINVAL; } ctx = dfu_alloc_ctx(udev); if(ctx == NULL) return -ENOMEM; dfu_stat_buf = &ctx->dfu_status; do { if (need_dfu_state) { status = dfu_get_state(ctx->udev, &ctx->dfu_state); if (!USB_SUCCESS(status)) { err("DFU: Failed to get DFU state: %d", status); goto exit; } dfu_state = ctx->dfu_state; need_dfu_state = 0; } switch (dfu_state) { case STATE_DFU_DOWNLOAD_SYNC: dbg("STATE_DFU_DOWNLOAD_SYNC"); if (USB_SUCCESS (status = dfu_get_status(ctx, dfu_stat_buf))) { dfu_state = dfu_stat_buf->bState; dfu_timeout = __get_timeout(dfu_stat_buf); need_dfu_state = 0; }else err("dfu_get_status failed with %d", status); break; case STATE_DFU_DOWNLOAD_BUSY: dbg("STATE_DFU_DOWNLOAD_BUSY"); need_dfu_state = 1; if (dfu_timeout >= 0){ dbg("DFU: Resetting device"); set_current_state( TASK_INTERRUPTIBLE ); schedule_timeout(1+dfu_timeout*HZ/1000); }else dbg("DFU: In progress"); break; case STATE_DFU_DOWNLOAD_IDLE: dbg("DOWNLOAD..."); /* fall through */ case STATE_DFU_IDLE: dbg("DFU IDLE"); if (dfu_bytes_left <= DFU_PACKETSIZE) dfu_block_bytes = dfu_bytes_left; else dfu_block_bytes = DFU_PACKETSIZE; dfu_bytes_left -= dfu_block_bytes; status = dfu_download_block(ctx, dfu_buffer + dfu_buffer_offset, dfu_block_bytes, dfu_block_cnt); dfu_buffer_offset += dfu_block_bytes; dfu_block_cnt++; if (!USB_SUCCESS(status)) err("dfu_download_block failed with %d", status); need_dfu_state = 1; break; case STATE_DFU_MANIFEST_SYNC: dbg("STATE_DFU_MANIFEST_SYNC"); 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; if (dfu_timeout >= 0){ dbg("DFU: Waiting for manifest phase"); set_current_state( TASK_INTERRUPTIBLE ); schedule_timeout((dfu_timeout*HZ+999)/1000); }else dbg("DFU: In progress"); } break; case STATE_DFU_MANIFEST: dbg("STATE_DFU_MANIFEST"); is_done = 1; break; case STATE_DFU_MANIFEST_WAIT_RESET: dbg("STATE_DFU_MANIFEST_WAIT_RESET");// usb_reset_device(udev); is_done = 1; break; case STATE_DFU_UPLOAD_IDLE: dbg("STATE_DFU_UPLOAD_IDLE"); break; case STATE_DFU_ERROR: dbg("STATE_DFU_ERROR");// usb_reset_device(udev); status = -EPIPE; break; default: dbg("DFU UNKNOWN STATE (%d)", dfu_state); status = -EINVAL; break; } } while (!is_done && USB_SUCCESS(status)); exit: kfree(ctx); if (status < 0) return status; else return 0;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -