/* 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 #include #include #include "../openpcd.h" #include #define SAM7DFU_SIZE 0x4000 #define SAM7DFU_RAM_SIZE 0x2000 /* 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 int past_manifest = 0; static int switch_to_ram = 0; /* IRQ handler requests main to jump to RAM */ static u_int16_t usb_if_nr = 0; /* last SET_INTERFACE */ static u_int16_t usb_if_alt_nr = 0; /* last SET_INTERFACE AltSetting */ static u_int16_t usb_if_alt_nr_dnload = 0; /* AltSetting during last dnload */ 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); } static void __dfufunc udp_ep0_send_zlp(void); /* Send Data through the control endpoint */ static void __dfufunc udp_ep0_send_data(const char *pData, u_int32_t length, u_int32_t window_length) { AT91PS_UDP pUdp = AT91C_BASE_UDP; u_int32_t cpt = 0, len_remain; AT91_REG csr; len_remain = MIN(length, window_length); DEBUGE("send_data: %u/%u bytes ", length, window_length); do { cpt = MIN(len_remain, 8); len_remain -= 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 (len_remain); if (pUdp->UDP_CSR[0] & AT91C_UDP_TXCOMP) { pUdp->UDP_CSR[0] &= ~(AT91C_UDP_TXCOMP); while (pUdp->UDP_CSR[0] & AT91C_UDP_TXCOMP) ; } if ((length % 8) == 0 && length < window_length) { /* if the length is a multiple of the EP size, we need * to send another ZLP (zero-length packet) to tell the * host the transfer has completed. */ DEBUGE("set_txpktrdy_zlp "); udp_ep0_send_zlp(); } } /* 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 int first_download = 1; static u_int8_t *ptr, *ptr_max; 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 void chk_first_dnload_set_ptr(void) { if (!first_download) return; switch (usb_if_alt_nr) { case 0: ptr = (u_int8_t *) AT91C_IFLASH + SAM7DFU_SIZE; ptr_max = AT91C_IFLASH + AT91C_IFLASH_SIZE - ENVIRONMENT_SIZE; break; case 1: ptr = (u_int8_t *) AT91C_IFLASH; ptr_max = AT91C_IFLASH + SAM7DFU_SIZE; break; case 2: ptr = (u_int8_t *) AT91C_ISRAM + SAM7DFU_RAM_SIZE; ptr_max = AT91C_ISRAM + AT91C_ISRAM_SIZE; break; } first_download = 0; } static int __dfufunc handle_dnload_flash(u_int16_t __unused val, u_int16_t len) { volatile u_int32_t *p; 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; } chk_first_dnload_set_ptr(); p = (volatile u_int32_t *)ptr; if (len == 0) { DEBUGP("zero-size write -> MANIFEST_SYNC "); if (((unsigned long)p % AT91C_IFLASH_PAGE_SIZE) != 0) flash_page(p); dfu_state = DFU_STATE_dfuMANIFEST_SYNC; first_download = 1; return RET_ZLP; } /* check if we would exceed end of memory */ if (ptr + len > ptr_max) { 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)); #ifndef DEBUG_DFU_NOFLASH DEBUGP("copying "); /* we can only access the write buffer with correctly aligned * 32bit writes ! */ 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; } static int __dfufunc handle_dnload_ram(u_int16_t __unused val, u_int16_t len) { 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; } chk_first_dnload_set_ptr(); if (len == 0) { DEBUGP("zero-size write -> MANIFEST_SYNC "); dfu_state = DFU_STATE_dfuMANIFEST_SYNC; first_download = 1; return RET_ZLP; } /* check if we would exceed end of memory */ if (ptr + len >= ptr_max) { DEBUGP("end of write exceeds RAM end "); dfu_state = DFU_STATE_dfuERROR; dfu_status = DFU_STATUS_errADDRESS; return RET_STALL; } /* drectly copy into RAM */ DEBUGP("try_to_recv=%u ", len); udp_ep0_recv_data(ptr, len); DEBUGR(hexdump(ptr, len)); ptr += len; return RET_ZLP; } static int __dfufunc handle_dnload(u_int16_t val, u_int16_t len) { usb_if_alt_nr_dnload = usb_if_alt_nr; switch (usb_if_alt_nr) { case 2: return handle_dnload_ram(val, len); default: return handle_dnload_flash(val, len); } } #define AT91C_IFLASH_END ((u_int8_t *)AT91C_IFLASH + AT91C_IFLASH_SIZE) static __dfufunc int handle_upload(u_int16_t __unused 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; } chk_first_dnload_set_ptr(); if (ptr + len > AT91C_IFLASH_END) { len = AT91C_IFLASH_END - (u_int8_t *)ptr; first_download = 1; } udp_ep0_send_data((char *)ptr, len, 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), sizeof(dstat)); } static void __dfufunc handle_getstate(void) { u_int8_t u8 = dfu_state; DEBUGE("getstate "); udp_ep0_send_data((char *)&u8, sizeof(u8), sizeof(u8)); } /* callback function for DFU requests */ int __dfufunc dfu_ep0_handler(u_int8_t __unused 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: 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; 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 = 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, #ifdef CONFIG_USB_STRING .iManufacturer = 1, .iProduct = 2, #else .iManufacturer = 0, .iProduct = 0, #endif .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 + 3* 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 }, .uif[2] = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0x00, .bAlternateSetting = 0x02, .bNumEndpoints = 0x00, .bInterfaceClass = 0xfe, .bInterfaceSubClass = 0x01, .bInterfaceProtocol = 0x02, #ifdef CONFIG_USB_STRING .iInterface = 6, #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, sizeof(dfu_dev_descriptor), wLength); break; case USB_DT_CONFIG: /* Return Configuration Descriptor */ udp_ep0_send_data((const char *) &dfu_cfg_descriptor, 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], 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, 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 "); /* Table 9.4 wLength One */ udp_ep0_send_data((char *)&(cur_config), sizeof(cur_config), 1); break; case STD_GET_STATUS_ZERO: DEBUGE("GET_STATUS_ZERO "); wStatus = 0; /* Table 9.4 wLength Two */ udp_ep0_send_data((char *)&wStatus, sizeof(wStatus), 2); break; case STD_GET_STATUS_INTERFACE: DEBUGE("GET_STATUS_INTERFACE "); wStatus = 0; /* Table 9.4 wLength Two */ udp_ep0_send_data((char *)&wStatus, sizeof(wStatus), 2); 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; /* Table 9.4 wLength Two */ udp_ep0_send_data((char *)&wStatus, sizeof(wStatus), 2); } else if ((pUDP->UDP_GLBSTATE & AT91C_UDP_FADDEN) && (wIndex == 0)) { wStatus = (pUDP->UDP_CSR[wIndex] & AT91C_UDP_EPEDS) ? 0 : 1; /* Table 9.4 wLength Two */ udp_ep0_send_data((char *)&wStatus, sizeof(wStatus), 2); } 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(if=%d, alt=%d) ", wIndex, wValue); /* store the interface number somewhere, once * we need to support DFU flashing DFU */ usb_if_alt_nr = wValue; usb_if_nr = wIndex; 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 || past_manifest) { AT91F_DBGU_Printk("sam7dfu: switching to APP mode\r\n"); switch (usb_if_alt_nr_dnload) { case 2: switch_to_ram = 1; break; default: /* reset back into the main application */ AT91F_RSTSoftReset(AT91C_BASE_RSTC, AT91C_RSTC_PROCRST| AT91C_RSTC_PERRST| AT91C_RSTC_EXTRST); break; } } } if (isr & AT91C_UDP_EPINT0) dfu_udp_ep0_handler(); /* clear all interrupts */ pUDP->UDP_ICR = isr; AT91F_AIC_ClearIt(AT91C_BASE_AIC, AT91C_ID_UDP); led1off(); } /* this is only called once before DFU mode, no __dfufunc required */ static __noreturn void dfu_switch(void) { 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-2011 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_BASE_AIC, 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); } if (switch_to_ram) { void (*ram_app_entry)(void); int j; for (j = 0; j < 32; j++) AT91F_AIC_DisableIt(AT91C_BASE_AIC, j); /* jump into RAM */ AT91F_DBGU_Printk("JUMP TO RAM\r\n"); ram_app_entry = AT91C_ISRAM + SAM7DFU_RAM_SIZE; ram_app_entry(); } } } 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;