#![feature(generic_const_exprs, maybe_uninit_slice)] #![allow(incomplete_features)] #![no_std] use core::{mem::MaybeUninit, 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::{ device::{block::BlockDevice, manager::probe_partitions}, error::Error, fs::devfs, task::runtime, }; use libk_mm::{address::PhysicalAddress, table::MapAttributes, PageProvider, PageSlice}; 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; // TODO SCSI detach pub struct ScsiDevice { transport: IrqSafeSpinlock, lba_count: u64, lba_size: usize, max_lba_per_request: usize, index: OneTimeInit, names: IrqSafeRwLock>, } impl ScsiDevice { // TODO support LUNs other than 0 pub async fn setup(transport: T) -> Result, Error> { let mut transport = ScsiTransportWrapper::new(transport); let mut attempts = 5; let mut timeout = 100; while attempts > 0 { // TEST UNIT READY (6) if transport .perform_command(0, ScsiTestUnitReady) .await .is_ok() { break; } // If not, send a REQUEST SENSE (6) if transport.perform_command(0, ScsiRequestSense).await.is_ok() { break; } log::warn!("scsi: unit not ready [{attempts}/5]"); runtime::sleep(Duration::from_millis(timeout)).await; timeout *= 2; attempts -= 1; } if attempts == 0 { log::error!("scsi: unit not ready"); return Err(Error::DoesNotExist); } // TODO INQUIRY fails for real USB flash drives // transport.perform_command(0, ScsiInquiry).await?; let capacity_info = transport.perform_command(0, ScsiReadCapacity).await?; let max_lba_per_request = transport.max_bytes_per_request() / capacity_info.block_size as usize; log::info!( "scsi: lba_size={}, lba_count={}, max_lba_per_request={}", capacity_info.block_size, capacity_info.block_count, max_lba_per_request ); Ok(Arc::new(Self { transport: IrqSafeSpinlock::new(transport), lba_count: capacity_info.block_count.into(), lba_size: capacity_info.block_size as usize, max_lba_per_request, index: OneTimeInit::new(), names: IrqSafeRwLock::new(Vec::new()), })) } pub fn detach(&self) { if let Some(&index) = self.index.try_get() { detach(index); } } } #[async_trait] impl BlockDevice for ScsiDevice { // TODO avoid copies by reading directly into the cache? async fn read_aligned( &self, position: u64, buffer: &mut PageSlice>, ) -> Result<(), Error> { if buffer.len() % self.lba_size != 0 { log::warn!("scsi: buffer is not multiple of LBA size"); return Err(Error::InvalidArgument); } let lba_start = position / self.lba_size as u64; let lba_count = buffer.len() / self.lba_size; let lba_end = lba_start + lba_count as u64; if lba_start.saturating_add(lba_count as u64) >= self.lba_count { log::warn!("scsi: read beyond medium end"); return Err(Error::InvalidArgument); } let mut transport = self.transport.lock(); let mut offset = 0; 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 slice = unsafe { MaybeUninit::slice_assume_init_mut(&mut buffer[offset..offset + amount]) }; let len = transport.read(0, lba, count as _, slice).await?; if len != amount { return Err(Error::InvalidArgument); } offset += amount; } Ok(()) } async fn write_aligned(&self, _position: u64, _buffer: &PageSlice) -> Result<(), Error> { // TODO AtaWriteDmaEx 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 ScsiDevice { fn get_page(&self, _offset: u64) -> Result { Err(Error::NotImplemented) } fn release_page(&self, _offset: u64, _phys: PhysicalAddress) -> Result<(), Error> { Err(Error::NotImplemented) } fn clone_page( &self, _offset: u64, _src_phys: PhysicalAddress, _src_attrs: MapAttributes, ) -> Result { Err(Error::NotImplemented) } } impl Device for ScsiDevice { fn display_name(&self) -> &str { "SCSI Storage Device" } } impl Drop for ScsiDevice { fn drop(&mut self) { if let Some(index) = self.index.try_get() { log::info!("scsi{index} dropped"); } } } // TODO this is crap static SCSI_DEVICES: IrqSafeSpinlock>> = IrqSafeSpinlock::new(BTreeMap::new()); static SCSI_BITMAP: IrqSafeSpinlock = IrqSafeSpinlock::new(0); pub fn attach(device: 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 device: too many of them"))? as u32; let mut devices = SCSI_DEVICES.lock(); *bitmap |= 1 << index; assert!(!devices.contains_key(&index)); devices.insert(index, device.clone()); index }; let name = format!("scsi{index}"); device.index.init(index); device.names.write().push(name.clone()); devfs::add_named_block_device(device.clone(), name.clone(), FileMode::new(0o600)).ok(); log::info!("{name} attached"); // TODO this code is repeated everywhere runtime::spawn(async move { let name = name; probe_partitions(device.clone(), |index, partition| { let partition_name = format!("{name}p{}", index + 1); log::info!("{name}: partition {partition_name}"); device.names.write().push(partition_name.clone()); devfs::add_named_block_device( Arc::new(partition), partition_name, FileMode::new(0o600), ) .ok(); }) .await .ok(); }) .ok(); Ok(()) } pub fn detach(index: u32) { let mut devices = SCSI_DEVICES.lock(); let mut bitmap = SCSI_BITMAP.lock(); if let Some(device) = devices.remove(&index) { { let names = device.names.read(); for name in names.iter() { devfs::remove_node(name).ok(); } } *bitmap &= !(1 << index); log::info!("scsi{index} detached"); } }