Skip to content

Commit 178d909

Browse files
committed
Implement proper manifestation according to the spec
The initial behavior of entering dfuMANIFEST-SYNC was incorrect, sorry about that. I've now thoroughly read the DFU spec, which actually states that dfuMANIFEST-SYNC shall be entered *twice* after a download operation if bitMainfestationTolerant = 1 (i.e. DFU_WILL_DETACH == 0), once after the download completes, which begins manifestation by transitioning to dfuMANIFEST, and again after manifestation has completed successfully, and only after that should the transition to dfuIDLE be made. This means that there should be *two* DFU_GETSTATUS calls from the host, and indeed dfu-util does exactly this (verified using Wireshark). Manifesting on USB reset or on detach is not according to spec. It should only happen right after a download operation has finished. For this I've overhauled the dfu_on_manifest_request() function to work as it should with both DFU_WILL_DETACH == 1 and DFU_WILL_DETACH == 0. Validation should actually be part of manifestation, and I've now added it as the manifestation callback (since there is no other manifestation work to be done). The scb_reset_system() call in the USB reset callback is what you need to perform detach-by-USB-reset, but to avoid having random reset calls in multiple places it makes much more sense to call the actual dfu_on_detach_complete() callback which will then perform the reset. The USB reset callback should also not validate, e.g. a blank flash with just the bootloader should not trigger a firmware error status, so I've removed that logic.
1 parent 191a092 commit 178d909

3 files changed

Lines changed: 38 additions & 53 deletions

File tree

src/dapboot.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ static inline void __set_MSP(uint32_t topOfMainStack) {
3232
}
3333

3434
bool validate_application(void) {
35-
if (((uint32_t)(APP_INITIAL_STACK) & 0x2FFE0000) == 0x20000000) {
36-
return true;
37-
}
38-
return false;
35+
return ((uint32_t)(APP_INITIAL_STACK) & 0x2FFE0000) == 0x20000000;
3936
}
4037

4138
static void jump_to_application(void) __attribute__ ((noreturn));
@@ -75,7 +72,7 @@ int main(void) {
7572
}
7673

7774
usbd_device* usbd_dev = usb_setup();
78-
dfu_setup(usbd_dev, target_manifest_app, NULL, NULL);
75+
dfu_setup(usbd_dev, validate_application, NULL, NULL);
7976
webusb_setup(usbd_dev);
8077
winusb_setup(usbd_dev);
8178

src/dfu.c

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ static enum dfu_state current_dfu_state;
5151
static enum dfu_status current_dfu_status;
5252
static size_t current_dfu_offset;
5353

54-
static bool dfu_modified_firmware = false;
54+
static bool manifestation_complete = false;
5555

5656
static uint8_t dfu_download_buffer[USB_CONTROL_BUF_SIZE];
5757
static size_t dfu_download_size;
5858

5959
/* User callbacks */
60-
static GenericCallback dfu_manifest_request_callback = NULL;
60+
static ManifestationCallback dfu_manifest_request_callback = NULL;
6161
static StateChangeCallback dfu_state_change_callback = NULL;
6262
static StatusChangeCallback dfu_status_change_callback = NULL;
6363

@@ -133,9 +133,6 @@ static void dfu_on_download_request(usbd_device* usbd_dev, struct usb_setup_data
133133
bool ok = target_flash_program_array(dest, data, dfu_download_size/2);
134134
target_flash_lock();
135135

136-
/* Record that we touched the application firmware */
137-
dfu_modified_firmware = true;
138-
139136
if (ok) {
140137
current_dfu_offset += dfu_download_size;
141138
/* We could go back to STATE_DFU_DNLOAD_SYNC, but then
@@ -151,12 +148,22 @@ static void dfu_on_manifest_request(usbd_device* usbd_dev, struct usb_setup_data
151148
(void)req;
152149

153150
if (dfu_manifest_request_callback) {
154-
dfu_manifest_request_callback();
151+
/* The manifestation callback returns a boolean indicating if it succeeded */
152+
if (dfu_manifest_request_callback()) {
153+
manifestation_complete = true;
154+
dfu_set_state(STATE_DFU_MANIFEST_SYNC);
155+
} else {
156+
dfu_set_status(DFU_STATUS_ERR_FIRMWARE);
157+
return; /* Avoid resetting on error */
158+
}
155159
}
156160

157-
/* Reset and maybe launch the application if the manifest callback
158-
didn't already do it */
159-
scb_reset_system();
161+
#if DFU_WILL_DETACH
162+
/* DFU_WILL_DETACH being enabled equates to transitioning to the dfuMANIFEST-WAIT-RESET state,
163+
* which combined with bitWillDetach being set with DFU_WILL_DETACH means that the device should
164+
* generate a detach-attach sequence and enter the application, i.e. reset itself, here */
165+
dfu_on_detach_complete(NULL, NULL);
166+
#endif
160167
}
161168

