usbd_picotool_reset/
lib.rs

1//! UsbClass implementation for the picotool reset feature.
2//!
3//! ## Note
4//!
5//! For picotool to recognize your device, your device must be using Raspberry Pi's vendor ID (`0x2e8a`)
6//! 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)
7//! for an exhaustive list.
8
9#![forbid(missing_docs)]
10#![no_std]
11
12use core::marker::PhantomData;
13use usb_device::class_prelude::{InterfaceNumber, StringIndex, UsbBus, UsbBusAllocator};
14use usb_device::LangID;
15
16// Vendor specific class
17const CLASS_VENDOR_SPECIFIC: u8 = 0xFF;
18// cf: https://github.com/raspberrypi/pico-sdk/blob/f396d05f8252d4670d4ea05c8b7ac938ef0cd381/src/common/pico_usb_reset_interface/include/pico/usb_reset_interface.h#L17
19const RESET_INTERFACE_SUBCLASS: u8 = 0x00;
20const RESET_INTERFACE_PROTOCOL: u8 = 0x01;
21const RESET_REQUEST_BOOTSEL: u8 = 0x01;
22//const RESET_REQUEST_FLASH: u8 = 0x02;
23
24/// Defines which feature of the bootloader are made available after reset.
25#[cfg_attr(feature = "defmt", derive(defmt::Format))]
26#[derive(Debug, PartialEq, Eq, Clone, Copy)]
27pub enum DisableInterface {
28    /// Both Mass Storage and Pico boot are enabled.
29    None,
30    /// Disables Mass Storage leaving only PicoBoot.
31    DisableMassStorage,
32    /// Disables PicoBoot leaving only Mass Storage.
33    DisablePicoBoot,
34}
35impl DisableInterface {
36    const fn into(self) -> u32 {
37        match self {
38            DisableInterface::None => 0,
39            DisableInterface::DisableMassStorage => 1,
40            DisableInterface::DisablePicoBoot => 2,
41        }
42    }
43}
44
45/// Allows to customize the configuration of the UsbClass.
46pub trait Config {
47    /// Configuration for which interface to enable/disable after reset.
48    const INTERFACE_DISABLE: DisableInterface;
49    /// Configuration for which pin to show mass storage activity after reset.
50    const BOOTSEL_ACTIVITY_LED: Option<usize>;
51}
52
53/// Default configuration for PicoTool class.
54///
55/// This lets both interface enabled after reset and does not display mass storage activity on any
56/// LED.
57pub enum DefaultConfig {}
58impl Config for DefaultConfig {
59    const INTERFACE_DISABLE: DisableInterface = DisableInterface::None;
60
61    const BOOTSEL_ACTIVITY_LED: Option<usize> = None;
62}
63
64/// UsbClass implementation for Picotool's reset feature.
65pub struct PicoToolReset<'a, B: UsbBus, C: Config = DefaultConfig> {
66    intf: InterfaceNumber,
67    str_idx: StringIndex,
68    _bus: PhantomData<&'a B>,
69    _cnf: PhantomData<C>,
70}
71impl<'a, B: UsbBus, C: Config> PicoToolReset<'a, B, C> {
72    /// Creates a new instance of PicoToolReset.
73    pub fn new(alloc: &'a UsbBusAllocator<B>) -> PicoToolReset<'a, B, C> {
74        Self {
75            intf: alloc.interface(),
76            str_idx: alloc.string(),
77            _bus: PhantomData,
78            _cnf: PhantomData,
79        }
80    }
81}
82
83impl<B: UsbBus, C: Config> usb_device::class::UsbClass<B> for PicoToolReset<'_, B, C> {
84    fn get_configuration_descriptors(
85        &self,
86        writer: &mut usb_device::descriptor::DescriptorWriter,
87    ) -> usb_device::Result<()> {
88        writer.interface_alt(
89            self.intf,
90            0,
91            CLASS_VENDOR_SPECIFIC,
92            RESET_INTERFACE_SUBCLASS,
93            RESET_INTERFACE_PROTOCOL,
94            Some(self.str_idx),
95        )
96    }
97
98    fn get_string(&self, index: StringIndex, _lang_id: LangID) -> Option<&str> {
99        (index == self.str_idx).then_some("Reset")
100    }
101
102    fn control_out(&mut self, xfer: usb_device::class_prelude::ControlOut<B>) {
103        let req = xfer.request();
104        if !(req.request_type == usb_device::control::RequestType::Class
105            && req.recipient == usb_device::control::Recipient::Interface
106            && req.index == u8::from(self.intf) as u16)
107        {
108            return;
109        }
110
111        match req.request {
112            RESET_REQUEST_BOOTSEL => {
113                let mut gpio_mask = C::BOOTSEL_ACTIVITY_LED.map(|led| 1 << led).unwrap_or(0);
114                if req.value & 0x100 != 0 {
115                    gpio_mask = 1 << (req.value >> 9);
116                }
117                rp2040_hal::rom_data::reset_to_usb_boot(
118                    gpio_mask,
119                    u32::from(req.value & 0x7F) | C::INTERFACE_DISABLE.into(),
120                );
121                // no-need to accept/reject, we'll reset the device anyway
122                unreachable!()
123            }
124            //RESET_REQUEST_FLASH => todo!(),
125            _ => {
126                // we are not expecting any other USB OUT requests
127                let _ = xfer.reject();
128            }
129        }
130    }
131
132    fn control_in(&mut self, xfer: usb_device::class_prelude::ControlIn<B>) {
133        let req = xfer.request();
134        if !(req.request_type == usb_device::control::RequestType::Class
135            && req.recipient == usb_device::control::Recipient::Interface
136            && req.index == u8::from(self.intf) as u16)
137        {
138            return;
139        }
140        // we are not expecting any USB IN requests
141        let _ = xfer.reject();
142    }
143}