8 Commits

Author SHA1 Message Date
f8fb1b81c2 stateless scsi 2025-07-30 14:59:53 -06:00
1d03640d5b automount works after reformat! 2025-07-29 14:52:50 -06:00
0d77321bb3 can unmount now 2025-07-29 13:49:58 -06:00
2bddec08e6 can mount and transfer files! 2025-07-29 13:21:06 -06:00
7ed35fb771 troubleshooting scsi block errors 2025-07-29 01:02:54 -06:00
60ed910a88 can mount but not access 2025-07-28 12:57:50 -06:00
a9e1120247 WIP, sd fails to init 2025-07-26 21:17:58 -06:00
beae5b2fd9 WIP 2025-07-21 23:12:16 -06:00
9 changed files with 802 additions and 84 deletions

156
Cargo.lock generated
View File

@@ -12,6 +12,18 @@ dependencies = [
"regex", "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]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.3" version = "1.1.3"
@@ -75,21 +87,6 @@ dependencies = [
"rustc_version", "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]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.5.3" version = "0.5.3"
@@ -126,6 +123,12 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bitfield"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -215,7 +218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
dependencies = [ dependencies = [
"bare-metal", "bare-metal",
"bitfield", "bitfield 0.13.2",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"volatile-register", "volatile-register",
] ]
@@ -661,6 +664,7 @@ dependencies = [
"defmt 0.3.100", "defmt 0.3.100",
"document-features", "document-features",
"embassy-time-driver", "embassy-time-driver",
"embassy-time-queue-utils",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-hal-async", "embedded-hal-async",
@@ -686,6 +690,21 @@ dependencies = [
"heapless", "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]] [[package]]
name = "embassy-usb-driver" name = "embassy-usb-driver"
version = "0.1.1" version = "0.1.1"
@@ -784,17 +803,14 @@ dependencies = [
[[package]] [[package]]
name = "embedded-sdmmc" name = "embedded-sdmmc"
version = "0.8.0" version = "0.9.0"
source = "git+https://github.com/Be-ing/embedded-sdmmc-rs?branch=bisync#835b2e4f9d3482b6287f674d7ecf6ae5d0618c18" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce3c7f9ea039eeafc4a49597b7bd5ae3a1c8e51b2803a381cb0f29ce90fe1ec6"
dependencies = [ dependencies = [
"bisync",
"byteorder", "byteorder",
"defmt 0.3.100", "defmt 0.3.100",
"embassy-futures",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-hal-async",
"embedded-io", "embedded-io",
"embedded-io-async",
"heapless", "heapless",
] ]
@@ -822,6 +838,12 @@ dependencies = [
"log", "log",
] ]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -996,6 +1018,15 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.4"
@@ -1031,7 +1062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.15.4",
] ]
[[package]] [[package]]
@@ -1370,12 +1401,14 @@ dependencies = [
"embassy-rp 0.4.0", "embassy-rp 0.4.0",
"embassy-sync 0.7.0", "embassy-sync 0.7.0",
"embassy-time", "embassy-time",
"embassy-usb",
"embedded-graphics", "embedded-graphics",
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
"embedded-hal-async", "embedded-hal-async",
"embedded-hal-bus", "embedded-hal-bus",
"embedded-sdmmc", "embedded-sdmmc",
"heapless", "heapless",
"num_enum 0.7.4",
"panic-probe", "panic-probe",
"portable-atomic", "portable-atomic",
"spin", "spin",
@@ -1710,6 +1743,26 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 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]] [[package]]
name = "sha2-const-stable" name = "sha2-const-stable"
version = "0.1.0" version = "0.1.0"
@@ -1765,6 +1818,16 @@ dependencies = [
"lock_api", "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]] [[package]]
name = "st7365p-lcd" name = "st7365p-lcd"
version = "0.11.0" version = "0.11.0"
@@ -1983,6 +2046,53 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 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]] [[package]]
name = "uuid" name = "uuid"
version = "1.17.0" version = "1.17.0"

View File

