Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature proposal: USB Device Firmware Upgrade (DFU) support #77

Open
kpfleming opened this issue Mar 26, 2024 · 6 comments
Open

Feature proposal: USB Device Firmware Upgrade (DFU) support #77

kpfleming opened this issue Mar 26, 2024 · 6 comments

Comments

@kpfleming
Copy link
Contributor

(I am proposing to implement this feature, just to be clear).

It would be very nice to be able to update the firmware without having to open the case (to use the BOOTSEL button) or to use a magic key sequence (which the requires copying the UF2 file to the board without functional input devices). This is what DFU was created for :-)

If this was supported, users could use a variety of tools (dfu-utils, dfu-buddy, and others, which are available on Windows, macOS, and Linux) to upload new firmware to the device. In the long term, Linux users could even benefit from having firmware published via LVFS, although that would probably require a different sort of configuration mechanism.

For implementation, I'll use the existing DFU functionality in TinyUSB, along with pico-flashloader.

The DeskHop application code will get two sets of changes:

  1. In 'normal operation' mode, the device will advertise support for "DFU runtime", which allows DFU tools to trigger a reboot into "DFU" mode.
  2. In 'DFU' mode, the device will only advertise support for "DFU", and will allow upload of an application binary. The binary will be received into RAM, and then the device will drop off of USB, write the binary into a reserved area of flash memory (high above the existing application, probably at the 1MB boundary), set some values into the watchdog scratch registers, and reboot.

Adding pico-flashloader to the UF2 will allow it to handle booting; when it notices that a new application binary has been put into flash it will copy that to the normal application location in flash and then start the application. If there is no new binary in flash then it will just start the application as normal, and if all of that fails it will restart into USB boot mode so that a UF2 file can be uploaded to the flash to reset the device to a working configuration.

Because the DFU upload will land in RAM (not directly into flash), there will be a hard limit on the size of the application binary (just a bit less than half of device RAM, so something in the area of 120KB). It would be possible for the DFU upload to write directly to flash instead, but that would be more complicated and is not worth pursuing until it is necessary.

I plan to work on this in three phases:

  1. Add "DFU runtime" to the USB configuration so that the device appears to support DFU, but where a "DFU Detach" command will just reboot without entering DFU mode.
  2. Add pico-flashloader to the source tree and final binary (the UF2 file), and ensure that it is able to start the DeskHop application with no other changes.
  3. Implement "DFU" mode in the application, receiving the upload into RAM and then transferring to flash, for pico-flashloader to relocate to the normal location.
@hrvach
Copy link
Owner

hrvach commented Mar 26, 2024

Thanks for your contributions to the project, really really appreciate them! I was looking into this already including flashing both boards through one input, but had more urgent things to fix :)

From a security standpoint, the possibility to trigger and update firmware without an explicit user intent provided out-of-band (like a button or a special key combo) is a huge risk. For development purposes - yes please, but for prod use those 2 seconds saved once in a blue moon are not worth the risk.

I'd propose doing it a bit differently - can you try making it conditional, i.e. press some key combo and DFU magically appears, while keyboard/mouse still work and you can use dfu tools to update? After completing the update or a timeout of 30-60 seconds, DFU support disappears again.

What do you think?

@kpfleming
Copy link
Contributor Author

kpfleming commented Mar 26, 2024

That's an interesting concern; there are a number of DFU-updatable devices on my systems already which have no such protection, which I guess means that if someone was able to open a root shell on those systems they'd be able to apply firmware to them... of course if that is the case then I've got bigger problems to worry about :-)

I don't think we can make DFU support appear and disappear (that would require triggering USB enumeration to happen again which would be disruptive), but we could make the "DFU DETACH" function be opt-in; the device would advertise DFU support to the hosts but not actually do anything unless the DFU function has been enabled (either via key combination or in user_config.h). If the function is dynamically enabled and then used, it would go back to being disabled as a result of the reboot; if the function is enabled and then not used, there could be a timeout to disable it again.

I'm definitely not opposed to that sort of implementation, even though I'd enable it statically on my own devices!

@hrvach
Copy link
Owner

hrvach commented Mar 26, 2024

I know, it sounds "too much" when I read it too, but whenever you think something is far fetched and impossible, this is always a good read to remind you it's not - somebody implemented a Turing-complete CPU in an image file for a zero-click exploit!

Opt-in detach would also work and reboot is not that bad either - it takes half a second and paired with e.g. leds flashing lets you know firmware update mode is enabled.

One other thing I was thinking about - having 2 partitions, so I can try out stuff and unless I press a key combo within 10 seconds after an upgrade, it reverts to the previous version. This would enable me to do some testing on the same device I use for typing, which is 95% of the cases anyway :)

Btw I noticed you figured out the firmware version value in the main.h and incremented it in your patch - I'm impressed 👍

@raldnor
Copy link

raldnor commented Mar 26, 2024

Just to add my 2cts as well; I really loved the fact that the firmware flash functionality is safe by default. This is the first device that I would not hesitate to use with process automation systems because of this security measure. You never know in which environment this device will be used and therefore I really like the 'security by design' approach. I'd wish that more developers and manufacturers would follow this approach.
Then there's also the tinkerers risk. I already mistakenly flashed an esp32 with the wrong firmware when I had multiple attached to my system. I think once this firmware has matured there should not be much reason to flash it, and when there's a reason it's not much effort to enable the functionality. My vote therefore goes to 'please leave it as is' :).

@kpfleming
Copy link
Contributor Author

This will be completely opt-in, if you don't want to use it you won't need to enable it in your builds.

In addition to firmware, I realized that this could also be useful for backup/restore of the configuration data; it would be challenging to do that in a human-readable format (like JSON), but a CLI tool could pipe data to/from dfu-util to transform the binary data into a format like that.

@kpfleming
Copy link
Contributor Author

Some updates:

  1. I won't be including 'pico-flashloader' in the process; there is no need for it, since this application runs in COPY_TO_RAM mode.
  2. The DFU uploads will go into flash, not RAM, so there won't be any "50% of RAM" limit on firmware binary size.
  3. It will be possible to update both boards through just one of them, by running two DFU upload operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants