From a53929f511c283ce2792e8bedf009c96e3ad8d92 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 30 Jul 2025 15:21:44 -0600 Subject: [PATCH] Merge usb-mass-storage branch --- Cargo.lock | 156 ++++++++++++++--- kernel/Cargo.toml | 8 +- kernel/src/display.rs | 21 +-- kernel/src/main.rs | 80 +++++---- kernel/src/scsi/mod.rs | 308 ++++++++++++++++++++++++++++++++++ kernel/src/scsi/scsi_types.rs | 164 ++++++++++++++++++ kernel/src/storage.rs | 100 +++++++++++ kernel/src/usb.rs | 36 ++++ kernel/src/utils.rs | 11 ++ 9 files changed, 801 insertions(+), 83 deletions(-) create mode 100644 kernel/src/scsi/mod.rs create mode 100644 kernel/src/scsi/scsi_types.rs create mode 100644 kernel/src/storage.rs create mode 100644 kernel/src/usb.rs create mode 100644 kernel/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index dc4cf18..0643f42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,18 @@ dependencies = [ "shared", ] +[[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" @@ -82,21 +94,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "bisync" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5020822f6d6f23196ccaf55e228db36f9de1cf788052b37992e17cbc96ec41a7" -dependencies = [ - "bisync_macros", -] - -[[package]] -name = "bisync_macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21f40d350a700f6aa107e45fb26448cf489d34794b2ba4522181dc9f1173af6" - [[package]] name = "bit-set" version = "0.5.3" @@ -133,6 +130,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" @@ -226,7 +229,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", ] @@ -672,6 +675,7 @@ dependencies = [ "defmt 0.3.100", "document-features", "embassy-time-driver", + "embassy-time-queue-utils", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -697,6 +701,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" @@ -795,17 +814,14 @@ dependencies = [ [[package]] name = "embedded-sdmmc" -version = "0.8.0" -source = "git+https://github.com/Be-ing/embedded-sdmmc-rs?branch=bisync#835b2e4f9d3482b6287f674d7ecf6ae5d0618c18" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3c7f9ea039eeafc4a49597b7bd5ae3a1c8e51b2803a381cb0f29ce90fe1ec6" dependencies = [ - "bisync", "byteorder", "defmt 0.3.100", - "embassy-futures", "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-io", - "embedded-io-async", "heapless", ] @@ -833,6 +849,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" @@ -1007,6 +1029,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" @@ -1042,7 +1073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -1382,12 +1413,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", "shared", @@ -1723,6 +1756,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" @@ -1786,6 +1839,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" @@ -2004,6 +2067,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/kernel/Cargo.toml b/kernel/Cargo.toml index 61fe107..d74fd95 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -43,9 +43,10 @@ embassy-rp = { version = "0.4.0", features = [ "time-driver", "binary-info", ] } +embassy-usb = "0.4.0" embassy-futures = "0.1.1" -embassy-time = "0.4.0" -embassy-embedded-hal = "0.3.0" +embassy-time = { version = "0.4.0", features = ["generic-queue-8"] } +embassy-embedded-hal = "0.3.1" embassy-sync = { version = "0.7" } trouble-host = { version = "0.1", features = [ "derive", @@ -70,7 +71,7 @@ defmt = { version = "0.3", optional = true } defmt-rtt = "0.4.2" embedded-graphics = { version = "0.8.1" } -embedded-sdmmc = { git = "https://github.com/Be-ing/embedded-sdmmc-rs", branch = "bisync", default-features = false } +embedded-sdmmc = { version = "0.9", default-features = false } st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", branch = "async" } shared = { path = "../shared" } @@ -81,3 +82,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/kernel/src/display.rs b/kernel/src/display.rs index 7cf9ab4..6766b8a 100644 --- a/kernel/src/display.rs +++ b/kernel/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/kernel/src/main.rs b/kernel/src/main.rs index 80184e9..cc25387 100644 --- a/kernel/src/main.rs +++ b/kernel/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,72 +15,76 @@ 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::SdCard as SdmmcSdCard; use heapless::String; mod peripherals; use peripherals::conf_peripherals; mod display; use display::display_handler; +mod scsi; +mod storage; +mod usb; +mod utils; 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_blocking( + p.SPI0, + p.PIN_18, // clk + p.PIN_19, // mosi + p.PIN_16, // miso + config.clone(), + ); + let cs = Output::new(p.PIN_17, Level::High); + let det = Input::new(p.PIN_22, Pull::None); + + 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, det) + }; + + usb_handler(usb, sdcard).await; } use abi::Syscall; @@ -87,7 +93,7 @@ use abi::Syscall; pub extern "C" fn syscall_dispatch(call: *const Syscall) -> usize { let call = unsafe { &*call }; match call { - Syscall::DrawPixel { x, y, color } => { + Syscall::DrawPixels { x, y, color } => { draw_pixel(*x, *y, *color); 0 } diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs new file mode 100644 index 0000000..a50ff26 --- /dev/null +++ b/kernel/src/scsi/mod.rs @@ -0,0 +1,308 @@ +use crate::format; +use embassy_usb::driver::{Driver, EndpointIn, EndpointOut}; +use embassy_usb::types::StringIndex; +use embassy_usb::{Builder, Config}; +use embedded_sdmmc::{Block, BlockIdx}; +use heapless::Vec; + +mod scsi_types; +use scsi_types::*; + +use crate::storage::SdCard; + +const BULK_ENDPOINT_PACKET_SIZE: usize = 64; + +pub struct MassStorageClass<'d, D: Driver<'d>> { + sdcard: SdCard, + bulk_out: D::EndpointOut, + bulk_in: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> MassStorageClass<'d, D> { + pub fn new(builder: &mut Builder<'d, D>, sdcard: SdCard) -> Self { + let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class + let mut interface = function.interface(); + let mut alt = interface.alt_setting(0x08, SUBCLASS_SCSI, 0x50, None); + + let bulk_out = alt.endpoint_bulk_out(BULK_ENDPOINT_PACKET_SIZE as u16); + let bulk_in = alt.endpoint_bulk_in(BULK_ENDPOINT_PACKET_SIZE as u16); + + Self { + bulk_out, + bulk_in, + sdcard, + } + } + + pub async fn poll(&mut self) { + loop { + let mut cbw_buf = [0u8; 31]; + if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await { + if n == 31 { + if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) { + // TODO: validate cbw + if self.handle_command(&cbw.CBWCB).await.is_ok() { + self.send_csw_success(cbw.dCBWTag).await + } else { + self.send_csw_fail(cbw.dCBWTag).await + } + } + } + } + } + } + + async fn handle_command(&mut self, cbw: &[u8]) -> Result<(), ()> { + let mut response: Vec = Vec::new(); + let mut block = [Block::new(); 1]; + + match parse_cb(cbw) { + ScsiCommand::Unknown => { + #[cfg(feature = "defmt")] + defmt::warn!("Got unexpected scsi command: {}", cbw); + Err(()) + } + ScsiCommand::Inquiry { + evpd, + page_code, + alloc_len, + } => { + if !evpd { + response.push(0x00).map_err(|_| ())?; // Direct-access block device + response.push(0x80).map_err(|_| ())?; // Removable + response.push(0x05).map_err(|_| ())?; // SPC-3 compliance + response.push(0x02).map_err(|_| ())?; // Response data format + response.push(0x00).map_err(|_| ())?; // Additional length - edited later + response.push(0x00).map_err(|_| ())?; // FLAGS + response.push(0x00).map_err(|_| ())?; // FLAGS + response.push(0).map_err(|_| ())?; // FLAGS + assert!(response.len() == 8); + + let vendor = b"LEGTCMPR"; + assert!(vendor.len() == 8); + response.extend_from_slice(vendor)?; + + let product = b"Pico Calc Sdcard"; + assert!(product.len() == 16); + response.extend_from_slice(product)?; + + let version = b"1.00"; + assert!(version.len() == 4); + response.extend_from_slice(version)?; // 4-byte firmware version + + let addl_len = response.len() - 5; + response[4] = addl_len as u8; + assert!(response.len() == 36); + } else { + match page_code { + 0x00 => { + response + .extend_from_slice(&[ + 0x00, // Peripheral Qualifier + Peripheral Device Type (0x00 = Direct-access block device) + 0x00, // Page Code (same as requested: 0x00) + 0x00, 0x03, // Page Length: 3 bytes follow + 0x00, // Supported VPD Page: 0x00 (this one — the "Supported VPD Pages" page itself) + 0x80, // Supported VPD Page: 0x80 (Unit Serial Number) + 0x83, // Supported VPD Page: 0x83 (Device Identification) + ]) + .map_err(|_| ())? + } + 0x80 => { + let serial = b"Pico Calc"; + response.extend_from_slice(&[ + 0x00, // Peripheral Qualifier & Device Type + 0x80, // Page Code = 0x80 (Unit Serial Number) + 0x00, // Reserved + serial.len() as u8, + ])?; + response.extend_from_slice(serial)?; + } + 0x83 => { + let id = b"SdCard"; + response.extend_from_slice(&[ + 0x00, + 0x83, // Page code + 0x00, + (4 + id.len()) as u8, // Length + 0x02, // ASCII identifier + 0x01, // Identifier type + 0x00, // Reserved + id.len() as u8, + ])?; + response.extend_from_slice(id)?; + } + _ => (), + } + }; + + let len = core::cmp::min(alloc_len as usize, response.len()); + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::TestUnitReady => { + if self.sdcard.is_attached() { + Ok(()) + } else { + Err(()) + } + } + ScsiCommand::RequestSense { desc, alloc_len } => Ok(()), + ScsiCommand::ModeSense6 { + dbd, + page_control, + page_code, + subpage_code, + alloc_len, + } => { + // DBD=0, no block descriptors; total length = 4 + let response = [ + 0x03, // Mode data length (excluding this byte): 3 + 0x00, // Medium type + 0x00, // Device-specific parameter + 0x00, // Block descriptor length = 0 (DBD = 1) + ]; + + let len = alloc_len.min(response.len() as u8) as usize; + + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::ModeSense10 { + dbd, + page_control, + page_code, + subpage_code, + alloc_len, + } => { + let response = [ + 0x00, 0x06, // Mode data length = 6 + 0x00, // Medium type + 0x00, // Device-specific parameter + 0x00, 0x00, // Reserved + 0x00, 0x00, // Block descriptor length = 0 + ]; + + let len = alloc_len.min(response.len() as u16) as usize; + + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::ReadCapacity10 => { + let block_size = SdCard::BLOCK_SIZE as u64; + let total_blocks = self.sdcard.size() / block_size; + + let last_lba = total_blocks.checked_sub(1).unwrap_or(0); + + response.extend_from_slice(&(last_lba as u32).to_be_bytes())?; + response.extend_from_slice(&(block_size as u32).to_be_bytes())?; + + self.bulk_in.write(&response).await.map_err(|_| ()) + } + ScsiCommand::ReadCapacity16 { alloc_len } => { + let block_size = SdCard::BLOCK_SIZE as u64; + let total_blocks = self.sdcard.size() / block_size; + + let last_lba = total_blocks.checked_sub(1).unwrap_or(0); + + response.extend_from_slice(&last_lba.to_be_bytes())?; // 8 bytes last LBA + response.extend_from_slice(&(block_size as u32).to_be_bytes())?; // 4 bytes block length + response.extend_from_slice(&[0u8; 20])?; // 20 reserved bytes zeroed + + let len = alloc_len.min(response.len() as u32) as usize; + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::Read { lba, len } => { + for i in 0..len { + let block_idx = BlockIdx(lba as u32 + i as u32); + self.sdcard.read_blocks(&mut block, block_idx)?; + for chunk in block[0].contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) { + self.bulk_in.write(chunk).await.map_err(|_| ())?; + } + } + Ok(()) + } + ScsiCommand::Write { lba, len } => { + for i in 0..len { + let block_idx = BlockIdx(lba as u32 + i as u32); + for chunk in block[0] + .contents + .chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into()) + { + self.bulk_out.read(chunk).await.map_err(|_| ())?; + } + self.sdcard.write_blocks(&mut block, block_idx)?; + } + Ok(()) + } + ScsiCommand::ReadFormatCapacities { alloc_len } => { + let block_size = SdCard::BLOCK_SIZE as u32; + let num_blocks = (self.sdcard.size() / block_size as u64) as u32; + + let mut response = [0u8; 12]; + + // Capacity List Length (8 bytes follows) + response[3] = 8; + + // Descriptor + response[4..8].copy_from_slice(&num_blocks.to_be_bytes()); + response[8] = 0x03; // formatted media + response[9..12].copy_from_slice(&block_size.to_be_bytes()[1..4]); // only 3 bytes + + let response_len = alloc_len.min(response.len() as u16) as usize; + self.bulk_in + .write(&response[..response_len]) + .await + .map_err(|_| ()) + } + ScsiCommand::PreventAllowMediumRemoval { prevent: _prevent } => Ok(()), + ScsiCommand::StartStopUnit { start, load_eject } => Ok(()), + } + } + + pub async fn send_csw_success(&mut self, tag: u32) { + self.send_csw(tag, 0x00, 0).await; + } + + pub async fn send_csw_fail(&mut self, tag: u32) { + defmt::error!("Command Failed"); + self.send_csw(tag, 0x01, 0).await; // 0x01 = Command Failed + } + + pub async fn send_csw(&mut self, tag: u32, status: u8, residue: u32) { + let mut csw = [0u8; 13]; + csw[0..4].copy_from_slice(&0x53425355u32.to_le_bytes()); // Signature "USBS" + csw[4..8].copy_from_slice(&tag.to_le_bytes()); + csw[8..12].copy_from_slice(&residue.to_le_bytes()); + csw[12] = status; + let _ = self.bulk_in.write(&csw).await; + } +} + +#[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().ok()?); + if dCBWSignature != 0x43425355 { + return None; // invalid signature + } + Some(Self { + dCBWSignature, + dCBWTag: u32::from_le_bytes(buf[4..8].try_into().ok()?), + dCBWDataTransferLength: u32::from_le_bytes(buf[8..12].try_into().ok()?), + bmCBWFlags: buf[12], + bCBWLUN: buf[13], + bCBWCBLength: buf[14], + CBWCB: buf[15..31].try_into().ok()?, + }) + } +} diff --git a/kernel/src/scsi/scsi_types.rs b/kernel/src/scsi/scsi_types.rs new file mode 100644 index 0000000..dbd27dd --- /dev/null +++ b/kernel/src/scsi/scsi_types.rs @@ -0,0 +1,164 @@ +use num_enum::TryFromPrimitive; + +#[derive(Debug, Clone, Copy)] +pub enum ScsiError { + NotReady, +} + +/// 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; + +const PREVENT_ALLOW_MEDIUM_REMOVAL: u8 = 0x1E; +const START_STOP_UNIT: u8 = 0x1B; + +/// 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, + }, + + PreventAllowMediumRemoval { + prevent: bool, + }, + + StartStopUnit { + start: bool, + load_eject: bool, + }, +} + +#[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]]), + }, + PREVENT_ALLOW_MEDIUM_REMOVAL => ScsiCommand::PreventAllowMediumRemoval { + prevent: (cb[1] & 0b00000001) != 0, + }, + START_STOP_UNIT => ScsiCommand::StartStopUnit { + start: (cb[4] & 0b00000001) != 0, + load_eject: (cb[4] & 0b00000010) != 0, + }, + _ => ScsiCommand::Unknown, + } +} diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs new file mode 100644 index 0000000..04b4353 --- /dev/null +++ b/kernel/src/storage.rs @@ -0,0 +1,100 @@ +use embassy_rp::gpio::{Input, Output}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::{ + Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, + Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error, +}; + +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 { + det: Input<'static>, + volume_mgr: VolMgr, +} + +impl SdCard { + pub const BLOCK_SIZE: u16 = 512; + + 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, + } + } + + /// Returns true if an SD card is inserted. + /// The DET pin is active-low via mechanical switch in the socket. + pub fn is_attached(&self) -> bool { + self.det.is_low() + } + + pub fn open_volume(&mut self) -> Result, ()> { + if self.is_attached() { + return Ok(self.volume_mgr.open_volume(VolumeIdx(0)).map_err(|_| ())?); + } + Err(()) + } + + pub fn size(&self) -> u64 { + let mut result = 0; + + self.volume_mgr.device(|sd| { + result = sd.num_bytes().unwrap_or(0); + DummyTimeSource {} + }); + + result + } + + pub fn num_blocks(&self) -> u32 { + let mut result = 0; + + self.volume_mgr.device(|sd| { + result = sd.num_blocks().unwrap_or(BlockCount(0)).0; + DummyTimeSource {} + }); + + result + } + + pub fn read_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> { + let mut res: Result<(), Error> = Ok(()); + self.volume_mgr.device(|sd| { + res = sd.read(blocks, start_block_idx); + DummyTimeSource {} + }); + res.map_err(|_| ()) + } + + pub fn write_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> { + let mut res: Result<(), Error> = Ok(()); + self.volume_mgr.device(|sd| { + let res = sd.write(blocks, start_block_idx); + DummyTimeSource {} + }); + res.map_err(|_| ()) + } +} diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs new file mode 100644 index 0000000..acf1d4c --- /dev/null +++ b/kernel/src/usb.rs @@ -0,0 +1,36 @@ +use crate::{scsi::MassStorageClass, storage::SdCard}; +use embassy_futures::select::select; +use embassy_rp::{peripherals::USB, usb::Driver}; +use embassy_usb::{Builder, Config}; + +pub async fn usb_handler(driver: Driver<'static, USB>, 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(); + + loop { + select(usb.run(), scsi.poll()).await; + + defmt::warn!("rebuilding usb"); + usb.disable().await; + } +} diff --git a/kernel/src/utils.rs b/kernel/src/utils.rs new file mode 100644 index 0000000..62683c6 --- /dev/null +++ b/kernel/src/utils.rs @@ -0,0 +1,11 @@ +#[macro_export] +macro_rules! format { + ($len:literal, $($arg:tt)*) => {{ + use heapless::String; + use core::fmt::Write; + + let mut s: String<$len> = String::new(); + let _ = write!(&mut s, $($arg)*); + s + }} +}