/* USB Device Firmware Update Implementation for OpenPCD * (C) 2006-2011 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 #include #include #include #include /* FIXME */ #define __dfudata #define __dfufunc /// Standard device driver instance. static USBDDriver usbdDriver; __dfudata struct dfu dfu = { .state = DFU_STATE_appIDLE, .past_manifest = 0, }; static __dfufunc void handle_getstatus(void) { /* has to be static as USBD_Write is async ? */ static struct dfu_status dstat; dfu_drv_updstatus(); /* send status response */ dstat.bStatus = dfu.status; dstat.bState = dfu.state; dstat.iString = 0; /* FIXME: set dstat.bwPollTimeout */ TRACE_DEBUG("handle_getstatus(%u, %u)\n\r", dstat.bStatus, dstat.bState); USBD_Write(0, (char *)&dstat, sizeof(dstat), NULL, 0); } static void __dfufunc handle_getstate(void) { uint8_t u8 = dfu.state; TRACE_DEBUG("handle_getstate(%u)\n\r", dfu.state); USBD_Write(0, (char *)&u8, sizeof(u8), NULL, 0); } static void TerminateCtrlInWithNull(void *pArg, unsigned char status, unsigned int transferred, unsigned int remaining) { USBD_Write(0, // Endpoint #0 0, // No data buffer 0, // No data buffer (TransferCallback) 0, (void *) 0); } /* this function gets daisy-chained into processing EP0 requests */ void USBDFU_DFU_RequestHandler(const USBGenericRequest *request) { uint8_t req = USBGenericRequest_GetRequest(request); uint16_t len = USBGenericRequest_GetLength(request); uint16_t val = USBGenericRequest_GetValue(request); int rc, ret; TRACE_DEBUG("type=0x%x, recipient=0x%x val=0x%x len=%u\n", USBGenericRequest_GetType(request), USBGenericRequest_GetRecipient(request), val, len); /* check for GET_DESCRIPTOR on DFU */ if (USBGenericRequest_GetType(request) == USBGenericRequest_STANDARD && USBGenericRequest_GetRecipient(request) == USBGenericRequest_DEVICE && USBGenericRequest_GetRequest(request) == USBGenericRequest_GETDESCRIPTOR && USBGetDescriptorRequest_GetDescriptorType(request) == USB_DT_DFU) { uint16_t length = sizeof(struct usb_dfu_func_descriptor); const USBDeviceDescriptor *pDevice; int terminateWithNull; if (USBD_IsHighSpeed()) pDevice = usbdDriver.pDescriptors->pHsDevice; else pDevice = usbdDriver.pDescriptors->pFsDevice; terminateWithNull = ((length % pDevice->bMaxPacketSize0) == 0); USBD_Write(0, &dfu_cfg_descriptor.func_dfu, length, terminateWithNull ? TerminateCtrlInWithNull : 0, 0); return; } /* forward all non-DFU specific messages to core handler*/ if (USBGenericRequest_GetType(request) != USBGenericRequest_CLASS || USBGenericRequest_GetRecipient(request) != USBGenericRequest_INTERFACE) { TRACE_DEBUG("std_ho_usbd "); USBDDriver_RequestHandler(&usbdDriver, request); } switch (dfu.state) { case DFU_STATE_appIDLE: switch (req) { case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; case USB_REQ_DFU_DETACH: TRACE_DEBUG("\r\n====dfu_detach\n\r"); dfu.state = DFU_STATE_appDETACH; ret = DFU_RET_ZLP; goto out; break; default: ret = DFU_RET_STALL; } break; case DFU_STATE_appDETACH: switch (req) { case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu.state = DFU_STATE_appIDLE; ret = DFU_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) { dfu.state = DFU_STATE_dfuERROR; ret = DFU_RET_STALL; goto out; } dfu.state = DFU_STATE_dfuDNLOAD_SYNC; ret = USBDFU_handle_dnload(val, len, 1); break; case USB_REQ_DFU_UPLOAD: dfu.state = DFU_STATE_dfuUPLOAD_IDLE; USBDFU_handle_upload(val, len, 1); break; case USB_REQ_DFU_ABORT: /* no zlp? */ ret = DFU_RET_ZLP; break; case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu.state = DFU_STATE_dfuERROR; ret = DFU_RET_STALL; goto out; break; } break; case DFU_STATE_dfuDNLOAD_SYNC: switch (req) { case USB_REQ_DFU_GETSTATUS: handle_getstatus(); /* FIXME: state transition depending on block completeness */ break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu.state = DFU_STATE_dfuERROR; ret = DFU_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(); break; default: dfu.state = DFU_STATE_dfuERROR; ret = DFU_RET_STALL; goto out; } break; case DFU_STATE_dfuDNLOAD_IDLE: switch (req) { case USB_REQ_DFU_DNLOAD: dfu.state = DFU_STATE_dfuDNLOAD_SYNC; ret = USBDFU_handle_dnload(val, len, 0); break; case USB_REQ_DFU_ABORT: dfu.state = DFU_STATE_dfuIDLE; ret = DFU_RET_ZLP; break; case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu.state = DFU_STATE_dfuERROR; ret = DFU_RET_STALL; break; } break; case DFU_STATE_dfuMANIFEST_SYNC: switch (req) { case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu.state = DFU_STATE_dfuERROR; ret = DFU_RET_STALL; break; } break; case DFU_STATE_dfuMANIFEST: switch (req) { case USB_REQ_DFU_GETSTATUS: /* we don't want to change to WAIT_RST, as it * would mean that we can not support another * DFU transaction before doing the actual * reset. Instead, we switch to idle and note * that we've already been through MANIFST in * the global variable 'past_manifest'. */ //dfu.state = DFU_STATE_dfuMANIFEST_WAIT_RST; dfu.state = DFU_STATE_dfuIDLE; dfu.past_manifest = 1; handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu.state = DFU_STATE_dfuERROR; ret = DFU_RET_STALL; break; } 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 = USBDFU_handle_upload(val, len, 0); if (rc >= 0 && rc < len) dfu.state = DFU_STATE_dfuIDLE; break; case USB_REQ_DFU_ABORT: dfu.state = DFU_STATE_dfuIDLE; /* no zlp? */ ret = DFU_RET_ZLP; break; case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu.state = DFU_STATE_dfuERROR; ret = DFU_RET_STALL; break; } break; case DFU_STATE_dfuERROR: switch (req) { case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; case USB_REQ_DFU_CLRSTATUS: dfu.state = DFU_STATE_dfuIDLE; dfu.status = DFU_STATUS_OK; /* no zlp? */ ret = DFU_RET_ZLP; break; default: dfu.state = DFU_STATE_dfuERROR; ret = DFU_RET_STALL; break; } break; } out: switch (ret) { case DFU_RET_NOTHING: break; case DFU_RET_ZLP: USBD_Write(0, 0, 0, 0, 0); break; case DFU_RET_STALL: USBD_Stall(0); break; } } void USBDFU_Initialize(const USBDDriverDescriptors *pDescriptors, unsigned char *pInterfaces) { /* We already start in DFU idle mode */ dfu.state = DFU_STATE_dfuIDLE; USBDDriver_Initialize(&usbdDriver, pDescriptors, pInterfaces); USBD_Init(); }