From 39cb47ed0951074afb24687fcb6e66947b4f8ef8 Mon Sep 17 00:00:00 2001 From: henryk Date: Wed, 7 Nov 2007 16:28:57 +0000 Subject: Start working at dfu by copying from main openpcd repository (with modifications), doesn't boot application yet git-svn-id: https://svn.openpcd.org:2342/trunk@315 6dc7ffe9-61d6-0310-9af1-9938baff3ed1 --- openpicc/dfu/dfu.c | 938 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 938 insertions(+) create mode 100644 openpicc/dfu/dfu.c (limited to 'openpicc/dfu/dfu.c') diff --git a/openpicc/dfu/dfu.c b/openpicc/dfu/dfu.c new file mode 100644 index 0000000..af09a62 --- /dev/null +++ b/openpicc/dfu/dfu.c @@ -0,0 +1,938 @@ +/* USB Device Firmware Update Implementation for OpenPCD + * (C) 2006 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 "usb_strings_dfu.h" + +#include +#include +#include +//#include +//#include "../openpcd.h" + +#include + +#define SAM7DFU_SIZE 0x4000 + +/* If debug is enabled, we need to access debug functions from flash + * and therefore have to omit flashing */ +#define DEBUG_DFU_NOFLASH + +#ifdef DEBUG +#define DEBUG_DFU_EP0 +//#define DEBUG_DFU_RECV +#endif + +#ifdef DEBUG_DFU_EP0 +#define DEBUGE DEBUGP +#else +#define DEBUGE(x, args ...) +#endif + +#ifdef DEBUG_DFU_RECV +#define DEBUGR DEBUGP +#else +#define DEBUGR(x, args ...) +#endif + +#define RET_NOTHING 0 +#define RET_ZLP 1 +#define RET_STALL 2 + +#define led1on() AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, OPENPCD_PIO_LED1) +#define led1off() AT91F_PIO_SetOutput(AT91C_BASE_PIOA, OPENPCD_PIO_LED1) + +#define led2on() AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, OPENPCD_PIO_LED2) +#define led2off() AT91F_PIO_SetOutput(AT91C_BASE_PIOA, OPENPCD_PIO_LED2) + +static void __dfufunc udp_init(void) +{ + /* Set the PLL USB Divider */ + AT91C_BASE_CKGR->CKGR_PLLR |= AT91C_CKGR_USBDIV_1; + + /* Enables the 48MHz USB clock UDPCK and System Peripheral USB Clock */ + AT91C_BASE_PMC->PMC_SCER = AT91C_PMC_UDP; + AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_UDP); + + /* Enable UDP PullUp (USB_DP_PUP) : enable & Clear of the + * corresponding PIO Set in PIO mode and Configure in Output */ +#if defined(PCD) + AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPCD_PIO_UDP_PUP); +#endif + AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPCD_PIO_UDP_PUPv4); +} + +/* Send Data through the control endpoint */ +static void __dfufunc udp_ep0_send_data(const char *pData, u_int32_t length) +{ + AT91PS_UDP pUdp = AT91C_BASE_UDP; + u_int32_t cpt = 0; + AT91_REG csr; + + DEBUGE("send_data: %u bytes ", length); + + do { + cpt = MIN(length, 8); + length -= cpt; + + while (cpt--) + pUdp->UDP_FDR[0] = *pData++; + + if (pUdp->UDP_CSR[0] & AT91C_UDP_TXCOMP) { + pUdp->UDP_CSR[0] &= ~(AT91C_UDP_TXCOMP); + while (pUdp->UDP_CSR[0] & AT91C_UDP_TXCOMP) ; + } + + pUdp->UDP_CSR[0] |= AT91C_UDP_TXPKTRDY; + do { + csr = pUdp->UDP_CSR[0]; + + /* Data IN stage has been stopped by a status OUT */ + if (csr & AT91C_UDP_RX_DATA_BK0) { + pUdp->UDP_CSR[0] &= ~(AT91C_UDP_RX_DATA_BK0); + DEBUGE("stopped by status out "); + return; + } + } while (!(csr & AT91C_UDP_TXCOMP)); + + } while (length); + + if (pUdp->UDP_CSR[0] & AT91C_UDP_TXCOMP) { + pUdp->UDP_CSR[0] &= ~(AT91C_UDP_TXCOMP); + while (pUdp->UDP_CSR[0] & AT91C_UDP_TXCOMP) ; + } +} + +static void udp_ep0_recv_clean(void) +{ + unsigned int i; + u_int8_t dummy; + const AT91PS_UDP pUdp = AT91C_BASE_UDP; + + while (!(pUdp->UDP_CSR[0] & AT91C_UDP_RX_DATA_BK0)) ; + + for (i = 0; i < (pUdp->UDP_CSR[0] >> 16); i++) + dummy = pUdp->UDP_FDR[0]; + + pUdp->UDP_CSR[0] &= ~(AT91C_UDP_RX_DATA_BK0); +} + +/* receive data from EP0 */ +static int __dfufunc udp_ep0_recv_data(u_int8_t *data, u_int16_t len) +{ + AT91PS_UDP pUdp = AT91C_BASE_UDP; + AT91_REG csr; + u_int16_t i, num_rcv; + u_int32_t num_rcv_total = 0; + + do { + /* FIXME: do we need to check whether we've been interrupted + * by a RX SETUP stage? */ + do { + csr = pUdp->UDP_CSR[0]; + DEBUGR("CSR=%08x ", csr); + } while (!(csr & AT91C_UDP_RX_DATA_BK0)) ; + + num_rcv = pUdp->UDP_CSR[0] >> 16; + + /* make sure we don't read more than requested */ + if (num_rcv_total + num_rcv > len) + num_rcv = num_rcv_total - len; + + DEBUGR("num_rcv = %u ", num_rcv); + for (i = 0; i < num_rcv; i++) + *data++ = pUdp->UDP_FDR[0]; + pUdp->UDP_CSR[0] &= ~(AT91C_UDP_RX_DATA_BK0); + + num_rcv_total += num_rcv; + + /* we need to continue to pull data until we either receive + * a packet < endpoint size or == 0 */ + } while (num_rcv == 8 && num_rcv_total < len); + + DEBUGE("ep0_rcv_returning(%u total) ", num_rcv_total); + + return num_rcv_total; +} + +/* Send zero length packet through the control endpoint */ +static void __dfufunc udp_ep0_send_zlp(void) +{ + AT91PS_UDP pUdp = AT91C_BASE_UDP; + pUdp->UDP_CSR[0] |= AT91C_UDP_TXPKTRDY; + while (!(pUdp->UDP_CSR[0] & AT91C_UDP_TXCOMP)) ; + pUdp->UDP_CSR[0] &= ~(AT91C_UDP_TXCOMP); + while (pUdp->UDP_CSR[0] & AT91C_UDP_TXCOMP) ; +} + +/* Stall the control endpoint */ +static void __dfufunc udp_ep0_send_stall(void) +{ + AT91PS_UDP pUdp = AT91C_BASE_UDP; + pUdp->UDP_CSR[0] |= AT91C_UDP_FORCESTALL; + while (!(pUdp->UDP_CSR[0] & AT91C_UDP_ISOERROR)) ; + pUdp->UDP_CSR[0] &= ~(AT91C_UDP_FORCESTALL | AT91C_UDP_ISOERROR); + while (pUdp->UDP_CSR[0] & (AT91C_UDP_FORCESTALL | AT91C_UDP_ISOERROR)) ; +} + + +static u_int8_t *ptr = (u_int8_t *) AT91C_IFLASH + SAM7DFU_SIZE; +static __dfudata u_int8_t dfu_status; +__dfudata u_int32_t dfu_state = DFU_STATE_appIDLE; +static u_int32_t pagebuf32[AT91C_IFLASH_PAGE_SIZE/4]; + +static int __dfufunc handle_dnload(u_int16_t val, u_int16_t len) +{ + volatile u_int32_t *p = (volatile u_int32_t *)ptr; + u_int8_t *pagebuf = (u_int8_t *) pagebuf32; + int i; + + DEBUGE("download "); + + if (len > AT91C_IFLASH_PAGE_SIZE) { + /* Too big. Not that we'd really care, but it's a + * DFU protocol violation */ + DEBUGP("length exceeds flash page size "); + dfu_state = DFU_STATE_dfuERROR; + dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + if (len & 0x3) { + /* reject non-four-byte-aligned writes */ + DEBUGP("not four-byte-aligned length "); + dfu_state = DFU_STATE_dfuERROR; + dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + if (len == 0) { + DEBUGP("zero-size write -> MANIFEST_SYNC "); + flash_page(p); + dfu_state = DFU_STATE_dfuMANIFEST_SYNC; + return RET_ZLP; + } + if (ptr + len >= (u_int8_t *) AT91C_IFLASH + AT91C_IFLASH_SIZE - ENVIRONMENT_SIZE ) { + DEBUGP("end of write exceeds flash end "); + dfu_state = DFU_STATE_dfuERROR; + dfu_status = DFU_STATUS_errADDRESS; + return RET_STALL; + } + + DEBUGP("try_to_recv=%u ", len); + udp_ep0_recv_data(pagebuf, len); + + DEBUGR(hexdump(pagebuf, len)); + + /* we can only access the write buffer with correctly aligned + * 32bit writes ! */ +#ifndef DEBUG_DFU_NOFLASH + DEBUGP("copying "); + for (i = 0; i < len/4; i++) { + *p++ = pagebuf32[i]; + /* If we have filled a page buffer, flash it */ + if (((unsigned long)p % AT91C_IFLASH_PAGE_SIZE) == 0) { + DEBUGP("page_full "); + flash_page(p-1); + } + } + ptr = (u_int8_t *) p; +#endif + + return RET_ZLP; +} + +#define AT91C_IFLASH_END ((u_int8_t *)AT91C_IFLASH + AT91C_IFLASH_SIZE) +static __dfufunc int handle_upload(u_int16_t val, u_int16_t len) +{ + DEBUGE("upload "); + if (len > AT91C_IFLASH_PAGE_SIZE) { + /* Too big */ + dfu_state = DFU_STATE_dfuERROR; + dfu_status = DFU_STATUS_errADDRESS; + udp_ep0_send_stall(); + return -EINVAL; + } + + if (ptr + len > AT91C_IFLASH_END) + len = AT91C_IFLASH_END - (u_int8_t *)ptr; + + udp_ep0_send_data((char *)ptr, len); + ptr+= len; + + return len; +} + +static __dfufunc void handle_getstatus(void) +{ + struct dfu_status dstat; + u_int32_t fsr = AT91F_MC_EFC_GetStatus(AT91C_BASE_MC); + + DEBUGE("getstatus(fsr=0x%08x) ", fsr); + + switch (dfu_state) { + case DFU_STATE_dfuDNLOAD_SYNC: + case DFU_STATE_dfuDNBUSY: + if (fsr & AT91C_MC_PROGE) { + DEBUGE("errPROG "); + dfu_status = DFU_STATUS_errPROG; + dfu_state = DFU_STATE_dfuERROR; + } else if (fsr & AT91C_MC_LOCKE) { + DEBUGE("errWRITE "); + dfu_status = DFU_STATUS_errWRITE; + dfu_state = DFU_STATE_dfuERROR; + } else if (fsr & AT91C_MC_FRDY) { + DEBUGE("DNLOAD_IDLE "); + dfu_state = DFU_STATE_dfuDNLOAD_IDLE; + } else { + DEBUGE("DNBUSY "); + dfu_state = DFU_STATE_dfuDNBUSY; + } + break; + case DFU_STATE_dfuMANIFEST_SYNC: + dfu_state = DFU_STATE_dfuMANIFEST; + break; + } + + /* send status response */ + dstat.bStatus = dfu_status; + dstat.bState = dfu_state; + dstat.iString = 0; + /* FIXME: set dstat.bwPollTimeout */ + + udp_ep0_send_data((char *)&dstat, sizeof(dstat)); +} + +static void __dfufunc handle_getstate(void) +{ + u_int8_t u8 = dfu_state; + DEBUGE("getstate "); + + udp_ep0_send_data((char *)&u8, sizeof(u8)); +} + +/* callback function for DFU requests */ +int __dfufunc dfu_ep0_handler(u_int8_t req_type, u_int8_t req, + u_int16_t val, u_int16_t len) +{ + int rc, ret = RET_NOTHING; + + DEBUGE("old_state = %u ", dfu_state); + + 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; + ptr = (u_int8_t *) AT91C_IFLASH + SAM7DFU_SIZE; + ret = handle_dnload(val, len); + break; + case USB_REQ_DFU_UPLOAD: + ptr = (u_int8_t *) AT91C_IFLASH + SAM7DFU_SIZE; + dfu_state = DFU_STATE_dfuUPLOAD_IDLE; + handle_upload(val, len); + 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 = handle_dnload(val, len); + 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: + 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) { + case USB_REQ_DFU_UPLOAD: + /* state transition if less data then requested */ + rc = handle_upload(val, len); + 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: + DEBUGE("new_state = %u\r\n", dfu_state); + + switch (ret) { + case RET_NOTHING: + break; + case RET_ZLP: + udp_ep0_send_zlp(); + break; + case RET_STALL: + udp_ep0_send_stall(); + break; + } + return 0; +} + +static u_int8_t cur_config; + +/* USB DFU Device descriptor in DFU mode */ +__dfustruct const struct usb_device_descriptor dfu_dev_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0100, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 8, + .idVendor = USB_VENDOR_ID, + .idProduct = USB_PRODUCT_ID, + .bcdDevice = 0x0000, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 0x00, + .bNumConfigurations = 0x01, +}; + +/* USB DFU Config descriptor in DFU mode */ +__dfustruct const struct _dfu_desc dfu_cfg_descriptor = { + .ucfg = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = USB_DT_CONFIG_SIZE + + 2* USB_DT_INTERFACE_SIZE + + USB_DT_DFU_SIZE, + .bNumInterfaces = 1, + .bConfigurationValue = 1, +#ifdef CONFIG_USB_STRING + .iConfiguration = 3, +#else + .iConfiguration = 0, +#endif + .bmAttributes = USB_CONFIG_ATT_ONE, + .bMaxPower = 100, + }, + .uif[0] = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, +#ifdef CONFIG_USB_STRING + .iInterface = 4, +#else + .iInterface = 0, +#endif + }, + .uif[1] = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x01, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, +#ifdef CONFIG_USB_STRING + .iInterface = 5, +#else + .iInterface = 0, +#endif + }, + + .func_dfu = DFU_FUNC_DESC, +}; + + +/* minimal USB EP0 handler in DFU mode */ +static __dfufunc void dfu_udp_ep0_handler(void) +{ + AT91PS_UDP pUDP = AT91C_BASE_UDP; + u_int8_t bmRequestType, bRequest; + u_int16_t wValue, wIndex, wLength, wStatus; + u_int32_t csr = pUDP->UDP_CSR[0]; + + DEBUGE("CSR=0x%04x ", csr); + + if (csr & AT91C_UDP_STALLSENT) { + DEBUGE("ACK_STALLSENT "); + pUDP->UDP_CSR[0] = ~AT91C_UDP_STALLSENT; + } + + if (csr & AT91C_UDP_RX_DATA_BK0) { + DEBUGE("ACK_BANK0 "); + pUDP->UDP_CSR[0] &= ~AT91C_UDP_RX_DATA_BK0; + } + + if (!(csr & AT91C_UDP_RXSETUP)) { + DEBUGE("no setup packet\r\n"); + return; + } + + DEBUGE("len=%d ", csr >> 16); + if (csr >> 16 == 0) { + DEBUGE("empty packet\r\n"); + return; + } + + bmRequestType = pUDP->UDP_FDR[0]; + bRequest = pUDP->UDP_FDR[0]; + wValue = (pUDP->UDP_FDR[0] & 0xFF); + wValue |= (pUDP->UDP_FDR[0] << 8); + wIndex = (pUDP->UDP_FDR[0] & 0xFF); + wIndex |= (pUDP->UDP_FDR[0] << 8); + wLength = (pUDP->UDP_FDR[0] & 0xFF); + wLength |= (pUDP->UDP_FDR[0] << 8); + + DEBUGE("bmRequestType=0x%2x ", bmRequestType); + + if (bmRequestType & 0x80) { + DEBUGE("DATA_IN=1 "); + pUDP->UDP_CSR[0] |= AT91C_UDP_DIR; + while (!(pUDP->UDP_CSR[0] & AT91C_UDP_DIR)) ; + } + pUDP->UDP_CSR[0] &= ~AT91C_UDP_RXSETUP; + while ((pUDP->UDP_CSR[0] & AT91C_UDP_RXSETUP)) ; + + /* Handle supported standard device request Cf Table 9-3 in USB + * speciication Rev 1.1 */ + switch ((bRequest << 8) | bmRequestType) { + u_int8_t desc_type, desc_index; + case STD_GET_DESCRIPTOR: + DEBUGE("GET_DESCRIPTOR "); + desc_type = wValue >> 8; + desc_index = wValue & 0xff; + switch (desc_type) { + case USB_DT_DEVICE: + /* Return Device Descriptor */ + udp_ep0_send_data((const char *) + &dfu_dev_descriptor, + MIN(sizeof(dfu_dev_descriptor), + wLength)); + break; + case USB_DT_CONFIG: + /* Return Configuration Descriptor */ + udp_ep0_send_data((const char *) + &dfu_cfg_descriptor, + MIN(sizeof(dfu_cfg_descriptor), + wLength)); + break; + case USB_DT_STRING: + /* Return String Descriptor */ + if (desc_index > ARRAY_SIZE(usb_strings)) { + udp_ep0_send_stall(); + break; + } + DEBUGE("bLength=%u, wLength=%u ", + usb_strings[desc_index]->bLength, wLength); + udp_ep0_send_data((const char *) usb_strings[desc_index], + MIN(usb_strings[desc_index]->bLength, + wLength)); + break; + case USB_DT_CS_DEVICE: + /* Return Function descriptor */ + udp_ep0_send_data((const char *) &dfu_cfg_descriptor.func_dfu, + MIN(sizeof(dfu_cfg_descriptor.func_dfu), + wLength)); + break; + default: + udp_ep0_send_stall(); + break; + } + break; + case STD_SET_ADDRESS: + DEBUGE("SET_ADDRESS "); + udp_ep0_send_zlp(); + pUDP->UDP_FADDR = (AT91C_UDP_FEN | wValue); + pUDP->UDP_GLBSTATE = (wValue) ? AT91C_UDP_FADDEN : 0; + break; + case STD_SET_CONFIGURATION: + DEBUGE("SET_CONFIG "); + if (wValue) + DEBUGE("VALUE!=0 "); + cur_config = wValue; + udp_ep0_send_zlp(); + pUDP->UDP_GLBSTATE = + (wValue) ? AT91C_UDP_CONFG : AT91C_UDP_FADDEN; + pUDP->UDP_CSR[1] = + (wValue) ? (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_OUT) : + 0; + pUDP->UDP_CSR[2] = + (wValue) ? (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_BULK_IN) : 0; + pUDP->UDP_CSR[3] = + (wValue) ? (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_INT_IN) : 0; + pUDP->UDP_IER = (AT91C_UDP_EPINT0|AT91C_UDP_EPINT1| + AT91C_UDP_EPINT2|AT91C_UDP_EPINT3); + break; + case STD_GET_CONFIGURATION: + DEBUGE("GET_CONFIG "); + udp_ep0_send_data((char *)&(cur_config), + sizeof(cur_config)); + break; + case STD_GET_STATUS_ZERO: + DEBUGE("GET_STATUS_ZERO "); + wStatus = 0; + udp_ep0_send_data((char *)&wStatus, sizeof(wStatus)); + break; + case STD_GET_STATUS_INTERFACE: + DEBUGE("GET_STATUS_INTERFACE "); + wStatus = 0; + udp_ep0_send_data((char *)&wStatus, sizeof(wStatus)); + break; + case STD_GET_STATUS_ENDPOINT: + DEBUGE("GET_STATUS_ENDPOINT(EPidx=%u) ", wIndex&0x0f); + wStatus = 0; + wIndex &= 0x0F; + if ((pUDP->UDP_GLBSTATE & AT91C_UDP_CONFG) && (wIndex == 0)) { + wStatus = + (pUDP->UDP_CSR[wIndex] & AT91C_UDP_EPEDS) ? 0 : 1; + udp_ep0_send_data((char *)&wStatus, + sizeof(wStatus)); + } else if ((pUDP->UDP_GLBSTATE & AT91C_UDP_FADDEN) + && (wIndex == 0)) { + wStatus = + (pUDP->UDP_CSR[wIndex] & AT91C_UDP_EPEDS) ? 0 : 1; + udp_ep0_send_data((char *)&wStatus, + sizeof(wStatus)); + } else + udp_ep0_send_stall(); + break; + case STD_SET_FEATURE_ZERO: + DEBUGE("SET_FEATURE_ZERO "); + udp_ep0_send_stall(); + break; + case STD_SET_FEATURE_INTERFACE: + DEBUGE("SET_FEATURE_INTERFACE "); + udp_ep0_send_zlp(); + break; + case STD_SET_FEATURE_ENDPOINT: + DEBUGE("SET_FEATURE_ENDPOINT "); + udp_ep0_send_stall(); + break; + case STD_CLEAR_FEATURE_ZERO: + DEBUGE("CLEAR_FEATURE_ZERO "); + udp_ep0_send_stall(); + break; + case STD_CLEAR_FEATURE_INTERFACE: + DEBUGE("CLEAR_FEATURE_INTERFACE "); + udp_ep0_send_zlp(); + break; + case STD_CLEAR_FEATURE_ENDPOINT: + DEBUGE("CLEAR_FEATURE_ENDPOINT(EPidx=%u) ", wIndex & 0x0f); + udp_ep0_send_stall(); + break; + case STD_SET_INTERFACE: + DEBUGE("SET INTERFACE "); + /* FIXME: store the interface number somewhere, once + * we need to support DFU flashing DFU */ + udp_ep0_send_zlp(); + break; + default: + DEBUGE("DEFAULT(req=0x%02x, type=0x%02x) ", + bRequest, bmRequestType); + if ((bmRequestType & 0x3f) == USB_TYPE_DFU) { + dfu_ep0_handler(bmRequestType, bRequest, + wValue, wLength); + } else + udp_ep0_send_stall(); + break; + } + DEBUGE("\r\n"); +} + +/* minimal USB IRQ handler in DFU mode */ +static __dfufunc void dfu_udp_irq(void) +{ + AT91PS_UDP pUDP = AT91C_BASE_UDP; + AT91_REG isr = pUDP->UDP_ISR; + led1on(); + + if (isr & AT91C_UDP_ENDBUSRES) { + led2on(); + pUDP->UDP_IER = AT91C_UDP_EPINT0; + /* reset all endpoints */ + pUDP->UDP_RSTEP = (unsigned int)-1; + pUDP->UDP_RSTEP = 0; + /* Enable the function */ + pUDP->UDP_FADDR = AT91C_UDP_FEN; + /* Configure endpoint 0 */ + pUDP->UDP_CSR[0] = (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_CTRL); + cur_config = 0; + + if (dfu_state == DFU_STATE_dfuMANIFEST_WAIT_RST || + dfu_state == DFU_STATE_dfuMANIFEST) { + AT91F_RSTSoftReset(AT91C_BASE_RSTC, AT91C_RSTC_PROCRST| + AT91C_RSTC_PERRST| + AT91C_RSTC_EXTRST); + } + + } + + if (isr & AT91C_UDP_EPINT0) + dfu_udp_ep0_handler(); + + /* clear all interrupts */ + pUDP->UDP_ICR = isr; + + AT91F_AIC_ClearIt(AT91C_ID_UDP); + + led1off(); +} + +/* this is only called once before DFU mode, no __dfufunc required */ +static void dfu_switch(void) +{ + AT91PS_AIC pAic = AT91C_BASE_AIC; + + DEBUGE("\r\nsam7dfu: switching to DFU mode\r\n"); + + dfu_state = DFU_STATE_appDETACH; + AT91F_RSTSoftReset(AT91C_BASE_RSTC, AT91C_RSTC_PROCRST| + AT91C_RSTC_PERRST|AT91C_RSTC_EXTRST); + + /* We should never reach here, but anyway avoid returning to the + * caller since he doesn't expect us to do so */ + while (1) ; +} + +void __dfufunc dfu_main(void) +{ + AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPCD_PIO_LED1); + AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPCD_PIO_LED2); + led1off(); + led2off(); + + AT91F_DBGU_Init(); + AT91F_DBGU_Printk("\n\r\n\rsam7dfu - AT91SAM7 USB DFU bootloader\n\r" + "(C) 2006 by Harald Welte \n\r" + "This software is FREE SOFTWARE licensed under GNU GPL\n\r"); + AT91F_DBGU_Printk("Version " COMPILE_SVNREV + " compiled " COMPILE_DATE + " by " COMPILE_BY "\n\r\n\r"); + + udp_init(); + + dfu_state = DFU_STATE_dfuIDLE; + + /* This implements + AT91F_AIC_ConfigureIt(AT91C_BASE_AIC, AT91C_ID_UDP, + OPENPCD_IRQ_PRIO_UDP, + AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, dfu_udp_irq); + */ + AT91PS_AIC pAic = AT91C_BASE_AIC; + pAic->AIC_IDCR = 1 << AT91C_ID_UDP; + pAic->AIC_SVR[AT91C_ID_UDP] = (unsigned int) &dfu_udp_irq; + pAic->AIC_SMR[AT91C_ID_UDP] = AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL | + OPENPCD_IRQ_PRIO_UDP; + pAic->AIC_ICCR = 1 << AT91C_ID_UDP; + + AT91F_AIC_EnableIt(AT91C_ID_UDP); + + /* End-of-Bus-Reset is always enabled */ + + /* Clear for set the Pull up resistor */ +#if defined(PCD) + AT91F_PIO_SetOutput(AT91C_BASE_PIOA, OPENPCD_PIO_UDP_PUP); +#endif + AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, OPENPCD_PIO_UDP_PUPv4); + + flash_init(); + + AT91F_DBGU_Printk("You may now start the DFU up/download process\r\n"); + /* do nothing, since all of DFU is interrupt driven */ + int i = 0; + while (1) { + /* Occasionally reset watchdog */ + i = (i+1) % 10000; + if( i== 0) { + AT91F_WDTRestart(AT91C_BASE_WDTC); + } + } +} + +const struct dfuapi __dfufunctab dfu_api = { + .udp_init = &udp_init, + .ep0_send_data = &udp_ep0_send_data, + .ep0_send_zlp = &udp_ep0_send_zlp, + .ep0_send_stall = &udp_ep0_send_stall, + .dfu_ep0_handler = &dfu_ep0_handler, + .dfu_switch = &dfu_switch, + .dfu_state = &dfu_state, + .dfu_dev_descriptor = &dfu_dev_descriptor, + .dfu_cfg_descriptor = &dfu_cfg_descriptor, +}; + +/* just for testing */ +int foo = 12345; -- cgit v1.2.3