sam7fc/src/dfu.c

339 lines
7.2 KiB
C

/***************************************************************************
* 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 <stdio.h>
#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;
}
}