summaryrefslogtreecommitdiffstats
path: root/boot/dfu.c
diff options
context:
space:
mode:
Diffstat (limited to 'boot/dfu.c')
-rw-r--r--boot/dfu.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/boot/dfu.c b/boot/dfu.c
new file mode 100644
index 0000000..f931a6e
--- /dev/null
+++ b/boot/dfu.c
@@ -0,0 +1,197 @@
+#include "project.h"
+
+/* Commands sent with wBlockNum == 0 as per ST implementation. */
+#define CMD_SETADDR 0x21
+#define CMD_ERASE 0x41
+
+
+/* We need a special large control buffer for this device: */
+
+static enum dfu_state usbdfu_state = STATE_DFU_IDLE;
+
+static uint32_t sector_bases[] = {
+ 0x08000000,
+ 0x08004000,
+ 0x08008000,
+ 0x0800C000,
+ 0x08010000,
+ 0x08020000,
+ 0x08040000,
+ 0x08060000,
+ 0x08080000,
+ 0x080A0000,
+ 0x080C0000,
+ 0x080E0000,
+};
+
+#define N_SECTORS (sizeof(sector_bases)/sizeof(sector_bases[0]))
+
+static struct {
+ uint8_t buf[sizeof (usbd_control_buffer)];
+ uint16_t len;
+ uint32_t addr;
+ uint16_t blocknum;
+} prog;
+
+const struct usb_dfu_descriptor dfu_function = {
+ .bLength = sizeof (struct usb_dfu_descriptor),
+ .bDescriptorType = DFU_FUNCTIONAL,
+ .bmAttributes = USB_DFU_CAN_DOWNLOAD,
+ .wDetachTimeout = 255,
+ .wTransferSize = 1024,
+ .bcdDFUVersion = 0x011A,
+};
+
+const struct usb_interface_descriptor dfu_iface = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = 0xFE, /* Device Firmware Upgrade */
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 2,
+
+ /* The ST Microelectronics DfuSe application needs this string.
+ * The format isn't documented... */
+ .iInterface = 4,
+
+ .extra = &dfu_function,
+ .extralen = sizeof (dfu_function),
+};
+
+static uint8_t usbdfu_getstatus (usbd_device *usbd_dev, uint32_t *bwPollTimeout)
+{
+ (void)usbd_dev;
+
+ switch (usbdfu_state) {
+ case STATE_DFU_DNLOAD_SYNC:
+ usbdfu_state = STATE_DFU_DNBUSY;
+ *bwPollTimeout = 100;
+ return DFU_STATUS_OK;
+
+ case STATE_DFU_MANIFEST_SYNC:
+ /* Device will reset when read is complete. */
+ usbdfu_state = STATE_DFU_MANIFEST;
+ return DFU_STATUS_OK;
+
+ default:
+ return DFU_STATUS_OK;
+ }
+}
+
+static int usbdfu_getstatus_complete (usbd_device *usbd_dev, struct usb_setup_data *req)
+{
+ unsigned i;
+ (void)req;
+ (void)usbd_dev;
+
+ switch (usbdfu_state) {
+ case STATE_DFU_DNBUSY:
+ flash_unlock();
+
+ if (prog.blocknum == 0) {
+ switch (prog.buf[0]) {
+ case CMD_ERASE: {
+ uint32_t *dat = (uint32_t *) (prog.buf + 1);
+
+ for (i = 0; i < N_SECTORS; ++i) {
+ if (*dat == sector_bases[i])
+ flash_erase_sector (i, FLASH_CR_PROGRAM_X32);
+ }
+ }
+
+ /*Fall through*/
+ case CMD_SETADDR: {
+ uint32_t *dat = (uint32_t *) (prog.buf + 1);
+ prog.addr = *dat;
+ }
+ }
+ } else {
+ uint32_t baseaddr = prog.addr + ((prog.blocknum - 2) *
+ dfu_function.wTransferSize);
+
+ for (i = 0; i < prog.len; i += 4) {
+ uint32_t *dat = (uint32_t *) (prog.buf + i);
+ flash_program_word (baseaddr + i, *dat);
+ }
+ }
+
+ flash_lock();
+
+ /* Jump straight to dfuDNLOAD-IDLE, skipping dfuDNLOAD-SYNC. */
+ usbdfu_state = STATE_DFU_DNLOAD_IDLE;
+ return 0;
+
+ case STATE_DFU_MANIFEST:
+ /* USB device must detach, we just reset... */
+ scb_reset_system();
+ return 0; /* Will never return. */
+
+ default:
+ return 0;
+ }
+}
+
+int usbdfu_control_request (usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf,
+ uint16_t *len, int (**complete) (usbd_device *usbd_dev, struct usb_setup_data *req))
+{
+
+ if ((req->bmRequestType & 0x7F) != 0x21)
+ return 0; /* Only accept class request. */
+
+ switch (req->bRequest) {
+ case DFU_DNLOAD:
+ if ((len == NULL) || (*len == 0)) {
+ usbdfu_state = STATE_DFU_MANIFEST_SYNC;
+ return 1;
+ } else {
+ /* Copy download data for use on GET_STATUS. */
+ prog.blocknum = req->wValue;
+ prog.len = *len;
+ memcpy (prog.buf, *buf, *len);
+ usbdfu_state = STATE_DFU_DNLOAD_SYNC;
+ return 1;
+ }
+
+ case DFU_CLRSTATUS:
+
+ /* Clear error and return to dfuIDLE. */
+ if (usbdfu_state == STATE_DFU_ERROR)
+ usbdfu_state = STATE_DFU_IDLE;
+
+ return 1;
+
+ case DFU_ABORT:
+ /* Abort returns to dfuIDLE state. */
+ usbdfu_state = STATE_DFU_IDLE;
+ return 1;
+
+ case DFU_UPLOAD:
+ /* Upload not supported for now. */
+ return 0;
+
+ case DFU_GETSTATUS: {
+ uint32_t bwPollTimeout = 0; /* 24-bit integer in DFU class spec */
+ (*buf)[0] = usbdfu_getstatus (usbd_dev, &bwPollTimeout);
+ (*buf)[1] = bwPollTimeout & 0xFF;
+ (*buf)[2] = (bwPollTimeout >> 8) & 0xFF;
+ (*buf)[3] = (bwPollTimeout >> 16) & 0xFF;
+ (*buf)[4] = usbdfu_state;
+ (*buf)[5] = 0; /* iString not used here */
+ *len = 6;
+ *complete = usbdfu_getstatus_complete;
+ return 1;
+ }
+
+ case DFU_GETSTATE:
+ /* Return state with no state transision. */
+ *buf[0] = usbdfu_state;
+ *len = 1;
+ return 1;
+ }
+
+ return 0;
+}
+
+