diff --git a/README.md b/README.md index 07abc3e..e370f08 100644 --- a/README.md +++ b/README.md @@ -3,33 +3,41 @@ USB DFU Bootloader for SAMD11 / SAMD21 Bootloaders may be a dime a dozen, but existing USB bootloaders for the Atmel/Microchip SAMD11/SAMD21 all seem to be 4kBytes or 8kBytes in size. To spend 25% or 50% of the SAMD11's flash on the bootloader seems quite excessive. The SAMD21 may have more flash to spare than the SAMD11, but why be so wasteful with it? -This USB bootloader is only 1kBytes and implements the industry-standard [DFU protocol](http://www.usb.org/developers/docs/devclass_docs/DFU_1.1.pdf) that is supported under multiple Operating Systems via existing tools such as [dfu-util](http://dfu-util.sourceforge.net/). +This USB bootloader is only 1kBytes and implements the industry-standard [DFU protocol](http://www.usb.org/developers/docs/devclass_docs/DFU_1.1.pdf) that is supported under multiple Operating Systems via existing tools such as [dfu-util](http://dfu-util.sourceforge.net/) and [webdfu](https://github.com/devanlai/webdfu). It is a much more space efficient alternative to the 4kB Atmel/Microchip [AN_42366](http://www.microchip.com//wwwAppNotes/AppNotes.aspx?appnote=en591491) SAM-BA Bootloader or the positively gluttonous 8kB Arduino Zero bootloaders. +## Features + +Despite the small size, it packs a punch. Unlike other bootloaders whose integrity check consists of merely sampling the first few bytes to see if they are not erased, this bootloader performs a proper CRC32 check of the application before letting it boot. It also supports the latest trend of detecting a double-tap of RESET to manually invoke the bootloader. + ## Usage -Downloading can be accomplished with any software that supports DFU, which includes the existing [dfu-util](http://dfu-util.sourceforge.net/) utilities. +Downloading can be accomplished with any software that supports DFU, which includes [dfu-util](http://dfu-util.sourceforge.net/) and [webdfu](https://github.com/devanlai/webdfu). -Using the provided dx1elf2dfu utility, one can create a .dfu file. The DFU software will accept that file; with [dfu-util](http://dfu-util.sourceforge.net/), downloading is like so: +Using the provided dx1elf2dfu utility, one can create a .dfu file. Your DFU software of choice will accept that file. + +With [dfu-util](http://dfu-util.sourceforge.net/), downloading is like so: ``` dfu-util -D write.dfu ``` +With [webdfu](https://devanlai.github.io/webdfu/dfu-util/), select the Vendor ID as 0x1209 and click Connect. Verify the transfer size is 64, and choose the .dfu file. Click Download, and after the download, click Disconnect. + ## Specifics Source code for some example apps is provided in the 'example-apps' subdirectory. The linker memory map of the user application must be modified to have an origin at 0x0000_0400 rather than at 0x0000_0000. This bootloader resides at 0x0000_0000. -When booting, the bootloader checks whether a GPIO pin (nominally PA15) is connected to ground. It also computes a CRC32 of the user application. If the user application is unprogrammed or corrupted, the CRC32 check should fall. If the CRC32 check fails or the GPIO pin is grounded, it runs the bootloader instead of the user application. +v1.04 features support for detecting a double-tap of a RESET button. For earlier versions (or by re-enabling legacy behavior in the source code), the bootloader checks at boot whether a GPIO pin (nominally PA15) is connected to ground. v1.02+ also computes a CRC32 of the user application. If the user application is unprogrammed or corrupted, the CRC32 check should fail. If the CRC32 check fails or a user request is detected, it runs the bootloader instead of the user application. When branching to the user application, the bootloader includes functionality to update the [VTOR (Vector Table Offset Register)](http://infocenter.arm.com/help/topic/com.arm.doc.dui0662a/Ciheijba.html) and update the stack pointer to suit the value in the user application's vector table. ## Requirements for compiling -[Rowley Crossworks for ARM](http://www.rowley.co.uk/arm/) is presently needed to compile this code. With Crossworks for ARM v4.3.0, using the Clang 7.0.0 compiler produces a 1014 byte image. The more mainstream GCC does not appear to be optimized enough to produce an image that comes anywhere close to fitting into 1024 bytes. +[Rowley Crossworks for ARM](http://www.rowley.co.uk/arm/) is presently needed to compile this code. With Crossworks for ARM v4.3.2, compiling v1.03 using the Clang 7.0.0 compiler produces a 1022 byte image. The more mainstream GCC does not appear to be optimized enough to produce an image that comes anywhere close to fitting into 1024 bytes. There is no dependency in the code on the [Rowley Crossworks for ARM](http://www.rowley.co.uk/arm/) toolchain per se, but at this time I am not aware of any other ready-to-use Clang ARM cross-compiler package that I can readily point users to. diff --git a/bootloader.c b/bootloader.c index 6ab5acf..256d7e4 100644 --- a/bootloader.c +++ b/bootloader.c @@ -45,6 +45,7 @@ /*- Definitions -------------------------------------------------------------*/ #define USB_CMD(dir, rcpt, type) ((USB_##dir##_TRANSFER << 7) | (USB_##type##_REQUEST << 5) | (USB_##rcpt##_RECIPIENT << 0)) +#define SIMPLE_USB_CMD(rcpt, type) ((USB_##type##_REQUEST << 5) | (USB_##rcpt##_RECIPIENT << 0)) /*- Types -------------------------------------------------------------------*/ typedef struct @@ -148,9 +149,18 @@ static void USB_Service(void) uint16_t length = request->wLength; static uint32_t *dfu_status = dfu_status_choices + 0; - switch (request->bmRequestType) + /* for these other USB requests, we must examine all fields in bmRequestType */ + if (USB_CMD(OUT, INTERFACE, STANDARD) == request->bmRequestType) { - case USB_CMD(IN, DEVICE, STANDARD): + udc_control_send_zlp(); + return; + } + + /* for these "simple" USB requests, we can ignore the direction and use only bRequest */ + switch (request->bmRequestType & 0x7F) + { + case SIMPLE_USB_CMD(DEVICE, STANDARD): + case SIMPLE_USB_CMD(INTERFACE, STANDARD): switch (request->bRequest) { case USB_GET_DESCRIPTOR: @@ -177,11 +187,6 @@ static void USB_Service(void) case USB_CLEAR_FEATURE: USB->DEVICE.DeviceEndpoint[0].EPSTATUSSET.bit.STALLRQ1 = 1; break; - } - break; - case USB_CMD(OUT, DEVICE, STANDARD): - switch (request->bRequest) - { case USB_SET_ADDRESS: udc_control_send_zlp(); USB->DEVICE.DADD.reg = USB_DEVICE_DADD_ADDEN | USB_DEVICE_DADD_DADD(request->wValue); @@ -192,18 +197,7 @@ static void USB_Service(void) break; } break; - case USB_CMD(IN, INTERFACE, STANDARD): - switch (request->bRequest) - { - case USB_GET_STATUS: - udc_control_send(dfu_status_choices + 0, 2); /* a 32-bit aligned zero in RAM is all we need */ - break; - } - break; - case USB_CMD(OUT, INTERFACE, STANDARD): - udc_control_send_zlp(); - break; - case USB_CMD(IN, INTERFACE, CLASS): + case SIMPLE_USB_CMD(INTERFACE, CLASS): switch (request->bRequest) { case 0x03: // DFU_GETSTATUS @@ -212,35 +206,36 @@ static void USB_Service(void) case 0x05: // DFU_GETSTATE udc_control_send(&dfu_status[1], 1); break; + case 0x01: // DFU_DNLOAD + dfu_status = dfu_status_choices + 0; + if (request->wLength) + { + dfu_status = dfu_status_choices + 2; + dfu_addr = 0x400 + request->wValue * 64; + } + /* fall through to below */ default: // DFU_UPLOAD & others - /* no uploads here */ - udc_control_send_zlp(); + /* 0x00 == DFU_DETACH, 0x04 == DFU_CLRSTATUS, 0x06 == DFU_ABORT, and 0x01 == DFU_DNLOAD and 0x02 == DFU_UPLOAD */ + if (!dfu_addr) + udc_control_send_zlp(); break; } break; - case USB_CMD(OUT, INTERFACE, CLASS): - if (0x01 /* DFU_DNLOAD */ == request->bRequest) - { - dfu_status = dfu_status_choices + 0; - if (request->wLength) - { - dfu_status = dfu_status_choices + 2; - dfu_addr = 0x400 + request->wValue * 64; - } - } - /* 0x00 == DFU_DETACH, 0x04 == DFU_CLRSTATUS, 0x06 == DFU_ABORT, and 0x01 == DFU_DNLOAD */ - if (!dfu_addr) - udc_control_send_zlp(); - break; } } } +extern int __RAM_segment_used_end__; +#define DBL_TAP_PTR (uint32_t *)(&__RAM_segment_used_end__) +#define DBL_TAP_MAGIC 0xf02669ef + void bootloader(void) { +#ifndef DBL_TAP_MAGIC /* configure PA15 (bootloader entry pin used by SAM-BA) as input pull-up */ PORT->Group[0].PINCFG[15].reg = PORT_PINCFG_PULLEN | PORT_PINCFG_INEN; PORT->Group[0].OUTSET.reg = (1UL << 15); +#endif PAC1->WPCLR.reg = 2; /* clear DSU */ @@ -252,11 +247,32 @@ void bootloader(void) DSU->CTRL.bit.CRC = 1; while (!DSU->STATUSA.bit.DONE); + if (DSU->DATA.reg) + goto run_bootloader; /* CRC failed, so run bootloader */ + +#ifndef DBL_TAP_MAGIC if (!(PORT->Group[0].IN.reg & (1UL << 15))) goto run_bootloader; /* pin grounded, so run bootloader */ - if (0 == DSU->DATA.reg) - return; /* CRC passes, so run user app */ + return; /* we've checked everything and there is no reason to run the bootloader */ +#else + if (PM->RCAUSE.reg & PM_RCAUSE_POR) + *DBL_TAP_PTR = 0; /* a power up event should never be considered a 'double tap' */ + + if (*DBL_TAP_PTR == DBL_TAP_MAGIC) + { + /* a 'double tap' has happened, so run bootloader */ + *DBL_TAP_PTR = 0; + goto run_bootloader; + } + + /* postpone boot for a short period of time; if a second reset happens during this window, the "magic" value will remain */ + *DBL_TAP_PTR = DBL_TAP_MAGIC; + volatile int wait = 65536; while (wait--); + /* however, if execution reaches this point, the window of opportunity has closed and the "magic" disappears */ + *DBL_TAP_PTR = 0; + return; +#endif run_bootloader: /* diff --git a/usb_descriptors.c b/usb_descriptors.c index ec03ccc..d2ba2ed 100644 --- a/usb_descriptors.c +++ b/usb_descriptors.c @@ -45,7 +45,7 @@ usb_device_descriptor_t usb_device_descriptor __attribute__ ((aligned (4))) = /* .bMaxPacketSize0 = 64, .idVendor = 0x1209, .idProduct = 0x2003, - .bcdDevice = 0x0103, + .bcdDevice = 0x0104, .iManufacturer = USB_STR_ZERO, .iProduct = USB_STR_ZERO,