Skip to content
This repository has been archived by the owner on Jan 29, 2024. It is now read-only.

Commit

Permalink
double-tap reset added by further squeezing USB request handler code …
Browse files Browse the repository at this point in the history
…size
  • Loading branch information
majbthrd committed Dec 8, 2018
1 parent 1bf1b2e commit c17b663
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 43 deletions.
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
90 changes: 53 additions & 37 deletions bootloader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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 */

Expand All @@ -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:
/*
Expand Down
2 changes: 1 addition & 1 deletion usb_descriptors.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit c17b663

Please sign in to comment.