/* 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 /* FIXME */ #define __dfudata #define __dfufunc /// Standard device driver instance. static USBDDriver usbdDriver; #define RET_NOTHING 0 #define RET_ZLP 1 #define RET_STALL 2 __dfudata struct dfu dfu = { .state = DFU_STATE_appIDLE, .past_manifest = 0, }; static __dfufunc void handle_getstatus(void) { 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 */ USBD_Write(0, (char *)&dstat, sizeof(dstat), NULL, 0); } static void __dfufunc handle_getstate(void) { uint8_t u8 = dfu.state; USBD_Write(0, (char *)&u8, sizeof(u8), NULL, 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; /* only process actual DFU specific messages */ 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: 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(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: 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) { dfu.state = DFU_STATE_dfuERROR; ret = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = RET_ZLP; break; default: dfu.state = DFU_STATE_dfuERROR; ret = RET_STALL; break; } break; } out: switch (ret) { case RET_NOTHING: break; case RET_ZLP: USBD_Write(0, 0, 0, 0, 0); break; case RET_STALL: USBD_Stall(0); break; } } void USBDFU_Initialize(const USBDDriverDescriptors *pDescriptors, unsigned char *pInterfaces) { USBDDriver_Initialize(&usbdDriver, pDescriptors, pInterfaces); USBD_Init(); }