From 6b70e80d207bb275f85b3450c9255ec5a3e59f37 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sun, 9 Feb 2020 17:17:36 +0100 Subject: [PATCH] Add (untested) transfer support. --- Cargo.lock | 1 + usbscsid/Cargo.toml | 1 + usbscsid/src/main.rs | 2 + usbscsid/src/protocol/bot.rs | 2 + usbscsid/src/scsi/cmds.rs | 104 ++++++++++++++++++++++++++++++ usbscsid/src/scsi/mod.rs | 6 ++ usbscsid/src/scsi/opcodes.rs | 112 ++++++++++++++++++++++++++++++++ xhcid/src/driver_interface.rs | 13 +++- xhcid/src/xhci/context.rs | 23 +++++++ xhcid/src/xhci/scheme.rs | 116 ++++++++++++++++++++++++++++++---- xhcid/src/xhci/trb.rs | 39 ++++++++++++ 11 files changed, 405 insertions(+), 14 deletions(-) create mode 100644 usbscsid/src/scsi/cmds.rs create mode 100644 usbscsid/src/scsi/mod.rs create mode 100644 usbscsid/src/scsi/opcodes.rs diff --git a/Cargo.lock b/Cargo.lock index 7609327c6f..99f7c69c29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1337,6 +1337,7 @@ dependencies = [ name = "usbscsid" version = "0.1.0" dependencies = [ + "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "xhcid 0.1.0", ] diff --git a/usbscsid/Cargo.toml b/usbscsid/Cargo.toml index 7b639e5938..8347ceb0e5 100644 --- a/usbscsid/Cargo.toml +++ b/usbscsid/Cargo.toml @@ -8,5 +8,6 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +plain = "0.2" thiserror = "1" xhcid = { path = "../xhcid" } diff --git a/usbscsid/src/main.rs b/usbscsid/src/main.rs index 13ceec3a12..c5d12dbba2 100644 --- a/usbscsid/src/main.rs +++ b/usbscsid/src/main.rs @@ -3,6 +3,7 @@ use std::env; use xhcid_interface::XhciClientHandle; pub mod protocol; +pub mod scsi; fn main() { let mut args = env::args().skip(1); @@ -17,4 +18,5 @@ fn main() { let handle = XhciClientHandle::new(scheme, port); let protocol = protocol::setup(&handle, protocol); + } diff --git a/usbscsid/src/protocol/bot.rs b/usbscsid/src/protocol/bot.rs index f2e8b5aab4..8db631e35d 100644 --- a/usbscsid/src/protocol/bot.rs +++ b/usbscsid/src/protocol/bot.rs @@ -46,6 +46,8 @@ pub struct BulkOnlyTransport<'a> { impl<'a> BulkOnlyTransport<'a> { pub fn init(handle: &'a XhciClientHandle) -> Result { + let lun = get_max_lun(handle, 0)?; + println!("BOT_MAX_LUN {}", lun); Ok(Self { handle, }) diff --git a/usbscsid/src/scsi/cmds.rs b/usbscsid/src/scsi/cmds.rs new file mode 100644 index 0000000000..dbd7f83fce --- /dev/null +++ b/usbscsid/src/scsi/cmds.rs @@ -0,0 +1,104 @@ +use super::opcodes::{Opcode, ServiceActionA3}; + +#[repr(packed)] +pub struct ReportIdentInfo { + pub opcode: u8, + /// bits 7:5 reserved + pub serviceaction: u8, + pub _rsvd: u16, + pub restricted: u16, + /// little endian + pub alloc_len: u32, + /// bit 0 reserved + pub info_ty: u8, + pub control: u8, +} + +pub const REP_ID_INFO_INFO_TY_MASK: u8 = 0xFE; +pub const REP_ID_INFO_INFO_TY_SHIFT: u8 = 1; + +#[repr(packed)] +pub struct ReportSuppOpcodes { + pub opcode: u8, + /// bits 7:5 reserved + pub serviceaction: u8, + /// bits 2:0 represent "REPORTING OPTIONS", bits 6:3 are reserved, and bit 7 is RCTD + pub rep_opts: u8, + pub req_opcode: u8, + /// little endian + pub req_serviceaction: u16, + /// little endian + pub alloc_len: u32, + pub _rsvd: u8, + pub control: u8, +} +impl ReportSuppOpcodes { + pub const fn new(rep_opts: ReportSuppOpcodesOptions, rctd: bool, req_opcode: u8, req_serviceaction: u16, alloc_len: u32, control: u8) -> Self { + Self { + opcode: Opcode::ServiceActionA3 as u8, + serviceaction: ServiceActionA3::ReportSuppOpcodes as u8, + rep_opts: ((rctd as u8) << REP_OPTS_RCTD_SHIFT) | rep_opts as u8, + req_opcode, + req_serviceaction: u16::to_le(req_serviceaction), + alloc_len: u32::to_le(alloc_len), + _rsvd: 0, + control, + } + } + pub const fn get_all(rctd: bool, alloc_len: u32, control: u8) -> Self { + Self::new(ReportSuppOpcodesOptions::ListAll, rctd, 0, 0, alloc_len, control) + } +} + +pub const REP_OPTS_MAIN_MASK: u8 = 0b0000_0111; +pub const REP_OPTS_MAIN_SHIFT: u8 = 0; +pub const REP_OPTS_RCTD_BIT: u8 = 1 << REP_OPTS_RCTD_SHIFT; +pub const REP_OPTS_RCTD_SHIFT: u8 = 7; + +/// Valid values of the `req_opts` field of `ReportSuppOpcodes`. +#[repr(u8)] +pub enum ReportSuppOpcodesOptions { + /// Returns all commands, no matter what parameters are set. + ListAll, + + /// Returns one command with the requested opcode. If the command has service actions, this + /// command fails. + NoServicaction, + + /// Returns one command with the requested opcode and service action. If the command doesn't + /// support service actions, this command fails. + ExplicitBoth, + + /// Returns one command with the requested opcode and service action. The command may or may + /// not implement service actions, but if it does, it has to be correct for the return value to + /// indicate SUPPORTED. + IndicateSupport, + +} + +#[repr(packed)] +pub struct AllCommandsParam { + /// Little endian + pub data_len: u32, + pub descs: [CommandDescriptor], +} + +#[repr(packed)] +pub struct CommandDescriptor { + pub opcode: u8, + pub _rsvd1: u8, + /// little endian + pub serviceaction: u16, + pub _rsvd2: u8, + /// bit 0 is SERVACTV, bit 1 is CTDP, and bits 7:2 reserved + pub a: u8, + /// little endian + pub cdb_len: u16, +} +#[repr(packed)] +pub struct OneCommandParam { + pub _rsvd: u8, + /// bits 2:0 for SUPPOR, bits 6:3 reserved, and bit 7 for CTDP + pub a: u8, + // TODO +} diff --git a/usbscsid/src/scsi/mod.rs b/usbscsid/src/scsi/mod.rs new file mode 100644 index 0000000000..1b01389e80 --- /dev/null +++ b/usbscsid/src/scsi/mod.rs @@ -0,0 +1,6 @@ +pub mod cmds; +pub mod opcodes; + + +pub struct Scsi { +} diff --git a/usbscsid/src/scsi/opcodes.rs b/usbscsid/src/scsi/opcodes.rs new file mode 100644 index 0000000000..d146238297 --- /dev/null +++ b/usbscsid/src/scsi/opcodes.rs @@ -0,0 +1,112 @@ +#[repr(u8)] +pub enum Opcode { + TestUnitReady = 0x00, + /// obsolete + RezeroUnit = 0x01, + RequestSense = 0x03, + FormatUnit = 0x04, + ReassignBlocks = 0x07, + /// obsolete + Read6 = 0x08, + /// obsolete + Write6 = 0x0A, + /// obsolete + Seek = 0x0B, + Inquiry = 0x12, + ModeSelect6 = 0x15, + /// obsolete + Reserve6 = 0x16, + /// obsolete + Release6 = 0x17, + ModeSense6 = 0x1A, + StartStopUnit = 0x1B, + RecvDiagnosticRes = 0x1C, + SendDiagnostic = 0x1D, + ReadCapacity10 = 0x25, + Read10 = 0x28, + Write10 = 0x2A, + /// obsolete + SeekExt = 0x2B, + WriteAndVerify10 = 0x2E, + Verify10 = 0x2F, + SyncCache10 = 0x35, + ReadDefectData10 = 0x37, + WriteBuf10 = 0x3B, + ReadBuf10 = 0x3C, + /// obsolete + ReadLong10 = 0x3E, + WriteLong10 = 0x3F, + /// obsolete + ChangeDef = 0x40, + WriteSame10 = 0x41, + Unmap = 0x42, + Sanitize = 0x48, + LogSelect = 0x4C, + LogSense = 0x4D, + ModeSelect10 = 0x55, + /// obsolete + Reserve10 = 0x56, + /// obsolete + Release10 = 0x57, + ModeSense10 = 0x5A, + PersistentResvIn = 0x5E, + PersistentResvOut = 0x5F, + ServiceAction7F = 0x7F, + Read16 = 0x88, + Write16 = 0x8A, + WriteAndVerify16 = 0x8E, + Verify16 = 0x8F, + SyncCache16 = 0x91, + WriteSame16 = 0x93, + WriteStream16 = 0x9A, + ReadBuf16 = 0x9B, + WriteAtomic16 = 0x9C, + ServiceAction9E = 0x9E, + ServiceAction9F, + ReportLuns = 0xA0, + SecurityProtoIn = 0xA2, + ServiceActionA3 = 0xA3, + ServiceActionA4 = 0xA4, + Read12 = 0xA8, + Write12 = 0xAA, + WriteAndVerify12 = 0xAE, + Verify12 = 0xAF, + SecurityProtoOut = 0xB5, + ReadDefectData12 = 0xB7, +} + +#[repr(u8)] +pub enum ServiceAction7F { + Read32 = 0x09, + Verify32 = 0x0A, + Write32 = 0x0B, + WriteAndVerify32 = 0x0C, + WriteSame32 = 0x0D, + WriteAtomic32 = 0x18, +} + +#[repr(u8)] +pub enum ServiceAction9E { + ReadCapacity16 = 0x10, + ReadLong16 = 0x11, + GetLbaStatus = 0x12, + StreamControl = 0x14, + BackgroundControl = 0x15, + GetStreamStatus = 0x16, +} +#[repr(u8)] +pub enum ServiceAction9F { + WriteLong16 = 0x11, +} +#[repr(u8)] +pub enum ServiceActionA3 { + ReportIdentInfo = 0x05, + ReportSuppOpcodes = 0x0C, + ReportSuppTaskManFuncs = 0x0D, + ReportTimestamp = 0x0F, +} +#[repr(u8)] +pub enum ServiceActionA4 { + SetIdentInfo = 0x06, + SetTimestamp = 0x0F, +} diff --git a/xhcid/src/driver_interface.rs b/xhcid/src/driver_interface.rs index 9d78352a3a..c68aeed058 100644 --- a/xhcid/src/driver_interface.rs +++ b/xhcid/src/driver_interface.rs @@ -279,20 +279,27 @@ pub enum DeviceReqData<'a> { NoData, } impl DeviceReqData<'_> { - fn len(&self) -> usize { + pub fn len(&self) -> usize { match self { Self::In(buf) => buf.len(), Self::Out(buf) => buf.len(), Self::NoData => 0, } } - fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.len() == 0 } + pub fn map_buf T>(&self, f: F) -> Option { + match self { + Self::In(sbuf) => Some(f(sbuf)), + Self::Out(dbuf) => Some(f(dbuf)), + _ => None, + } + } } impl<'a> DeviceReqData<'a> { - fn direction(&self) -> PortReqDirection { + pub fn direction(&self) -> PortReqDirection { match self { DeviceReqData::Out(_) => PortReqDirection::HostToDevice, DeviceReqData::NoData => PortReqDirection::HostToDevice, diff --git a/xhcid/src/xhci/context.rs b/xhcid/src/xhci/context.rs index 0e7c882e0b..77614e3d94 100644 --- a/xhcid/src/xhci/context.rs +++ b/xhcid/src/xhci/context.rs @@ -1,6 +1,8 @@ use syscall::error::Result; use syscall::io::{Dma, Io, Mmio}; +use super::ring::Ring; + #[repr(packed)] pub struct SlotContext { pub a: Mmio, @@ -105,8 +107,23 @@ pub struct StreamContext { rsvd: Mmio, } +unsafe impl plain::Plain for StreamContext {} + +#[repr(u8)] +pub enum StreamContextType { + SecondaryRing, + PrimaryRing, + PrimarySsa8, + PrimarySsa16, + PrimarySsa32, + PrimarySsa64, + PrimarySsa128, + PrimarySsa256, +} + pub struct StreamContextArray { pub contexts: Dma<[StreamContext]>, + pub rings: Vec, } impl StreamContextArray { @@ -114,9 +131,15 @@ impl StreamContextArray { unsafe { Ok(Self { contexts: Dma::zeroed_unsized(count)?, + rings: Vec::new(), }) } } + pub fn add_ring(&mut self, stream_id: u16, link: bool) -> Result<()> { + // NOTE: stream_id is reserved + self.rings.insert(stream_id as usize, Ring::new(link)?); + Ok(()) + } pub fn register(&self) -> u64 { self.contexts.physical() as u64 } diff --git a/xhcid/src/xhci/scheme.rs b/xhcid/src/xhci/scheme.rs index 1b8d6d63af..6811ff0216 100644 --- a/xhcid/src/xhci/scheme.rs +++ b/xhcid/src/xhci/scheme.rs @@ -9,7 +9,7 @@ use syscall::io::{Dma, Io}; use syscall::scheme::SchemeMut; use syscall::{ Error, Result, Stat, EACCES, EBADF, EBADMSG, EEXIST, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, - ENOTDIR, ENXIO, EOVERFLOW, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_CREAT, O_DIRECTORY, + ENOTDIR, ENXIO, EOVERFLOW, EPERM, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_CREAT, O_DIRECTORY, O_RDONLY, O_RDWR, O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, }; @@ -310,7 +310,10 @@ impl Xhci { assert_ne!(ep_ty, 0); // 0 means invalid. let ring_ptr = if max_streams != 0 { - let array = StreamContextArray::new(1 << (max_streams + 1))?; + let mut array = StreamContextArray::new(1 << (max_streams + 1))?; + + // TODO: Use as many stream rings as needed. + array.add_ring(1, true)?; let array_ptr = array.register(); assert_eq!( @@ -398,11 +401,86 @@ impl Xhci { Ok(()) } - fn transfer_read(&mut self, port_num: usize, endp_num: u8, buf: &mut [u8]) -> Result<()> { - Err(Error::new(ENOSYS)) + fn transfer_read(&mut self, port_num: usize, endp_idx: u8, buf: &mut [u8]) -> Result { + self.transfer(port_num, endp_idx, if !buf.is_empty() { DeviceReqData::In(buf) } else { DeviceReqData::NoData }) } - fn transfer_write(&mut self, port_num: usize, endp_num: u8, buf: &[u8]) -> Result<()> { - Err(Error::new(ENOSYS)) + fn transfer_write(&mut self, port_num: usize, endp_idx: u8, buf: &[u8]) -> Result { + self.transfer(port_num, endp_idx, if !buf.is_empty() { DeviceReqData::Out(buf) } else { DeviceReqData::NoData }) + } + // TODO: Rename DeviceReqData to something more general. + fn transfer(&mut self, port_num: usize, endp_idx: u8, buf: DeviceReqData) -> Result { + let endp_num = endp_idx + 1; + // TODO: Check that buf has a nonzero size, otherwise (at least for Rust's GlobalAlloc), + // UB. + let mut dma_buffer = match buf { + DeviceReqData::Out(sbuf) => { + let dma_buffer = unsafe { Dma::<[u8]>::zeroed_unsized(sbuf.len()) }?; + dma_buffer.copy_from_slice(sbuf); + Some(dma_buffer) + } + DeviceReqData::In(dbuf) => { + Some(unsafe { Dma::<[u8]>::zeroed_unsized(dbuf.len()) }?) + } + DeviceReqData::NoData => None, + }; + + let port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADF))?; + let endp_desc: &EndpDesc = port_state.dev_desc.config_descs.get(0).ok_or(Error::new(EIO))?.interface_descs.get(0).ok_or(Error::new(EIO))?.endpoints.get(endp_idx as usize).ok_or(Error::new(EBADF))?; + let endp_state = port_state.endpoint_states.get_mut(&endp_idx).ok_or(Error::new(EBADF))?; + + let ring: &mut Ring = match endp_state { + EndpointState::Ready(super::RingOrStreams::Ring(ref mut ring)) => ring, + EndpointState::Ready(super::RingOrStreams::Streams(stream_ctx_array)) => stream_ctx_array.rings.get_mut(1).ok_or(Error::new(EBADF))?, + EndpointState::Init => return Err(Error::new(EIO)), + }; + // TODO: Scatter-gather transfers, possibly allowing >64KiB sizes. + let len = u16::try_from(buf.len()).or(Err(Error::new(ENOSYS)))?; + let max_packet_size = endp_desc.max_packet_size; + { + let (trb, cycle) = ring.next(); + let (buffer, idt) = if len <= 8 && max_packet_size >= 8 { + buf.map_buf(|buf| { + let bytes = match <[u8; 8]>::try_from(buf) { + Ok(b) => b, + Err(_) => unreachable!(), + }; + // FIXME: little endian, right? + (u64::from_le_bytes(bytes), true) + }).unwrap_or((0, false)) + } else { + (dma_buffer.map(|dma| dma.physical()).unwrap_or(0) as u64, false) + }; + let estimated_td_size = mem::size_of_val(&trb) as u8; // one trb per td + trb.normal(buffer, len, cycle, estimated_td_size, false, true, false, true, idt, false); + } + + self.dbs[port_state.slot as usize].write(endp_num.into()); + + let bytes_transferred = { + let event = self.cmd.next_event(); + while event.data.read() == 0 { + println!(" - Waiting for event"); + } + + // FIXME: EDTLA if event data was set + if event.completion_code() != TrbCompletionCode::ShortPacket as u8 && event.transfer_length() != 0 { + println!("Event trb yielded a short packet, but all bytes were still transferred"); + } + + if event.completion_code() != TrbCompletionCode::Success as u8 || event.trb_type() != TrbType::Transfer as u8 { + println!("Custom transfer event failed with {:#0x} {:#0x} {:#0x}", event.data.read(), event.status.read(), event.control.read()); + } + // TODO: Handle event data + println!("EVENT DATA: {:?}", event.event_data()); + + u32::from(len) - event.transfer_length() + }; + + if let DeviceReqData::In(ref mut dbuf) = buf { + dbuf.copy_from_slice(dma_buffer.unwrap()); + } + + Ok(bytes_transferred) } pub(crate) fn get_dev_desc(&mut self, port_id: usize) -> Result { let st = self @@ -814,10 +892,9 @@ impl SchemeMut for Xhci { return Err(Error::new(EISDIR)); } - if self - .port_states - .get(&port_num) - .ok_or(Error::new(ENOENT))? + let port_state = self.port_states.get(&port_num).ok_or(Error::new(ENOENT))?; + + if port_state .endpoint_states .get(&endpoint_num) .is_none() @@ -833,7 +910,24 @@ impl SchemeMut for Xhci { } EndpointHandleTy::Status(0) } - "transfer" => EndpointHandleTy::Transfer, + "transfer" => { + if endpoint_num == 0 { + // Don't allow user programs to interface directly with the control + // endpoint. + return Err(Error::new(EPERM)); + } + let endp_desc = &port_state.dev_desc.config_descs.get(0).ok_or(Error::new(EIO))?.interface_descs.get(0).ok_or(Error::new(EIO))?.endpoints.get(endpoint_num as usize).ok_or(Error::new(ENOENT))?; + match endp_desc.direction() { + EndpDirection::Out => if flags & O_RDWR != O_WRONLY && flags & O_STAT != 0 { + return Err(Error::new(EACCES)); + } + EndpDirection::In => if flags & O_RDWR != O_RDONLY && flags & O_STAT != 0 { + return Err(Error::new(EACCES)); + } + _ => (), + } + EndpointHandleTy::Transfer + } _ => return Err(Error::new(ENOENT)), }; Handle::Endpoint(port_num, endpoint_num, st) diff --git a/xhcid/src/xhci/trb.rs b/xhcid/src/xhci/trb.rs index 03d2931e11..91e35115ce 100644 --- a/xhcid/src/xhci/trb.rs +++ b/xhcid/src/xhci/trb.rs @@ -117,9 +117,18 @@ pub const TRB_STATUS_COMPLETION_CODE_MASK: u32 = 0xFF00_0000; pub const TRB_STATUS_COMPLETION_PARAM_SHIFT: u8 = 0; pub const TRB_STATUS_COMPLETION_PARAM_MASK: u32 = 0x00FF_FFFF; +pub const TRB_STATUS_TRANSFER_LENGTH_SHIFT: u8 = 0; +pub const TRB_STATUS_TRANSFER_LENGTH_MASK: u32 = 0x00FF_FFFF; + pub const TRB_CONTROL_TRB_TYPE_SHIFT: u8 = 10; pub const TRB_CONTROL_TRB_TYPE_MASK: u32 = 0x0000_FC00; +pub const TRB_CONTROL_EVENT_DATA_SHIFT: u8 = 2; +pub const TRB_CONTROL_EVENT_DATA_BIT: u32 = 1 << TRB_CONTROL_EVENT_DATA_SHIFT; + +pub const TRB_CONTROL_ENDPOINT_ID_MASK: u32 = 0x001F_0000; +pub const TRB_CONTROL_ENDPOINT_ID_SHIFT: u8 = 16; + impl Trb { pub fn set(&mut self, data: u64, status: u32, control: u32) { self.data.write(data); @@ -137,6 +146,19 @@ impl Trb { pub fn completion_param(&self) -> u32 { self.status.read() & TRB_STATUS_COMPLETION_PARAM_MASK } + /// Returns the number of bytes that should have been transmitten, but weren't. + pub fn transfer_length(&self) -> u32 { + self.status.read() & TRB_STATUS_TRANSFER_LENGTH_MASK + } + pub fn event_data_bit(&self) -> bool { + self.control.readf(TRB_CONTROL_EVENT_DATA_BIT) + } + pub fn event_data(&self) -> Option { + if self.event_data_bit() { Some(self.data.read()) } else { None } + } + pub fn endpoint_id(&self) -> u8 { + ((self.control.read() & TRB_CONTROL_ENDPOINT_ID_MASK) >> TRB_CONTROL_ENDPOINT_ID_SHIFT) as u8 + } pub fn trb_type(&self) -> u8 { ((self.control.read() & TRB_CONTROL_TRB_TYPE_MASK) >> TRB_CONTROL_TRB_TYPE_SHIFT) as u8 } @@ -216,6 +238,23 @@ impl Trb { | (cycle as u32), ); } + pub fn normal(&mut self, buffer: u64, len: u16, cycle: bool, estimated_td_size: u8, ent: bool, isp: bool, chain: bool, ioc: bool, idt: bool, bei: bool) { + assert_eq!(estimated_td_size & 0x1F, estimated_td_size); + // NOTE: The interrupter target and no snoop flags have been omitted. + self.set( + buffer, + u32::from(len) + | (u32::from(estimated_td_size) << 17), + u32::from(cycle) + | (u32::from(ent) << 1) + | (u32::from(isp) << 2) + | (u32::from(ent) << 4) + | (u32::from(ioc) << 5) + | (u32::from(idt) << 6) + | (u32::from(bei) << 9) + | ((TrbType::Normal as u32) << 10) + ) + } } impl fmt::Debug for Trb {