/* * (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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DT_DFU_SIZE 9 #define USB_DT_DFU 0x21 #define CONFIG_USBD_DFU_XFER_SIZE 4096 #define DFU_TEMPFILE "/dfu_temp" static int dfualt; static int dfufd = -EINVAL;; static struct usb_dfu_dev *dfu_devs; static int dfu_num_alt; /* USB DFU functional descriptor */ static struct usb_dfu_func_descriptor usb_dfu_func = { .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, }; struct f_dfu { struct usb_function func; u8 port_num; u8 dfu_state; u8 dfu_status; struct usb_request *dnreq; }; static inline struct f_dfu *func_to_dfu(struct usb_function *f) { return container_of(f, struct f_dfu, func); } /* static strings, in UTF-8 */ static struct usb_string *dfu_string_defs; static struct usb_gadget_strings dfu_string_table = { .language = 0x0409, /* en-us */ }; static struct usb_gadget_strings *dfu_strings[] = { &dfu_string_table, NULL, }; static struct usb_interface_descriptor dfu_control_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ .bNumEndpoints = 0, .bInterfaceClass = 0xfe, .bInterfaceSubClass = 1, .bInterfaceProtocol = 1, /* .iInterface = DYNAMIC */ }; static int dfu_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_descriptor_header **header; struct usb_interface_descriptor *desc; int i; int status; /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) return status; dfu_control_interface_desc.bInterfaceNumber = status; header = xzalloc(sizeof(void *) * (dfu_num_alt + 2)); desc = xzalloc(sizeof(struct usb_interface_descriptor) * dfu_num_alt); for (i = 0; i < dfu_num_alt; i++) { desc[i].bLength = USB_DT_INTERFACE_SIZE; desc[i].bDescriptorType = USB_DT_INTERFACE; desc[i].bNumEndpoints = 0; desc[i].bInterfaceClass = 0xfe; desc[i].bInterfaceSubClass = 1; desc[i].bInterfaceProtocol = 1; desc[i].bAlternateSetting = i; desc[i].iInterface = dfu_string_defs[i + 1].id; header[i] = (struct usb_descriptor_header *)&desc[i]; } header[i] = (struct usb_descriptor_header *) &usb_dfu_func; header[i + 1] = NULL; /* copy descriptors, and track endpoint copies */ f->descriptors = usb_copy_descriptors(header); if (!f->descriptors) goto out; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at * both speeds */ if (gadget_is_dualspeed(c->cdev->gadget)) { /* copy descriptors, and track endpoint copies */ f->hs_descriptors = usb_copy_descriptors(header); } for (i = 0; i < dfu_num_alt; i++) printf("dfu: register alt%d(%s) with device %s\n", i, dfu_devs[i].name, dfu_devs[i].dev); out: free(desc); free(header); if (status) ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); return status; } static void dfu_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_dfu *dfu = func_to_dfu(f); free(f->descriptors); if (gadget_is_dualspeed(c->cdev->gadget)) free(f->hs_descriptors); free(dfu->dnreq->buf); usb_ep_free_request(c->cdev->gadget->ep0, dfu->dnreq); free(dfu); } static int dfu_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { dfualt = alt; return 0; } static int dfu_status(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct f_dfu *dfu = func_to_dfu(f); struct usb_composite_dev *cdev = f->config->cdev; struct usb_request *req = cdev->req; struct dfu_status *dstat = (struct dfu_status *) req->buf; dstat->bStatus = dfu->dfu_status; dstat->bState = dfu->dfu_state; dstat->iString = 0; return sizeof(*dstat); } static void dfu_cleanup(struct f_dfu *dfu) { struct stat s; if (dfufd > 0) { close(dfufd); dfufd = -EINVAL; } if (!stat(DFU_TEMPFILE, &s)) unlink(DFU_TEMPFILE); } static void dn_complete(struct usb_ep *ep, struct usb_request *req) { struct f_dfu *dfu = req->context; int ret; ret = write(dfufd, req->buf, req->length); if (ret < (int)req->length) { perror("write"); dfu->dfu_status = DFU_STATUS_errWRITE; dfu_cleanup(dfu); } } static int handle_dnload(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct f_dfu *dfu = func_to_dfu(f); struct usb_composite_dev *cdev = f->config->cdev; u16 w_length = le16_to_cpu(ctrl->wLength); int ret; if (w_length == 0) { dfu->dfu_state = DFU_STATE_dfuIDLE; if (dfu_devs[dfualt].flags & DFU_FLAG_SAVE) { int fd; fd = open(dfu_devs[dfualt].dev, O_WRONLY); if (fd < 0) { perror("open"); ret = -EINVAL; goto err_out; } ret = erase(fd, ~0, 0); close(fd); if (ret && ret != -ENOSYS) { perror("erase"); ret = -EINVAL; goto err_out; } ret = copy_file(DFU_TEMPFILE, dfu_devs[dfualt].dev); if (ret) { printf("copy file failed\n"); ret = -EINVAL; goto err_out; } } dfu_cleanup(dfu); return 0; } dfu->dnreq->length = w_length; dfu->dnreq->context = dfu; usb_ep_queue(cdev->gadget->ep0, dfu->dnreq); return 0; err_out: dfu_cleanup(dfu); return ret; } static void up_complete(struct usb_ep *ep, struct usb_request *req) { } static int handle_upload(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct f_dfu *dfu = func_to_dfu(f); struct usb_composite_dev *cdev = f->config->cdev; u16 w_length = le16_to_cpu(ctrl->wLength); int len; len = read(dfufd, dfu->dnreq->buf, w_length); dfu->dnreq->length = len; if (len < w_length) { dfu_cleanup(dfu); dfu->dfu_state = DFU_STATE_dfuIDLE; } dfu->dnreq->complete = up_complete; usb_ep_queue(cdev->gadget->ep0, dfu->dnreq); return 0; } static void dfu_abort(struct f_dfu *dfu) { dfu->dfu_state = DFU_STATE_dfuIDLE; dfu->dfu_status = DFU_STATUS_OK; dfu_cleanup(dfu); } static int dfu_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct f_dfu *dfu = func_to_dfu(f); struct usb_composite_dev *cdev = f->config->cdev; struct usb_request *req = cdev->req; int value = -EOPNOTSUPP; int w_length = le16_to_cpu(ctrl->wLength); int w_value = le16_to_cpu(ctrl->wValue); int ret; if (ctrl->bRequestType == USB_DIR_IN && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR && (w_value >> 8) == 0x21) { value = min(w_length, (int)sizeof(usb_dfu_func)); memcpy(req->buf, &usb_dfu_func, value); goto out; } /* Allow GETSTATUS in every state */ if (ctrl->bRequest == USB_REQ_DFU_GETSTATUS) { value = dfu_status(f, ctrl); value = min(value, w_length); goto out; } /* Allow GETSTATE in every state */ if (ctrl->bRequest == USB_REQ_DFU_GETSTATE) { *(u8 *)req->buf = dfu->dfu_state; value = sizeof(u8); goto out; } switch (dfu->dfu_state) { case DFU_STATE_appIDLE: switch (ctrl->bRequest) { case USB_REQ_DFU_DETACH: dfu->dfu_state = DFU_STATE_appDETACH; value = 0; goto out; break; default: value = -EINVAL; } break; case DFU_STATE_appDETACH: switch (ctrl->bRequest) { default: dfu->dfu_state = DFU_STATE_appIDLE; value = -EINVAL; goto out; break; } break; case DFU_STATE_dfuIDLE: switch (ctrl->bRequest) { case USB_REQ_DFU_DNLOAD: if (w_length == 0) { dfu->dfu_state = DFU_STATE_dfuERROR; value = -EINVAL; goto out; } debug("dfu: starting download to %s\n", dfu_devs[dfualt].dev); if (dfu_devs[dfualt].flags & DFU_FLAG_SAVE) dfufd = open(DFU_TEMPFILE, O_WRONLY | O_CREAT); else dfufd = open(dfu_devs[dfualt].dev, O_WRONLY); if (dfufd < 0) { dfu->dfu_state = DFU_STATE_dfuERROR; perror("open"); goto out; } ret = erase(dfufd, ~0, 0); if (ret && ret != -ENOSYS) { dfu->dfu_status = DFU_STATUS_errERASE; perror("erase"); goto out; } value = handle_dnload(f, ctrl); dfu->dfu_state = DFU_STATE_dfuDNLOAD_IDLE; return 0; break; case USB_REQ_DFU_UPLOAD: dfu->dfu_state = DFU_STATE_dfuUPLOAD_IDLE; debug("dfu: starting upload from %s\n", dfu_devs[dfualt].dev); if (!(dfu_devs[dfualt].flags & DFU_FLAG_READBACK)) { dfu->dfu_state = DFU_STATE_dfuERROR; goto out; } dfufd = open(dfu_devs[dfualt].dev, O_RDONLY); if (dfufd < 0) { dfu->dfu_state = DFU_STATE_dfuERROR; perror("open"); goto out; } handle_upload(f, ctrl); return 0; break; case USB_REQ_DFU_ABORT: dfu->dfu_status = DFU_STATUS_OK; value = 0; 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 :( */ dfu->dfu_state = DFU_STATE_dfuMANIFEST_WAIT_RST; break; default: dfu->dfu_state = DFU_STATE_dfuERROR; value = -EINVAL; goto out; break; } break; case DFU_STATE_dfuDNLOAD_IDLE: switch (ctrl->bRequest) { case USB_REQ_DFU_DNLOAD: value = handle_dnload(f, ctrl); if (dfu->dfu_state != DFU_STATE_dfuIDLE) { return 0; } break; case USB_REQ_DFU_ABORT: dfu_abort(dfu); value = 0; break; default: dfu->dfu_state = DFU_STATE_dfuERROR; value = -EINVAL; break; } break; case DFU_STATE_dfuUPLOAD_IDLE: switch (ctrl->bRequest) { case USB_REQ_DFU_UPLOAD: handle_upload(f, ctrl); return 0; break; case USB_REQ_DFU_ABORT: dfu_abort(dfu); value = 0; break; default: dfu->dfu_state = DFU_STATE_dfuERROR; value = -EINVAL; break; } break; case DFU_STATE_dfuERROR: switch (ctrl->bRequest) { case USB_REQ_DFU_CLRSTATUS: dfu_abort(dfu); /* no zlp? */ value = 0; break; default: dfu->dfu_state = DFU_STATE_dfuERROR; value = -EINVAL; break; } break; case DFU_STATE_dfuDNLOAD_SYNC: case DFU_STATE_dfuDNBUSY: case DFU_STATE_dfuMANIFEST_SYNC: case DFU_STATE_dfuMANIFEST: case DFU_STATE_dfuMANIFEST_WAIT_RST: dfu->dfu_state = DFU_STATE_dfuERROR; value = -EINVAL; goto out; break; default: break; } out: /* respond with data transfer or status phase? */ if (value >= 0) { req->zero = 0; req->length = value; value = usb_ep_queue(cdev->gadget->ep0, req); if (value < 0) ERROR(cdev, "dfu response on ttyGS%d, err %d\n", dfu->port_num, value); } return value; } static void dfu_disable(struct usb_function *f) { struct f_dfu *dfu = func_to_dfu(f); switch (dfu->dfu_state) { case DFU_STATE_appDETACH: dfu->dfu_state = DFU_STATE_dfuIDLE; break; case DFU_STATE_dfuMANIFEST_WAIT_RST: dfu->dfu_state = DFU_STATE_appIDLE; break; default: dfu->dfu_state = DFU_STATE_appDETACH; break; } dfu_cleanup(dfu); } #define STRING_MANUFACTURER_IDX 0 #define STRING_PRODUCT_IDX 1 #define STRING_DESCRIPTION_IDX 2 static struct usb_string strings_dev[] = { [STRING_MANUFACTURER_IDX].s = NULL, [STRING_PRODUCT_IDX].s = NULL, [STRING_DESCRIPTION_IDX].s = "USB Device Firmware Upgrade", { } /* end of list */ }; static struct usb_gadget_strings stringtab_dev = { .language = 0x0409, /* en-us */ .strings = strings_dev, }; static struct usb_gadget_strings *dev_strings[] = { &stringtab_dev, NULL, }; static int dfu_bind_config(struct usb_configuration *c) { struct f_dfu *dfu; struct usb_function *func; int status; int i; /* config description */ status = usb_string_id(c->cdev); if (status < 0) return status; strings_dev[STRING_DESCRIPTION_IDX].id = status; status = usb_string_id(c->cdev); if (status < 0) return status; dfu_control_interface_desc.iInterface = status; /* allocate and initialize one new instance */ dfu = xzalloc(sizeof *dfu); dfu->dfu_state = DFU_STATE_appIDLE; dfu->dfu_status = DFU_STATUS_OK; dfu_string_defs = xzalloc(sizeof(struct usb_string) * (dfu_num_alt + 2)); dfu_string_defs[0].s = "Generic DFU"; dfu_string_defs[0].id = status; for (i = 0; i < dfu_num_alt; i++) { dfu_string_defs[i + 1].s = dfu_devs[i].name; status = usb_string_id(c->cdev); if (status < 0) goto out; dfu_string_defs[i + 1].id = status; } dfu_string_defs[i + 1].s = NULL; dfu_string_table.strings = dfu_string_defs; func = &dfu->func; func->name = "DFU"; func->strings = dfu_strings; /* descriptors are per-instance copies */ func->bind = dfu_bind; func->unbind = dfu_unbind; func->set_alt = dfu_set_alt; func->setup = dfu_setup; func->disable = dfu_disable; dfu->dnreq = usb_ep_alloc_request(c->cdev->gadget->ep0); if (!dfu->dnreq) printf("usb_ep_alloc_request failed\n"); dfu->dnreq->buf = xmalloc(CONFIG_USBD_DFU_XFER_SIZE); dfu->dnreq->complete = dn_complete; dfu->dnreq->zero = 0; status = usb_add_function(c, func); if (status) goto out; return 0; out: free(dfu); free(dfu_string_defs); return status; } static void dfu_unbind_config(struct usb_configuration *c) { free(dfu_string_defs); } static struct usb_configuration dfu_config_driver = { .label = "USB DFU", .bind = dfu_bind_config, .unbind = dfu_unbind_config, .bConfigurationValue = 1, .bmAttributes = USB_CONFIG_ATT_SELFPOWER, }; static struct usb_device_descriptor dfu_dev_descriptor = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, .bcdUSB = 0x0100, .bDeviceClass = 0x00, .bDeviceSubClass = 0x00, .bDeviceProtocol = 0x00, /* .idVendor = dynamic */ /* .idProduct = dynamic */ .bcdDevice = 0x0000, .bNumConfigurations = 0x01, }; static int dfu_driver_bind(struct usb_composite_dev *cdev) { int status; status = usb_string_id(cdev); if (status < 0) goto fail; strings_dev[STRING_MANUFACTURER_IDX].id = status; dfu_dev_descriptor.iManufacturer = status; status = usb_string_id(cdev); if (status < 0) goto fail; strings_dev[STRING_PRODUCT_IDX].id = status; dfu_dev_descriptor.iProduct = status; /* config description */ status = usb_string_id(cdev); if (status < 0) goto fail; strings_dev[STRING_DESCRIPTION_IDX].id = status; dfu_config_driver.iConfiguration = status; status = usb_add_config(cdev, &dfu_config_driver); if (status < 0) goto fail; return 0; fail: return status; } static struct usb_composite_driver dfu_driver = { .name = "g_dfu", .dev = &dfu_dev_descriptor, .strings = dev_strings, .bind = dfu_driver_bind, }; int usb_dfu_register(struct usb_dfu_pdata *pdata) { dfu_devs = pdata->alts; dfu_num_alt = pdata->num_alts; dfu_dev_descriptor.idVendor = pdata->idVendor; dfu_dev_descriptor.idProduct = pdata->idProduct; strings_dev[STRING_MANUFACTURER_IDX].s = pdata->manufacturer; strings_dev[STRING_PRODUCT_IDX].s = pdata->productname; usb_composite_register(&dfu_driver); while (1) { usb_gadget_poll(); if (ctrlc()) goto out; } out: usb_composite_unregister(&dfu_driver); return 0; }