#![forbid(missing_docs)]
#![no_std]
use core::marker::PhantomData;
use usb_device::class_prelude::{InterfaceNumber, StringIndex, UsbBus, UsbBusAllocator};
const CLASS_VENDOR_SPECIFIC: u8 = 0xFF;
const RESET_INTERFACE_SUBCLASS: u8 = 0x00;
const RESET_INTERFACE_PROTOCOL: u8 = 0x01;
const RESET_REQUEST_BOOTSEL: u8 = 0x01;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum DisableInterface {
None,
DisableMassStorage,
DisablePicoBoot,
}
impl DisableInterface {
const fn into(self) -> u32 {
match self {
DisableInterface::None => 0,
DisableInterface::DisableMassStorage => 1,
DisableInterface::DisablePicoBoot => 2,
}
}
}
pub trait Config {
const INTERFACE_DISABLE: DisableInterface;
const BOOTSEL_ACTIVITY_LED: Option<usize>;
}
pub enum DefaultConfig {}
impl Config for DefaultConfig {
const INTERFACE_DISABLE: DisableInterface = DisableInterface::None;
const BOOTSEL_ACTIVITY_LED: Option<usize> = None;
}
pub struct PicoToolReset<'a, B: UsbBus, C: Config = DefaultConfig> {
intf: InterfaceNumber,
str_idx: StringIndex,
_bus: PhantomData<&'a B>,
_cnf: PhantomData<C>,
}
impl<'a, B: UsbBus, C: Config> PicoToolReset<'a, B, C> {
pub fn new(alloc: &'a UsbBusAllocator<B>) -> PicoToolReset<'a, B, C> {
Self {
intf: alloc.interface(),
str_idx: alloc.string(),
_bus: PhantomData,
_cnf: PhantomData,
}
}
}
impl<B: UsbBus, C: Config> usb_device::class::UsbClass<B> 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<B>) {
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(),
);
unreachable!()
}
_ => {
let _ = xfer.reject();
}
}
}
fn control_in(&mut self, xfer: usb_device::class_prelude::ControlIn<B>) {
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;
}
let _ = xfer.reject();
}
}