diff --git a/Cargo.lock b/Cargo.lock index c4d93077a9..3ac510dde4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,11 @@ dependencies = [ "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bgad" version = "0.1.0" @@ -1070,6 +1075,24 @@ dependencies = [ "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thiserror" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.42" @@ -1305,9 +1328,22 @@ dependencies = [ "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "usbhidd" +version = "0.1.0" +dependencies = [ + "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "xhcid 0.1.0", +] + [[package]] name = "usbscsid" version = "0.1.0" +dependencies = [ + "xhcid 0.1.0", +] [[package]] name = "utf8parse" @@ -1322,6 +1358,11 @@ dependencies = [ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ux" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vboxd" version = "0.1.0" @@ -1423,6 +1464,7 @@ dependencies = [ "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1431,6 +1473,7 @@ dependencies = [ "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" @@ -1541,6 +1584,8 @@ dependencies = [ "checksum stb_truetype 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51" "checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8" "checksum termion 1.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "818ef3700c2a7b447dca1a1dd28341fe635e6ee103c806c636bb9c929991b2cd" +"checksum thiserror 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "205684fd018ca14432b12cce6ea3d46763311a571c3d294e71ba3f01adcf1aad" +"checksum thiserror-impl 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "57e4d2e50ca050ed44fb58309bdce3efa79948f84f9993ad1978de5eebdce5a7" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum tokio 0.1.13 (git+https://gitlab.redox-os.org/redox-os/tokio)" = "" "checksum tokio-codec 0.1.1 (git+https://gitlab.redox-os.org/redox-os/tokio)" = "" @@ -1566,6 +1611,7 @@ dependencies = [ "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +"checksum ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum vte 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4f42f536e22f7fcbb407639765c8fd78707a33109301f834a594758bedd6e8cf" "checksum webpki 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)" = "17d7967316d8411ca3b01821ee6c332bde138ba4363becdb492f12e514daa17f" diff --git a/Cargo.toml b/Cargo.toml index 023056b6c8..0044204257 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,6 @@ members = [ "vboxd", "vesad", "xhcid", + "usbhidd", "usbscsid", ] diff --git a/usbhidd/.gitignore b/usbhidd/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/usbhidd/.gitignore @@ -0,0 +1 @@ +/target diff --git a/usbhidd/Cargo.toml b/usbhidd/Cargo.toml new file mode 100644 index 0000000000..f535b7e35e --- /dev/null +++ b/usbhidd/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "usbhidd" +version = "0.1.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2018" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base64 = "0.11" +bitflags = "1.2" +ux = "0.1" +xhcid = { path = "../xhcid" } diff --git a/usbhidd/src/main.rs b/usbhidd/src/main.rs new file mode 100644 index 0000000000..76c80af142 --- /dev/null +++ b/usbhidd/src/main.rs @@ -0,0 +1,266 @@ +use std::convert::TryInto; +use std::env; + +use bitflags::bitflags; +use ux::{u2, u4}; +use xhcid_interface::{DevDesc, XhciClientHandle}; + +/*#[repr(u8)] +enum Protocol { + +}*/ + +bitflags! { + pub struct MainItemFlags: u32 { + const CONSTANT = 1 << 0; + const VARIABLE = 1 << 1; + const RELATIVE = 1 << 2; + const WRAP = 1 << 3; + const NONLINEAR = 1 << 4; + const NO_PREFERRED_STATE = 1 << 5; + const NULL_STATE = 1 << 6; + const VOLATILE = 1 << 7; + const BUFFERED_BYTES = 1 << 8; + } +} +#[repr(u8)] +pub enum MainCollectionFlags { + Physical = 0, + Application, + Logical, + Report, + NamedArray, + UsageSwitch, + UsageModifier, +} + +const REPORT_DESC_TY: u8 = 34; + +#[derive(Debug)] +enum MainItem { + Input(u32), + Output(u32), + Feature(u32), + Collection(u8), + EndOfCollection, +} +#[derive(Debug)] +enum GlobalItem { + UsagePage(u32), + LogicalMinimum(u32), + LogicalMaximum(u32), + PhysicalMinimum(u32), + PhysicalMaximum(u32), + UnitExponent(u32), + Unit(u32), + ReportSize(u32), + RepordId(u32), + ReportCount(u32), + Push(u32), + Pop(u32), +} +#[derive(Debug)] +enum LocalItem { + Usage(u32), + UsageMinimum(u32), + UsageMaximum(u32), + DesignatorIndex(u32), + DesignatorMinimum(u32), + DesignatorMaximum(u32), + StringIndex(u32), + StringMinimum(u32), + StringMaximum(u32), + Delimeter(u32), +} +#[derive(Debug)] +enum ReportItem { + Main(MainItem), + Global(GlobalItem), + Local(LocalItem), +} +impl From for ReportItem { + fn from(main: MainItem) -> Self { + Self::Main(main) + } +} +impl From for ReportItem { + fn from(main: GlobalItem) -> Self { + Self::Global(main) + } +} +impl From for ReportItem { + fn from(main: LocalItem) -> Self { + Self::Local(main) + } +} + +impl ReportItem { + fn size(size: u2) -> u8 { + match u8::from(size) { + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 4, + _ => unreachable!(), + } + } + fn parse_short(size: u2, ty: u2, tag: u4, bytes: &[u8]) -> Option<(Self, usize)> { + Some(match (u8::from(tag), u8::from(ty)) { + (tag, 0b00) => { + let real_size = Self::size(size) as usize; + let mut value_bytes = [0u8; 4]; + if real_size > 0 { + value_bytes[..real_size].copy_from_slice(&bytes[..real_size]) + }; + let value = u32::from_le_bytes(value_bytes); + + match tag { + 0b1000 => (MainItem::Input(value).into(), 1 + real_size), + 0b1001 => (MainItem::Output(value).into(), 1 + real_size), + 0b1011 => (MainItem::Feature(value).into(), 1 + real_size), + 0b1010 => (MainItem::Collection(bytes[0]).into(), 2), + 0b1100 => (MainItem::EndOfCollection.into(), 1 + real_size), + _ => return None, + } + } + (tag, 0b01) => { + let real_size = Self::size(size) as usize; + let mut value_bytes = [0u8; 4]; + if real_size > 0 { + value_bytes[..real_size].copy_from_slice(&bytes[..real_size]) + }; + let value = u32::from_le_bytes(value_bytes); + match tag { + 0b0000 => (GlobalItem::UsagePage(value).into(), 1 + real_size), + 0b0001 => (GlobalItem::LogicalMinimum(value).into(), 1 + real_size), + 0b0010 => (GlobalItem::LogicalMaximum(value).into(), 1 + real_size), + 0b0011 => (GlobalItem::PhysicalMinimum(value).into(), 1 + real_size), + 0b0100 => (GlobalItem::PhysicalMaximum(value).into(), 1 + real_size), + 0b0101 => (GlobalItem::UnitExponent(value).into(), 1 + real_size), + 0b0110 => (GlobalItem::Unit(value).into(), 1 + real_size), + 0b0111 => (GlobalItem::ReportSize(value).into(), 1 + real_size), + 0b1000 => (GlobalItem::RepordId(value).into(), 1 + real_size), + 0b1001 => (GlobalItem::ReportCount(value).into(), 1 + real_size), + 0b1010 => (GlobalItem::Push(value).into(), 1 + real_size), + 0b1011 => (GlobalItem::Pop(value).into(), 1 + real_size), + _ => return None, + } + } + (tag, 0b10) => { + let real_size = Self::size(size) as usize; + let mut value_bytes = [0u8; 4]; + if real_size > 0 { + value_bytes[..real_size].copy_from_slice(&bytes[..real_size]) + }; + let value = u32::from_le_bytes(value_bytes); + match tag { + 0b0000 => (LocalItem::Usage(value).into(), 1 + real_size), + 0b0001 => (LocalItem::UsageMinimum(value).into(), 1 + real_size), + 0b0010 => (LocalItem::UsageMaximum(value).into(), 1 + real_size), + 0b0011 => (LocalItem::DesignatorIndex(value).into(), 1 + real_size), + 0b0100 => (LocalItem::DesignatorMinimum(value).into(), 1 + real_size), + 0b0101 => (LocalItem::DesignatorMaximum(value).into(), 1 + real_size), + 0b0111 => (LocalItem::StringIndex(value).into(), 1 + real_size), + 0b1000 => (LocalItem::StringMinimum(value).into(), 1 + real_size), + 0b1001 => (LocalItem::StringMaximum(value).into(), 1 + real_size), + 0b1010 => (LocalItem::Delimeter(value).into(), 1 + real_size), + _ => return None, + } + } + (_, 0b11) => panic!("Calling parse_short but with long item"), + _ => unreachable!(), + }) + } + fn parse_long(size: u8, long_tag: u8, bytes: &[u8]) -> (Self, usize) { + todo!() + } +} + +struct ReportIter<'a> { + desc: &'a [u8], + offset: usize, +} +impl<'a> ReportIter<'a> { + fn new(desc: &'a [u8]) -> Self { + Self { desc, offset: 0 } + } +} +impl<'a> Iterator for ReportIter<'a> { + type Item = ReportItem; + + fn next(&mut self) -> Option { + let first = self.desc[self.offset]; + let size = first & 0b11; + let ty = first & 0b1100 >> 2; + let tag = first & 0b11110000 >> 4; + + if size == 0b10 && ty == 3 && tag == 0b1111 { + // Long Item + let size = self.desc[self.offset + 1]; + let long_tag = self.desc[self.offset + 2]; + let data = &self.desc[self.offset + 2..self.offset + 2 + size as usize]; + + let (item, len) = ReportItem::parse_long(size, long_tag, data); + self.offset += len; + Some(item) + } else { + // Short Item + + // Although there is a 2-bit size field, the size doesn't have to be the actual size of the data. + let data = &self.desc[self.offset + 1..]; + + let (item, len) = + ReportItem::parse_short(u2::new(size), u2::new(ty), u4::new(tag), data)?; + self.offset += len; + Some(item) + } + } +} + +fn main() { + let mut args = env::args().skip(1); + + const USAGE: &'static str = "usbhidd "; + + let scheme = args.next().expect(USAGE); + let port = args + .next() + .expect(USAGE) + .parse::() + .expect("Expected integer as input of port"); + let protocol = args.next().expect(USAGE); + + println!( + "USB HID driver spawned with scheme `{}`, port {}, protocol {}", + scheme, port, protocol + ); + + let handle = XhciClientHandle::new(scheme, port); + let dev_desc: DevDesc = handle + .get_standard_descs() + .expect("Failed to get standard descriptors"); + let hid_desc = dev_desc.config_descs[0].interface_descs[0].hid_descs[0]; + + // TODO: Currently it's assumed that config 0 and interface 0 are used. + + let interface_num = 0; + let report_desc_len = hid_desc.desc_len; + assert_eq!(hid_desc.desc_ty, REPORT_DESC_TY); + + let report_desc_bytes: Vec = handle + .get_class_descriptor( + u16::from(REPORT_DESC_TY) << 8, + interface_num, + report_desc_len, + ) + .expect("Failed to retrieve report descriptor"); + use std::io::Write as _; + std::io::stdout() + .write_all(base64::encode(&report_desc_bytes).as_bytes()) + .unwrap(); + let iterator = ReportIter::new(&report_desc_bytes); + + for item in iterator { + println!("HID ITEM {:?}", item); + } +} diff --git a/usbscsid/Cargo.toml b/usbscsid/Cargo.toml index 098e899269..90c80d0779 100644 --- a/usbscsid/Cargo.toml +++ b/usbscsid/Cargo.toml @@ -3,7 +3,9 @@ name = "usbscsid" version = "0.1.0" authors = ["4lDO2 <4lDO2@protonmail.com>"] edition = "2018" +license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +xhcid = { path = "../xhcid" } diff --git a/xhcid/Cargo.toml b/xhcid/Cargo.toml index a7a055459c..7a2b3de990 100644 --- a/xhcid/Cargo.toml +++ b/xhcid/Cargo.toml @@ -21,4 +21,5 @@ redox_syscall = { git = "https://gitlab.redox-os.org/4lDO2/syscall.git", branch serde = { version = "1", features = ["derive"] } serde_json = "1" smallvec = { version = "1", features = ["serde"] } +thiserror = "1" toml = "0.5" diff --git a/xhcid/drivers.toml b/xhcid/drivers.toml index 426a19114e..53cfc0eb39 100644 --- a/xhcid/drivers.toml +++ b/xhcid/drivers.toml @@ -3,3 +3,9 @@ name = "SCSI over USB" class = 8 # Mass Storage class subclass = 6 # SCSI transparent command set command = ["/bin/usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"] + +[[drivers]] +name = "USB HID" +class = 3 # HID class +subclass = -1 +command = ["/bin/usbhidd", "$SCHEME", "$PORT", "$IF_PROTO"] diff --git a/xhcid/src/driver_interface.rs b/xhcid/src/driver_interface.rs index 1b2a158a91..b20cce5e74 100644 --- a/xhcid/src/driver_interface.rs +++ b/xhcid/src/driver_interface.rs @@ -1,9 +1,14 @@ pub extern crate serde; pub extern crate smallvec; +use std::fs::File; +use std::io::prelude::*; +use std::{io, result, str}; + use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use syscall::{Error, Result, EINVAL}; +use thiserror::Error; pub use crate::usb::{EndpointTy, ENDP_ATTR_TY_MASK}; @@ -100,6 +105,31 @@ impl EndpDesc { _ => return Err(Error::new(EINVAL)), }) } + pub fn max_streams(&self) -> u8 { + self.ssc + .as_ref() + .map(|ssc| { + if self.is_bulk() { + 1 << (ssc.attributes & 0x1F) + } else { + 0 + } + }) + .unwrap_or(0) + } + pub fn isoch_mult(&self, lec: bool) -> u8 { + if !lec && self.is_isoch() { + self.ssc + .as_ref() + .map(|ssc| ssc.attributes & 0x3) + .unwrap_or(0) + } else { + 0 + } + } + pub fn max_burst(&self) -> u8 { + self.ssc.map(|ssc| ssc.max_burst).unwrap_or(0) + } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct IfDesc { @@ -133,3 +163,175 @@ pub struct HidDesc { pub optional_desc_ty: u8, pub optional_desc_len: u16, } +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct PortReq<'a> { + pub direction: &'a str, + pub req_type: &'a str, + pub req_recipient: &'a str, + pub request: u8, + pub value: u16, + pub index: u16, + pub length: u16, + pub transfers_data: bool, +} + +#[derive(Debug)] +pub struct XhciClientHandle { + scheme: String, + port: usize, +} +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum PortState { + EnabledOrDisabled, + Default, + Addressed, + Configured, +} +impl PortState { + pub fn as_str(&self) -> &'static str { + match self { + Self::EnabledOrDisabled => "enabled_or_disabled", + Self::Default => "default", + Self::Addressed => "addressed", + Self::Configured => "configured", + } + } +} +#[derive(Debug, Error)] +#[error("invalid input")] +pub struct Invalid; + +impl str::FromStr for PortState { + type Err = Invalid; + + fn from_str(s: &str) -> result::Result { + Ok(match s { + "enabled_or_disabled" | "enabled/disabled" => Self::EnabledOrDisabled, + "default" => Self::Default, + "addressed" => Self::Addressed, + "configured" => Self::Configured, + _ => return Err(Invalid), + }) + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum EndpointStatus { + Disabled, + Enabled, + Halted, + Stopped, + Error, +} + +impl EndpointStatus { + pub fn as_str(&self) -> &'static str { + match self { + Self::Disabled => "disabled", + Self::Enabled => "enabled", + Self::Halted => "halted", + Self::Stopped => "stopped", + Self::Error => "error", + } + } +} + +impl str::FromStr for EndpointStatus { + type Err = Invalid; + + fn from_str(s: &str) -> result::Result { + Ok(match s { + "disabled" => Self::Disabled, + "enabled" => Self::Enabled, + "halted" => Self::Halted, + "stopped" => Self::Stopped, + "error" => Self::Error, + _ => return Err(Invalid), + }) + } +} + +impl XhciClientHandle { + pub fn new(scheme: String, port: usize) -> Self { + Self { scheme, port } + } + + pub fn get_standard_descs(&self) -> result::Result { + let path = format!("{}:port{}/descriptors", self.scheme, self.port); + let json = std::fs::read(path)?; + std::io::stdout().lock().write_all(&json)?; + Ok(serde_json::from_slice(&json)?) + } + pub fn configure_endpoints( + &self, + req: &ConfigureEndpointsReq, + ) -> result::Result<(), XhciClientHandleError> { + let path = format!("{}:port{}/configure_endpoints", self.scheme, self.port); + serde_json::to_writer(File::open(path)?, req)?; + Ok(()) + } + pub fn port_state(&self) -> result::Result { + let path = format!("{}:port{}/state", self.scheme, self.port); + let string = std::fs::read_to_string(path)?; + Ok(string.parse()?) + } + pub fn endpoint_status( + &self, + num: u8, + ) -> result::Result { + let path = format!("{}:port{}/endpoints/{}/status", self.scheme, self.port, num); + let string = std::fs::read_to_string(path)?; + Ok(string.parse()?) + } + // TODO: Device-specific request, with data + pub fn get_class_descriptor( + &self, + value: u16, + index: u16, + length: u16, + ) -> result::Result, XhciClientHandleError> { + // TODO: Base this on the to-be-written generic device request function. + let req = PortReq { + direction: "device_to_host", + req_type: "standard", // TODO: Add as a parameter, with its own enum. + req_recipient: "interface", + request: 0x06, + value, + index, + length, + transfers_data: true, + }; + let json = serde_json::to_vec(&req)?; + + let path = format!("{}:port{}/request", self.scheme, self.port); + let mut file = File::open(path)?; + + let bytes_written = file.write(&json)?; + if bytes_written != json.len() { + return Err(XhciClientHandleError::InvalidResponse(Invalid)); + } + + let mut buf = vec![0u8; length as usize]; + let bytes_read = file.read(&mut buf)?; + if bytes_read != buf.len() { + println!("HIDd B"); + return Err(XhciClientHandleError::InvalidResponse(Invalid)); + } + + Ok(buf) + } +} + +#[derive(Debug, Error)] +pub enum XhciClientHandleError { + #[error("i/o error: {0}")] + IoError(#[from] io::Error), + + #[error("serialization error: {0}")] + SerializationError(#[from] serde_json::Error), + + #[error("invalid response")] + InvalidResponse(#[from] Invalid), +} diff --git a/xhcid/src/usb/config.rs b/xhcid/src/usb/config.rs index 28dfe96199..11f5e4cbbd 100644 --- a/xhcid/src/usb/config.rs +++ b/xhcid/src/usb/config.rs @@ -12,3 +12,16 @@ pub struct ConfigDescriptor { } unsafe impl plain::Plain for ConfigDescriptor {} + +#[repr(packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct OtherSpeedConfig { + pub length: u8, + pub kind: u8, + pub total_length: u16, + pub interfaces: u8, + pub configuration_value: u8, + pub configuration_str: u8, + pub attributes: u8, + pub max_power: u8, +} diff --git a/xhcid/src/usb/device.rs b/xhcid/src/usb/device.rs index 93f50234ce..7c99946bfb 100644 --- a/xhcid/src/usb/device.rs +++ b/xhcid/src/usb/device.rs @@ -27,3 +27,18 @@ impl DeviceDescriptor { ((self.usb >> 8) & 0xFF) as u8 } } + +#[repr(packed)] +pub struct DeviceQualifier { + pub length: u8, + pub kind: u8, + pub usb: u16, + pub class: u8, + pub sub_class: u8, + pub protocol: u8, + pub pkgsz_other_speed: u8, + pub num_other_speed_cfgs: u8, + pub _rsvd: u8, +} + +unsafe impl plain::Plain for DeviceQualifier {} diff --git a/xhcid/src/xhci/extended.rs b/xhcid/src/xhci/extended.rs new file mode 100644 index 0000000000..93962b8347 --- /dev/null +++ b/xhcid/src/xhci/extended.rs @@ -0,0 +1,10 @@ +use syscall::Mmio; + +#[repr(packed)] +pub struct SupportedProtoCap { + a: Mmio, + b: Mmio, + c: Mmio, + d: Mmio, + protocols: [Mmio], +} diff --git a/xhcid/src/xhci/mod.rs b/xhcid/src/xhci/mod.rs index 640254b880..3b0fea760d 100644 --- a/xhcid/src/xhci/mod.rs +++ b/xhcid/src/xhci/mod.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::ffi::OsStr; +use std::convert::TryFrom; use std::pin::Pin; use std::sync::{atomic::AtomicBool, Arc, Mutex, Weak}; use std::{mem, process, slice, sync::atomic, task}; @@ -15,6 +15,7 @@ mod command; mod context; mod doorbell; mod event; +mod extended; mod operational; mod port; mod ring; @@ -450,11 +451,13 @@ impl Xhci { .ok_or(Error::new(EBADF))?; let drivers_usercfg: &DriversConfig = &DRIVERS_CONFIG; - if let Some(driver) = drivers_usercfg - .drivers - .iter() - .find(|driver| driver.class == ifdesc.class && driver.subclass == ifdesc.sub_class) - { + if let Some(driver) = drivers_usercfg.drivers.iter().find(|driver| { + driver.class == ifdesc.class + && driver + .subclass() + .map(|subclass| subclass == ifdesc.sub_class) + .unwrap_or(true) + }) { println!("Loading driver \"{}\"", driver.name); let (command, args) = driver.command.split_first().ok_or(Error::new(EBADMSG))?; @@ -485,9 +488,14 @@ impl Xhci { struct DriverConfig { name: String, class: u8, - subclass: u8, + subclass: i16, // The subclass may be meaningless for some drivers, hence negative values (and values above 255) mean "undefined". command: Vec, } +impl DriverConfig { + fn subclass(&self) -> Option { + u8::try_from(self.subclass).ok() + } +} #[derive(Deserialize)] struct DriversConfig { drivers: Vec, diff --git a/xhcid/src/xhci/scheme.rs b/xhcid/src/xhci/scheme.rs index 287f78b534..11b8b3d4f1 100644 --- a/xhcid/src/xhci/scheme.rs +++ b/xhcid/src/xhci/scheme.rs @@ -3,14 +3,14 @@ use std::io::prelude::*; use std::{cmp, mem, path, str}; use serde::{Deserialize, Serialize}; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; 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, 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, + ENOTDIR, ENXIO, EOVERFLOW, 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, }; use super::{port, usb}; @@ -42,12 +42,17 @@ pub enum EndpointHandleTy { Root(usize, Vec), // offset, content } +pub enum PortReqState { + Init, + Ready(TransferKind, Dma<[u8]>), // direction, buffers +} + pub enum Handle { TopLevel(usize, Vec), // offset, contents (ports) Port(usize, usize, Vec), // port, offset, contents PortDesc(usize, usize, Vec), // port, offset, contents PortState(usize, usize), // port, offset - PortReq(usize), // port + PortReq(usize, PortReqState), // port, state Endpoints(usize, usize, Vec), // port, offset, contents Endpoint(usize, u8, EndpointHandleTy), // port, endpoint, offset, state ConfigureEndpoints(usize), // port @@ -229,8 +234,6 @@ impl Xhci { const CONTEXT_ENTRIES_SHIFT: u8 = 27; let current_slot_a = input_context.device.slot.a.read(); - let current_context_entries = - ((current_slot_a & CONTEXT_ENTRIES_MASK) >> CONTEXT_ENTRIES_SHIFT) as u8; let endpoints = &port_state.dev_desc.config_descs[req.config_desc].interface_descs[0].endpoints; @@ -261,18 +264,8 @@ impl Xhci { .ok_or(Error::new(EIO))?; let endp_desc = endpoints.get(index as usize).ok_or(Error::new(EIO))?; - let superspeed_companion = endp_desc.ssc; - // TODO: Check if streams are actually supported. - let max_streams = superspeed_companion - .map(|ssc| { - if endp_desc.is_bulk() { - 1 << (ssc.attributes & 0x1F) - } else { - 0 - } - }) - .unwrap_or(0); + let max_streams = endp_desc.max_streams(); let max_psa_size = self.cap.max_psa_size(); // TODO: Secondary streams. @@ -291,18 +284,10 @@ impl Xhci { // I presume that USB 3 devices can never be in low/full/high-speed mode, but // always SuperSpeed (gen 1 and 2 etc.). - let max_burst_size = superspeed_companion.map(|ssc| ssc.max_burst).unwrap_or(0); + let max_burst_size = endp_desc.max_burst(); let max_packet_size = endp_desc.max_packet_size; - let mult = if !lec && endp_desc.is_isoch() { - if let Some(ssc) = superspeed_companion { - ssc.attributes & 0x3 - } else { - 0 - } - } else { - 0 - }; + let mult = endp_desc.isoch_mult(lec); let interval = endp_desc.interval; let max_error_count = 3; @@ -378,8 +363,6 @@ impl Xhci { endp_ctx.c.write(u32::from(avg_trb_len)); } - input_context.dump_control(); - self.run.ints[0].erdp.write(self.cmd.erdp()); { @@ -571,14 +554,13 @@ impl Xhci { config_descs, }) } - fn write_port_desc(&mut self, port_id: usize, contents: &mut Vec) -> Result<()> { + fn port_desc_json(&mut self, port_id: usize) -> Result> { let dev_desc = &self .port_states .get(&port_id) .ok_or(Error::new(ENOENT))? .dev_desc; - serde_json::to_writer_pretty(contents, dev_desc).unwrap(); - Ok(()) + serde_json::to_vec(dev_desc).or(Err(Error::new(EIO))) } fn write_dyn_string(string: &[u8], buf: &mut [u8], offset: &mut usize) -> usize { let max_bytes_to_read = cmp::min(string.len(), buf.len()); @@ -589,35 +571,29 @@ impl Xhci { bytes_to_read } - fn port_req(&mut self, port_num: usize, buf: &[u8]) -> Result<()> { + fn port_req(&mut self, fd: usize, port_num: usize, buf: &[u8]) -> Result<()> { use usb::setup::*; - #[derive(Deserialize)] - struct PortReq<'a> { - direction: &'a str, - req_type: &'a str, - req_recipient: &'a str, - request: u8, - value: u16, - index: u16, - length: u16, - } // TODO: This json format might be too high level, but is useful for debugging, // but when actual device-specific drivers are written, a binary format would // be better. + // FIXME: Make sure there aren't any other PortReq handles, perhaps by storing the state in + // PortState? + let req = serde_json::from_slice::>(buf).or(Err(Error::new(EBADMSG)))?; let direction = match req.direction { "host_to_device" => ReqDirection::HostToDevice, "device_to_host" => ReqDirection::DeviceToHost, _ => return Err(Error::new(EBADMSG)), - } as u8; + }; let ty = match req.req_type { "class" => ReqType::Class, "vendor" => ReqType::Vendor, - "standard" | _ => return Err(Error::new(EBADMSG)), + "standard" => ReqType::Standard, + _ => return Err(Error::new(EBADMSG)), } as u8; let recipient = match req.req_recipient { @@ -629,8 +605,14 @@ impl Xhci { _ => return Err(Error::new(EBADMSG)), } as u8; + let transfer_kind = match direction { + _ if !req.transfers_data => TransferKind::NoData, + ReqDirection::DeviceToHost => TransferKind::In, + ReqDirection::HostToDevice => TransferKind::Out, + }; + let setup = Setup { - kind: (direction << USB_SETUP_DIR_SHIFT) + kind: ((direction as u8) << USB_SETUP_DIR_SHIFT) | (ty << USB_SETUP_REQ_TY_SHIFT) | (recipient << USB_SETUP_RECIPIENT_SHIFT), request: req.request, @@ -643,6 +625,7 @@ impl Xhci { .port_states .get_mut(&port_num) .ok_or(Error::new(EBADF))?; + let ring = match port_state .endpoint_states .get_mut(&0) @@ -654,17 +637,32 @@ impl Xhci { _ => return Err(Error::new(EIO)), }; + let mut buffer = None; + { let (cmd, cycle) = ring.next(); - cmd.setup( - setup, - TransferKind::NoData, // FIXME + cmd.setup(setup, transfer_kind, cycle); + } + if transfer_kind != TransferKind::NoData { + let (cmd, cycle) = ring.next(); + + // TODO: Reuse buffers, or something. + // TODO: Validate the size. + // TODO: Sizes above 65536, *perhaps*. + let data_buffer = unsafe { Dma::<[u8]>::zeroed_unsized(req.length as usize)? }; + assert_eq!(data_buffer.len(), req.length as usize); + + cmd.data( + data_buffer.physical(), + req.length, + transfer_kind == TransferKind::In, cycle, ); + buffer = Some(data_buffer); } { let (cmd, cycle) = ring.next(); - cmd.status(false, cycle); + cmd.status(transfer_kind == TransferKind::In, cycle); } self.dbs[port_state.slot as usize].write(1); @@ -673,10 +671,27 @@ impl Xhci { while event.data.read() == 0 { println!(" - Waiting for event"); } + if event.completion_code() != TrbCompletionCode::Success as u8 + || event.trb_type() != TrbType::Transfer as u8 + { + println!( + "Custom device request failed with EVENT {:#0x} {:#0x} {:#0x}", + event.data.read(), + event.status.read(), + event.control.read() + ); + } } self.run.ints[0].erdp.write(self.cmd.erdp()); + match self.handles.get_mut(&fd).ok_or(Error::new(EBADF))? { + Handle::PortReq(_, ref mut st) if buffer.is_some() => { + *st = PortReqState::Ready(transfer_kind, buffer.unwrap()) + } + _ => return Err(Error::new(EBADF)), + } + Ok(()) } } @@ -733,9 +748,7 @@ impl SchemeMut for Xhci { return Err(Error::new(ENOTDIR)); } - let mut contents = Vec::new(); - self.write_port_desc(port_num, &mut contents)?; - + let contents = self.port_desc_json(port_num)?; Handle::PortDesc(port_num, 0, contents) } "configure" => { @@ -759,10 +772,7 @@ impl SchemeMut for Xhci { if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { return Err(Error::new(ENOTDIR)); } - if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { - return Err(Error::new(EACCES)); - } - Handle::PortReq(port_num) + Handle::PortReq(port_num, PortReqState::Init) } "endpoints" => { if flags & O_DIRECTORY == 0 && flags & O_STAT == 0 { @@ -852,12 +862,12 @@ impl SchemeMut for Xhci { write!(contents, "descriptors\nendpoints\n").unwrap(); - if dbg!(self.slot_state( + if self.slot_state( self.port_states .get(&port_num) .ok_or(Error::new(ENOENT))? - .slot as usize - )) != SlotState::Configured as u8 + .slot as usize, + ) != SlotState::Configured as u8 { write!(contents, "configure\n").unwrap(); } @@ -889,7 +899,12 @@ impl SchemeMut for Xhci { stat.st_mode = MODE_FILE; stat.st_size = buf.len() as u64; } - Handle::PortState(_, _) => stat.st_mode = MODE_CHR, + Handle::PortReq(_, PortReqState::Ready(TransferKind::In, ref buf)) => { + stat.st_mode = MODE_CHR; + stat.st_size = buf.len() as u64; + } + + Handle::PortState(_, _) | Handle::PortReq(_, _) => stat.st_mode = MODE_CHR, Handle::Endpoint(_, _, st) => match st { EndpointHandleTy::Status(_) | EndpointHandleTy::Transfer => stat.st_mode = MODE_CHR, EndpointHandleTy::Root(_, ref buf) => { @@ -897,7 +912,7 @@ impl SchemeMut for Xhci { stat.st_size = buf.len() as u64; } }, - Handle::ConfigureEndpoints(_) | Handle::PortReq(_) => { + Handle::ConfigureEndpoints(_) => { stat.st_mode = MODE_CHR | 0o200; // write only } } @@ -914,7 +929,7 @@ impl SchemeMut for Xhci { write!(src, "/port{}/descriptors", port_num).unwrap() } Handle::PortState(port_num, _) => write!(src, "/port{}/state", port_num).unwrap(), - Handle::PortReq(port_num) => write!(src, "/port{}/request", port_num).unwrap(), + Handle::PortReq(port_num, _) => write!(src, "/port{}/request", port_num).unwrap(), Handle::Endpoints(port_num, _, _) => { write!(src, "/port{}/endpoints/", port_num).unwrap() } @@ -967,7 +982,7 @@ impl SchemeMut for Xhci { Ok(*offset) } // Write-once configure or transfer - Handle::Endpoint(_, _, _) | Handle::ConfigureEndpoints(_) | Handle::PortReq(_) => { + Handle::Endpoint(_, _, _) | Handle::ConfigureEndpoints(_) | Handle::PortReq(_, _) => { return Err(Error::new(ESPIPE)) } } @@ -988,7 +1003,7 @@ impl SchemeMut for Xhci { Ok(bytes_to_read) } - Handle::ConfigureEndpoints(_) | Handle::PortReq(_) => return Err(Error::new(EBADF)), + Handle::ConfigureEndpoints(_) => return Err(Error::new(EBADF)), &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { EndpointHandleTy::Transfer => { @@ -1012,14 +1027,16 @@ impl SchemeMut for Xhci { & ENDPOINT_CONTEXT_STATUS_MASK; let string = match status { - // TODO: Give this its own enum. - 0 => "disabled", - 1 => "enabled", - 2 => "halted", - 3 => "stopped", - 4 => "error", - _ => "unknown", + 0 => Some(EndpointStatus::Disabled), + 1 => Some(EndpointStatus::Enabled), + 2 => Some(EndpointStatus::Halted), + 3 => Some(EndpointStatus::Stopped), + 4 => Some(EndpointStatus::Error), + _ => None, } + .as_ref() + .map(EndpointStatus::as_str) + .unwrap_or("unknown") .as_bytes(); Ok(Self::write_dyn_string(string, buf, offset)) @@ -1037,34 +1054,53 @@ impl SchemeMut for Xhci { .state(); let string = match state { - // TODO: Give this its own enum. - 0 => "enabled_or_disabled", // Maybe "enabled/disabled"? - 1 => "default", - 2 => "addressed", - 3 => "configured", - _ => "unknown", + 0 => Some(PortState::EnabledOrDisabled), + 1 => Some(PortState::Default), + 2 => Some(PortState::Addressed), + 3 => Some(PortState::Configured), + _ => None, } + .as_ref() + .map(PortState::as_str) + .unwrap_or("unknown") .as_bytes(); Ok(Self::write_dyn_string(string, buf, offset)) } + Handle::PortReq(_, st) => { + if let PortReqState::Ready(TransferKind::In, ref src_buf) = st { + if buf.len() != src_buf.len() { + return Err(Error::new(EINVAL)); + } + buf.copy_from_slice(src_buf); + Ok(buf.len()) + } else { + Err(Error::new(EBADF)) + } + } } } fn write(&mut self, fd: usize, buf: &[u8]) -> Result { - match self.handles.get(&fd).ok_or(Error::new(EBADF))? { - &Handle::ConfigureEndpoints(port_num) => { + match self.handles.get_mut(&fd).ok_or(Error::new(EBADF))? { + &mut Handle::ConfigureEndpoints(port_num) => { self.configure_endpoints(port_num, buf)?; Ok(buf.len()) } - &Handle::Endpoint(port_num, endp_num, EndpointHandleTy::Transfer) => { + &mut Handle::Endpoint(port_num, endp_num, EndpointHandleTy::Transfer) => { self.transfer_write(port_num, endp_num, buf)?; // TODO Ok(buf.len()) } - &Handle::PortReq(port_num) => { - self.port_req(port_num, buf)?; + &mut Handle::PortReq(port_num, PortReqState::Init) => { + self.port_req(fd, port_num, buf)?; Ok(buf.len()) } + // TODO: Introduce PortReqState::Waiting, which this write call changes to + // PortReqState::ReadyToWrite when all bytes are written. + &mut Handle::PortReq( + port_num, + PortReqState::Ready(TransferKind::Out, ref mut src_buf), + ) => return Err(Error::new(ENOSYS)), _ => return Err(Error::new(EBADF)), } } diff --git a/xhcid/src/xhci/trb.rs b/xhcid/src/xhci/trb.rs index 6eb6169b28..03d2931e11 100644 --- a/xhcid/src/xhci/trb.rs +++ b/xhcid/src/xhci/trb.rs @@ -96,6 +96,7 @@ pub enum TrbCompletionCode { } #[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum TransferKind { NoData, Reserved,