adafruit / Adafruit_nRF52_Bootloader

USB-enabled bootloaders for the nRF52 BLE SoC chips
MIT License
432 stars 381 forks source link

AdaDFU does not re-appear after a failed OTA #259

Open Montvydas opened 2 years ago

Montvydas commented 2 years ago

Operating System

MacOS

INFO_UF2.TXT

UF2 Bootloader 0.6.3 lib/nrfx (v2.0.0) lib/tinyusb (0.12.0-145-g9775e769) lib/uf2 (remotes/origin/configupdate-9-gadbb8c7) Model: Particle Xenon Board-ID: nRF52840-Xenon-v1 Date: Dec 14 2021 SoftDevice: S140 6.1.1

What happened ?

I noticed that whenever the OTA through the nRF Connect app fails or is being cancelled, the device effectively becomes bricked, meaning there is no way to recover the application or upload a new firmware over BLE. This is extremely risky for battery powered devices, where the user has no access to the USB port. And since there is no way to do dual bank updates, this creates a potential for many failed devices, since I noticed there is a number of scenarios, where the OTA can fail: incompatible mobile devices, OS version changes, nRF library changes, app crashes, user error, etc etc etc.

How to reproduce ?

  1. Upload a blinky_ota example onto the board.
  2. Find the location of the application bin file and transfer it to the phone.
  3. From the nRF Connect app connect to the Xenon device.
  4. Start a DFU update with a valid firmware binary image.
  5. When the firmware transfer is in progress, close the application or disconnect the device, then you will notice that AdaDFU isn't discovered. It sometimes IS discovered, but only for a second, after which it disappears again.

Alternatively try uploading an invalid firmware image and the app - the same result. Note, that if I cancel BEFORE the transfer starts, then AdaDFU does come alive and I can try to re-upload the firmware!

Debug Log

No response

Screenshots

No response

Montvydas commented 2 years ago

So I managed to identify a neat enough way to solve this without affecting the default bootloader behavior. I added a oneliner inside the main.c -> int main(void), just before resetting the system: int main(void) { ... NRF_POWER->GPREGRET = 0xA8; // No application was loaded, reset the system to OTA DFU update NVIC_SystemReset(); }

As mentioned, this does not alter the default behavior of the bootloader, thus double-reset / freset etc. will continue to work, however, if during the OTA update the process is cancelled or breaks in any other way, this will provide a backdoor to re-upload the firmware again through an OTA update to AdaDFU. I believe this should be the default behavior of the bootloader, or at least deserves some build flag, such as --SAFER_OTA :) For someone else this might also become very useful if his devices are intended to continue working even in some unexpected situations when there is no physical access to the device..

hathach commented 2 years ago

@Montvydas thank for reporting the issue, and sorry for late response. However, could you make an PR to illustrate your solution. Note: the default mode of bootloader is USB interface, not OTA, if there is no valid firmware, bootloader will go into USB UF2 mode. However, we could make some tweak to switch between usb <-> ota after some defined period.

NOTE: there has been lots of change with Softdevice and Bootloader change, the OTA feature is lagging behind since it is not used much by us (Adafruit). Therefore there could be issues.

stewpend0us commented 2 years ago

I don't know if it's a pull request but he made the fix here:

https://github.com/Montvydas/Adafruit_nRF52_Bootloader/commit/8f31fd999e863028461d7fffa8b4860cf57466db

I tried it out and it does seem to work. The device name changes when it's falling back on this but you can still do OTA update manually. Note that I also had better luck (fewer random drops in the middle of an OTA update) when also applying this fix:

https://github.com/adafruit/Adafruit_nRF52_Bootloader/pull/80/commits/d999121155f2e27f8b186d99197eea07216f2df9 or..same thing: https://github.com/adafruit/Adafruit_nRF52_Bootloader/pull/90/commits/516d7cceab8ec3e8272e699856d5c4707d157d30

I will be diving into this a little more in the coming weeks but OTA updates are essential for my application.

Montvydas commented 2 years ago

It wasn't a pull request but rather a fork from the main repo to add the wanted features for my application without waiting for them to be approved, since I primarely needed the OTA updates to work flawlessly but yes, it can be used to test this. As @stewpend0us mentioned, I also had to add a fix for OTA to start working on my Pixel 6. Honestly, I could not ever perform a successful OTA without the value changed: d3f3923c7c9636b11c1486a00db1218a4b8ed480.

@stewpend0us you might also be interested in this specific commit: f18e66d5ba058cb25897d8c0b6c1cab63d160a94, where I added a precompiler flags to specifically select to boot into DFU OTA when on of the conditions happens. I added optional flags ONLY_OTA_DFU (never goes into DFU over USB ever, so OTA DFU happens if the application is deleted or if double reset is pressed etc.) and OTA_DFU_ON_INVALID_APP (automatically goes into OTA DFU when no valid application is found). The reason why I used these instead of the previously mentioned fix (which I think would work for 95% of applications) was because after I seal the product, I have to way to access its electronic parts EVER. And setting _NRFPOWER->GPREGRET = 0xA8; would still fail me in one specific occassion, when the update fails due to some reason and then the device is left aside until the battery dies and the MCU is powered off. During the next power up it would boot into a USB DFU instead of the OTA DFU... Since my device costs A LOT I could not risk any accident from happening even if there is very little chance ⚔️