Index: u-boot/drivers/usbdcore_ep0.c =================================================================== --- u-boot.orig/drivers/usbdcore_ep0.c +++ u-boot/drivers/usbdcore_ep0.c @@ -42,10 +42,15 @@ */ #include +DECLARE_GLOBAL_DATA_PTR; #if defined(CONFIG_USB_DEVICE) #include "usbdcore.h" +#ifdef CONFIG_USBD_DFU +#include +#endif + #if 0 #define dbg_ep0(lvl,fmt,args...) serial_printf("[%s] %s:%d: "fmt"\n",__FILE__,__FUNCTION__,__LINE__,##args) #else @@ -213,7 +218,7 @@ urb->buffer = device_descriptor; urb->actual_length = MIN(sizeof(*device_descriptor), max); } - /*dbg_ep0(3, "copied device configuration, actual_length: %x", urb->actual_length); */ + dbg_ep0(3, "using device configuration, actual_length: %x", urb->actual_length); break; case USB_DESCRIPTOR_TYPE_CONFIGURATION: @@ -267,7 +272,24 @@ return -1; case USB_DESCRIPTOR_TYPE_ENDPOINT: return -1; + /* This really means "Class Specific Descriptor #1 == USB_DT_DFU */ case USB_DESCRIPTOR_TYPE_HID: +#ifdef CONFIG_USBD_DFU + { + int bNumInterface = + le16_to_cpu(urb->device_request.wIndex); + + /* In runtime mode, we only respond to the DFU INTERFACE, + * whereas in DFU mode, we respond for all intrfaces */ + if (device->dfu_state != DFU_STATE_appIDLE && + device->dfu_state != DFU_STATE_appDETACH || + bNumInterface == CONFIG_USBD_DFU_INTERFACE) { + urb->buffer = &device->dfu_cfg_desc->func_dfu; + urb->actual_length = sizeof(struct usb_dfu_func_descriptor); + } else + return -1; + } +#else /* CONFIG_USBD_DFU */ { return -1; /* unsupported at this time */ #if 0 @@ -294,6 +316,7 @@ max); #endif } +#endif /* CONFIG_USBD_DFU */ break; case USB_DESCRIPTOR_TYPE_REPORT: { @@ -388,6 +411,24 @@ le16_to_cpu (request->wLength), USBD_DEVICE_REQUESTS (request->bRequest)); +#ifdef CONFIG_USBD_DFU + if ((request->bmRequestType & 0x3f) == USB_TYPE_DFU && + (device->dfu_state != DFU_STATE_appIDLE || + le16_to_cpu(request->wIndex) == CONFIG_USBD_DFU_INTERFACE)) { + int rc = dfu_ep0_handler(urb); + switch (rc) { + case DFU_EP0_NONE: + case DFU_EP0_UNHANDLED: + break; + case DFU_EP0_ZLP: + case DFU_EP0_DATA: + return 0; + case DFU_EP0_STALL: + return -1; + } + } +#endif /* CONFIG_USB_DFU */ + /* handle USB Standard Request (c.f. USB Spec table 9-2) */ if ((request->bmRequestType & USB_REQ_TYPE_MASK) != 0) { if (device->device_state <= STATE_CONFIGURED) @@ -570,7 +611,8 @@ device->interface = le16_to_cpu (request->wIndex); device->alternate = le16_to_cpu (request->wValue); /*dbg_ep0(2, "set interface: %d alternate: %d", device->interface, device->alternate); */ - serial_printf ("DEVICE_SET_INTERFACE.. event?\n"); + usbd_device_event_irq(device, DEVICE_SET_INTERFACE, + (request->wIndex << 16 | request->wValue)); return 0; case USB_REQ_GET_STATUS: Index: u-boot/drivers/usbdfu.c =================================================================== --- /dev/null +++ u-boot/drivers/usbdfu.c @@ -0,0 +1,1069 @@ +/* + * (C) 2007 by Openmoko, Inc. + * Author: Harald Welte + * + * based on existing SAM7DFU code from OpenPCD: + * (C) Copyright 2006 by Harald Welte + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * TODO: + * - make NAND support reasonably self-contained and put in apropriate + * ifdefs + * - add some means of synchronization, i.e. block commandline access + * while DFU transfer is in progress, and return to commandline once + * we're finished + * - add VERIFY support after writing to flash + * - sanely free() resources allocated during first uppload/download + * request when aborting + * - sanely free resources when another alternate interface is selected + * + * Maybe: + * - add something like uImage or some other header that provides CRC + * checking? + * - make 'dnstate' attached to 'struct usb_device_instance' + */ + +#include +#if defined(CONFIG_USBD_DFU) + +#include +DECLARE_GLOBAL_DATA_PTR; + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +int mtdparts_init(void); +extern struct list_head devices; + +#include "usbdcore_s3c2410.h" +#include "usbtty.h" /* for STR_* defs */ + +#define RET_NOTHING 0 +#define RET_ZLP 1 +#define RET_STALL 2 + +volatile enum dfu_state *system_dfu_state; /* for 3rd parties */ + + +struct dnload_state { + nand_info_t *nand; + struct part_info *part; + unsigned int part_net_size; /* net sizee (excl. bad blocks) of part */ + + nand_erase_options_t erase_opts; + nand_write_options_t write_opts; + nand_read_options_t read_opts; + + unsigned char *ptr; /* pointer to next empty byte in buffer */ + unsigned int off; /* offset of current erase page in flash chip */ + unsigned char *buf; /* pointer to allocated erase page buffer */ + + /* unless doing an atomic transfer, we use the static buffer below. + * This saves us from having to clean up dynamic allications in the + * various error paths of the code. Also, it will always work, no + * matter what the memory situation is. */ + unsigned char _buf[0x20000]; /* FIXME: depends flash page size */ +}; + +static struct dnload_state _dnstate; + +static int dfu_trailer_matching(const struct uboot_dfu_trailer *trailer) +{ + if (trailer->magic != UBOOT_DFU_TRAILER_MAGIC || + trailer->version != UBOOT_DFU_TRAILER_V1 || + trailer->vendor != CONFIG_USBD_VENDORID || + (trailer->product != CONFIG_USBD_PRODUCTID_CDCACM && + trailer->product != CONFIG_USBD_PRODUCTID_GSERIAL)) + return 0; +#ifdef CONFIG_REVISION_TAG + if (trailer->revision != get_board_rev()) + return 0; +#endif + + return 1; +} + +static struct part_info *get_partition_nand(int idx) +{ + struct mtd_device *dev; + struct part_info *part; + struct list_head *pentry; + int i; + + if (mtdparts_init()) + return NULL; + if (list_empty(&devices)) + return NULL; + + dev = list_entry(devices.next, struct mtd_device, link); + i = 0; + list_for_each(pentry, &dev->parts) { + if (i == idx) { + part = list_entry(pentry, struct part_info, link); + return part; + } + i++; + } + + return NULL; +} + +#define LOAD_ADDR ((unsigned char *)0x32000000) + +static int initialize_ds_nand(struct usb_device_instance *dev, struct dnload_state *ds) +{ + ds->part = get_partition_nand(dev->alternate - 1); + if (!ds->part) { + printf("DFU: unable to find partition %u\b", dev->alternate-1); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + ds->nand = &nand_info[ds->part->dev->id->num]; + ds->off = ds->part->offset; + ds->part_net_size = nand_net_part_size(ds->part); + + if (ds->nand->erasesize > sizeof(ds->_buf)) { + printf("*** Warning - NAND ERASESIZE bigger than static buffer\n"); + ds->buf = malloc(ds->nand->erasesize); + if (!ds->buf) { + printf("DFU: can't allocate %u bytes\n", ds->nand->erasesize); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + } else + ds->buf = ds->_buf; + + ds->ptr = ds->buf; + + memset(&ds->read_opts, 0, sizeof(ds->read_opts)); + + memset(&ds->erase_opts, 0, sizeof(ds->erase_opts)); + ds->erase_opts.quiet = 1; + /* FIXME: do this more dynamic */ + if (!strcmp(ds->part->name, "rootfs")) + ds->erase_opts.jffs2 = 1; + + memset(&ds->write_opts, 0, sizeof(ds->write_opts)); + ds->write_opts.pad = 1; + ds->write_opts.blockalign = 1; + ds->write_opts.quiet = 1; + + debug("initialize_ds_nand(dev=%p, ds=%p): ", dev, ds); + debug("nand=%p, ptr=%p, buf=%p, off=0x%x\n", ds->nand, ds->ptr, ds->buf, ds->off); + + return RET_NOTHING; +} + +static int erase_flash_verify_nand(struct urb *urb, struct dnload_state *ds, + unsigned long erasesize, unsigned long size) +{ + struct usb_device_instance *dev = urb->device; + int rc; + + debug("erase_flash_verify_nand(urb=%p, ds=%p, erase=0x%x size=0x%x)\n", + urb, ds, erasesize, size); + + if (erasesize == ds->nand->erasesize) { + /* we're only writing a single block and need to + * do bad block skipping / offset adjustments our own */ + while (ds->nand->block_isbad(ds->nand, ds->off)) { + debug("SKIP_ONE_BLOCK(0x%08x)!!\n", ds->off); + ds->off += ds->nand->erasesize; + } + } + + /* we have finished one eraseblock, flash it */ + ds->erase_opts.offset = ds->off; + ds->erase_opts.length = erasesize; + debug("Erasing 0x%x bytes @ offset 0x%x (jffs=%u)\n", + ds->erase_opts.length, ds->erase_opts.offset, + ds->erase_opts.jffs2); + rc = nand_erase_opts(ds->nand, &ds->erase_opts); + if (rc) { + debug("Error erasing\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errERASE; + return RET_STALL; + } + + ds->write_opts.buffer = ds->buf; + ds->write_opts.length = size; + ds->write_opts.offset = ds->off; + debug("Writing 0x%x bytes @ offset 0x%x\n", size, ds->off); + rc = nand_write_opts(ds->nand, &ds->write_opts); + if (rc) { + debug("Error writing\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errWRITE; + return RET_STALL; + } + + ds->off += size; + ds->ptr = ds->buf; + + /* FIXME: implement verify! */ + return RET_NOTHING; +} + +static int erase_tail_clean_nand(struct urb *urb, struct dnload_state *ds) +{ + struct usb_device_instance *dev = urb->device; + int rc; + + ds->erase_opts.offset = ds->off; + ds->erase_opts.length = ds->part->size-ds->off; + debug("Erasing 0x%x bytes @ offset 0x%x (jffs=%u)\n", + ds->erase_opts.length, ds->erase_opts.offset, + ds->erase_opts.jffs2); + rc = nand_erase_opts(ds->nand, &ds->erase_opts); + if (rc) { + debug("Error erasing\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errERASE; + return RET_STALL; + } + + ds->off += ds->part->size; /* for consistency */ + + return RET_NOTHING; +} + +/* Read the next erase blcok from NAND into buffer */ +static int read_next_nand(struct urb *urb, struct dnload_state *ds) +{ + struct usb_device_instance *dev = urb->device; + int rc; + + ds->read_opts.buffer = ds->buf; + ds->read_opts.length = ds->nand->erasesize; + ds->read_opts.offset = ds->off; + ds->read_opts.quiet = 1; + + debug("Reading 0x%x@0x%x to 0x%08p\n", ds->nand->erasesize, + ds->off, ds->buf); + rc = nand_read_opts(ds->nand, &ds->read_opts); + if (rc) { + debug("Error reading\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errWRITE; + return RET_STALL; + } + ds->off += ds->nand->erasesize; + ds->ptr = ds->buf; + + return RET_NOTHING; +} + + +static int handle_dnload(struct urb *urb, u_int16_t val, u_int16_t len, int first) +{ + struct usb_device_instance *dev = urb->device; + struct dnload_state *ds = &_dnstate; + unsigned int actual_len = len; + unsigned int remain_len; + unsigned long size; + int rc; + + debug("download(len=%u, first=%u) ", len, first); + + if (len > CONFIG_USBD_DFU_XFER_SIZE) { + /* Too big. Not that we'd really care, but it's a + * DFU protocol violation */ + debug("length exceeds flash page size "); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + + if (first) { + /* Make sure that we have a valid mtd partition table */ + char *mtdp = getenv("mtdparts"); + if (!mtdp) + run_command("dynpart", 0); + } + + if (len == 0) { + debug("zero-size write -> MANIFEST_SYNC "); + dev->dfu_state = DFU_STATE_dfuMANIFEST_SYNC; + + /* cleanup */ + switch (dev->alternate) { + char buf[12]; + case 0: + sprintf(buf, "%lx", ds->ptr - ds->buf); + setenv("filesize", buf); + ds->ptr = ds->buf; + break; + case 1: + if (ds->ptr > + ds->buf + sizeof(struct uboot_dfu_trailer)) { + struct uboot_dfu_trailer trailer; + dfu_trailer_mirror(&trailer, ds->ptr); + if (!dfu_trailer_matching(&trailer)) { + printf("DFU TRAILER NOT OK\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errTARGET; + return RET_STALL; + } + + rc = erase_flash_verify_nand(urb, ds, + ds->part->size, + ds->part_net_size); + /* re-write dynenv marker in OOB */ + run_command("dynenv set u-boot_env", 0); + } + ds->nand = NULL; + free(ds->buf); + ds->ptr = ds->buf = ds->_buf; + break; + default: + rc = 0; + if (ds->ptr > ds->buf) + rc = erase_flash_verify_nand(urb, ds, + ds->nand->erasesize, + ds->nand->erasesize); + /* rootfs partition */ + if (!rc && dev->alternate == 5) + rc = erase_tail_clean_nand(urb, ds); + + ds->nand = NULL; + break; + } + + return RET_ZLP; + } + + if (urb->actual_length != len) { + debug("urb->actual_length(%u) != len(%u) ?!? ", + urb->actual_length, len); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + + if (first && ds->buf && ds->buf != ds->_buf && ds->buf != LOAD_ADDR) { + free(ds->buf); + ds->buf = ds->_buf; + } + + switch (dev->alternate) { + case 0: + if (first) { + printf("Starting DFU DOWNLOAD to RAM (0x%08p)\n", + LOAD_ADDR); + ds->buf = LOAD_ADDR; + ds->ptr = ds->buf; + } + + memcpy(ds->ptr, urb->buffer, len); + ds->ptr += len; + break; + case 1: + if (first) { + rc = initialize_ds_nand(dev, ds); + if (rc) + return rc; + ds->buf = malloc(ds->part_net_size); + if (!ds->buf) { + printf("No memory for atomic buffer!!\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errUNKNOWN; + return RET_STALL; + } + ds->ptr = ds->buf; + printf("Starting Atomic DFU DOWNLOAD to partition '%s'\n", + ds->part->name); + } + + remain_len = (ds->buf + ds->part_net_size) - ds->ptr; + if (remain_len < len) { + len = remain_len; + printf("End of write exceeds partition end\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + memcpy(ds->ptr, urb->buffer, len); + ds->ptr += len; + break; + default: + if (first) { + rc = initialize_ds_nand(dev, ds); + if (rc) + return rc; + printf("Starting DFU DOWNLOAD to partition '%s'\n", + ds->part->name); + } + + size = ds->nand->erasesize; + remain_len = ds->buf + size - ds->ptr; + if (remain_len < len) + actual_len = remain_len; + + memcpy(ds->ptr, urb->buffer, actual_len); + ds->ptr += actual_len; + + /* check partition end */ + if (ds->off + (ds->ptr - ds->buf) > ds->part->offset + ds->part->size) { + printf("End of write exceeds partition end\n"); + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + + if (ds->ptr >= ds->buf + size) { + rc = erase_flash_verify_nand(urb, ds, + ds->nand->erasesize, + ds->nand->erasesize); + if (rc) + return rc; + /* copy remainder of data into buffer */ + memcpy(ds->ptr, urb->buffer + actual_len, len - actual_len); + ds->ptr += (len - actual_len); + } + break; + } + + return RET_ZLP; +} + +static int handle_upload(struct urb *urb, u_int16_t val, u_int16_t len, int first) +{ + struct usb_device_instance *dev = urb->device; + struct dnload_state *ds = &_dnstate; + unsigned int remain; + int rc; + + debug("upload(val=0x%02x, len=%u, first=%u) ", val, len, first); + + if (len > CONFIG_USBD_DFU_XFER_SIZE) { + /* Too big */ + dev->dfu_state = DFU_STATE_dfuERROR; + dev->dfu_status = DFU_STATUS_errADDRESS; + //udc_ep0_send_stall(); + debug("Error: Transfer size > CONFIG_USBD_DFU_XFER_SIZE "); + return -EINVAL; + } + + switch (dev->alternate) { + case 0: + if (first) { + printf("Starting DFU Upload of RAM (0x%08p)\n", + LOAD_ADDR); + ds->ptr = ds->buf; + } + + /* FIXME: end at some more dynamic point */ + if (ds->ptr + len > LOAD_ADDR + 0x200000) + len = (LOAD_ADDR + 0x200000) - ds->ptr; + + urb->buffer = ds->ptr; + urb->actual_length = len; + ds->ptr += len; + break; + default: + if (first) { + rc = initialize_ds_nand(dev, ds); + if (rc) + return -EINVAL; + printf("Starting DFU Upload of partition '%s'\n", + ds->part->name); + rc = read_next_nand(urb, ds); + if (rc) + return -EINVAL; + } + + if (len > ds->nand->erasesize) { + printf("We don't support transfers bigger than %u\n", + ds->nand->erasesize); + len = ds->nand->erasesize; + } + + remain = ds->nand->erasesize - (ds->ptr - ds->buf); + if (len < remain) + remain = len; + + debug("copying %u bytes ", remain); + urb->buffer = ds->ptr; + ds->ptr += remain; + urb->actual_length = remain; + + if (ds->ptr >= ds->buf + ds->nand->erasesize && + ds->off < ds->part->offset + ds->part->size) { + rc = read_next_nand(urb, ds); + if (rc) + return -EINVAL; + if (len > remain) { + debug("copying another %u bytes ", len - remain); + memcpy(urb->buffer + remain, ds->ptr, len - remain); + ds->ptr += (len - remain); + urb->actual_length += (len - remain); + } + } + break; + } + + debug("returning len=%u\n", len); + return len; +} + +static void handle_getstatus(struct urb *urb, int max) +{ + struct usb_device_instance *dev = urb->device; + struct dfu_status *dstat = (struct dfu_status *) urb->buffer; + + debug("getstatus "); + + if (!urb->buffer || urb->buffer_length < sizeof(*dstat)) { + debug("invalid urb! "); + return; + } + + switch (dev->dfu_state) { + case DFU_STATE_dfuDNLOAD_SYNC: + case DFU_STATE_dfuDNBUSY: +#if 0 + if (fsr & AT91C_MC_PROGE) { + debug("errPROG "); + dev->dfu_status = DFU_STATUS_errPROG; + dev->dfu_state = DFU_STATE_dfuERROR; + } else if (fsr & AT91C_MC_LOCKE) { + debug("errWRITE "); + dev->dfu_status = DFU_STATUS_errWRITE; + dev->dfu_state = DFU_STATE_dfuERROR; + } else if (fsr & AT91C_MC_FRDY) { +#endif + debug("DNLOAD_IDLE "); + dev->dfu_state = DFU_STATE_dfuDNLOAD_IDLE; +#if 0 + } else { + debug("DNBUSY "); + dev->dfu_state = DFU_STATE_dfuDNBUSY; + } +#endif + break; + case DFU_STATE_dfuMANIFEST_SYNC: + break; + default: + //return; + break; + } + + /* send status response */ + dstat->bStatus = dev->dfu_status; + dstat->bState = dev->dfu_state; + dstat->iString = 0; + /* FIXME: set dstat->bwPollTimeout */ + urb->actual_length = MIN(sizeof(*dstat), max); + + /* we don't need to explicitly send data here, will + * be done by the original caller! */ +} + +static void handle_getstate(struct urb *urb, int max) +{ + debug("getstate "); + + if (!urb->buffer || urb->buffer_length < sizeof(u_int8_t)) { + debug("invalid urb! "); + return; + } + + urb->buffer[0] = urb->device->dfu_state & 0xff; + urb->actual_length = sizeof(u_int8_t); +} + +#ifndef CONFIG_USBD_PRODUCTID_DFU +#define CONFIG_USBD_PRODUCTID_DFU CONFIG_USBD_PRODUCTID_CDCACM +#endif + +static const struct usb_device_descriptor dfu_dev_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0100, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = EP0_MAX_PACKET_SIZE, + .idVendor = CONFIG_USBD_VENDORID, + .idProduct = CONFIG_USBD_PRODUCTID_DFU, + .bcdDevice = 0x0000, + .iManufacturer = DFU_STR_MANUFACTURER, + .iProduct = DFU_STR_PRODUCT, + .iSerialNumber = DFU_STR_SERIAL, + .bNumConfigurations = 0x01, +}; + +static const struct _dfu_desc dfu_cfg_descriptor = { + .ucfg = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = USB_DT_CONFIG_SIZE + + DFU_NUM_ALTERNATES * USB_DT_INTERFACE_SIZE + + USB_DT_DFU_SIZE, + .bNumInterfaces = 5, + .bConfigurationValue = 1, + .iConfiguration = DFU_STR_CONFIG, + .bmAttributes = BMATTRIBUTE_RESERVED, + .bMaxPower = 50, + }, + .uif[0] = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, + .iInterface = DFU_STR_ALT0, + }, + .uif[1] = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x01, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, + .iInterface = DFU_STR_ALT1, + }, + .uif[2] = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x02, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, + .iInterface = DFU_STR_ALT2, + }, + .uif[3] = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x03, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, + .iInterface = DFU_STR_ALT3, + }, + .uif[4] = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x04, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, + .iInterface = DFU_STR_ALT4, + }, + .uif[5] = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x05, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, + .iInterface = DFU_STR_ALT5, + }, + .func_dfu = DFU_FUNC_DESC, +}; + +int dfu_ep0_handler(struct urb *urb) +{ + int rc, ret = RET_NOTHING; + u_int8_t req = urb->device_request.bRequest; + u_int16_t val = urb->device_request.wValue; + u_int16_t len = urb->device_request.wLength; + struct usb_device_instance *dev = urb->device; + + debug("dfu_ep0(req=0x%x, val=0x%x, len=%u) old_state = %u ", + req, val, len, dev->dfu_state); + + switch (dev->dfu_state) { + case DFU_STATE_appIDLE: + switch (req) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(urb, len); + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(urb, len); + break; + case USB_REQ_DFU_DETACH: + dev->dfu_state = DFU_STATE_appDETACH; + ret = RET_ZLP; + goto out; + break; + default: + ret = RET_STALL; + } + break; + case DFU_STATE_appDETACH: + switch (req) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(urb, len); + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(urb, len); + break; + default: + dev->dfu_state = DFU_STATE_appIDLE; + ret = RET_STALL; + goto out; + break; + } + /* FIXME: implement timer to return to appIDLE */ + break; + case DFU_STATE_dfuIDLE: + switch (req) { + case USB_REQ_DFU_DNLOAD: + if (len == 0) { + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + goto out; + } + dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + ret = handle_dnload(urb, val, len, 1); + break; + case USB_REQ_DFU_UPLOAD: + dev->dfu_state = DFU_STATE_dfuUPLOAD_IDLE; + handle_upload(urb, val, len, 1); + break; + case USB_REQ_DFU_ABORT: + /* no zlp? */ + ret = RET_ZLP; + break; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(urb, len); + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(urb, len); + break; + case USB_REQ_DFU_DETACH: + /* Proprietary extension: 'detach' from idle mode and + * get back to runtime mode in case of USB Reset. As + * much as I dislike this, we just can't use every USB + * bus reset to switch back to runtime mode, since at + * least the Linux USB stack likes to send a number of resets + * in a row :( */ + dev->dfu_state = DFU_STATE_dfuMANIFEST_WAIT_RST; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + goto out; + break; + } + break; + case DFU_STATE_dfuDNLOAD_SYNC: + switch (req) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(urb, len); + /* FIXME: state transition depending on block completeness */ + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(urb, len); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + goto out; + } + break; + case DFU_STATE_dfuDNBUSY: + switch (req) { + case USB_REQ_DFU_GETSTATUS: + /* FIXME: only accept getstatus if bwPollTimeout + * has elapsed */ + handle_getstatus(urb, len); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + goto out; + } + break; + case DFU_STATE_dfuDNLOAD_IDLE: + switch (req) { + case USB_REQ_DFU_DNLOAD: + dev->dfu_state = DFU_STATE_dfuDNLOAD_SYNC; + ret = handle_dnload(urb, val, len, 0); + break; + case USB_REQ_DFU_ABORT: + dev->dfu_state = DFU_STATE_dfuIDLE; + ret = RET_ZLP; + break; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(urb, len); + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(urb, len); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + break; + } + break; + case DFU_STATE_dfuMANIFEST_SYNC: + switch (req) { + case USB_REQ_DFU_GETSTATUS: + /* We're MainfestationTolerant */ + dev->dfu_state = DFU_STATE_dfuIDLE; + handle_getstatus(urb, len); + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(urb, len); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + break; + } + break; + case DFU_STATE_dfuMANIFEST: + /* we should never go here */ + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + break; + case DFU_STATE_dfuMANIFEST_WAIT_RST: + /* we should never go here */ + break; + case DFU_STATE_dfuUPLOAD_IDLE: + switch (req) { + case USB_REQ_DFU_UPLOAD: + /* state transition if less data then requested */ + rc = handle_upload(urb, val, len, 0); + if (rc >= 0 && rc < len) + dev->dfu_state = DFU_STATE_dfuIDLE; + break; + case USB_REQ_DFU_ABORT: + dev->dfu_state = DFU_STATE_dfuIDLE; + /* no zlp? */ + ret = RET_ZLP; + break; + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(urb, len); + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(urb, len); + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + break; + } + break; + case DFU_STATE_dfuERROR: + switch (req) { + case USB_REQ_DFU_GETSTATUS: + handle_getstatus(urb, len); + break; + case USB_REQ_DFU_GETSTATE: + handle_getstate(urb, len); + break; + case USB_REQ_DFU_CLRSTATUS: + dev->dfu_state = DFU_STATE_dfuIDLE; + dev->dfu_status = DFU_STATUS_OK; + /* no zlp? */ + ret = RET_ZLP; + break; + default: + dev->dfu_state = DFU_STATE_dfuERROR; + ret = RET_STALL; + break; + } + break; + default: + return DFU_EP0_UNHANDLED; + break; + } + +out: + debug("new_state = %u, ret = %u\n", dev->dfu_state, ret); + + switch (ret) { + case RET_ZLP: + //udc_ep0_send_zlp(); + urb->actual_length = 0; + return DFU_EP0_ZLP; + break; + case RET_STALL: + //udc_ep0_send_stall(); + return DFU_EP0_STALL; + break; + case RET_NOTHING: + break; + } + + return DFU_EP0_DATA; +} + +void str2wide (char *str, u16 * wide); +static struct usb_string_descriptor *create_usbstring(char *string) +{ + struct usb_string_descriptor *strdesc; + int size = sizeof(*strdesc) + strlen(string)*2; + + if (size > 255) + return NULL; + + strdesc = malloc(size); + if (!strdesc) + return NULL; + + strdesc->bLength = size; + strdesc->bDescriptorType = USB_DT_STRING; + str2wide(string, strdesc->wData); + + return strdesc; +} + + +static void dfu_init_strings(struct usb_device_instance *dev) +{ + int i; + struct usb_string_descriptor *strdesc; + + strdesc = create_usbstring(CONFIG_DFU_CFG_STR); + usb_strings[DFU_STR_CONFIG] = strdesc; + + for (i = 0; i < DFU_NUM_ALTERNATES; i++) { + if (i == 0) { + strdesc = create_usbstring(CONFIG_DFU_ALT0_STR); + } else { + struct part_info *part = get_partition_nand(i-1); + + if (part) + strdesc = create_usbstring(part->name); + else + strdesc = + create_usbstring("undefined partition"); + } + if (!strdesc) + continue; + usb_strings[STR_COUNT+i+1] = strdesc; + } +} + +int dfu_init_instance(struct usb_device_instance *dev) +{ + dev->dfu_dev_desc = &dfu_dev_descriptor; + dev->dfu_cfg_desc = &dfu_cfg_descriptor; + dev->dfu_state = DFU_STATE_appIDLE; + dev->dfu_status = DFU_STATUS_OK; + + if (system_dfu_state) + printf("SURPRISE: system_dfu_state is already set\n"); + system_dfu_state = &dev->dfu_state; + + dfu_init_strings(dev); + + return 0; +} + +static int stdout_switched; + +/* event handler for usb device state events */ +void dfu_event(struct usb_device_instance *device, + usb_device_event_t event, int data) +{ + char *out; + + switch (event) { + case DEVICE_RESET: + switch (device->dfu_state) { + case DFU_STATE_appDETACH: + device->dfu_state = DFU_STATE_dfuIDLE; + out = getenv("stdout"); + if (out && !strcmp(out, "usbtty")) { + setenv("stdout", "vga"); + setenv("stderr", "vga"); + stdout_switched = 1; + } + printf("DFU: Switching to DFU Mode\n"); + break; + case DFU_STATE_dfuMANIFEST_WAIT_RST: + device->dfu_state = DFU_STATE_appIDLE; + printf("DFU: Switching back to Runtime mode\n"); + if (stdout_switched) { + setenv("stdout", "usbtty"); + setenv("stderr", "usbtty"); + stdout_switched = 0; + } + break; + default: + break; + } + break; + case DEVICE_CONFIGURED: + case DEVICE_DE_CONFIGURED: + debug("SET_CONFIGURATION(%u) ", device->configuration); + /* fallthrough */ + case DEVICE_SET_INTERFACE: + debug("SET_INTERFACE(%u,%u) old_state = %u ", + device->interface, device->alternate, + device->dfu_state); + switch (device->dfu_state) { + case DFU_STATE_appIDLE: + case DFU_STATE_appDETACH: + case DFU_STATE_dfuIDLE: + case DFU_STATE_dfuMANIFEST_WAIT_RST: + /* do nothing, we're fine */ + break; + case DFU_STATE_dfuDNLOAD_SYNC: + case DFU_STATE_dfuDNBUSY: + case DFU_STATE_dfuDNLOAD_IDLE: + case DFU_STATE_dfuMANIFEST: + device->dfu_state = DFU_STATE_dfuERROR; + device->dfu_status = DFU_STATUS_errNOTDONE; + /* FIXME: free malloc()ed buffer! */ + break; + case DFU_STATE_dfuMANIFEST_SYNC: + case DFU_STATE_dfuUPLOAD_IDLE: + case DFU_STATE_dfuERROR: + device->dfu_state = DFU_STATE_dfuERROR; + device->dfu_status = DFU_STATUS_errUNKNOWN; + break; + } + debug("new_state = %u\n", device->dfu_state); + break; + default: + break; + } +} +#endif /* CONFIG_USBD_DFU */ Index: u-boot/drivers/Makefile =================================================================== --- u-boot.orig/drivers/Makefile +++ u-boot/drivers/Makefile @@ -47,7 +47,7 @@ status_led.o sym53c8xx.o systemace.o ahci.o \ ti_pci1410a.o tigon3.o tsec.o \ tsi108_eth.o tsi108_i2c.o tsi108_pci.o \ - usbdcore.o usbdcore_ep0.o usbdcore_omap1510.o usbdcore_s3c2410.o usbtty.o \ + usbdcore.o usbdfu.o usbdcore_ep0.o usbdcore_omap1510.o usbdcore_s3c2410.o usbtty.o \ videomodes.o w83c553f.o \ ks8695eth.o \ pcf50606.o \ Index: u-boot/drivers/usbdcore.c =================================================================== --- u-boot.orig/drivers/usbdcore.c +++ u-boot/drivers/usbdcore.c @@ -31,6 +31,7 @@ #include #include "usbdcore.h" +#include #define MAX_INTERFACES 2 @@ -212,6 +213,10 @@ */ struct usb_device_descriptor *usbd_device_device_descriptor (struct usb_device_instance *device, int port) { +#ifdef CONFIG_USBD_DFU + if (device->dfu_state != DFU_STATE_appIDLE) + return device->dfu_dev_desc; +#endif return (device->device_descriptor); } @@ -232,6 +237,10 @@ if (!(configuration_instance = usbd_device_configuration_instance (device, port, configuration))) { return NULL; } +#ifdef CONFIG_USBD_DFU + if (device->dfu_state != DFU_STATE_appIDLE) + return (&device->dfu_cfg_desc->ucfg); +#endif return (configuration_instance->configuration_descriptor); } @@ -253,6 +262,13 @@ if (!(interface_instance = usbd_device_interface_instance (device, port, configuration, interface))) { return NULL; } +#ifdef CONFIG_USBD_DFU + if (device->dfu_state != DFU_STATE_appIDLE) { + if (alternate < 0 || alternate >= DFU_NUM_ALTERNATES) + return NULL; + return &device->dfu_cfg_desc->uif[alternate]; + } +#endif if ((alternate < 0) || (alternate >= interface_instance->alternates)) { return NULL; } @@ -681,4 +697,7 @@ /* usbdbg("calling device->event"); */ device->event(device, event, data); } +#ifdef CONFIG_USBD_DFU + dfu_event(device, event, data); +#endif } Index: u-boot/drivers/usbtty.c =================================================================== --- u-boot.orig/drivers/usbtty.c +++ u-boot/drivers/usbtty.c @@ -31,6 +31,8 @@ #include "usbtty.h" #include "usb_cdc_acm.h" #include "usbdescriptors.h" +#include +#include #include /* If defined, override Linux identifiers with * vendor specific ones */ @@ -118,7 +120,7 @@ static unsigned short rx_endpoint = 0; static unsigned short tx_endpoint = 0; static unsigned short interface_count = 0; -static struct usb_string_descriptor *usbtty_string_table[STR_COUNT]; +static struct usb_string_descriptor *usbtty_string_table[NUM_STRINGS]; /* USB Descriptor Strings */ static u8 wstrLang[4] = {4,USB_DT_STRING,0x9,0x4}; @@ -169,6 +171,10 @@ struct usb_interface_descriptor data_class_interface; struct usb_endpoint_descriptor data_endpoints[NUM_ENDPOINTS-1] __attribute__((packed)); +#ifdef CONFIG_USBD_DFU + struct usb_interface_descriptor uif_dfu; + struct usb_dfu_func_descriptor func_dfu; +#endif } __attribute__((packed)); static struct acm_config_desc acm_configuration_descriptors[NUM_CONFIGS] = { @@ -179,7 +185,11 @@ .bDescriptorType = USB_DT_CONFIG, .wTotalLength = cpu_to_le16(sizeof(struct acm_config_desc)), +#ifdef CONFIG_USBD_DFU + .bNumInterfaces = NUM_ACM_INTERFACES +1, +#else .bNumInterfaces = NUM_ACM_INTERFACES, +#endif .bConfigurationValue = 1, .iConfiguration = STR_CONFIG, .bmAttributes = @@ -278,6 +288,11 @@ .bInterval = 0xFF, }, }, +#ifdef CONFIG_USBD_DFU + /* Interface 3 */ + .uif_dfu = DFU_RT_IF_DESC, + .func_dfu = DFU_FUNC_DESC, +#endif }, }; @@ -390,7 +405,7 @@ void usbtty_poll (void); /* utility function for converting char* to wide string used by USB */ -static void str2wide (char *str, u16 * wide) +void str2wide (char *str, u16 * wide) { int i; for (i = 0; i < strlen (str) && str[i]; i++){ @@ -652,6 +667,9 @@ device_instance->bus = bus_instance; device_instance->configurations = NUM_CONFIGS; device_instance->configuration_instance_array = config_instance; +#ifdef CONFIG_USBD_DFU + dfu_init_instance(device_instance); +#endif /* initialize bus instance */ memset (bus_instance, 0, sizeof (struct usb_bus_instance)); Index: u-boot/include/configs/neo1973_gta01.h =================================================================== --- u-boot.orig/include/configs/neo1973_gta01.h +++ u-boot/include/configs/neo1973_gta01.h @@ -167,7 +167,7 @@ */ #define CONFIG_STACKSIZE (128*1024) /* regular stack */ #ifdef CONFIG_USE_IRQ -#define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */ +#define CONFIG_STACKSIZE_IRQ (8*1024) /* IRQ stack */ #define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */ #endif @@ -184,6 +184,10 @@ #define CONFIG_USBD_MANUFACTURER "Openmoko, Inc" #define CONFIG_USBD_PRODUCT_NAME "Neo1973 Bootloader " U_BOOT_VERSION #define CONFIG_EXTRA_ENV_SETTINGS "usbtty=cdc_acm\0" +#define CONFIG_USBD_DFU 1 +#define CONFIG_USBD_DFU_XFER_SIZE 4096 /* 0x4000 */ +#define CONFIG_USBD_DFU_INTERFACE 2 + /*----------------------------------------------------------------------- * Physical Memory Map Index: u-boot/include/usb_dfu.h =================================================================== --- /dev/null +++ u-boot/include/usb_dfu.h @@ -0,0 +1,99 @@ +#ifndef _DFU_H +#define _DFU_H + +/* USB Device Firmware Update Implementation for u-boot + * (C) 2007 by Openmoko, Inc. + * Author: Harald Welte + * + * based on: USB Device Firmware Update Implementation for OpenPCD + * (C) 2006 by Harald Welte + * + * This ought to be compliant to the USB DFU Spec 1.0 as available from + * http://www.usb.org/developers/devclass_docs/usbdfu10.pdf + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +/* USB DFU functional descriptor */ +#define DFU_FUNC_DESC { \ + .bLength = USB_DT_DFU_SIZE, \ + .bDescriptorType = USB_DT_DFU, \ + .bmAttributes = USB_DFU_CAN_UPLOAD | USB_DFU_CAN_DOWNLOAD | USB_DFU_MANIFEST_TOL, \ + .wDetachTimeOut = 0xff00, \ + .wTransferSize = CONFIG_USBD_DFU_XFER_SIZE, \ + .bcdDFUVersion = 0x0100, \ +} + +/* USB Interface descriptor in Runtime mode */ +#define DFU_RT_IF_DESC { \ + .bLength = USB_DT_INTERFACE_SIZE, \ + .bDescriptorType = USB_DT_INTERFACE, \ + .bInterfaceNumber = CONFIG_USBD_DFU_INTERFACE, \ + .bAlternateSetting = 0x00, \ + .bNumEndpoints = 0x00, \ + .bInterfaceClass = 0xfe, \ + .bInterfaceSubClass = 0x01, \ + .bInterfaceProtocol = 0x01, \ + .iInterface = DFU_STR_CONFIG, \ +} + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#define DFU_NUM_ALTERNATES 6 + +#define DFU_STR_MANUFACTURER STR_MANUFACTURER +#define DFU_STR_PRODUCT STR_PRODUCT +#define DFU_STR_SERIAL STR_SERIAL +#define DFU_STR_CONFIG (STR_COUNT) +#define DFU_STR_ALT0 (STR_COUNT+1) +#define DFU_STR_ALT1 (STR_COUNT+2) +#define DFU_STR_ALT2 (STR_COUNT+3) +#define DFU_STR_ALT3 (STR_COUNT+4) +#define DFU_STR_ALT4 (STR_COUNT+5) +#define DFU_STR_ALT5 (STR_COUNT+6) +#define DFU_STR_COUNT (STR_COUNT+7) + +#define DFU_NUM_STRINGS (STR_COUNT+8) + +#define CONFIG_DFU_CFG_STR "USB Device Firmware Upgrade" +#define CONFIG_DFU_ALT0_STR "RAM 0x32000000" + +struct _dfu_desc { + struct usb_configuration_descriptor ucfg; + struct usb_interface_descriptor uif[DFU_NUM_ALTERNATES]; + struct usb_dfu_func_descriptor func_dfu; +}; + +int dfu_init_instance(struct usb_device_instance *dev); + +#define DFU_EP0_NONE 0 +#define DFU_EP0_UNHANDLED 1 +#define DFU_EP0_STALL 2 +#define DFU_EP0_ZLP 3 +#define DFU_EP0_DATA 4 + +extern volatile enum dfu_state *system_dfu_state; /* for 3rd parties */ + +int dfu_ep0_handler(struct urb *urb); + +void dfu_event(struct usb_device_instance *device, + usb_device_event_t event, int data); + +#endif /* _DFU_H */ Index: u-boot/include/usb_dfu_descriptors.h =================================================================== --- /dev/null +++ u-boot/include/usb_dfu_descriptors.h @@ -0,0 +1,94 @@ +#ifndef _USB_DFU_H +#define _USB_DFU_H +/* USB Device Firmware Update Implementation for OpenPCD + * (C) 2006 by Harald Welte + * + * Protocol definitions for USB DFU + * + * This ought to be compliant to the USB DFU Spec 1.0 as available from + * http://www.usb.org/developers/devclass_docs/usbdfu10.pdf + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#define USB_DT_DFU 0x21 + +struct usb_dfu_func_descriptor { + u_int8_t bLength; + u_int8_t bDescriptorType; + u_int8_t bmAttributes; +#define USB_DFU_CAN_DOWNLOAD (1 << 0) +#define USB_DFU_CAN_UPLOAD (1 << 1) +#define USB_DFU_MANIFEST_TOL (1 << 2) +#define USB_DFU_WILL_DETACH (1 << 3) + u_int16_t wDetachTimeOut; + u_int16_t wTransferSize; + u_int16_t bcdDFUVersion; +} __attribute__ ((packed)); + +#define USB_DT_DFU_SIZE 9 + +#define USB_TYPE_DFU (USB_TYPE_CLASS|USB_RECIP_INTERFACE) + +/* DFU class-specific requests (Section 3, DFU Rev 1.1) */ +#define USB_REQ_DFU_DETACH 0x00 +#define USB_REQ_DFU_DNLOAD 0x01 +#define USB_REQ_DFU_UPLOAD 0x02 +#define USB_REQ_DFU_GETSTATUS 0x03 +#define USB_REQ_DFU_CLRSTATUS 0x04 +#define USB_REQ_DFU_GETSTATE 0x05 +#define USB_REQ_DFU_ABORT 0x06 + +struct dfu_status { + u_int8_t bStatus; + u_int8_t bwPollTimeout[3]; + u_int8_t bState; + u_int8_t iString; +} __attribute__((packed)); + +#define DFU_STATUS_OK 0x00 +#define DFU_STATUS_errTARGET 0x01 +#define DFU_STATUS_errFILE 0x02 +#define DFU_STATUS_errWRITE 0x03 +#define DFU_STATUS_errERASE 0x04 +#define DFU_STATUS_errCHECK_ERASED 0x05 +#define DFU_STATUS_errPROG 0x06 +#define DFU_STATUS_errVERIFY 0x07 +#define DFU_STATUS_errADDRESS 0x08 +#define DFU_STATUS_errNOTDONE 0x09 +#define DFU_STATUS_errFIRMWARE 0x0a +#define DFU_STATUS_errVENDOR 0x0b +#define DFU_STATUS_errUSBR 0x0c +#define DFU_STATUS_errPOR 0x0d +#define DFU_STATUS_errUNKNOWN 0x0e +#define DFU_STATUS_errSTALLEDPKT 0x0f + +enum dfu_state { + DFU_STATE_appIDLE = 0, + DFU_STATE_appDETACH = 1, + DFU_STATE_dfuIDLE = 2, + DFU_STATE_dfuDNLOAD_SYNC = 3, + DFU_STATE_dfuDNBUSY = 4, + DFU_STATE_dfuDNLOAD_IDLE = 5, + DFU_STATE_dfuMANIFEST_SYNC = 6, + DFU_STATE_dfuMANIFEST = 7, + DFU_STATE_dfuMANIFEST_WAIT_RST = 8, + DFU_STATE_dfuUPLOAD_IDLE = 9, + DFU_STATE_dfuERROR = 10, +}; + +#endif /* _USB_DFU_H */ Index: u-boot/include/usbdcore.h =================================================================== --- u-boot.orig/include/usbdcore.h +++ u-boot/include/usbdcore.h @@ -33,6 +33,7 @@ #include #include "usbdescriptors.h" +#include #define MAX_URBS_QUEUED 5 @@ -475,7 +476,11 @@ * function driver to inform it that data has arrived. */ +#ifdef CONFIG_USBD_DFU +#define URB_BUF_SIZE (128+CONFIG_USBD_DFU_XFER_SIZE) +#else #define URB_BUF_SIZE 128 /* in linux we'd malloc this, but in u-boot we prefer static data */ +#endif struct urb { struct usb_endpoint_instance *endpoint; @@ -603,6 +608,12 @@ unsigned long usbd_rxtx_timestamp; unsigned long usbd_last_rxtx_timestamp; +#ifdef CONFIG_USBD_DFU + const struct usb_device_descriptor *dfu_dev_desc; + const struct _dfu_desc *dfu_cfg_desc; + enum dfu_state dfu_state; + u_int8_t dfu_status; +#endif }; /* Bus Interface configuration structure @@ -632,6 +643,8 @@ extern char *usbd_device_requests[]; extern char *usbd_device_descriptors[]; +extern struct usb_string_descriptor **usb_strings; + void urb_link_init (urb_link * ul); void urb_detach (struct urb *urb); urb_link *first_urb_link (urb_link * hd); Index: u-boot/drivers/usbtty.h =================================================================== --- u-boot.orig/drivers/usbtty.h +++ u-boot/drivers/usbtty.h @@ -71,4 +71,10 @@ #define STR_CTRL_INTERFACE 0x06 #define STR_COUNT 0x07 +#ifdef CONFIG_USBD_DFU +#define NUM_STRINGS DFU_STR_COUNT +#else +#define NUM_STRINGS STR_COUNT +#endif + #endif Index: u-boot/include/configs/qt2410.h =================================================================== --- u-boot.orig/include/configs/qt2410.h +++ u-boot/include/configs/qt2410.h @@ -199,7 +199,8 @@ #define CONFIG_USBD_PRODUCT_NAME "QT2410 Bootloader " U_BOOT_VERSION #define CONFIG_EXTRA_ENV_SETTINGS "usbtty=cdc_acm\0" #define CONFIG_USBD_DFU 1 -#define CONFIG_USBD_DFU_XFER_SIZE 0x4000 +#define CONFIG_USBD_DFU_XFER_SIZE 4096 +#define CONFIG_USBD_DFU_INTERFACE 2 /*----------------------------------------------------------------------- * Physical Memory Map Index: u-boot/tools/Makefile =================================================================== --- u-boot.orig/tools/Makefile +++ u-boot/tools/Makefile @@ -21,10 +21,10 @@ # MA 02111-1307 USA # -BIN_FILES = img2srec$(SFX) mkimage$(SFX) envcrc$(SFX) gen_eth_addr$(SFX) bmp_logo$(SFX) +BIN_FILES = img2srec$(SFX) mkimage$(SFX) envcrc$(SFX) gen_eth_addr$(SFX) bmp_logo$(SFX) mkudfu$(SFX) OBJ_LINKS = environment.o crc32.o -OBJ_FILES = img2srec.o mkimage.o envcrc.o gen_eth_addr.o bmp_logo.o +OBJ_FILES = img2srec.o mkimage.o envcrc.o gen_eth_addr.o bmp_logo.o mkudfu.o ifeq ($(ARCH),mips) BIN_FILES += inca-swap-bytes$(SFX) @@ -137,6 +137,10 @@ $(CC) $(CFLAGS) $(HOST_LDFLAGS) -o $@ $^ $(STRIP) $@ +$(obj)mkudfu$(SFX): $(obj)mkudfu.o + $(CC) $(CFLAGS) $(HOST_LDFLAGS) -o $@ $^ + $(STRIP) $@ + $(obj)ncb$(SFX): $(obj)ncb.o $(CC) $(CFLAGS) $(HOST_LDFLAGS) -o $@ $^ $(STRIP) $@ Index: u-boot/tools/mkudfu.c =================================================================== --- /dev/null +++ u-boot/tools/mkudfu.c @@ -0,0 +1,314 @@ +/* + * USB DFU file trailer tool + * (C) Copyright by Openmoko, Inc. + * Author: Harald Welte + * + * based on mkimage.c, copyright information as follows: + * + * (C) Copyright 2000-2004 + * DENX Software Engineering + * Wolfgang Denk, wd@denx.de + * All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#ifndef __WIN32__ +#include /* for host / network byte order conversions */ +#endif +#include +#include +#include +#include + +#if defined(__BEOS__) || defined(__NetBSD__) || defined(__APPLE__) +#include +#endif + +#ifdef __WIN32__ +typedef unsigned int __u32; + +#define SWAP_LONG(x) \ + ((__u32)( \ + (((__u32)(x) & (__u32)0x000000ffUL) << 24) | \ + (((__u32)(x) & (__u32)0x0000ff00UL) << 8) | \ + (((__u32)(x) & (__u32)0x00ff0000UL) >> 8) | \ + (((__u32)(x) & (__u32)0xff000000UL) >> 24) )) +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; + +#define ntohl(a) SWAP_LONG(a) +#define htonl(a) SWAP_LONG(a) +#endif /* __WIN32__ */ + +#ifndef O_BINARY /* should be define'd on __WIN32__ */ +#define O_BINARY 0 +#endif + +#include + +extern int errno; + +#ifndef MAP_FAILED +#define MAP_FAILED (-1) +#endif + +static char *cmdname; + +static char *datafile; +static char *imagefile; + + +static void usage() +{ + fprintf (stderr, "%s - create / display u-boot DFU trailer\n", cmdname); + fprintf (stderr, "Usage: %s -l image\n" + " -l ==> list image header information\n" + " %s -v VID -p PID -r REV -d data_file image\n", + cmdname, cmdname); + fprintf (stderr, " -v ==> set vendor ID to 'VID'\n" + " -p ==> set product ID system to 'PID'\n" + " -r ==> set hardware revision to 'REV'\n" + " -d ==> use 'data_file' as input file\n" + ); + exit (EXIT_FAILURE); +} + +static void print_trailer(struct uboot_dfu_trailer *trailer) +{ + printf("===> DFU Trailer information:\n"); + printf("Trailer Vers.: %d\n", trailer->version); + printf("Trailer Length: %d\n", trailer->length); + printf("VendorID: 0x%04x\n", trailer->vendor); + printf("ProductID: 0x%04x\n", trailer->product); + printf("HW Revision: 0x%04x\n", trailer->revision); +} + +static void copy_file (int ifd, const char *datafile, int pad) +{ + int dfd; + struct stat sbuf; + unsigned char *ptr; + int tail; + int zero = 0; + int offset = 0; + int size; + + if ((dfd = open(datafile, O_RDONLY|O_BINARY)) < 0) { + fprintf (stderr, "%s: Can't open %s: %s\n", + cmdname, datafile, strerror(errno)); + exit (EXIT_FAILURE); + } + + if (fstat(dfd, &sbuf) < 0) { + fprintf (stderr, "%s: Can't stat %s: %s\n", + cmdname, datafile, strerror(errno)); + exit (EXIT_FAILURE); + } + + ptr = (unsigned char *)mmap(0, sbuf.st_size, + PROT_READ, MAP_SHARED, dfd, 0); + if (ptr == (unsigned char *)MAP_FAILED) { + fprintf (stderr, "%s: Can't read %s: %s\n", + cmdname, datafile, strerror(errno)); + exit (EXIT_FAILURE); + } + + size = sbuf.st_size - offset; + if (write(ifd, ptr + offset, size) != size) { + fprintf (stderr, "%s: Write error on %s: %s\n", + cmdname, imagefile, strerror(errno)); + exit (EXIT_FAILURE); + } + + if (pad && ((tail = size % 4) != 0)) { + + if (write(ifd, (char *)&zero, 4-tail) != 4-tail) { + fprintf (stderr, "%s: Write error on %s: %s\n", + cmdname, imagefile, strerror(errno)); + exit (EXIT_FAILURE); + } + } + + (void) munmap((void *)ptr, sbuf.st_size); + (void) close (dfd); +} + + +int main(int argc, char **argv) +{ + int ifd; + int lflag = 0; + struct stat sbuf; + u_int16_t opt_vendor, opt_product, opt_revision; + struct uboot_dfu_trailer _hdr, _mirror, *hdr = &_hdr; + + opt_vendor = opt_product = opt_revision = 0; + + cmdname = *argv; + + while (--argc > 0 && **++argv == '-') { + while (*++*argv) { + switch (**argv) { + case 'l': + lflag = 1; + break; + case 'v': + if (--argc <= 0) + usage (); + opt_vendor = strtoul(*++argv, NULL, 16); + goto NXTARG; + case 'p': + if (--argc <= 0) + usage (); + opt_product = strtoul(*++argv, NULL, 16); + goto NXTARG; + case 'r': + if (--argc <= 0) + usage (); + opt_revision = strtoul(*++argv, NULL, 16); + goto NXTARG; + case 'd': + if (--argc <= 0) + usage (); + datafile = *++argv; + goto NXTARG; + case 'h': + usage(); + break; + default: + usage(); + } + } +NXTARG: ; + } + + if (argc != 1) + usage(); + + imagefile = *argv; + + if (lflag) + ifd = open(imagefile, O_RDONLY|O_BINARY); + else + ifd = open(imagefile, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0666); + + if (ifd < 0) { + fprintf (stderr, "%s: Can't open %s: %s\n", + cmdname, imagefile, strerror(errno)); + exit (EXIT_FAILURE); + } + + if (lflag) { + unsigned char *ptr; + /* list header information of existing image */ + if (fstat(ifd, &sbuf) < 0) { + fprintf (stderr, "%s: Can't stat %s: %s\n", + cmdname, imagefile, strerror(errno)); + exit (EXIT_FAILURE); + } + + if ((unsigned)sbuf.st_size < sizeof(struct uboot_dfu_trailer)) { + fprintf (stderr, + "%s: Bad size: \"%s\" is no valid image\n", + cmdname, imagefile); + exit (EXIT_FAILURE); + } + + ptr = (unsigned char *)mmap(0, sbuf.st_size, + PROT_READ, MAP_SHARED, ifd, 0); + if ((caddr_t)ptr == (caddr_t)-1) { + fprintf (stderr, "%s: Can't read %s: %s\n", + cmdname, imagefile, strerror(errno)); + exit (EXIT_FAILURE); + } + + dfu_trailer_mirror(hdr, ptr+sbuf.st_size); + + if (hdr->magic != UBOOT_DFU_TRAILER_MAGIC) { + fprintf (stderr, + "%s: Bad Magic Number: \"%s\" is no valid image\n", + cmdname, imagefile); + exit (EXIT_FAILURE); + } + + /* for multi-file images we need the data part, too */ + print_trailer(hdr); + + (void) munmap((void *)ptr, sbuf.st_size); + (void) close (ifd); + + exit (EXIT_SUCCESS); + } + + /* if we're not listing: */ + + copy_file (ifd, datafile, 0); + + memset (hdr, 0, sizeof(struct uboot_dfu_trailer)); + + /* Build new header */ + hdr->version = UBOOT_DFU_TRAILER_V1; + hdr->magic = UBOOT_DFU_TRAILER_MAGIC; + hdr->length = sizeof(struct uboot_dfu_trailer); + hdr->vendor = opt_vendor; + hdr->product = opt_product; + hdr->revision = opt_revision; + + print_trailer(hdr); + dfu_trailer_mirror(&_mirror, (unsigned char *)hdr+sizeof(*hdr)); + + if (write(ifd, &_mirror, sizeof(struct uboot_dfu_trailer)) + != sizeof(struct uboot_dfu_trailer)) { + fprintf (stderr, "%s: Write error on %s: %s\n", + cmdname, imagefile, strerror(errno)); + exit (EXIT_FAILURE); + } + + /* We're a bit of paranoid */ +#if defined(_POSIX_SYNCHRONIZED_IO) && !defined(__sun__) && !defined(__FreeBSD__) + (void) fdatasync (ifd); +#else + (void) fsync (ifd); +#endif + + if (fstat(ifd, &sbuf) < 0) { + fprintf (stderr, "%s: Can't stat %s: %s\n", + cmdname, imagefile, strerror(errno)); + exit (EXIT_FAILURE); + } + + /* We're a bit of paranoid */ +#if defined(_POSIX_SYNCHRONIZED_IO) && !defined(__sun__) && !defined(__FreeBSD__) + (void) fdatasync (ifd); +#else + (void) fsync (ifd); +#endif + + if (close(ifd)) { + fprintf (stderr, "%s: Write error on %s: %s\n", + cmdname, imagefile, strerror(errno)); + exit (EXIT_FAILURE); + } + + exit (EXIT_SUCCESS); +} Index: u-boot/include/usb_dfu_trailer.h =================================================================== --- /dev/null +++ u-boot/include/usb_dfu_trailer.h @@ -0,0 +1,31 @@ +#ifndef _USB_DFU_TRAILER_H +#define _USB_DFU_TRAILER_H + +/* trailer handling for DFU files */ + +#define UBOOT_DFU_TRAILER_V1 1 +#define UBOOT_DFU_TRAILER_MAGIC 0x19731978 +struct uboot_dfu_trailer { + u_int32_t magic; + u_int16_t version; + u_int16_t length; + u_int16_t vendor; + u_int16_t product; + u_int32_t revision; +} __attribute__((packed)); + +/* we mirror the trailer because we want it to be longer in later versions + * while keeping backwards compatibility */ +static inline void dfu_trailer_mirror(struct uboot_dfu_trailer *trailer, + unsigned char *eof) +{ + int i; + int len = sizeof(struct uboot_dfu_trailer); + unsigned char *src = eof - len; + unsigned char *dst = (unsigned char *) trailer; + + for (i = 0; i < len; i++) + dst[len-1-i] = src[i]; +} + +#endif /* _USB_DFU_TRAILER_H */ Index: u-boot/Makefile =================================================================== --- u-boot.orig/Makefile +++ u-boot/Makefile @@ -261,6 +261,12 @@ $(obj)u-boot.bin: $(obj)u-boot $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@ +$(obj)u-boot.udfu: $(obj)u-boot.bin + ./tools/mkudfu -v $(CONFIG_USB_DFU_VENDOR) \ + -p $(CONFIG_USB_DFU_PRODUCT) \ + -r $(CONFIG_USB_DFU_REVISION) \ + -d $< $@ + $(obj)u-boot.img: $(obj)u-boot.bin ./tools/mkimage -A $(ARCH) -T firmware -C none \ -a $(TEXT_BASE) -e 0 \ Index: u-boot/board/neo1973/gta01/split_by_variant.sh =================================================================== --- u-boot.orig/board/neo1973/gta01/split_by_variant.sh +++ u-boot/board/neo1973/gta01/split_by_variant.sh @@ -15,37 +15,44 @@ echo "$0:: No parameters - using GTA01Bv3 config" echo "#define CONFIG_ARCH_GTA01B_v3" > $CFGINC echo "GTA01_BIG_RAM=y" > $CFGTMP + echo "CONFIG_USB_DFU_REVISION=0x0230" > $CFGTMP else case "$1" in gta01v4_config) echo "#define CONFIG_ARCH_GTA01_v4" > $CFGINC echo "GTA01_BIG_RAM=n" > $CFGTMP + echo "CONFIG_USB_DFU_REVISION=0x0140" > $CFGTMP ;; gta01v3_config) echo "#define CONFIG_ARCH_GTA01_v3" > $CFGINC echo "GTA01_BIG_RAM=n" > $CFGTMP + echo "CONFIG_USB_DFU_REVISION=0x0130" > $CFGTMP ;; gta01bv2_config) echo "#define CONFIG_ARCH_GTA01B_v2" > $CFGINC echo "GTA01_BIG_RAM=y" > $CFGTMP + echo "CONFIG_USB_DFU_REVISION=0x0220" > $CFGTMP ;; gta01bv3_config) echo "#define CONFIG_ARCH_GTA01B_v3" > $CFGINC echo "GTA01_BIG_RAM=y" > $CFGTMP + echo "CONFIG_USB_DFU_REVISION=0x0230" > $CFGTMP ;; gta01bv4_config) echo "#define CONFIG_ARCH_GTA01B_v4" > $CFGINC echo "GTA01_BIG_RAM=y" > $CFGTMP + echo "CONFIG_USB_DFU_REVISION=0x0240" > $CFGTMP ;; *) echo "$0:: Unrecognised config - using GTA01Bv4 config" echo "#define CONFIG_ARCH_GTA01B_v4" > $CFGINC echo "GTA01_BIG_RAM=y" > $CFGTMP + echo "CONFIG_USB_DFU_REVISION=0x0240" > $CFGTMP ;; esac Index: u-boot/board/neo1973/gta01/config.mk =================================================================== --- u-boot.orig/board/neo1973/gta01/config.mk +++ u-boot/board/neo1973/gta01/config.mk @@ -24,6 +24,9 @@ # # download area is 3200'0000 or 3300'0000 +CONFIG_USB_DFU_VENDOR=0x1457 +CONFIG_USB_DFU_PRODUCT=0x5119 + sinclude $(OBJTREE)/board/$(BOARDDIR)/config.tmp ifeq ($(GTA01_BIG_RAM),y)