trying proper scsi handling

This commit is contained in:
2025-11-09 14:04:34 -07:00
parent 700e007c87
commit b30ccc4485
3 changed files with 209 additions and 244 deletions

View File

@@ -1,5 +1,5 @@
use crate::storage::{SDCARD, SdCard};
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use embassy_futures::yield_now;
use embassy_time::Timer; use embassy_time::Timer;
use embassy_usb::Builder; use embassy_usb::Builder;
use embassy_usb::driver::{Driver, EndpointIn, EndpointOut}; use embassy_usb::driver::{Driver, EndpointIn, EndpointOut};
@@ -12,25 +12,24 @@ use scsi_types::*;
mod error; mod error;
use error::ScsiSense; use error::ScsiSense;
use crate::storage::{SDCARD, SdCard};
use crate::usb::stop_usb;
const BULK_ENDPOINT_PACKET_SIZE: usize = 64; const BULK_ENDPOINT_PACKET_SIZE: usize = 64;
// indicates that a scsi transaction is occurring and is // indicates that a scsi transaction is occurring and is
// NOT safe to disable usb, or acquire sdcard // NOT safe to disable usb, or acquire sdcard
pub static SCSI_BUSY: AtomicBool = AtomicBool::new(false); pub static SCSI_BUSY: AtomicBool = AtomicBool::new(false);
// start no more transfers, usb is trying to stop // stop scsi and return sdcard when safe to do so
pub static SCSI_HALT: AtomicBool = AtomicBool::new(false); pub static SCSI_HALT: AtomicBool = AtomicBool::new(false);
pub static SCSI_EJECTED: AtomicBool = AtomicBool::new(false);
pub struct MassStorageClass<'d, D: Driver<'d>> { pub struct MassStorageClass<'d, D: Driver<'d>> {
temp_sd: Option<SdCard>, // temporary owns sdcard when scsi is running temp_sd: Option<SdCard>, // temporary owns sdcard when scsi is running
pub pending_eject: bool,
pub prevent_removal: bool,
bulk_out: D::EndpointOut, bulk_out: D::EndpointOut,
bulk_in: D::EndpointIn, bulk_in: D::EndpointIn,
last_sense: ScsiSense, last_sense: ScsiSense,
prevent_removal: bool,
pending_eject: bool,
} }
impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
@@ -44,11 +43,11 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
Self { Self {
temp_sd: None, temp_sd: None,
pending_eject: false,
prevent_removal: false,
bulk_out, bulk_out,
bulk_in, bulk_in,
last_sense: ScsiSense::no_sense(), last_sense: ScsiSense::no_sense(),
prevent_removal: false,
pending_eject: false,
} }
} }
@@ -64,6 +63,7 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
} }
} }
// return sdcard to global, so other tasks can use it
pub async fn return_sdcard(&mut self) { pub async fn return_sdcard(&mut self) {
if let Some(card) = self.temp_sd.take() { if let Some(card) = self.temp_sd.take() {
let mut guard = SDCARD.get().lock().await; let mut guard = SDCARD.get().lock().await;
@@ -73,19 +73,26 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
pub async fn poll(&mut self) { pub async fn poll(&mut self) {
loop { loop {
if !self.prevent_removal { if !self.prevent_removal && (SCSI_HALT.load(Ordering::Acquire) || self.pending_eject) {
if SCSI_HALT.load(Ordering::Acquire) || self.pending_eject { if self.temp_sd.is_some() {
break; self.return_sdcard().await;
SCSI_BUSY.store(false, Ordering::Release);
self.pending_eject = false;
} }
} else {
self.handle_cbw().await;
} }
self.handle_cbw().await; if SCSI_EJECTED.load(Ordering::Acquire) {
Timer::after_millis(100).await;
if !self.prevent_removal && self.pending_eject { continue;
break;
} }
Timer::after_millis(1).await; if SCSI_BUSY.load(Ordering::Relaxed) {
Timer::after_millis(1).await;
} else {
Timer::after_millis(100).await;
}
} }
} }
@@ -94,16 +101,12 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await { if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
if n == 31 { if n == 31 {
if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) { if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
SCSI_BUSY.store(true, Ordering::Release);
let command = parse_cb(&cbw.CBWCB); let command = parse_cb(&cbw.CBWCB);
if self.handle_command(command).await.is_ok() { if self.handle_command(command).await.is_ok() {
self.send_csw_success(cbw.dCBWTag).await self.send_csw_success(cbw.dCBWTag).await
} else { } else {
self.send_csw_fail(cbw.dCBWTag).await self.send_csw_fail(cbw.dCBWTag).await
} }
SCSI_BUSY.store(false, Ordering::Release);
} else { } else {
self.last_sense = ScsiSense::invalid_cdb(); self.last_sense = ScsiSense::invalid_cdb();
self.send_csw_fail(0).await; self.send_csw_fail(0).await;
@@ -203,10 +206,24 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
self.bulk_in.write(&response[..len]).await.map_err(|_| ()) self.bulk_in.write(&response[..len]).await.map_err(|_| ())
} }
ScsiCommand::TestUnitReady => { ScsiCommand::TestUnitReady => {
if self.temp_sd.as_ref().unwrap().is_attached() { if !SCSI_EJECTED.load(Ordering::Acquire) && self.temp_sd.is_none() {
Ok(()) SCSI_BUSY.store(true, Ordering::Release);
} else { self.take_sdcard().await;
Err(()) }
match &self.temp_sd {
Some(sd) => {
if sd.is_attached() {
Ok(())
} else {
self.last_sense = ScsiSense::medium_not_present();
Err(())
}
}
None => {
self.last_sense = ScsiSense::medium_not_present();
Err(())
}
} }
} }
ScsiCommand::RequestSense { desc: _, alloc_len } => { ScsiCommand::RequestSense { desc: _, alloc_len } => {
@@ -262,148 +279,183 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
self.bulk_in.write(&response[..len]).await.map_err(|_| ()) self.bulk_in.write(&response[..len]).await.map_err(|_| ())
} }
ScsiCommand::ReadCapacity10 => { ScsiCommand::ReadCapacity10 => match self.temp_sd.as_ref() {
let total_blocks = self.temp_sd.as_ref().unwrap().size() / BLOCK_SIZE as u64; Some(sd) => {
let total_blocks = sd.size() / BLOCK_SIZE as u64;
let last_lba = total_blocks.checked_sub(1).unwrap_or(0); 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(&(last_lba as u32).to_be_bytes())?;
response.extend_from_slice(&(BLOCK_SIZE as u32).to_be_bytes())?; response.extend_from_slice(&(BLOCK_SIZE as u32).to_be_bytes())?;
self.bulk_in.write(&response).await.map_err(|_| ()) self.bulk_in.write(&response).await.map_err(|_| ())
} }
ScsiCommand::ReadCapacity16 { alloc_len } => { None => {
let total_blocks = self.temp_sd.as_ref().unwrap().size() / BLOCK_SIZE as u64; self.last_sense = ScsiSense::medium_not_present();
Err(())
}
},
ScsiCommand::ReadCapacity16 { alloc_len } => match self.temp_sd.as_ref() {
Some(sd) => {
let total_blocks = sd.size() / BLOCK_SIZE as u64;
let last_lba = total_blocks.checked_sub(1).unwrap_or(0); 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(&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(&(BLOCK_SIZE as u32).to_be_bytes())?; // 4 bytes block length
response.extend_from_slice(&[0u8; 20])?; // 20 reserved bytes zeroed response.extend_from_slice(&[0u8; 20])?; // 20 reserved bytes zeroed
let len = alloc_len.min(response.len() as u32) as usize; let len = alloc_len.min(response.len() as u32) as usize;
self.bulk_in.write(&response[..len]).await.map_err(|_| ()) self.bulk_in.write(&response[..len]).await.map_err(|_| ())
} }
ScsiCommand::Read { lba, len } => { None => {
let sdcard = self.temp_sd.as_ref().unwrap(); self.last_sense = ScsiSense::medium_not_present();
let mut blocks = len; Err(())
let mut idx = lba; }
},
ScsiCommand::Read { lba, len } => match self.temp_sd.as_ref() {
Some(sd) => {
let mut blocks = len;
let mut idx = lba;
let mut error_occurred = false; let mut error_occurred = false;
while blocks > 0 { while blocks > 0 {
if blocks >= block_buf.len() as u64 { if blocks >= block_buf.len() as u64 {
sdcard.read_blocks(&mut block_buf, BlockIdx(idx as u32))?; sd.read_blocks(&mut block_buf, BlockIdx(idx as u32))?;
for block in &mut block_buf { for block in &mut block_buf {
for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) { for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into())
if self.bulk_in.write(chunk).await.map_err(|_| ()).is_err() { {
error_occurred = true if self.bulk_in.write(chunk).await.map_err(|_| ()).is_err() {
error_occurred = true
}
} }
} }
}
blocks -= block_buf.len() as u64; blocks -= block_buf.len() as u64;
idx += block_buf.len() as u64; idx += block_buf.len() as u64;
} else { } else {
sdcard sd.read_blocks(
.read_blocks(&mut block_buf[..blocks as usize], BlockIdx(idx as u32))?; &mut block_buf[..blocks as usize],
BlockIdx(idx as u32),
)?;
for block in &block_buf[..blocks as usize] { for block in &block_buf[..blocks as usize] {
for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) { for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into())
if self.bulk_in.write(chunk).await.map_err(|_| ()).is_err() { {
error_occurred = true if self.bulk_in.write(chunk).await.map_err(|_| ()).is_err() {
error_occurred = true
}
} }
} }
}
idx += blocks; idx += blocks;
blocks = 0; blocks = 0;
}
} }
}
if error_occurred { if error_occurred {
self.last_sense = ScsiSense::unrecovered_read_error(); self.last_sense = ScsiSense::unrecovered_read_error();
return Err(()); return Err(());
}
Ok(())
}
ScsiCommand::Write { lba, len } => {
let sdcard = self.temp_sd.as_ref().unwrap();
let mut blocks = len;
let mut idx = lba;
let mut error_occurred = false;
while blocks > 0 {
if blocks >= block_buf.len() as u64 {
for block in block_buf.as_mut() {
for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
if self.bulk_out.read(chunk).await.map_err(|_| ()).is_err() {
error_occurred = true
}
}
}
sdcard.read_blocks(&mut block_buf, BlockIdx(idx as u32))?;
blocks -= block_buf.len() as u64;
idx += block_buf.len() as u64;
} else {
for block in block_buf[..blocks as usize].as_mut() {
for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
if self.bulk_out.read(chunk).await.map_err(|_| ()).is_err() {
error_occurred = true
}
}
}
sdcard.write_blocks(
&mut block_buf[..blocks as usize],
BlockIdx(idx as u32),
)?;
idx += blocks;
blocks = 0;
} }
Ok(())
} }
None => {
if error_occurred { self.last_sense = ScsiSense::medium_not_present();
self.last_sense = ScsiSense::unrecovered_write_error(); Err(())
return Err(());
} }
},
ScsiCommand::Write { lba, len } => match self.temp_sd.as_ref() {
Some(sd) => {
let mut blocks = len;
let mut idx = lba;
Ok(()) let mut error_occurred = false;
}
ScsiCommand::ReadFormatCapacities { alloc_len } => {
let block_size = SdCard::BLOCK_SIZE as u32;
let num_blocks = (self.temp_sd.as_ref().unwrap().size() / block_size as u64) as u32;
let mut response = [0u8; 12]; while blocks > 0 {
if blocks >= block_buf.len() as u64 {
for block in block_buf.as_mut() {
for chunk in
block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
if self.bulk_out.read(chunk).await.map_err(|_| ()).is_err() {
error_occurred = true
}
}
}
// Capacity List Length (8 bytes follows) sd.read_blocks(&mut block_buf, BlockIdx(idx as u32))?;
response[3] = 8;
// Descriptor blocks -= block_buf.len() as u64;
response[4..8].copy_from_slice(&num_blocks.to_be_bytes()); idx += block_buf.len() as u64;
response[8] = 0x03; // formatted media } else {
response[9..12].copy_from_slice(&block_size.to_be_bytes()[1..4]); // only 3 bytes for block in block_buf[..blocks as usize].as_mut() {
for chunk in
block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
if self.bulk_out.read(chunk).await.map_err(|_| ()).is_err() {
error_occurred = true
}
}
}
let response_len = alloc_len.min(response.len() as u16) as usize; sd.write_blocks(
self.bulk_in &mut block_buf[..blocks as usize],
.write(&response[..response_len]) BlockIdx(idx as u32),
.await )?;
.map_err(|_| ())
} idx += blocks;
blocks = 0;
}
}
if error_occurred {
self.last_sense = ScsiSense::unrecovered_write_error();
return Err(());
}
Ok(())
}
None => {
self.last_sense = ScsiSense::medium_not_present();
Err(())
}
},
ScsiCommand::ReadFormatCapacities { alloc_len } => match self.temp_sd.as_ref() {
Some(sd) => {
let block_size = SdCard::BLOCK_SIZE as u32;
let num_blocks = (sd.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(|_| ())
}
None => {
self.last_sense = ScsiSense::medium_not_present();
Err(())
}
},
ScsiCommand::PreventAllowMediumRemoval { prevent } => { ScsiCommand::PreventAllowMediumRemoval { prevent } => {
self.prevent_removal = prevent; self.prevent_removal = prevent;
Ok(()) Ok(())
} }
ScsiCommand::StartStopUnit { start, load_eject } => { ScsiCommand::StartStopUnit { start, load_eject } => {
if !start && load_eject { if !start && load_eject {
SCSI_EJECTED.store(true, Ordering::Release);
self.pending_eject = true; self.pending_eject = true;
} }
Ok(()) Ok(())

View File

@@ -3,8 +3,8 @@ use crate::{
display::FRAMEBUFFER, display::FRAMEBUFFER,
framebuffer::FB_PAUSED, framebuffer::FB_PAUSED,
peripherals::keyboard, peripherals::keyboard,
scsi::SCSI_BUSY,
storage::{FileName, SDCARD}, storage::{FileName, SDCARD},
usb::{USB_ACTIVE, start_usb, stop_usb},
}; };
use abi_sys::keyboard::{KeyCode, KeyState}; use abi_sys::keyboard::{KeyCode, KeyState};
use alloc::{str::FromStr, string::String, vec::Vec}; use alloc::{str::FromStr, string::String, vec::Vec};
@@ -37,25 +37,25 @@ pub async fn ui_handler() {
changed: true, changed: true,
}; };
let mut scsi = ScsiPage { last_bounds: None }; let mut scsi = ScsiPage { last_bounds: None };
let mut overlay = Overlay::new();
update_selections().await; update_selections().await;
loop { loop {
// reset page, if usb was disabled internally // reset page, if usb was disabled internally
if ui.page == UiPage::Scsi && !USB_ACTIVE.load(Ordering::Acquire) { if ui.page == UiPage::Scsi && !SCSI_BUSY.load(Ordering::Acquire) {
menu.clear().await; scsi.clear().await;
ui.page = UiPage::Menu; ui.page = UiPage::Menu;
} else if ui.page == UiPage::Menu && SCSI_BUSY.load(Ordering::Acquire) {
menu.clear().await;
ui.page = UiPage::Scsi;
} }
if input_handler(&mut ui, &mut menu, &mut scsi).await { if input_handler(&mut ui, &mut menu, &mut scsi).await {
overlay.clear().await;
return; return;
} }
match ui.page { match ui.page {
UiPage::Menu => menu.draw().await, UiPage::Menu => menu.draw().await,
UiPage::Scsi => scsi.draw().await, UiPage::Scsi => scsi.draw().await,
} }
overlay.draw().await;
Timer::after_millis(5).await; Timer::after_millis(5).await;
} }
} }
@@ -64,17 +64,6 @@ async fn input_handler(ui: &mut UiState, menu: &mut MenuPage, scsi: &mut ScsiPag
if let Some(event) = keyboard::read_keyboard_fifo().await { if let Some(event) = keyboard::read_keyboard_fifo().await {
if event.state == KeyState::Pressed { if event.state == KeyState::Pressed {
match (&mut ui.page, event.key) { match (&mut ui.page, event.key) {
(UiPage::Menu, KeyCode::F1) => {
start_usb();
menu.clear().await;
ui.page = UiPage::Scsi;
}
(UiPage::Scsi, KeyCode::F1) => {
stop_usb();
scsi.clear().await;
ui.page = UiPage::Menu;
update_selections().await;
}
(UiPage::Menu, _) => return menu.handle_input(event.key).await, (UiPage::Menu, _) => return menu.handle_input(event.key).await,
(UiPage::Scsi, _) => return scsi.handle_input(event.key).await, (UiPage::Scsi, _) => return scsi.handle_input(event.key).await,
} }
@@ -83,43 +72,6 @@ async fn input_handler(ui: &mut UiState, menu: &mut MenuPage, scsi: &mut ScsiPag
false false
} }
struct Overlay {
f1_label: &'static str,
last_bounds: Option<Rectangle>,
}
impl Overlay {
pub fn new() -> Self {
Self {
f1_label: "Press F1 to enable/disable mass storage",
last_bounds: None,
}
}
async fn draw(&mut self) {
let text_style = MonoTextStyle::new(&FONT_4X6, Rgb565::WHITE);
let fb = unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() };
let bounds = fb.bounding_box();
let text = Text::with_alignment(
self.f1_label,
Point::new(10, bounds.size.height as i32 - 24), // bottom-left corner
text_style,
Alignment::Left,
);
self.last_bounds = Some(text.bounds());
text.draw(fb).unwrap();
}
async fn clear(&mut self) {
if let Some(rect) = self.last_bounds {
clear_rect(rect).await
}
self.last_bounds = None;
}
}
#[derive(PartialEq)] #[derive(PartialEq)]
enum UiPage { enum UiPage {
Menu, Menu,
@@ -173,8 +125,8 @@ static SELECTIONS: Mutex<CriticalSectionRawMutex, Vec<FileName>> = Mutex::new(Ve
static mut SELECTIONS_CHANGED: bool = true; static mut SELECTIONS_CHANGED: bool = true;
async fn update_selections() { async fn update_selections() {
while USB_ACTIVE.load(Ordering::Acquire) { while SCSI_BUSY.load(Ordering::Acquire) {
Timer::after_millis(50).await; Timer::after_millis(100).await;
} }
let mut guard = SDCARD.get().lock().await; let mut guard = SDCARD.get().lock().await;
let sd = guard.as_mut().unwrap(); let sd = guard.as_mut().unwrap();

View File

@@ -1,18 +1,9 @@
use crate::scsi::{MassStorageClass, SCSI_BUSY, SCSI_HALT}; use crate::scsi::{MassStorageClass, SCSI_EJECTED};
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::Ordering;
use embassy_futures::select::select; use embassy_futures::join::join;
use embassy_rp::{peripherals::USB, usb::Driver}; use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
use embassy_time::Timer;
use embassy_usb::{Builder, Config}; use embassy_usb::{Builder, Config};
static START_USB: Channel<CriticalSectionRawMutex, (), 1> = Channel::new();
static STOP_USB: Channel<CriticalSectionRawMutex, (), 1> = Channel::new();
// for other tasks to query the status of usb (like ui)
// this is read only for ALL other tasks
pub static USB_ACTIVE: AtomicBool = AtomicBool::new(false);
#[embassy_executor::task] #[embassy_executor::task]
pub async fn usb_handler(driver: Driver<'static, USB>) { pub async fn usb_handler(driver: Driver<'static, USB>) {
let mut config = Config::new(0xc0de, 0xbabe); let mut config = Config::new(0xc0de, 0xbabe);
@@ -38,45 +29,15 @@ pub async fn usb_handler(driver: Driver<'static, USB>) {
let mut scsi = MassStorageClass::new(&mut builder); let mut scsi = MassStorageClass::new(&mut builder);
let mut usb = builder.build(); let mut usb = builder.build();
loop { join(
START_USB.receiver().receive().await; async {
USB_ACTIVE.store(true, Ordering::Release); loop {
SCSI_HALT.store(false, Ordering::Release); usb.wait_resume().await;
scsi.take_sdcard().await; SCSI_EJECTED.store(false, Ordering::Release);
scsi.pending_eject = false; usb.run_until_suspend().await;
}
// waits for cancellation signal, and then waits for },
// transfers to stop before dropping usb future scsi.poll(),
select( )
async { .await;
STOP_USB.receiver().receive().await;
SCSI_HALT.store(true, Ordering::Release);
while SCSI_BUSY.load(Ordering::Acquire) {
Timer::after_millis(100).await;
}
},
// runs the usb, until cancelled
select(
async {
let _ = usb.remote_wakeup().await;
usb.run().await;
},
scsi.poll(),
),
)
.await;
usb.disable().await;
scsi.return_sdcard().await;
USB_ACTIVE.store(false, Ordering::Release);
}
}
pub fn start_usb() {
let _ = STOP_USB.receiver().try_receive();
let _ = START_USB.sender().try_send(());
}
pub fn stop_usb() {
let _ = START_USB.receiver().try_receive();
let _ = STOP_USB.sender().try_send(());
} }