/*************************************************************************** * Copyright (C) 01/2008 by Olaf Rempel * * razzor@kopf-tisch.de * * * * 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; version 2 of the License * * * * 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 "AT91SAM7S256.h" #include "board.h" #include "at91_udp.h" #include "usb_ch9.h" #include "usb_dfu.h" static uint8_t dfu_state = DFU_STATE_dfuIDLE; static uint8_t dfu_status = DFU_STATUS_OK; #define RET_NOTHING 0 #define RET_STALL 1 #define RET_ZLP 2 static void handle_getstatus(void) { struct dfu_status dstat; uint32_t fmr = *AT91C_MC_FMR; switch (dfu_state) { case DFU_STATE_dfuDNLOAD_SYNC: case DFU_STATE_dfuDNBUSY: if (fmr & AT91C_MC_PROGE) { dfu_status = DFU_STATUS_errPROG; dfu_state = DFU_STATE_dfuERROR; } else if (fmr & AT91C_MC_LOCKE) { dfu_status = DFU_STATUS_errWRITE; dfu_state = DFU_STATE_dfuERROR; } else if (fmr & AT91C_MC_FRDY) { dfu_state = DFU_STATE_dfuDNLOAD_IDLE; } else { // dfu_state = DFU_STATE_dfuDNBUSY; dfu_state = DFU_STATE_dfuDNLOAD_IDLE; } break; } /* send status response */ dstat.bStatus = dfu_status; dstat.bState = dfu_state; dstat.iString = 0; /* FIXME: set dstat.bwPollTimeout */ ep_transfer_send(0, (char *)&dstat, sizeof(dstat), NULL); } static void handle_getstate(void) { ep_transfer_send(0, (char *)&dfu_state, sizeof(dfu_state), NULL); } static uint8_t buf[256]; static void handle_dnload_cb(void) { ep_transfer_send(0, NULL, 0, NULL); } static uint32_t handle_dnload(uint16_t index, uint16_t value, uint16_t length) { printf("down:%x-%x-%x\n\r", index, value, length); if (length == 0) { dfu_state = DFU_STATE_dfuMANIFEST_SYNC; return RET_ZLP; } ep_transfer_receive(0, (char *)&buf, length, handle_dnload_cb); return RET_NOTHING; } static uint32_t handle_upload(uint16_t index, uint16_t value, uint16_t length) { printf("up:%x-%x-%x\n\r", index, value, length); return 0; } void ep0_handle_dfu(struct usb_ctrlrequest *req) { uint32_t rc, ret = RET_NOTHING; // printf("state:%x\n\r", dfu_state); switch (dfu_state) { case DFU_STATE_appIDLE: switch (req->bRequest) { case USB_REQ_DFU_DETACH: dfu_state = DFU_STATE_appDETACH; ret = RET_ZLP; break; case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: ret = RET_STALL; break; } break; case DFU_STATE_appDETACH: switch (req->bRequest) { 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; break; } /* FIXME: implement timer to return to appIDLE */ break; case DFU_STATE_dfuIDLE: switch (req->bRequest) { case USB_REQ_DFU_DNLOAD: if (req->wLength == 0) { dfu_state = DFU_STATE_dfuERROR; ret = RET_STALL; break; } dfu_state = DFU_STATE_dfuDNLOAD_SYNC; ret = handle_dnload(req->wIndex, req->wValue, req->wLength); break; case USB_REQ_DFU_UPLOAD: dfu_state = DFU_STATE_dfuUPLOAD_IDLE; handle_upload(req->wIndex, req->wValue, req->wLength); break; case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; case USB_REQ_DFU_ABORT: /* no zlp? */ ret = RET_ZLP; break; default: dfu_state = DFU_STATE_dfuERROR; ret = RET_STALL; break; } break; case DFU_STATE_dfuDNLOAD_SYNC: switch (req->bRequest) { 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; break; } break; case DFU_STATE_dfuDNBUSY: switch (req->bRequest) { 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; break; } break; case DFU_STATE_dfuDNLOAD_IDLE: switch (req->bRequest) { case USB_REQ_DFU_DNLOAD: dfu_state = DFU_STATE_dfuDNLOAD_SYNC; ret = handle_dnload(req->wIndex, req->wValue, req->wLength); break; case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; case USB_REQ_DFU_ABORT: dfu_state = DFU_STATE_dfuIDLE; ret = RET_ZLP; break; default: dfu_state = DFU_STATE_dfuERROR; ret = RET_STALL; break; } break; case DFU_STATE_dfuMANIFEST_SYNC: switch (req->bRequest) { case USB_REQ_DFU_GETSTATUS: handle_getstatus(); dfu_state = DFU_STATE_dfuIDLE; break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu_state = DFU_STATE_dfuERROR; ret = RET_STALL; break; } break; case DFU_STATE_dfuMANIFEST: 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->bRequest) { case USB_REQ_DFU_UPLOAD: /* state transition if less data then requested */ rc = handle_upload(req->wIndex, req->wValue, req->wLength); if (rc >= 0 && rc < req->wLength) dfu_state = DFU_STATE_dfuIDLE; break; case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; case USB_REQ_DFU_ABORT: dfu_state = DFU_STATE_dfuIDLE; /* no zlp? */ ret = RET_ZLP; break; default: dfu_state = DFU_STATE_dfuERROR; ret = RET_STALL; break; } break; case DFU_STATE_dfuERROR: switch (req->bRequest) { case USB_REQ_DFU_GETSTATUS: handle_getstatus(); break; case USB_REQ_DFU_CLRSTATUS: dfu_state = DFU_STATE_dfuIDLE; dfu_status = DFU_STATUS_OK; /* no zlp? */ ret = RET_ZLP; break; case USB_REQ_DFU_GETSTATE: handle_getstate(); break; default: dfu_state = DFU_STATE_dfuERROR; ret = RET_STALL; break; } break; } switch (ret) { case RET_NOTHING: break; case RET_ZLP: ep_transfer_send(0, NULL, 0, NULL); break; case RET_STALL: ep_send_stall(0); break; } }