Add (untested) transfer support.

This commit is contained in:
4lDO2
2020-02-09 17:17:36 +01:00
parent ae76f57162
commit 6b70e80d20
11 changed files with 405 additions and 14 deletions
Generated
+1
View File
@@ -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",
]
+1
View File
@@ -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" }
+2
View File
@@ -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);
}
+2
View File
@@ -46,6 +46,8 @@ pub struct BulkOnlyTransport<'a> {
impl<'a> BulkOnlyTransport<'a> {
pub fn init(handle: &'a XhciClientHandle) -> Result<Self, ProtocolError> {
let lun = get_max_lun(handle, 0)?;
println!("BOT_MAX_LUN {}", lun);
Ok(Self {
handle,
})
+104
View File
@@ -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
}
+6
View File
@@ -0,0 +1,6 @@
pub mod cmds;
pub mod opcodes;
pub struct Scsi {
}
+112
View File
@@ -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,
}
+10 -3
View File
@@ -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, F: Fn(&[u8]) -> T>(&self, f: F) -> Option<T> {
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,
+23
View File
@@ -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<u32>,
@@ -105,8 +107,23 @@ pub struct StreamContext {
rsvd: Mmio<u32>,
}
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<Ring>,
}
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
}
+105 -11
View File
@@ -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<u32> {
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<u32> {
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<u32> {
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<DevDesc> {
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)
+39
View File
@@ -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<u64> {
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 {