diff --git a/Cargo.lock b/Cargo.lock index 3ac510dde4..3326bb7579 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,11 +53,6 @@ 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" @@ -1332,7 +1327,6 @@ dependencies = [ 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", @@ -1473,7 +1467,6 @@ 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" diff --git a/usbhidd/src/main.rs b/usbhidd/src/main.rs index 3723b45877..0f86635770 100644 --- a/usbhidd/src/main.rs +++ b/usbhidd/src/main.rs @@ -1,11 +1,11 @@ -use std::convert::TryInto; use std::env; -use xhcid_interface::{DevDesc, XhciClientHandle}; +use xhcid_interface::{DevDesc, PortReqRecipient, XhciClientHandle}; mod report_desc; +mod reqs; -use report_desc::{ReportIter, ReportFlatIter, REPORT_DESC_TY}; +use report_desc::{ReportFlatIter, ReportIter, REPORT_DESC_TY}; fn main() { let mut args = env::args().skip(1); @@ -37,11 +37,14 @@ fn main() { 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, + let mut report_desc_bytes = vec![0u8; report_desc_len as usize]; + handle + .get_descriptor( + PortReqRecipient::Interface, + REPORT_DESC_TY, + 0, interface_num, - report_desc_len, + &mut report_desc_bytes, ) .expect("Failed to retrieve report descriptor"); diff --git a/usbhidd/src/report_desc.rs b/usbhidd/src/report_desc.rs index e77da8afb5..85d8526f85 100644 --- a/usbhidd/src/report_desc.rs +++ b/usbhidd/src/report_desc.rs @@ -185,7 +185,9 @@ impl<'a> Iterator for ReportFlatIter<'a> { type Item = ReportItem; fn next(&mut self) -> Option { - if self.offset >= self.desc.len() { return None } + if self.offset >= self.desc.len() { + return None; + } let first = self.desc[self.offset]; let size = first & 0b11; @@ -219,6 +221,9 @@ pub struct ReportIter<'a> { flat: ReportFlatIter<'a>, error: bool, // this is just for reusing the vec + // TODO: When GATs are available, this could be done simply using iterators. Every collection + // yields a child iterator, which returns the mutable reference to the flat iter to its parent + // when dropped. open_collections: Vec<(u8, Vec)>, } #[derive(Debug)] @@ -240,7 +245,9 @@ impl<'a> Iterator for ReportIter<'a> { type Item = ReportIterItem; fn next(&mut self) -> Option { - if self.error { return None } + if self.error { + return None; + } self.open_collections.clear(); @@ -265,10 +272,15 @@ impl<'a> Iterator for ReportIter<'a> { return Some(ReportIterItem::Collection(value, finished_collection)); } } - other if self.open_collections.is_empty() => return Some(ReportIterItem::Item(other)), - other => { - self.open_collections.last_mut().unwrap().1.push(ReportIterItem::Item(other)) + other if self.open_collections.is_empty() => { + return Some(ReportIterItem::Item(other)) } + other => self + .open_collections + .last_mut() + .unwrap() + .1 + .push(ReportIterItem::Item(other)), } } } diff --git a/usbhidd/src/reqs.rs b/usbhidd/src/reqs.rs new file mode 100644 index 0000000000..e834474fc2 --- /dev/null +++ b/usbhidd/src/reqs.rs @@ -0,0 +1,111 @@ +use std::slice; + +use xhcid_interface::{ + DeviceReqData, PortReqRecipient, PortReqTy, XhciClientHandle, XhciClientHandleError, +}; + +const GET_REPORT_REQ: u8 = 0x1; +const SET_REPORT_REQ: u8 = 0x9; +const GET_IDLE_REQ: u8 = 0x2; +const SET_IDLE_REQ: u8 = 0xA; +const GET_PROTOCOL_REQ: u8 = 0x3; +const SET_PROTOCOL_REQ: u8 = 0xB; + +fn concat(hi: u8, lo: u8) -> u16 { + (u16::from(hi) << 8) | u16::from(lo) +} + +pub fn get_report( + handle: &mut XhciClientHandle, + report_ty: u8, + report_id: u8, + if_num: u16, + buffer: &mut [u8], +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + GET_REPORT_REQ, + concat(report_ty, report_id), + if_num, + DeviceReqData::In(buffer), + ) +} +pub fn set_report( + handle: &mut XhciClientHandle, + report_ty: u8, + report_id: u8, + if_num: u16, + buffer: &[u8], +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + SET_REPORT_REQ, + concat(report_id, report_ty), + if_num, + DeviceReqData::Out(buffer), + ) +} +pub fn get_idle( + handle: &mut XhciClientHandle, + report_id: u8, + if_num: u16, +) -> Result { + let mut idle_rate = 0; + let buffer = slice::from_mut(&mut idle_rate); + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + GET_IDLE_REQ, + u16::from(report_id), + if_num, + DeviceReqData::In(buffer), + )?; + Ok(idle_rate) +} +pub fn set_idle( + handle: &mut XhciClientHandle, + duration: u8, + report_id: u8, + if_num: u16, +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + SET_IDLE_REQ, + concat(duration, report_id), + if_num, + DeviceReqData::NoData, + ) +} +pub fn get_protocol( + handle: &mut XhciClientHandle, + if_num: u16, +) -> Result { + let mut protocol = 0; + let buffer = slice::from_mut(&mut protocol); + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + GET_PROTOCOL_REQ, + 0, + if_num, + DeviceReqData::In(buffer), + )?; + Ok(protocol) +} +pub fn set_protocol( + handle: &mut XhciClientHandle, + protocol: u8, + if_num: u16, +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + SET_PROTOCOL_REQ, + u16::from(protocol), + if_num, + DeviceReqData::NoData, + ) +} diff --git a/xhcid/src/driver_interface.rs b/xhcid/src/driver_interface.rs index dc88a7a6bc..a2cb08b361 100644 --- a/xhcid/src/driver_interface.rs +++ b/xhcid/src/driver_interface.rs @@ -1,6 +1,7 @@ pub extern crate serde; pub extern crate smallvec; +use std::convert::TryFrom; use std::fs::File; use std::io::prelude::*; use std::{io, result, str}; @@ -164,16 +165,35 @@ pub struct HidDesc { 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 struct PortReq { + pub direction: PortReqDirection, + pub req_type: PortReqTy, + pub req_recipient: PortReqRecipient, pub request: u8, pub value: u16, pub index: u16, pub length: u16, pub transfers_data: bool, } +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum PortReqDirection { + HostToDevice, + DeviceToHost, +} +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum PortReqTy { + Class, + Vendor, + Standard, +} +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum PortReqRecipient { + Device, + Interface, + Endpoint, + Other, + VendorSpecific, +} #[derive(Debug)] pub struct XhciClientHandle { @@ -253,6 +273,34 @@ impl str::FromStr for EndpointStatus { } } +pub enum DeviceReqData<'a> { + In(&'a mut [u8]), + Out(&'a [u8]), + NoData, +} +impl DeviceReqData<'_> { + fn len(&self) -> usize { + match self { + Self::In(buf) => buf.len(), + Self::Out(buf) => buf.len(), + Self::NoData => 0, + } + } + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'a> DeviceReqData<'a> { + fn direction(&self) -> PortReqDirection { + match self { + DeviceReqData::Out(_) => PortReqDirection::HostToDevice, + DeviceReqData::NoData => PortReqDirection::HostToDevice, + DeviceReqData::In(_) => PortReqDirection::DeviceToHost, + } + } +} + impl XhciClientHandle { pub fn new(scheme: String, port: usize) -> Self { Self { scheme, port } @@ -284,19 +332,22 @@ impl XhciClientHandle { let string = std::fs::read_to_string(path)?; Ok(string.parse()?) } - // TODO: Device-specific request, with data - pub fn get_class_descriptor( + pub fn device_request<'a>( &self, + req_type: PortReqTy, + req_recipient: PortReqRecipient, + request: u8, value: u16, index: u16, - length: u16, - ) -> result::Result, XhciClientHandleError> { - // TODO: Base this on the to-be-written generic device request function. + data: DeviceReqData<'a>, + ) -> result::Result<(), XhciClientHandleError> { + let length = u16::try_from(data.len()).or(Err(XhciClientHandleError::TransferBufTooLarge(data.len())))?; + let req = PortReq { - direction: "device_to_host", - req_type: "standard", // TODO: Add as a parameter, with its own enum. - req_recipient: "interface", - request: 0x06, + direction: data.direction(), + req_type, + req_recipient, + request, value, index, length, @@ -307,19 +358,32 @@ impl XhciClientHandle { 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() { + let json_bytes_written = file.write(&json)?; + if json_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)); - } + match data { + DeviceReqData::In(buf) => { + let bytes_read = file.read(buf)?; - Ok(buf) + if bytes_read != buf.len() { + return Err(XhciClientHandleError::InvalidResponse(Invalid)); + } + } + DeviceReqData::Out(buf) => { + let bytes_read = file.write(&buf)?; + + if bytes_read != buf.len() { + return Err(XhciClientHandleError::InvalidResponse(Invalid)); + } + } + DeviceReqData::NoData => (), + } + Ok(()) + } + pub fn get_descriptor(&self, recipient: PortReqRecipient, ty: u8, idx: u8, windex: u16, buffer: &mut [u8]) -> result::Result<(), XhciClientHandleError> { + self.device_request(PortReqTy::Standard, recipient, 0x06, (u16::from(ty) << 8) | u16::from(idx), windex, DeviceReqData::In(buffer)) } } @@ -333,4 +397,7 @@ pub enum XhciClientHandleError { #[error("invalid response")] InvalidResponse(#[from] Invalid), + + #[error("transfer buffer too large ({0} > 65536)")] + TransferBufTooLarge(usize), } diff --git a/xhcid/src/usb/setup.rs b/xhcid/src/usb/setup.rs index 3ba4462206..ff793c133f 100644 --- a/xhcid/src/usb/setup.rs +++ b/xhcid/src/usb/setup.rs @@ -1,4 +1,5 @@ use super::DescriptorKind; +use crate::driver_interface::*; #[repr(packed)] #[derive(Clone, Copy, Debug, Default)] @@ -15,6 +16,14 @@ pub enum ReqDirection { HostToDevice = 0, DeviceToHost = 1, } +impl From for ReqDirection { + fn from(d: PortReqDirection) -> Self { + match d { + PortReqDirection::DeviceToHost => Self::DeviceToHost, + PortReqDirection::HostToDevice => Self::HostToDevice, + } + } +} #[repr(u8)] pub enum ReqType { @@ -31,6 +40,15 @@ pub enum ReqType { /// Reserved Reserved = 3, } +impl From for ReqType { + fn from(d: PortReqTy) -> Self { + match d { + PortReqTy::Standard => Self::Standard, + PortReqTy::Class => Self::Class, + PortReqTy::Vendor => Self::Vendor, + } + } +} #[repr(u8)] pub enum ReqRecipient { @@ -41,6 +59,17 @@ pub enum ReqRecipient { // 4..=30 are reserved VendorSpecific = 31, } +impl From for ReqRecipient { + fn from(d: PortReqRecipient) -> Self { + match d { + PortReqRecipient::Device => Self::Device, + PortReqRecipient::Interface => Self::Interface, + PortReqRecipient::Endpoint => Self::Endpoint, + PortReqRecipient::Other => Self::Other, + PortReqRecipient::VendorSpecific => Self::VendorSpecific, + } + } +} pub const USB_SETUP_DIR_BIT: u8 = 1 << 7; pub const USB_SETUP_DIR_SHIFT: u8 = 7; diff --git a/xhcid/src/xhci/scheme.rs b/xhcid/src/xhci/scheme.rs index 11b8b3d4f1..1b8d6d63af 100644 --- a/xhcid/src/xhci/scheme.rs +++ b/xhcid/src/xhci/scheme.rs @@ -576,34 +576,17 @@ impl Xhci { // 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. + // be better. Maybe something simple like bincode could be used, if a custom binary struct + // is too much overkill. // 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 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)), - }; - - let ty = match req.req_type { - "class" => ReqType::Class, - "vendor" => ReqType::Vendor, - "standard" => ReqType::Standard, - _ => return Err(Error::new(EBADMSG)), - } as u8; - - let recipient = match req.req_recipient { - "device" => ReqRecipient::Device, - "interface" => ReqRecipient::Interface, - "endpoint" => ReqRecipient::Endpoint, - "other" => ReqRecipient::Other, - "vendor_specific" => ReqRecipient::VendorSpecific, - _ => return Err(Error::new(EBADMSG)), - } as u8; + let direction = ReqDirection::from(req.direction); + let ty = ReqType::from(req.req_type) as u8; + let recipient = ReqRecipient::from(req.req_recipient) as u8; let transfer_kind = match direction { _ if !req.transfers_data => TransferKind::NoData,