rpi-pico-usb-ramdisk/src/main.rs
2025-09-23 20:48:57 +02:00

348 lines
13 KiB
Rust

#![no_std]
#![no_main]
use rp_pico::entry;
use defmt as _;
use defmt_rtt as _;
use panic_probe as _;
use embedded_hal::digital::StatefulOutputPin;
use rp_pico::hal::pac;
use rp_pico::hal;
use usb_device::{class_prelude::*, prelude::*};
use usbd_storage::subclass::scsi::{Scsi, ScsiCommand};
use usbd_storage::subclass::Command;
use usbd_storage::transport::bbb::{BulkOnly, BulkOnlyError};
use usbd_storage::transport::TransportError;
const DISK_BLOCK_SIZE: u32 = 512;
const DISK_BLOCK_NUM: u32 = 256;
const USB_PACKET_SIZE: u16 = 64; // 8,16,32,64
const MAX_LUN: u8 = 0; // max 0x0F
const INQUIRY_RESPONSE: [u8; 36] = {
let vendor: [u8; 8] = *b"jalr\0\0\0\0";
let model: [u8; 16] = *b"USB RAM disk\0\0\0\0";
let rev: [u8; 4] = *b"1312";
[
0x00, // periph qualifier, periph device type
0x80, // Removable
0x04, // SPC-2 compliance
0x02, // NormACA, HiSu, Response data format
0x20, // 36 bytes in total
0x00, // additional fields, none set
0x00, // additional fields, none set
0x00, // additional fields, none set
vendor[0], vendor[1], vendor[2], vendor[3],
vendor[4], vendor[5], vendor[6], vendor[7],
model[0], model[1], model[2], model[3],
model[4], model[5], model[6], model[7],
model[8], model[9], model[10], model[11],
model[12], model[13], model[14], model[15],
rev[0], rev[1], rev[2], rev[3]
]
};
#[derive(Default)]
struct State {
storage_offset: usize,
sense_key: Option<u8>,
sense_key_code: Option<u8>,
sense_qualifier: Option<u8>,
}
impl State {
fn reset(&mut self) {
self.storage_offset = 0;
self.sense_key = None;
self.sense_key_code = None;
self.sense_qualifier = None;
}
}
#[entry]
fn main() -> ! {
let mut pac = pac::Peripherals::take().unwrap();
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
let clocks = hal::clocks::init_clocks_and_plls(
rp_pico::XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
let timer = hal::timer::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);
let sio = hal::Sio::new(pac.SIO);
let pins = rp_pico::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
let mut led_pin = pins.led.into_push_pull_output();
let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new(
pac.USBCTRL_REGS,
pac.USBCTRL_DPRAM,
clocks.usb_clock,
true,
&mut pac.RESETS,
));
let mut usb_transport_buf: [u8; 512] = [0; 512];
let mut scsi = usbd_storage::subclass::scsi::Scsi::new(&usb_bus, USB_PACKET_SIZE, MAX_LUN, usb_transport_buf.as_mut_slice()).unwrap();
let mut usb_device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0xcafe, 0x4002))
.strings(&[StringDescriptors::new(LangID::EN)
.manufacturer("jalr")
.product("RAM Mass Storage")])
.unwrap()
.device_class(0x08)
.self_powered(false)
.build();
let mut storage: [u8; (DISK_BLOCK_SIZE * DISK_BLOCK_NUM) as usize] = [0u8; (DISK_BLOCK_SIZE * DISK_BLOCK_NUM) as usize];
const LBA: u32 = DISK_BLOCK_NUM - 1;
let (last_cyl, last_head, last_sect) = {
const HPC: u32 = 16;
const SPT: u32 = 63;
(
LBA / (HPC * SPT),
(LBA / SPT) % HPC,
(LBA % SPT) + 1
)
};
const OEM: [u8; 8] = *b"mkfs.fat";
const FILESYSTEM_ID: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
const FILESYSTEM_LABEL: [u8; 11] = *b"RAM_USB ";
storage[0x1B9] = 0x10;
storage[0x1BE..0x1C3].copy_from_slice(&[0x00, 0x00, 0x02, 0x00, 0x0c]);
storage[0x1C3] = last_head as u8;
storage[0x1C4] = ((last_cyl >> 8) & 0xc0) as u8 | ((last_sect as u8) & 0x3f);
storage[0x1C5] = last_cyl as u8;
storage[0x1C6..0x1CA].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]); // LBA of first absolute sector in the partition
storage[0x1CA] = LBA as u8;
storage[0x1CB] = (LBA >> 8) as u8;
storage[0x1CC] = (LBA >> 16) as u8;
storage[0x1CD] = (LBA >> 24) as u8;
storage[0x1FE..0x200].copy_from_slice(&[0x55, 0xaa]); // boot signature
// FAT12 Partition
storage[0x200..0x203].copy_from_slice(&[0xEB, 0x3C, 0x90]);
storage[0x203..0x20B].copy_from_slice(&OEM);
storage[0x20B..0x21d].copy_from_slice(&[0x00, 0x02, 0x04, 0x01, 0x00,
0x02, 0x00, 0x02, (LBA & 0xff) as u8,
(LBA >> 8) as u8,
0xF8, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01]);
storage[0x224..0x227].copy_from_slice(&[0x80, 0x00, 0x29]);
storage[0x227..0x22B].copy_from_slice(&FILESYSTEM_ID);
storage[0x22B..0x236].copy_from_slice(&FILESYSTEM_LABEL);
storage[0x236..0x23E].copy_from_slice(b"FAT12 ");
storage[0x3FE..0x400].copy_from_slice(&[0x55, 0xAA]);
storage[0x400..0x403].copy_from_slice(&[0xF8, 0xFF, 0xFF]);
storage[0x600..0x603].copy_from_slice(&[0xF8, 0xFF, 0xFF]);
storage[0x800..0x80B].copy_from_slice(&FILESYSTEM_LABEL);
storage[0x80B] = 0x08; // file attribute 0x08 = volume label
// https://de.wikipedia.org/wiki/File_Allocation_Table
// Remove bootloader code: dd if=/dev/zero conv=notrunc of=foo bs=1 count=420 seek=602
let mut state: State = State {
storage_offset: 0,
sense_key: None,
sense_key_code: None,
sense_qualifier: None,
};
defmt::info!("entering main loop");
let mut start_time: u64 = 0;
loop {
let interval = if timer.get_counter().ticks() < 2_000_000 {
25_000
}
else {
match (usb_device.state(), led_pin.is_set_high().unwrap_or_else(|_| false)) {
// The USB device has just been created or reset.
(UsbDeviceState::Default, true) => 100_000,
(UsbDeviceState::Default, false) => 100_000,
// The USB device has received an address from the host.
(UsbDeviceState::Addressed, true) => 250_000,
(UsbDeviceState::Addressed, false) => 250_000,
// The USB device has been configured and is fully functional.
(UsbDeviceState::Configured, true) => 1_000_000,
(UsbDeviceState::Configured, false) => 0,
// The USB device has been suspended by the host or it has been unplugged from the USB bus.
(UsbDeviceState::Suspend, true) => 25_000,
(UsbDeviceState::Suspend, false) => 2_000_000,
}
};
if timer.get_counter().ticks() - start_time > interval {
let _ = led_pin.toggle();
start_time = timer.get_counter().ticks();
}
if !usb_device.poll(&mut [&mut scsi]) {
continue;
}
if matches!(usb_device.state(), UsbDeviceState::Default) {
state.reset();
}
let _ = scsi.poll(|command| {
if let Err(err) = process_command(command, &mut state, &mut storage) {
defmt::error!("Got error: {}", err);
}
});
}
}
fn process_command(
mut command: Command<ScsiCommand, Scsi<BulkOnly<hal::usb::UsbBus, &mut [u8]>>>,
state: &mut State,
storage: &mut [u8; (DISK_BLOCK_SIZE * DISK_BLOCK_NUM) as usize]
) -> Result<(), TransportError<BulkOnlyError>> {
defmt::info!("Handling: {}", command.kind);
match command.kind {
ScsiCommand::TestUnitReady { .. } => {
command.pass();
}
ScsiCommand::Inquiry { .. } => {
command.try_write_data_all(&INQUIRY_RESPONSE)?;
command.pass();
}
ScsiCommand::RequestSense { .. } => {
command.try_write_data_all(&[
0x70, // RESPONSE CODE. Set to 70h for information on current errors
0x00, // obsolete
state.sense_key.unwrap_or(0), // Bits 3..0: SENSE KEY. Contains information describing the error.
0x00,
0x00,
0x00,
0x00, // INFORMATION. Device-specific or command-specific information.
0x00, // ADDITIONAL SENSE LENGTH.
0x00,
0x00,
0x00,
0x00, // COMMAND-SPECIFIC INFORMATION
state.sense_key_code.unwrap_or(0), // ASC
state.sense_qualifier.unwrap_or(0), // ASCQ
0x00,
0x00,
0x00,
0x00,
])?;
state.reset();
command.pass();
},
ScsiCommand::ReadCapacity10 { .. } => {
let mut data = [0u8; 8];
let _ = &mut data[0..4].copy_from_slice(&u32::to_be_bytes(DISK_BLOCK_NUM - 1));
let _ = &mut data[4..8].copy_from_slice(&u32::to_be_bytes(DISK_BLOCK_SIZE));
command.try_write_data_all(&data)?;
command.pass();
}
ScsiCommand::ReadCapacity16 { .. } => {
let mut data = [0u8; 16];
let _ = &mut data[0..8].copy_from_slice(&u32::to_be_bytes(DISK_BLOCK_NUM - 1));
let _ = &mut data[8..12].copy_from_slice(&u32::to_be_bytes(DISK_BLOCK_SIZE));
command.try_write_data_all(&data)?;
command.pass();
}
ScsiCommand::ReadFormatCapacities { .. } => {
let mut data = [0u8; 12];
let _ = &mut data[0..4].copy_from_slice(&[
0x00, 0x00, 0x00, 0x08, // capacity list length
]);
let _ = &mut data[4..8].copy_from_slice(&u32::to_be_bytes(DISK_BLOCK_NUM as u32)); // number of blocks
data[8] = 0x01; //unformatted media
let block_length_be = u32::to_be_bytes(DISK_BLOCK_SIZE);
data[9] = block_length_be[1];
data[10] = block_length_be[2];
data[11] = block_length_be[3];
command.try_write_data_all(&data)?;
command.pass();
}
ScsiCommand::Read { lba, len } => {
let lba = lba as u32;
let len = len as u32;
if state.storage_offset != (len * DISK_BLOCK_SIZE) as usize {
let start = (DISK_BLOCK_SIZE * lba) as usize + state.storage_offset;
let end = (DISK_BLOCK_SIZE * lba) as usize + (DISK_BLOCK_SIZE * len) as usize;
// Uncomment this in order to push data in chunks smaller than a USB packet.
// let end = min(start + USB_PACKET_SIZE as usize - 1, end);
defmt::info!("Data transfer >>>>>>>> [{}..{}]", start, end);
let count = command.write_data(&mut storage[start..end])?;
state.storage_offset += count;
} else {
command.pass();
state.storage_offset = 0;
}
},
ScsiCommand::Write { lba, len } => {
let lba = lba as u32;
let len = len as u32;
if state.storage_offset != (len * DISK_BLOCK_SIZE) as usize {
let start = (DISK_BLOCK_SIZE * lba) as usize + state.storage_offset;
let end = (DISK_BLOCK_SIZE * lba) as usize + (DISK_BLOCK_SIZE * len) as usize;
defmt::info!("Data transfer <<<<<<<< [{}..{}]", start, end);
let count = command.read_data(&mut storage[start..end])?;
state.storage_offset += count;
if state.storage_offset == (len * DISK_BLOCK_SIZE) as usize {
command.pass();
state.storage_offset = 0;
}
} else {
command.pass();
state.storage_offset = 0;
}
},
ScsiCommand::ModeSense6 { .. } => {
command.try_write_data_all(&[
0x03, // number of bytes that follow
0x00, // the media type is SBC
0x00, // not write-protected, no cache-control bytes support
0x00, // no mode-parameter block descriptors
])?;
command.pass();
}
ScsiCommand::ModeSense10 { .. } => {
command.try_write_data_all(&[0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])?;
command.pass();
}
ref unknown_scsi_kind => {
defmt::error!("Unknown SCSI command: {}", unknown_scsi_kind);
state.sense_key.replace(0x05); // illegal request Sense Key
state.sense_key_code.replace(0x20); // Invalid command operation ASC
state.sense_qualifier.replace(0x00); // Invalid command operation ASCQ
command.fail();
}
}
Ok(())
}