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

Commit c17b663

Browse files
committed
double-tap reset added by further squeezing USB request handler code size
1 parent 1bf1b2e commit c17b663

File tree

3 files changed

+67
-43
lines changed

3 files changed

+67
-43
lines changed

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,41 @@ USB DFU Bootloader for SAMD11 / SAMD21
33

44
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?
55

6-
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/).
6+
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).
77

88
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.
99

10+
## Features
11+
12+
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.
13+
1014
## Usage
1115

12-
Downloading can be accomplished with any software that supports DFU, which includes the existing [dfu-util](http://dfu-util.sourceforge.net/) utilities.
16+
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).
1317

14-
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:
18+
Using the provided dx1elf2dfu utility, one can create a .dfu file. Your DFU software of choice will accept that file.
19+
20+
With [dfu-util](http://dfu-util.sourceforge.net/), downloading is like so:
1521

1622
```
1723
dfu-util -D write.dfu
1824
```
1925

26+
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.
27+
2028
## Specifics
2129

2230
Source code for some example apps is provided in the 'example-apps' subdirectory.
2331

2432
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.
2533

26-
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.
34+
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.
2735

2836
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.
2937

3038
## Requirements for compiling
3139

32-
[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.
40+
[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.
3341

3442
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.
3543

bootloader.c

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
/*- Definitions -------------------------------------------------------------*/
4747
#define USB_CMD(dir, rcpt, type) ((USB_##dir##_TRANSFER << 7) | (USB_##type##_REQUEST << 5) | (USB_##rcpt##_RECIPIENT << 0))
48+
#define SIMPLE_USB_CMD(rcpt, type) ((USB_##type##_REQUEST << 5) | (USB_##rcpt##_RECIPIENT << 0))
4849

4950
/*- Types -------------------------------------------------------------------*/
5051
typedef struct
@@ -148,9 +149,18 @@ static void USB_Service(void)
148149
uint16_t length = request->wLength;
149150
static uint32_t *dfu_status = dfu_status_choices + 0;
150151

151-
switch (request->bmRequestType)
152+
/* for these other USB requests, we must examine all fields in bmRequestType */
153+
if (USB_CMD(OUT, INTERFACE, STANDARD) == request->bmRequestType)
152154
{
153-
case USB_CMD(IN, DEVICE, STANDARD):
155+
udc_control_send_zlp();
156+
return;
157+
}
158+
159+
/* for these "simple" USB requests, we can ignore the direction and use only bRequest */
160+
switch (request->bmRequestType & 0x7F)
161+
{
162+
case SIMPLE_USB_CMD(DEVICE, STANDARD):
163+
case SIMPLE_USB_CMD(INTERFACE, STANDARD):
154164
switch (request->bRequest)
155165
{
156166
case USB_GET_DESCRIPTOR:
@@ -177,11 +187,6 @@ static void USB_Service(void)
177187
case USB_CLEAR_FEATURE:
178188
USB->DEVICE.DeviceEndpoint[0].EPSTATUSSET.bit.STALLRQ1 = 1;
179189
break;
180-
}
181-
break;
182-
case USB_CMD(OUT, DEVICE, STANDARD):
183-
switch (request->bRequest)
184-
{
185190
case USB_SET_ADDRESS:
186191
udc_control_send_zlp();
187192
USB->DEVICE.DADD.reg = USB_DEVICE_DADD_ADDEN | USB_DEVICE_DADD_DADD(request->wValue);
@@ -192,18 +197,7 @@ static void USB_Service(void)
192197
break;
193198
}
194199
break;
195-
case USB_CMD(IN, INTERFACE, STANDARD):
196-
switch (request->bRequest)
197-
{
198-
case USB_GET_STATUS:
199-
udc_control_send(dfu_status_choices + 0, 2); /* a 32-bit aligned zero in RAM is all we need */
200-
break;
201-
}
202-
break;
203-
case USB_CMD(OUT, INTERFACE, STANDARD):
204-
udc_control_send_zlp();
205-
break;
206-
case USB_CMD(IN, INTERFACE, CLASS):
200+
case SIMPLE_USB_CMD(INTERFACE, CLASS):
207201
switch (request->bRequest)
208202
{
209203
case 0x03: // DFU_GETSTATUS
@@ -212,35 +206,36 @@ static void USB_Service(void)
212206
case 0x05: // DFU_GETSTATE
213207
udc_control_send(&dfu_status[1], 1);
214208
break;
209+
case 0x01: // DFU_DNLOAD
210+
dfu_status = dfu_status_choices + 0;
211+
if (request->wLength)
212+
{
213+
dfu_status = dfu_status_choices + 2;
214+
dfu_addr = 0x400 + request->wValue * 64;
215+
}
216+
/* fall through to below */
215217
default: // DFU_UPLOAD & others
216-
/* no uploads here */
217-
udc_control_send_zlp();
218+
/* 0x00 == DFU_DETACH, 0x04 == DFU_CLRSTATUS, 0x06 == DFU_ABORT, and 0x01 == DFU_DNLOAD and 0x02 == DFU_UPLOAD */
219+
if (!dfu_addr)
220+
udc_control_send_zlp();
218221
break;
219222
}
220223
break;
221-
case USB_CMD(OUT, INTERFACE, CLASS):
222-
if (0x01 /* DFU_DNLOAD */ == request->bRequest)
223-
{
224-
dfu_status = dfu_status_choices + 0;
225-
if (request->wLength)
226-
{
227-
dfu_status = dfu_status_choices + 2;
228-
dfu_addr = 0x400 + request->wValue * 64;
229-
}
230-
}
231-
/* 0x00 == DFU_DETACH, 0x04 == DFU_CLRSTATUS, 0x06 == DFU_ABORT, and 0x01 == DFU_DNLOAD */
232-
if (!dfu_addr)
233-
udc_control_send_zlp();
234-
break;
235224
}
236225
}
237226
}
238227

228+
extern int __RAM_segment_used_end__;
229+
#define DBL_TAP_PTR (uint32_t *)(&__RAM_segment_used_end__)
230+
#define DBL_TAP_MAGIC 0xf02669ef
231+
239232
void bootloader(void)
240233
{
234+
#ifndef DBL_TAP_MAGIC
241235
/* configure PA15 (bootloader entry pin used by SAM-BA) as input pull-up */
242236
PORT->Group[0].PINCFG[15].reg = PORT_PINCFG_PULLEN | PORT_PINCFG_INEN;
243237
PORT->Group[0].OUTSET.reg = (1UL << 15);
238+
#endif
244239

245240
PAC1->WPCLR.reg = 2; /* clear DSU */
246241

@@ -252,11 +247,32 @@ void bootloader(void)
252247
DSU->CTRL.bit.CRC = 1;
253248
while (!DSU->STATUSA.bit.DONE);
254249

250+
if (DSU->DATA.reg)
251+
goto run_bootloader; /* CRC failed, so run bootloader */
252+
253+
#ifndef DBL_TAP_MAGIC
255254
if (!(PORT->Group[0].IN.reg & (1UL << 15)))
256255
goto run_bootloader; /* pin grounded, so run bootloader */
257256

258-
if (0 == DSU->DATA.reg)
259-
return; /* CRC passes, so run user app */
257+
return; /* we've checked everything and there is no reason to run the bootloader */
258+
#else
259+
if (PM->RCAUSE.reg & PM_RCAUSE_POR)
260+
*DBL_TAP_PTR = 0; /* a power up event should never be considered a 'double tap' */
261+
262+
if (*DBL_TAP_PTR == DBL_TAP_MAGIC)
263+
{
264+
/* a 'double tap' has happened, so run bootloader */
265+
*DBL_TAP_PTR = 0;
266+
goto run_bootloader;
267+
}
268+
269+
/* postpone boot for a short period of time; if a second reset happens during this window, the "magic" value will remain */
270+
*DBL_TAP_PTR = DBL_TAP_MAGIC;
271+
volatile int wait = 65536; while (wait--);
272+
/* however, if execution reaches this point, the window of opportunity has closed and the "magic" disappears */
273+
*DBL_TAP_PTR = 0;
274+
return;
275+
#endif
260276

261277
run_bootloader:
262278
/*

usb_descriptors.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ usb_device_descriptor_t usb_device_descriptor __attribute__ ((aligned (4))) = /*
4545
.bMaxPacketSize0 = 64,
4646
.idVendor = 0x1209,
4747
.idProduct = 0x2003,
48-
.bcdDevice = 0x0103,
48+
.bcdDevice = 0x0104,
4949

5050
.iManufacturer = USB_STR_ZERO,
5151
.iProduct = USB_STR_ZERO,

0 commit comments

Comments
 (0)