@@ -12,7 +12,7 @@ opt-level = "z"
[features] [features]
default = ["rp235x", "defmt"] default = ["rp235x", "defmt"]
rp2040 = ["embassy-rp/rp2040"] # rp2040 = ["embassy-rp/rp2040"]
rp235x = ["embassy-rp/rp235xb"] rp235x = ["embassy-rp/rp235xb"]
trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"]
defmt = [ defmt = [
@@ -44,9 +44,10 @@ embassy-rp = { version = "0.4.0", features = [
"binary-info", "binary-info",
] } ] }
embassy-futures = "0.1.1" embassy-futures = "0.1.1"
embassy-time = "0.4.0" embassy-time = { version = "0.4.0", features = ["generic-queue-8"] }
embassy-embedded-hal = "0.3.0" embassy-embedded-hal = "0.3.1"
embassy-sync = { version = "0.7" } embassy-sync = "0.7"
embassy-usb = "0.4.0"
trouble-host = { version = "0.1", features = [ trouble-host = { version = "0.1", features = [
"derive", "derive",
"scan", "scan",
@@ -70,7 +71,7 @@ defmt = { version = "0.3", optional = true }
defmt-rtt = "0.4.2" defmt-rtt = "0.4.2"
embedded-graphics = { version = "0.8.1" } 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" } st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", branch = "async" }
static_cell = "2.1.1" static_cell = "2.1.1"
@@ -78,3 +79,4 @@ bitflags = "2.9.1"
talc = "4.4.3" talc = "4.4.3"
spin = "0.10.0" spin = "0.10.0"
heapless = "0.8.0" heapless = "0.8.0"
num_enum = { version = "0.7.4", default-features = false }

View File

@@ -21,8 +21,6 @@ use embedded_hal_bus::spi::ExclusiveDevice;
use portable_atomic::AtomicBool; use portable_atomic::AtomicBool;
use st7365p_lcd::{FrameBuffer, ST7365P}; use st7365p_lcd::{FrameBuffer, ST7365P};
use crate::LAST_TEXT_RECT;
const SCREEN_WIDTH: usize = 320; const SCREEN_WIDTH: usize = 320;
const SCREEN_HEIGHT: usize = 320; const SCREEN_HEIGHT: usize = 320;
@@ -58,24 +56,7 @@ pub async fn display_handler(
loop { loop {
DISPLAY_SIGNAL.wait().await; DISPLAY_SIGNAL.wait().await;
let text_string = crate::STRING.lock().await.clone(); // text.draw(&mut framebuffer).unwrap();
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();
let start = Instant::now(); let start = Instant::now();
framebuffer framebuffer

View File

@@ -6,6 +6,8 @@
use crate::{ use crate::{
display::DISPLAY_SIGNAL, display::DISPLAY_SIGNAL,
peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo}, peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo},
storage::SdCard,
usb::usb_handler,
}; };
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
@@ -13,70 +15,74 @@ use {defmt_rtt as _, panic_probe as _};
use core::cell::RefCell; use core::cell::RefCell;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_futures::join::join; 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_rp::{i2c, i2c::I2c, spi};
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::mutex::Mutex; use embassy_sync::mutex::Mutex;
use embassy_time::Timer; use embassy_time::{Delay, Timer};
use embedded_graphics::primitives::Rectangle; use embedded_graphics::primitives::Rectangle;
use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::SdCard as SdmmcSdCard;
use heapless::String; use heapless::String;
mod peripherals; mod peripherals;
use peripherals::conf_peripherals; use peripherals::conf_peripherals;
mod display; mod display;
use display::display_handler; use display::display_handler;
mod scsi;
mod storage;
mod usb;
mod utils;
embassy_rp::bind_interrupts!(struct Irqs { embassy_rp::bind_interrupts!(struct Irqs {
I2C1_IRQ => i2c::InterruptHandler<I2C1>; I2C1_IRQ => i2c::InterruptHandler<I2C1>;
USBCTRL_IRQ => embassy_rp_usb::InterruptHandler<USB>;
}); });
static STRING: Mutex<ThreadModeRawMutex, String<25>> = Mutex::new(String::new());
static LAST_TEXT_RECT: Mutex<ThreadModeRawMutex, RefCell<Option<Rectangle>>> =
Mutex::new(RefCell::new(None));
#[embassy_executor::main] #[embassy_executor::main]
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default()); let p = embassy_rp::init(Default::default());
STRING.lock().await.push_str("Press Del").unwrap(); // MCU i2c bus for peripherals
// configure keyboard event handler
let mut config = i2c::Config::default(); let mut config = i2c::Config::default();
config.frequency = 400_000; config.frequency = 400_000;
let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config); let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config);
conf_peripherals(i2c1).await; conf_peripherals(i2c1).await;
// SPI1 bus display
let mut config = spi::Config::default(); let mut config = spi::Config::default();
config.frequency = 16_000_000; config.frequency = 16_000_000;
let spi1 = spi::Spi::new( let spi1 = spi::Spi::new(
p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config, p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config,
); );
join( let usb = embassy_rp_usb::Driver::new(p.USB, Irqs);
async {
loop { let sdcard = {
Timer::after_millis(20).await; let mut config = spi::Config::default();
if let Some(key) = read_keyboard_fifo().await config.frequency = 400_000;
&& key.state == KeyState::Pressed let spi = Spi::new_blocking(
{ p.SPI0,
let mut string = STRING.lock().await; p.PIN_18, // clk
match key.key { p.PIN_19, // mosi
KeyCode::Backspace => { p.PIN_16, // miso
string.pop().unwrap(); config.clone(),
} );
KeyCode::Del => { let cs = Output::new(p.PIN_17, Level::High);
string.clear(); let det = Input::new(p.PIN_22, Pull::None);
}
KeyCode::Char(c) => { let device = ExclusiveDevice::new(spi, cs, Delay).unwrap();
string.push(c).unwrap(); let sdcard = SdmmcSdCard::new(device, Delay);
}
_ => (), config.frequency = 32_000_000;
} sdcard.spi(|dev| dev.bus_mut().set_config(&config));
DISPLAY_SIGNAL.signal(()); SdCard::new(sdcard, det)
} };
}
}, usb_handler(usb, sdcard).await;
display_handler(spi1, p.PIN_13, p.PIN_14, p.PIN_15),
)
.await;
} }

308
src/scsi/mod.rs Normal file
View File

@@ -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<u8, BULK_ENDPOINT_PACKET_SIZE> = 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<Self> {
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()?,
})
}
}

164
src/scsi/scsi_types.rs Normal file
View File

@@ -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,
}
}

100
src/storage.rs Normal file
View File

@@ -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<Spi<'static, SPI0, Blocking>, Output<'static>, embassy_time::Delay>;
type SD = SdmmcSdCard<Device, Delay>;
type VolMgr = VolumeManager<SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>;
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<Vol<'_>, ()> {
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(|_| ())
}
}

36
src/usb.rs Normal file
View File

@@ -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;
}
}

11
src/utils.rs Normal file
View File

@@ -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
}}
}