#![feature(generic_const_exprs, maybe_uninit_slice)] #![allow(incomplete_features)] #![no_std] use core::{ mem::MaybeUninit, sync::atomic::{AtomicBool, Ordering}, time::Duration, }; use alloc::{ boxed::Box, collections::btree_map::BTreeMap, format, string::String, sync::Arc, vec::Vec, }; use async_trait::async_trait; use command::{ScsiReadCapacity, ScsiRequestSense, ScsiTestUnitReady}; use device_api::device::Device; use libk::{ block, device::{block::BlockDevice, manager::probe_partitions}, dma::{DmaBuffer, DmaSlice, DmaSliceMut}, error::Error, fs::devfs, task::{runtime, sync::AsyncMutex}, }; use libk_mm::{ address::PhysicalAddress, table::MapAttributes, OnDemandPage, PageProvider, VirtualPage, }; use libk_util::{ sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock}, OneTimeInit, }; use transport::{ScsiTransport, ScsiTransportWrapper}; use yggdrasil_abi::io::FileMode; extern crate alloc; pub mod command; pub mod device; pub mod transport; pub struct ScsiEnclosure { transport: AsyncMutex, units: Vec>>>, index: OneTimeInit, shutdown: AtomicBool, } pub struct ScsiUnit { enclosure: Arc, lun: u8, lba_count: u64, lba_size: usize, max_lba_per_request: usize, names: IrqSafeRwLock>, } impl ScsiEnclosure { pub async fn setup( transport: Box, lun_count: usize, ) -> Result, Error> { let transport = AsyncMutex::new(ScsiTransportWrapper::new(transport)); let units = (0..lun_count).map(|_| IrqSafeRwLock::new(None)).collect(); let this = Arc::new(Self { transport, units, index: OneTimeInit::new(), shutdown: AtomicBool::new(false), }); register_enclosure(this.clone())?; // Probe LUNs for i in 0..lun_count { if this.probe_lun(i as u8).await && let Ok(unit) = ScsiUnit::setup(this.clone(), i as u8).await { *this.units[i].write() = Some(unit); } } // Start enclosure poll task let enclosure = this.clone(); runtime::spawn(async move { while !enclosure.shutdown.load(Ordering::Acquire) { enclosure.poll().await; runtime::sleep(Duration::from_millis(100)).await; } }) .ok(); Ok(this) } async fn probe_lun(self: &Arc, lun: u8) -> bool { let mut attempts = 3; let mut timeout = 10; // TODO get statuses to better see if there's a real error or the LUN is not present while attempts > 0 { let mut transport = self.transport.lock().await; // TEST UNIT READY (6) if transport .perform_command(lun, ScsiTestUnitReady) .await .is_ok() { break; } // If not, send a REQUEST SENSE (6) transport.perform_command(lun, ScsiRequestSense).await.ok(); drop(transport); runtime::sleep(Duration::from_millis(timeout)).await; timeout *= 2; attempts -= 1; } attempts != 0 } async fn poll(self: &Arc) { let index = *self.index.get(); for lun in 0..self.units.len() { let mut slot = self.units[lun].write(); let present = self.probe_lun(lun as u8).await; if let Some(unit) = slot.as_ref() { if !present { log::warn!("scsi{index}u{lun} lost"); unit.detach(); *slot = None; } } else if present { if let Ok(unit) = ScsiUnit::setup(self.clone(), lun as u8).await { log::info!("scsi{index}u{lun} attached"); *slot = Some(unit); } else { log::warn!("scsi{index}u{lun} attached, but could not setup"); } } } } pub fn detach(&self) { self.shutdown.store(true, Ordering::Release); let index = self.index.try_get().copied(); for unit in self.units.iter() { if let Some(unit) = unit.write().take() { unit.detach(); } } // Deregister the enclosure if let Some(index) = index { remove_enclosure(index); } } } impl ScsiUnit { pub async fn setup(enclosure: Arc, lun: u8) -> Result, Error> { let enclosure_index = *enclosure.index.get(); let mut transport = enclosure.transport.lock().await; // TODO INQUIRY fails for real USB flash drives // transport.perform_command(0, ScsiInquiry).await?; let capacity_info = transport.perform_command(lun, ScsiReadCapacity).await?; let max_lba_per_request = transport.max_bytes_per_request() / capacity_info.block_size as usize; log::info!( "scsi{enclosure_index}u{lun}: lba_size={}, lba_count={}, max_lba_per_request={}", capacity_info.block_size, capacity_info.block_count, max_lba_per_request ); drop(transport); let unit = Arc::new(Self { enclosure, lun, lba_count: capacity_info.block_count.into(), lba_size: capacity_info.block_size as usize, max_lba_per_request, names: IrqSafeRwLock::new(Vec::new()), }); register_unit(enclosure_index, lun, unit.clone()); Ok(unit) } fn detach(&self) { let id = *self.enclosure.index.get(); log::info!("scsi{id}u{} detached", self.lun); for name in self.names.read().iter() { devfs::remove_node(name).ok(); } } } #[async_trait] impl BlockDevice for ScsiUnit { fn allocate_buffer(&self, size: usize) -> Result]>, Error> { block!(self.enclosure.transport.lock().await.allocate_buffer(size))? } async fn read_aligned( &self, position: u64, buffer: DmaSliceMut<'_, MaybeUninit>, ) -> Result<(), Error> { if position % self.lba_size as u64 != 0 { log::warn!("scsi: misaligned read"); return Err(Error::InvalidArgument); } if buffer.len() % self.lba_size != 0 { log::warn!("scsi: misaligned buffer size"); return Err(Error::InvalidArgument); } let lba_start = position / self.lba_size as u64; let lba_count = buffer.len() / self.lba_size; if lba_start.saturating_add(lba_count as u64) >= self.lba_count { log::warn!("scsi: read beyond medium end"); return Err(Error::InvalidArgument); } let lba_end = lba_start + lba_count as u64; let mut transport = self.enclosure.transport.lock().await; // TODO DmaSliceMut subslicing let (buffer, range) = buffer.into_parts(); let mut offset = range.start; for i in (0..lba_count).step_by(self.max_lba_per_request) { let lba = lba_start + i as u64; let end = (lba + self.max_lba_per_request as u64).min(lba_end); let count = (end - lba) as usize; let amount = count * self.lba_size; let dst_slice = buffer.slice_mut(offset..offset + amount); let len = transport .read(self.lun, lba, count as u16, dst_slice) .await?; if len != amount { return Err(Error::InvalidArgument); } offset += amount; } Ok(()) } async fn write_aligned(&self, _position: u64, _buffer: DmaSlice<'_, u8>) -> Result<(), Error> { Err(Error::NotImplemented) } fn block_size(&self) -> usize { self.lba_size } fn block_count(&self) -> u64 { self.lba_count } fn max_blocks_per_request(&self) -> usize { self.max_lba_per_request } } impl PageProvider for ScsiUnit { fn ondemand_fetch(&self, _opaque: u64) -> Result { unimplemented!() } fn get_page(&self, _offset: u64) -> Result { unimplemented!() } fn release_page( &self, _offset: u64, _phys: PhysicalAddress, _dirty: bool, ) -> Result<(), Error> { unimplemented!() } fn clone_page( &self, _offset: u64, _src_phys: PhysicalAddress, _src_attrs: MapAttributes, ) -> Result { unimplemented!() } } impl Device for ScsiUnit { fn display_name(&self) -> &str { "SCSI Unit" } } impl Drop for ScsiUnit { fn drop(&mut self) { if let Some(index) = self.enclosure.index.try_get() { log::info!("scsi{index}u{} dropped", self.lun); } } } // TODO this is crap static SCSI_ENCLOSURES: IrqSafeSpinlock>> = IrqSafeSpinlock::new(BTreeMap::new()); static SCSI_BITMAP: IrqSafeSpinlock = IrqSafeSpinlock::new(0); fn register_enclosure(enclosure: Arc) -> Result<(), Error> { let index = { let mut bitmap = SCSI_BITMAP.lock(); let index = (0..8) .position(|p| *bitmap & (1 << p) == 0) .ok_or(Error::InvalidOperation) .inspect_err(|_| log::warn!("Cannot attach SCSI enclosure: too many of them"))? as u32; let mut devices = SCSI_ENCLOSURES.lock(); *bitmap |= 1 << index; assert!(!devices.contains_key(&index)); devices.insert(index, enclosure.clone()); index }; enclosure.index.init(index); Ok(()) } fn register_unit(enclosure_index: u32, lun: u8, unit: Arc) { let name = format!("scsi{enclosure_index}u{lun}"); unit.names.write().push(name.clone()); devfs::add_named_block_device(unit.clone(), name.clone(), FileMode::new(0o600)).ok(); // TODO this code is repeated everywhere runtime::spawn(async move { let name = name; probe_partitions(unit.clone(), |index, partition| { let partition_name = format!("{name}p{}", index + 1); log::info!("{name}: partition {partition_name}"); unit.names.write().push(partition_name.clone()); devfs::add_named_block_device( Arc::new(partition), partition_name, FileMode::new(0o600), ) .ok(); }) .await .ok(); }) .ok(); } fn remove_enclosure(index: u32) { let mut devices = SCSI_ENCLOSURES.lock(); let mut bitmap = SCSI_BITMAP.lock(); *bitmap &= !(1 << index); devices.remove(&index); log::info!("scsi: enclosure {index} detached"); }