From beae5b2fd9771864fba3e2c9f8110e3b9f5da8cf Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Mon, 21 Jul 2025 23:12:16 -0600 Subject: [PATCH] WIP --- Cargo.lock | 131 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 6 +- src/display.rs | 21 +------ src/main.rs | 78 ++++++++++++----------- src/scsi/mod.rs | 107 +++++++++++++++++++++++++++++++ src/scsi/scsi_types.rs | 140 +++++++++++++++++++++++++++++++++++++++++ src/storage.rs | 66 +++++++++++++++++++ src/usb.rs | 37 +++++++++++ 8 files changed, 526 insertions(+), 60 deletions(-) create mode 100644 src/scsi/mod.rs create mode 100644 src/scsi/scsi_types.rs create mode 100644 src/storage.rs create mode 100644 src/usb.rs diff --git a/Cargo.lock b/Cargo.lock index 13db5ac..34c418d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,18 @@ dependencies = [ "regex", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -126,6 +138,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "1.3.2" @@ -215,7 +233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ "bare-metal", - "bitfield", + "bitfield 0.13.2", "embedded-hal 0.2.7", "volatile-register", ] @@ -686,6 +704,21 @@ dependencies = [ "heapless", ] +[[package]] +name = "embassy-usb" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e651b9b7b47b514e6e6d1940a6e2e300891a2c33641917130643602a0cb6386" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync 0.6.2", + "embassy-usb-driver", + "heapless", + "ssmarshal", + "usbd-hid", +] + [[package]] name = "embassy-usb-driver" version = "0.1.1" @@ -822,6 +855,12 @@ dependencies = [ "log", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "equivalent" version = "1.0.2" @@ -996,6 +1035,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.4" @@ -1031,7 +1079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -1370,12 +1418,14 @@ dependencies = [ "embassy-rp 0.4.0", "embassy-sync 0.7.0", "embassy-time", + "embassy-usb", "embedded-graphics", "embedded-hal 0.2.7", "embedded-hal-async", "embedded-hal-bus", "embedded-sdmmc", "heapless", + "num_enum 0.7.4", "panic-probe", "portable-atomic", "spin", @@ -1710,6 +1760,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "sha2-const-stable" version = "0.1.0" @@ -1765,6 +1835,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + [[package]] name = "st7365p-lcd" version = "0.11.0" @@ -1983,6 +2063,53 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown 0.13.2", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "uuid" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 6477884..9ff2bd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,8 +45,9 @@ embassy-rp = { version = "0.4.0", features = [ ] } embassy-futures = "0.1.1" embassy-time = "0.4.0" -embassy-embedded-hal = "0.3.0" -embassy-sync = { version = "0.7" } +embassy-embedded-hal = "0.3.1" +embassy-sync = "0.7" +embassy-usb = "0.4.0" trouble-host = { version = "0.1", features = [ "derive", "scan", @@ -78,3 +79,4 @@ bitflags = "2.9.1" talc = "4.4.3" spin = "0.10.0" heapless = "0.8.0" +num_enum = { version = "0.7.4", default-features = false } diff --git a/src/display.rs b/src/display.rs index 7cf9ab4..6766b8a 100644 --- a/src/display.rs +++ b/src/display.rs @@ -21,8 +21,6 @@ use embedded_hal_bus::spi::ExclusiveDevice; use portable_atomic::AtomicBool; use st7365p_lcd::{FrameBuffer, ST7365P}; -use crate::LAST_TEXT_RECT; - const SCREEN_WIDTH: usize = 320; const SCREEN_HEIGHT: usize = 320; @@ -58,24 +56,7 @@ pub async fn display_handler( loop { DISPLAY_SIGNAL.wait().await; - let text_string = crate::STRING.lock().await.clone(); - - let text = Text::with_alignment( - &text_string, - Point::new(160, 160), - MonoTextStyle::new(&FONT_10X20, Rgb565::RED), - Alignment::Center, - ); - - { - let rect = LAST_TEXT_RECT.lock().await; - if let Some(rect) = *rect.borrow() { - framebuffer.fill_solid(&rect, Rgb565::BLACK).unwrap(); - } - *rect.borrow_mut() = Some(text.bounding_box()); - } - - text.draw(&mut framebuffer).unwrap(); + // text.draw(&mut framebuffer).unwrap(); let start = Instant::now(); framebuffer diff --git a/src/main.rs b/src/main.rs index e754b93..5c4bdea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ use crate::{ display::DISPLAY_SIGNAL, peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo}, + storage::SdCard, + usb::usb_handler, }; use {defmt_rtt as _, panic_probe as _}; @@ -13,70 +15,74 @@ use {defmt_rtt as _, panic_probe as _}; use core::cell::RefCell; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_rp::peripherals::I2C1; +use embassy_rp::{ + gpio::{Input, Level, Output, Pull}, + peripherals::{I2C1, USB}, + spi::Spi, + usb as embassy_rp_usb, +}; use embassy_rp::{i2c, i2c::I2c, spi}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; -use embassy_time::Timer; +use embassy_time::{Delay, Timer}; use embedded_graphics::primitives::Rectangle; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::asynchronous::SdCard as SdmmcSdCard; use heapless::String; mod peripherals; use peripherals::conf_peripherals; mod display; use display::display_handler; +mod scsi; +mod storage; +mod usb; embassy_rp::bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; + USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; }); -static STRING: Mutex> = Mutex::new(String::new()); -static LAST_TEXT_RECT: Mutex>> = - Mutex::new(RefCell::new(None)); - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); - STRING.lock().await.push_str("Press Del").unwrap(); - - // configure keyboard event handler + // MCU i2c bus for peripherals let mut config = i2c::Config::default(); config.frequency = 400_000; let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config); conf_peripherals(i2c1).await; + // SPI1 bus display let mut config = spi::Config::default(); config.frequency = 16_000_000; let spi1 = spi::Spi::new( p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config, ); - join( - async { - loop { - Timer::after_millis(20).await; - if let Some(key) = read_keyboard_fifo().await - && key.state == KeyState::Pressed - { - let mut string = STRING.lock().await; - match key.key { - KeyCode::Backspace => { - string.pop().unwrap(); - } - KeyCode::Del => { - string.clear(); - } - KeyCode::Char(c) => { - string.push(c).unwrap(); - } - _ => (), - } - DISPLAY_SIGNAL.signal(()); - } - } - }, - display_handler(spi1, p.PIN_13, p.PIN_14, p.PIN_15), - ) - .await; + let usb = embassy_rp_usb::Driver::new(p.USB, Irqs); + + let sdcard = { + let mut config = spi::Config::default(); + config.frequency = 400_000; + let spi = Spi::new( + p.SPI0, + p.PIN_18, + p.PIN_19, + p.PIN_16, + p.DMA_CH2, + p.DMA_CH3, + config.clone(), + ); + let cs = Output::new(p.PIN_5, Level::High); + + let device = ExclusiveDevice::new(spi, cs, Delay).unwrap(); + let sdcard = SdmmcSdCard::new(device, Delay); + + config.frequency = 32_000_000; + sdcard.spi(|dev| dev.bus_mut().set_config(&config)); + SdCard::new(sdcard, Input::new(p.PIN_22, Pull::None)) + }; + + usb_handler(usb, sdcard).await; } diff --git a/src/scsi/mod.rs b/src/scsi/mod.rs new file mode 100644 index 0000000..4889bf4 --- /dev/null +++ b/src/scsi/mod.rs @@ -0,0 +1,107 @@ +use embassy_usb::driver::{Driver, EndpointIn, EndpointOut}; +use embassy_usb::types::StringIndex; +use embassy_usb::{Builder, Config}; + +mod scsi_types; +use scsi_types::*; + +use crate::storage::SdCard; + +pub struct MassStorageClass<'d, 'c, D: Driver<'d>> { + sdcard: SdCard<'c>, + bulk_out: D::EndpointOut, + bulk_in: D::EndpointIn, +} + +impl<'d, 'c, D: Driver<'d>> MassStorageClass<'d, 'c, D> { + pub fn new(builder: &mut Builder<'d, D>, sdcard: SdCard<'c>) -> Self { + let mut function = builder.function(0x08, 0x06, 0x50); // Mass Storage class + let mut interface = function.interface(); + let mut alt = interface.alt_setting(0x08, 0x06, 0x50, None); + + let bulk_out = alt.endpoint_bulk_out(64); + let bulk_in = alt.endpoint_bulk_in(64); + + Self { + bulk_out, + bulk_in, + sdcard, + } + } + + pub async fn poll(&mut self) { + let mut cbw_buf = [0u8; 31]; + if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await { + if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) { + self.handle_command(&cbw.CBWCB).await; + } + } + } + + async fn handle_command(&self, cbw: &[u8]) { + match parse_cb(cbw) { + ScsiCommand::Unknown => { + #[cfg(feature = "defmt")] + defmt::info!("Got unexpected scsi command: {}", cbw); + } + ScsiCommand::Inquiry { + evpd, + page_code, + alloc_len, + } => todo!(), + ScsiCommand::TestUnitReady => todo!(), + ScsiCommand::RequestSense { desc, alloc_len } => todo!(), + ScsiCommand::ModeSense6 { + dbd, + page_control, + page_code, + subpage_code, + alloc_len, + } => todo!(), + ScsiCommand::ModeSense10 { + dbd, + page_control, + page_code, + subpage_code, + alloc_len, + } => todo!(), + ScsiCommand::ReadCapacity10 => todo!(), + ScsiCommand::ReadCapacity16 { alloc_len } => todo!(), + ScsiCommand::Read { lba, len } => todo!(), + ScsiCommand::Write { lba, len } => todo!(), + ScsiCommand::ReadFormatCapacities { alloc_len } => todo!(), + } + } +} + +#[repr(C, packed)] +struct CommandBlockWrapper { + dCBWSignature: u32, + dCBWTag: u32, + dCBWDataTransferLength: u32, + bmCBWFlags: u8, + bCBWLUN: u8, + bCBWCBLength: u8, + CBWCB: [u8; 16], +} + +impl CommandBlockWrapper { + fn parse(buf: &[u8]) -> Option { + if buf.len() < 31 { + return None; + } + let dCBWSignature = u32::from_le_bytes(buf[0..4].try_into().unwrap()); + if dCBWSignature != 0x43425355 { + return None; // invalid signature + } + Some(Self { + dCBWSignature, + dCBWTag: u32::from_le_bytes(buf[4..8].try_into().unwrap()), + dCBWDataTransferLength: u32::from_le_bytes(buf[8..12].try_into().unwrap()), + bmCBWFlags: buf[12], + bCBWLUN: buf[13], + bCBWCBLength: buf[14], + CBWCB: buf[15..31].try_into().unwrap(), + }) + } +} diff --git a/src/scsi/scsi_types.rs b/src/scsi/scsi_types.rs new file mode 100644 index 0000000..99dce71 --- /dev/null +++ b/src/scsi/scsi_types.rs @@ -0,0 +1,140 @@ +use num_enum::TryFromPrimitive; + +/// THE CODE BELOW ORIGINATES FROM: https://github.com/apohrebniak/usbd-storage/blob/master/usbd-storage/src/subclass/scsi.rs + +/// SCSI device subclass code +pub const SUBCLASS_SCSI: u8 = 0x06; // SCSI Transparent command set + +/* SCSI codes */ + +/* SPC */ +const TEST_UNIT_READY: u8 = 0x00; +const REQUEST_SENSE: u8 = 0x03; +const INQUIRY: u8 = 0x12; +const MODE_SENSE_6: u8 = 0x1A; +const MODE_SENSE_10: u8 = 0x5A; + +/* SBC */ +const READ_10: u8 = 0x28; +const READ_16: u8 = 0x88; +const READ_CAPACITY_10: u8 = 0x25; +const READ_CAPACITY_16: u8 = 0x9E; +const WRITE_10: u8 = 0x2A; + +/* MMC */ +const READ_FORMAT_CAPACITIES: u8 = 0x23; + +/// SCSI command +/// +/// Refer to specifications (SPC,SAM,SBC,MMC,etc.) +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ScsiCommand { + Unknown, + + /* SPC */ + Inquiry { + evpd: bool, + page_code: u8, + alloc_len: u16, + }, + TestUnitReady, + RequestSense { + desc: bool, + alloc_len: u8, + }, + ModeSense6 { + dbd: bool, + page_control: PageControl, + page_code: u8, + subpage_code: u8, + alloc_len: u8, + }, + ModeSense10 { + dbd: bool, + page_control: PageControl, + page_code: u8, + subpage_code: u8, + alloc_len: u16, + }, + + /* SBC */ + ReadCapacity10, + ReadCapacity16 { + alloc_len: u32, + }, + Read { + lba: u64, + len: u64, + }, + Write { + lba: u64, + len: u64, + }, + + /* MMC */ + ReadFormatCapacities { + alloc_len: u16, + }, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, TryFromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PageControl { + CurrentValues = 0b00, + ChangeableValues = 0b01, + DefaultValues = 0b10, + SavedValues = 0b11, +} + +#[allow(dead_code)] +pub fn parse_cb(cb: &[u8]) -> ScsiCommand { + match cb[0] { + TEST_UNIT_READY => ScsiCommand::TestUnitReady, + INQUIRY => ScsiCommand::Inquiry { + evpd: (cb[1] & 0b00000001) != 0, + page_code: cb[2], + alloc_len: u16::from_be_bytes([cb[3], cb[4]]), + }, + REQUEST_SENSE => ScsiCommand::RequestSense { + desc: (cb[1] & 0b00000001) != 0, + alloc_len: cb[4], + }, + READ_CAPACITY_10 => ScsiCommand::ReadCapacity10, + READ_CAPACITY_16 => ScsiCommand::ReadCapacity16 { + alloc_len: u32::from_be_bytes([cb[10], cb[11], cb[12], cb[13]]), + }, + READ_10 => ScsiCommand::Read { + lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64, + len: u16::from_be_bytes([cb[7], cb[8]]) as u64, + }, + READ_16 => ScsiCommand::Read { + lba: u64::from_be_bytes((&cb[2..10]).try_into().unwrap()), + len: u32::from_be_bytes((&cb[10..14]).try_into().unwrap()) as u64, + }, + WRITE_10 => ScsiCommand::Write { + lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64, + len: u16::from_be_bytes([cb[7], cb[8]]) as u64, + }, + MODE_SENSE_6 => ScsiCommand::ModeSense6 { + dbd: (cb[1] & 0b00001000) != 0, + page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(), + page_code: cb[2] & 0b00111111, + subpage_code: cb[3], + alloc_len: cb[4], + }, + MODE_SENSE_10 => ScsiCommand::ModeSense10 { + dbd: (cb[1] & 0b00001000) != 0, + page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(), + page_code: cb[2] & 0b00111111, + subpage_code: cb[3], + alloc_len: u16::from_be_bytes([cb[7], cb[8]]), + }, + READ_FORMAT_CAPACITIES => ScsiCommand::ReadFormatCapacities { + alloc_len: u16::from_be_bytes([cb[7], cb[8]]), + }, + _ => ScsiCommand::Unknown, + } +} diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000..685267a --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,66 @@ +use embassy_rp::gpio::{Input, Output}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Async, Spi}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc; +use embedded_sdmmc::asynchronous::{ + Directory, SdCard as SdmmcSdCard, Volume, VolumeIdx, VolumeManager, +}; +use embedded_sdmmc::blocking::{TimeSource, Timestamp}; + +pub const MAX_DIRS: usize = 4; +pub const MAX_FILES: usize = 5; +pub const MAX_VOLUMES: usize = 1; + +type Device = ExclusiveDevice, Output<'static>, embassy_time::Delay>; +type SD = SdmmcSdCard; +type VolMgr = VolumeManager; +type Vol<'a> = Volume<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; +type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; + +pub struct DummyTimeSource {} +impl TimeSource for DummyTimeSource { + fn get_timestamp(&self) -> Timestamp { + Timestamp::from_calendar(2022, 1, 1, 0, 0, 0).unwrap() + } +} + +pub struct SdCard<'a> { + det: Input<'static>, + volume_mgr: VolMgr, + volume: Option>, + root: Option>, +} + +impl<'a> SdCard<'a> { + pub fn new(sdcard: SD, det: Input<'static>) -> Self { + let volume_mgr = VolumeManager::<_, _, MAX_DIRS, MAX_FILES, MAX_VOLUMES>::new_with_limits( + sdcard, + DummyTimeSource {}, + 5000, + ); + Self { + det: det, + volume_mgr, + volume: None, + root: None, + } + } + + /// Returns true if an SD card is inserted. + /// The DET pin is active-low via mechanical switch in the socket. + fn attached(&self) -> bool { + self.det.is_low() + } + + async fn get_root(&'a mut self) { + let vol = self.volume.as_mut().unwrap(); + let root = vol.open_root_dir().unwrap(); + self.root = Some(root); + } + + async fn get_volume(&'a mut self) { + let vol = self.volume_mgr.open_volume(VolumeIdx(0)).await.unwrap(); + self.volume = Some(vol); + } +} diff --git a/src/usb.rs b/src/usb.rs new file mode 100644 index 0000000..1659839 --- /dev/null +++ b/src/usb.rs @@ -0,0 +1,37 @@ +use crate::{scsi::MassStorageClass, storage::SdCard}; +use embassy_futures::join::{self, join}; +use embassy_rp::{ + gpio::Output, + peripherals::{SPI0, USB}, + spi::{Async, Spi}, + usb::Driver, +}; +use embassy_time::Delay; +use embassy_usb::{Builder, Config}; + +pub async fn usb_handler(driver: Driver<'static, USB>, mut sdcard: SdCard<'_>) { + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("LegitCamper"); + config.product = Some("PicoCalc"); + config.serial_number = Some("01001100"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 64]; + let mut control_buf = [0; 64]; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], + &mut control_buf, + ); + + let mut scsi = MassStorageClass::new(&mut builder, sdcard); + let mut usb = builder.build(); + + join(usb.run(), scsi.poll()).await; +}