162169
static enum usbd_request_return_codes
@@ -190,17 +197,18 @@ dfu_control_class_request(usbd_device *usbd_dev,
190197
break;
191198
}
192199
case STATE_DFU_MANIFEST_SYNC: {
193-
if (validate_application()) {
194-
#if DFU_WILL_DETACH
195-
/* Manifest by resetting after responding */
196-
dfu_set_state(STATE_DFU_MANIFEST);
197-
*complete = &dfu_on_manifest_request;
198-
#else
199-
/* Return to the idle state and await commands */
200+
/* According to the DFU spec the dfuMANIFEST-SYNC state is entered twice,
201+
* once after the download completes, and again after manifestation if
202+
* the device is manifestation tolerant (DFU_WILL_DETACH == 0) */
203+
if (manifestation_complete) {
204+
/* Only enter idle state after manifestation has completed successfully */
205+
manifestation_complete = false;
200206
dfu_set_state(STATE_DFU_IDLE);
201-
#endif
202207
} else {
203-
dfu_set_status(DFU_STATUS_ERR_FIRMWARE);
208+
/* Perform manifestation after download as described in the
209+
* spec regardless of if DFU_WILL_DETACH is enabled or not */
210+
dfu_set_state(STATE_DFU_MANIFEST);
211+
*complete = &dfu_on_manifest_request;
204212
}
205213
break;
206214
}
@@ -317,11 +325,7 @@ dfu_control_class_request(usbd_device *usbd_dev,
317325
#endif
318326

319327
case DFU_DETACH: {
320-
if (dfu_modified_firmware) {
321-
*complete = &dfu_on_manifest_request;
322-
} else {
323-
*complete = &dfu_on_detach_complete;
324-
}
328+
*complete = &dfu_on_detach_complete;
325329
status = USBD_REQ_HANDLED;
326330
break;
327331
}
@@ -355,36 +359,20 @@ static void dfu_set_config(usbd_device* usbd_dev, uint16_t wValue) {
355359
}
356360

357361
static void dfu_on_usb_reset(void) {
358-
/* Ignore the first USB reset after boot, before anyone has had
359-
a chance to do anything to the device; otherwise we'd never
360-
enter DFU mode when the application is valid */
362+
/* Ignore all USB resets until the DFU control callback has been registered, since
363+
* reset callback will fire once as the USB connection is established. Without this
364+
* the target enters a reset loop when trying to enter the bootloader. */
361365
if (!dfu_enumerated) {
362366
return;
363367
}
364368

365-
/* On subsequent USB reset requests, either reset/manifest
366-
or enter the error state if firmware is invalid, per the spec */
367-
if (validate_application()) {
368-
if (dfu_modified_firmware) {
369-
/* Manifest by resetting after responding */
370-
dfu_set_state(STATE_DFU_MANIFEST);
371-
372-
if (dfu_manifest_request_callback) {
373-
dfu_manifest_request_callback();
374-
}
375-
}
376-
377-
/* Reset and launch the application if the manifest callback
378-
didn't already do it */
379-
scb_reset_system();
380-
} else {
381-
/* Enter the error state and await further commands */
382-
dfu_set_status(DFU_STATUS_ERR_FIRMWARE);
383-
}
369+
/* Perform a DFU detach (which resets the target), this enables issuing a USB bus
370+
* reset as an alternative means to submitting a DFU_DETACH command post-download. */
371+
dfu_on_detach_complete(NULL, NULL);
384372
}
385373

386374
void dfu_setup(usbd_device* usbd_dev,
387-
GenericCallback on_manifest_request,
375+
ManifestationCallback on_manifest_request,
388376
StateChangeCallback on_state_change,
389377
StatusChangeCallback on_status_change) {
390378
dfu_manifest_request_callback = on_manifest_request;

src/dfu.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929

3030
extern const struct usb_dfu_descriptor dfu_function;
3131

32-
typedef void (*GenericCallback)(void);
32+
typedef bool (*ManifestationCallback)(void);
3333
typedef void (*StateChangeCallback)(enum dfu_state);
3434
typedef void (*StatusChangeCallback)(enum dfu_status);
3535

3636
extern void dfu_setup(usbd_device* usbd_dev,
37-
GenericCallback on_manifest_request,
37+
ManifestationCallback on_manifest_request,
3838
StateChangeCallback on_state_change,
3939
StatusChangeCallback on_status_change);
4040

0 commit comments

Comments
 (0)