From 4dbbf667a4f7733e685486df6b7d6d7f76386158 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 17 Nov 2024 17:34:32 +0800 Subject: [PATCH 1/4] Implement picotool reboot Works: ``` > sudo ./build/picotool reboot -f -u > sudo ./build/picotool reboot -f ``` TODO: Can we keep USB VID/PID? Signed-off-by: Daniel Schaefer --- Cargo.toml | 2 + qtpy/Cargo.toml | 1 + qtpy/src/main.rs | 17 +++- qtpy/src/usbd_picotool_reset.rs | 145 ++++++++++++++++++++++++++++++++ rust-toolchain.toml | 2 + 5 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 qtpy/src/usbd_picotool_reset.rs diff --git a/Cargo.toml b/Cargo.toml index a8e11c4..7ab6096 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,8 @@ usb-device = "0.2.9" heapless = "0.7.16" usbd-serial = "0.1.1" usbd-hid = "0.6.1" +# Can't use right now, depends on embedded-hal 1.0.0 +# usbd-picotool-reset = "0.3.0" fugit = "0.3.7" # LED Matrix is31fl3741 = "0.3.0" diff --git a/qtpy/Cargo.toml b/qtpy/Cargo.toml index 39e128f..a16c1ae 100644 --- a/qtpy/Cargo.toml +++ b/qtpy/Cargo.toml @@ -23,6 +23,7 @@ usb-device.workspace = true heapless.workspace = true usbd-serial.workspace = true usbd-hid.workspace = true +#usbd-picotool-reset.workspace = true fugit.workspace = true # C1 Minimal diff --git a/qtpy/src/main.rs b/qtpy/src/main.rs index 1ba7cd1..45a7376 100644 --- a/qtpy/src/main.rs +++ b/qtpy/src/main.rs @@ -34,11 +34,15 @@ use bsp::hal::{ }; // USB Device support -use usb_device::{class_prelude::*, prelude::*}; +use usb_device::{class_prelude::*, prelude::*, descriptor::lang_id}; // USB Communications Class Device support use usbd_serial::{SerialPort, USB_CLASS_CDC}; +//use usbd_picotool_reset::PicoToolReset; +mod usbd_picotool_reset; +use crate::usbd_picotool_reset::PicoToolReset; + // Used to demonstrate writing formatted strings // use core::fmt::Write; // use heapless::String; @@ -95,10 +99,16 @@ fn main() -> ! { &mut pac.RESETS, )); + let mut picotool: PicoToolReset<_> = PicoToolReset::new(&usb_bus); + // Set up the USB Communications Class Device driver let mut serial = SerialPort::new(&usb_bus); - let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(FRAMEWORK_VID, COMMUNITY_PID)) + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x2e8a, 0x000a)) + //.strings(&[StringDescriptors::new(lang_id::ENGLISH_US) + // .manufacturer("Adafruit") + // .product("QT PY - Framework 16 Inputmodule FW")]) + //.expect("Failed to set strings") .manufacturer("Adafruit") .product("QT PY - Framework 16 Inputmodule FW") .max_power(500) @@ -141,7 +151,8 @@ fn main() -> ! { } // Check for new data - if usb_dev.poll(&mut [&mut serial]) { + // usb_dev.poll(&mut [&mut picotool, &mut serial]); + if usb_dev.poll(&mut [&mut picotool, &mut serial]) { let mut buf = [0u8; 64]; match serial.read(&mut buf) { Err(_e) => { diff --git a/qtpy/src/usbd_picotool_reset.rs b/qtpy/src/usbd_picotool_reset.rs new file mode 100644 index 0000000..b8793a8 --- /dev/null +++ b/qtpy/src/usbd_picotool_reset.rs @@ -0,0 +1,145 @@ +//! From https://github.com/ithinuel/usbd-picotool-reset +//! UsbClass implementation for the picotool reset feature. +//! +//! ## Note +//! +//! For picotool to recognize your device, your device must be using Raspberry Pi's vendor ID (`0x2e8a`) +//! and one of the product ID. You can check [picotool's sources](https://github.com/raspberrypi/picotool/blob/master/picoboot_connection/picoboot_connection.c#L23-L27) +//! for an exhaustive list. + +#![forbid(missing_docs)] +// #![no_std] + +use core::marker::PhantomData; +use usb_device::class_prelude::{InterfaceNumber, StringIndex, UsbBus, UsbBusAllocator}; +//use usb_device::LangID; +use usb_device::descriptor::lang_id; + +// Vendor specific class +const CLASS_VENDOR_SPECIFIC: u8 = 0xFF; +// cf: https://github.com/raspberrypi/pico-sdk/blob/f396d05f8252d4670d4ea05c8b7ac938ef0cd381/src/common/pico_usb_reset_interface/include/pico/usb_reset_interface.h#L17 +const RESET_INTERFACE_SUBCLASS: u8 = 0x00; +const RESET_INTERFACE_PROTOCOL: u8 = 0x01; +const RESET_REQUEST_BOOTSEL: u8 = 0x01; +//const RESET_REQUEST_FLASH: u8 = 0x02; + +/// Defines which feature of the bootloader are made available after reset. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DisableInterface { + /// Both Mass Storage and Pico boot are enabled. + None, + /// Disables Mass Storage leaving only PicoBoot. + DisableMassStorage, + /// Disables PicoBoot leaving only Mass Storage. + DisablePicoBoot, +} +impl DisableInterface { + const fn into(self) -> u32 { + match self { + DisableInterface::None => 0, + DisableInterface::DisableMassStorage => 1, + DisableInterface::DisablePicoBoot => 2, + } + } +} + +/// Allows to customize the configuration of the UsbClass. +pub trait Config { + /// Configuration for which interface to enable/disable after reset. + const INTERFACE_DISABLE: DisableInterface; + /// Configuration for which pin to show mass storage activity after reset. + const BOOTSEL_ACTIVITY_LED: Option; +} + +/// Default configuration for PicoTool class. +/// +/// This lets both interface enabled after reset and does not display mass storage activity on any +/// LED. +pub enum DefaultConfig {} +impl Config for DefaultConfig { + const INTERFACE_DISABLE: DisableInterface = DisableInterface::None; + + const BOOTSEL_ACTIVITY_LED: Option = None; +} + +/// UsbClass implementation for Picotool's reset feature. +pub struct PicoToolReset<'a, B: UsbBus, C: Config = DefaultConfig> { + intf: InterfaceNumber, + str_idx: StringIndex, + _bus: PhantomData<&'a B>, + _cnf: PhantomData, +} +impl<'a, B: UsbBus, C: Config> PicoToolReset<'a, B, C> { + /// Creates a new instance of PicoToolReset. + pub fn new(alloc: &'a UsbBusAllocator) -> PicoToolReset<'a, B, C> { + Self { + intf: alloc.interface(), + str_idx: alloc.string(), + _bus: PhantomData, + _cnf: PhantomData, + } + } +} + +impl usb_device::class::UsbClass for PicoToolReset<'_, B, C> { + fn get_configuration_descriptors( + &self, + writer: &mut usb_device::descriptor::DescriptorWriter, + ) -> usb_device::Result<()> { + writer.interface_alt( + self.intf, + 0, + CLASS_VENDOR_SPECIFIC, + RESET_INTERFACE_SUBCLASS, + RESET_INTERFACE_PROTOCOL, + Some(self.str_idx), + ) + } + + fn get_string(&self, index: StringIndex, _lang_id: u16) -> Option<&str> { + (index == self.str_idx).then_some("Reset") + } + + fn control_out(&mut self, xfer: usb_device::class_prelude::ControlOut) { + let req = xfer.request(); + if !(req.request_type == usb_device::control::RequestType::Class + && req.recipient == usb_device::control::Recipient::Interface + && req.index == u8::from(self.intf) as u16) + { + return; + } + + match req.request { + RESET_REQUEST_BOOTSEL => { + let mut gpio_mask = C::BOOTSEL_ACTIVITY_LED.map(|led| 1 << led).unwrap_or(0); + if req.value & 0x100 != 0 { + gpio_mask = 1 << (req.value >> 9); + } + rp2040_hal::rom_data::reset_to_usb_boot( + gpio_mask, + u32::from(req.value & 0x7F) | C::INTERFACE_DISABLE.into(), + ); + // no-need to accept/reject, we'll reset the device anyway + unreachable!() + } + //RESET_REQUEST_FLASH => todo!(), + _ => { + // we are not expecting any other USB OUT requests + let _ = xfer.reject(); + } + } + } + + fn control_in(&mut self, xfer: usb_device::class_prelude::ControlIn) { + let req = xfer.request(); + if !(req.request_type == usb_device::control::RequestType::Class + && req.recipient == usb_device::control::Recipient::Interface + && req.index == u8::from(self.intf) as u16) + { + return; + } + // we are not expecting any USB IN requests + let _ = xfer.reject(); + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 460b3c4..264c950 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,6 @@ [toolchain] +# Needed for embeded-hal 1.0.0 +# channel = "1.75.0" channel = "1.74.0" targets = ["thumbv6m-none-eabi"] components = ["clippy", "rustfmt"] From cba1dfda0e8b442bdd07637b7da3baaa506e7ed2 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 17 Nov 2024 18:36:11 +0800 Subject: [PATCH 2/4] Use usbd-picotool-reset from forked repo Signed-off-by: Daniel Schaefer --- Cargo.lock | 10 +++ Cargo.toml | 1 + qtpy/Cargo.toml | 2 +- qtpy/src/main.rs | 4 +- qtpy/src/usbd_picotool_reset.rs | 145 -------------------------------- 5 files changed, 13 insertions(+), 149 deletions(-) delete mode 100644 qtpy/src/usbd_picotool_reset.rs diff --git a/Cargo.lock b/Cargo.lock index 50a4c9f..c1fca89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1647,6 +1647,7 @@ dependencies = [ "smart-leds", "usb-device", "usbd-hid", + "usbd-picotool-reset", "usbd-serial", "ws2812-pio", ] @@ -2362,6 +2363,15 @@ dependencies = [ "usbd-hid-descriptors", ] +[[package]] +name = "usbd-picotool-reset" +version = "0.3.0" +source = "git+https://github.com/JohnAZoidberg/usbd-picotool-reset?branch=rp-2040-hal-0.8#46cbb075a39031cd0fb8c4539aa9ed20dabff76d" +dependencies = [ + "rp2040-hal", + "usb-device", +] + [[package]] name = "usbd-serial" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 7ab6096..8fbc51e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ usbd-serial = "0.1.1" usbd-hid = "0.6.1" # Can't use right now, depends on embedded-hal 1.0.0 # usbd-picotool-reset = "0.3.0" +usbd-picotool-reset = { git = "https://github.com/JohnAZoidberg/usbd-picotool-reset", branch = "rp-2040-hal-0.8" } fugit = "0.3.7" # LED Matrix is31fl3741 = "0.3.0" diff --git a/qtpy/Cargo.toml b/qtpy/Cargo.toml index a16c1ae..412b5e6 100644 --- a/qtpy/Cargo.toml +++ b/qtpy/Cargo.toml @@ -23,7 +23,7 @@ usb-device.workspace = true heapless.workspace = true usbd-serial.workspace = true usbd-hid.workspace = true -#usbd-picotool-reset.workspace = true +usbd-picotool-reset.workspace = true fugit.workspace = true # C1 Minimal diff --git a/qtpy/src/main.rs b/qtpy/src/main.rs index 45a7376..48b747c 100644 --- a/qtpy/src/main.rs +++ b/qtpy/src/main.rs @@ -39,9 +39,7 @@ use usb_device::{class_prelude::*, prelude::*, descriptor::lang_id}; // USB Communications Class Device support use usbd_serial::{SerialPort, USB_CLASS_CDC}; -//use usbd_picotool_reset::PicoToolReset; -mod usbd_picotool_reset; -use crate::usbd_picotool_reset::PicoToolReset; +use usbd_picotool_reset::PicoToolReset; // Used to demonstrate writing formatted strings // use core::fmt::Write; diff --git a/qtpy/src/usbd_picotool_reset.rs b/qtpy/src/usbd_picotool_reset.rs deleted file mode 100644 index b8793a8..0000000 --- a/qtpy/src/usbd_picotool_reset.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! From https://github.com/ithinuel/usbd-picotool-reset -//! UsbClass implementation for the picotool reset feature. -//! -//! ## Note -//! -//! For picotool to recognize your device, your device must be using Raspberry Pi's vendor ID (`0x2e8a`) -//! and one of the product ID. You can check [picotool's sources](https://github.com/raspberrypi/picotool/blob/master/picoboot_connection/picoboot_connection.c#L23-L27) -//! for an exhaustive list. - -#![forbid(missing_docs)] -// #![no_std] - -use core::marker::PhantomData; -use usb_device::class_prelude::{InterfaceNumber, StringIndex, UsbBus, UsbBusAllocator}; -//use usb_device::LangID; -use usb_device::descriptor::lang_id; - -// Vendor specific class -const CLASS_VENDOR_SPECIFIC: u8 = 0xFF; -// cf: https://github.com/raspberrypi/pico-sdk/blob/f396d05f8252d4670d4ea05c8b7ac938ef0cd381/src/common/pico_usb_reset_interface/include/pico/usb_reset_interface.h#L17 -const RESET_INTERFACE_SUBCLASS: u8 = 0x00; -const RESET_INTERFACE_PROTOCOL: u8 = 0x01; -const RESET_REQUEST_BOOTSEL: u8 = 0x01; -//const RESET_REQUEST_FLASH: u8 = 0x02; - -/// Defines which feature of the bootloader are made available after reset. -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum DisableInterface { - /// Both Mass Storage and Pico boot are enabled. - None, - /// Disables Mass Storage leaving only PicoBoot. - DisableMassStorage, - /// Disables PicoBoot leaving only Mass Storage. - DisablePicoBoot, -} -impl DisableInterface { - const fn into(self) -> u32 { - match self { - DisableInterface::None => 0, - DisableInterface::DisableMassStorage => 1, - DisableInterface::DisablePicoBoot => 2, - } - } -} - -/// Allows to customize the configuration of the UsbClass. -pub trait Config { - /// Configuration for which interface to enable/disable after reset. - const INTERFACE_DISABLE: DisableInterface; - /// Configuration for which pin to show mass storage activity after reset. - const BOOTSEL_ACTIVITY_LED: Option; -} - -/// Default configuration for PicoTool class. -/// -/// This lets both interface enabled after reset and does not display mass storage activity on any -/// LED. -pub enum DefaultConfig {} -impl Config for DefaultConfig { - const INTERFACE_DISABLE: DisableInterface = DisableInterface::None; - - const BOOTSEL_ACTIVITY_LED: Option = None; -} - -/// UsbClass implementation for Picotool's reset feature. -pub struct PicoToolReset<'a, B: UsbBus, C: Config = DefaultConfig> { - intf: InterfaceNumber, - str_idx: StringIndex, - _bus: PhantomData<&'a B>, - _cnf: PhantomData, -} -impl<'a, B: UsbBus, C: Config> PicoToolReset<'a, B, C> { - /// Creates a new instance of PicoToolReset. - pub fn new(alloc: &'a UsbBusAllocator) -> PicoToolReset<'a, B, C> { - Self { - intf: alloc.interface(), - str_idx: alloc.string(), - _bus: PhantomData, - _cnf: PhantomData, - } - } -} - -impl usb_device::class::UsbClass for PicoToolReset<'_, B, C> { - fn get_configuration_descriptors( - &self, - writer: &mut usb_device::descriptor::DescriptorWriter, - ) -> usb_device::Result<()> { - writer.interface_alt( - self.intf, - 0, - CLASS_VENDOR_SPECIFIC, - RESET_INTERFACE_SUBCLASS, - RESET_INTERFACE_PROTOCOL, - Some(self.str_idx), - ) - } - - fn get_string(&self, index: StringIndex, _lang_id: u16) -> Option<&str> { - (index == self.str_idx).then_some("Reset") - } - - fn control_out(&mut self, xfer: usb_device::class_prelude::ControlOut) { - let req = xfer.request(); - if !(req.request_type == usb_device::control::RequestType::Class - && req.recipient == usb_device::control::Recipient::Interface - && req.index == u8::from(self.intf) as u16) - { - return; - } - - match req.request { - RESET_REQUEST_BOOTSEL => { - let mut gpio_mask = C::BOOTSEL_ACTIVITY_LED.map(|led| 1 << led).unwrap_or(0); - if req.value & 0x100 != 0 { - gpio_mask = 1 << (req.value >> 9); - } - rp2040_hal::rom_data::reset_to_usb_boot( - gpio_mask, - u32::from(req.value & 0x7F) | C::INTERFACE_DISABLE.into(), - ); - // no-need to accept/reject, we'll reset the device anyway - unreachable!() - } - //RESET_REQUEST_FLASH => todo!(), - _ => { - // we are not expecting any other USB OUT requests - let _ = xfer.reject(); - } - } - } - - fn control_in(&mut self, xfer: usb_device::class_prelude::ControlIn) { - let req = xfer.request(); - if !(req.request_type == usb_device::control::RequestType::Class - && req.recipient == usb_device::control::Recipient::Interface - && req.index == u8::from(self.intf) as u16) - { - return; - } - // we are not expecting any USB IN requests - let _ = xfer.reject(); - } -} From 30841c53b60b598a6390a003f272a8babf650f23 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 17 Nov 2024 20:54:13 +0800 Subject: [PATCH 3/4] qtpy: Switch back to Framework VID/PID Depends on: https://github.com/raspberrypi/picotool/pull/177 Signed-off-by: Daniel Schaefer --- qtpy/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtpy/src/main.rs b/qtpy/src/main.rs index 48b747c..b659da2 100644 --- a/qtpy/src/main.rs +++ b/qtpy/src/main.rs @@ -102,7 +102,7 @@ fn main() -> ! { // Set up the USB Communications Class Device driver let mut serial = SerialPort::new(&usb_bus); - let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x2e8a, 0x000a)) + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(FRAMEWORK_VID, COMMUNITY_PID)) //.strings(&[StringDescriptors::new(lang_id::ENGLISH_US) // .manufacturer("Adafruit") // .product("QT PY - Framework 16 Inputmodule FW")]) From 70edb66306dc47541a77caa31c4935a8ca9f8137 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 18 Nov 2024 14:29:24 +0800 Subject: [PATCH 4/4] fwupd: Add initial xml and Makefile Signed-off-by: Daniel Schaefer --- fwupd/.gitignore | 1 + fwupd/Makefile | 3 +++ fwupd/firmware.metainfo.xml | 43 +++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 fwupd/.gitignore create mode 100644 fwupd/Makefile create mode 100644 fwupd/firmware.metainfo.xml diff --git a/fwupd/.gitignore b/fwupd/.gitignore new file mode 100644 index 0000000..c249887 --- /dev/null +++ b/fwupd/.gitignore @@ -0,0 +1 @@ +*.cab diff --git a/fwupd/Makefile b/fwupd/Makefile new file mode 100644 index 0000000..dfc882d --- /dev/null +++ b/fwupd/Makefile @@ -0,0 +1,3 @@ +qtpy.cab: firmware.metainfo.xml + cp ../target/thumbv6m-none-eabi/release/qtpy.uf2 . + gcab -c -v $@ qtpy.uf2 firmware.metainfo.xml diff --git a/fwupd/firmware.metainfo.xml b/fwupd/firmware.metainfo.xml new file mode 100644 index 0000000..c511a7c --- /dev/null +++ b/fwupd/firmware.metainfo.xml @@ -0,0 +1,43 @@ + + + work.frame.inputmodule.qtpy + Framework QT Py + Firmware for the Framework QT Py + +

+ Updating the firmware on your Framework QT Py improves performance and + adds new features. +

+
+ + + 5a5b58d0-9cb4-5f8b-a449-e7b7c820b0cb + + https://github.com/FrameworkComputer/inputmodule-rs + CC0-1.0 + proprietary + + X-Device + + + + + + New firmware! + + + + {{ min_ver }} + org.freedesktop.fwupd + + + + com.microsoft.uf2 + + +