diff --git a/Cargo.toml b/Cargo.toml index c268e9d..80d23f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,4 @@ env_logger = "0.10.0" rustix = { version = "0.38.17", features = ["fs", "event"] } [target.'cfg(target_os="windows")'.dependencies] -windows-sys = { version = "0.48.0", features = ["Win32_Devices_Usb", "Win32_Devices_DeviceAndDriverInstallation", "Win32_Foundation", "Win32_Devices_Properties", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_IO"] } +windows-sys = { version = "0.48.0", features = ["Win32_Devices_Usb", "Win32_Devices_DeviceAndDriverInstallation", "Win32_Foundation", "Win32_Devices_Properties", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_IO", "Win32_System_Registry", "Win32_System_Com"] } diff --git a/README.md b/README.md index a8cc340..a821027 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,10 @@ A new pure-Rust library for cross-platform low-level access to USB devices. a specific interface, not a device as a whole. `nusb`'s API makes working with interfaces a required step so that it can map directly to Windows APIs. -### Current status +### :construction: Current status -:construction: Control, bulk and interrupt transfers work on Linux, minimally tested +* Linux: Control, bulk and interrupt transfers work, minimally tested +* Windows: Control, bulk and interrupt transfers work, minimally tested ### License MIT or Apache 2.0, at your option diff --git a/src/enumeration.rs b/src/enumeration.rs index 4eea63f..d13c954 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, ffi::{OsStr, OsString}, fmt::Display, str::FromStr, @@ -23,6 +24,9 @@ pub struct DeviceInfo { #[cfg(target_os = "windows")] pub(crate) driver: Option, + #[cfg(target_os = "windows")] + pub(crate) interfaces: HashMap, + pub(crate) bus_number: u8, pub(crate) device_address: u8, diff --git a/src/platform/windows_winusb/device.rs b/src/platform/windows_winusb/device.rs index b89068f..a9b9b43 100644 --- a/src/platform/windows_winusb/device.rs +++ b/src/platform/windows_winusb/device.rs @@ -1,42 +1,82 @@ -use std::sync::Arc; +use std::{ + collections::HashMap, + ffi::OsString, + io::{self, ErrorKind}, + os::windows::prelude::OwnedHandle, + sync::Arc, +}; + +use log::{debug, error}; +use windows_sys::Win32::{ + Devices::Usb::{WinUsb_Free, WinUsb_Initialize, WINUSB_INTERFACE_HANDLE}, + Foundation::FALSE, +}; use crate::{ transfer::{EndpointType, TransferHandle}, DeviceInfo, Error, }; -pub(crate) struct WindowsDevice {} +use super::util::{create_file, raw_handle}; + +pub(crate) struct WindowsDevice { + interface_paths: HashMap, +} impl WindowsDevice { pub(crate) fn from_device_info(d: &DeviceInfo) -> Result, Error> { - todo!() + debug!("Creating device for {:?}", d.instance_id); + + Ok(Arc::new(WindowsDevice { + interface_paths: d.interfaces.clone(), + })) } - pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), Error> { - todo!() + pub(crate) fn set_configuration(&self, _configuration: u8) -> Result<(), Error> { + Err(io::Error::new( + ErrorKind::Unsupported, + "set_configuration not supported by WinUSB", + )) } pub(crate) fn reset(&self) -> Result<(), Error> { - todo!() + Err(io::Error::new( + ErrorKind::Unsupported, + "reset not supported by WinUSB", + )) } pub(crate) fn claim_interface( self: &Arc, interface: u8, ) -> Result, Error> { - todo!() - } -} + let path = self.interface_paths.get(&interface).ok_or_else(|| { + Error::new(ErrorKind::NotFound, "interface not found or not compatible") + })?; -impl Drop for WindowsDevice { - fn drop(&mut self) { - todo!() + let handle = create_file(path)?; + + super::events::register(&handle)?; + + let winusb_handle = unsafe { + let mut h = 0; + if WinUsb_Initialize(raw_handle(&handle), &mut h) == FALSE { + error!("WinUsb_Initialize failed: {:?}", io::Error::last_os_error()); + return Err(io::Error::last_os_error()); + } + h + }; + + Ok(Arc::new(WindowsInterface { + handle, + winusb_handle, + })) } } pub(crate) struct WindowsInterface { - pub(crate) interface: u8, - pub(crate) device: Arc, + pub(crate) handle: OwnedHandle, + pub(crate) winusb_handle: WINUSB_INTERFACE_HANDLE, } impl WindowsInterface { @@ -47,18 +87,12 @@ impl WindowsInterface { ) -> TransferHandle { TransferHandle::new(super::TransferData::new(self.clone(), endpoint, ep_type)) } - - pub(crate) unsafe fn submit_urb(&self, urb: *mut ()) { - todo!() - } - - pub(crate) unsafe fn cancel_urb(&self, urb: *mut ()) { - todo!() - } } impl Drop for WindowsInterface { fn drop(&mut self) { - todo!() + unsafe { + WinUsb_Free(self.winusb_handle); + } } } diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index 2e2c76e..63d3997 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -1,11 +1,11 @@ -use std::io::ErrorKind; +use std::{collections::HashMap, ffi::OsString, io::ErrorKind}; -use log::debug; +use log::{debug, error}; use windows_sys::Win32::Devices::{ Properties::{ - DEVPKEY_Device_Address, DEVPKEY_Device_BusNumber, DEVPKEY_Device_DeviceDesc, - DEVPKEY_Device_FriendlyName, DEVPKEY_Device_InstanceId, DEVPKEY_Device_Manufacturer, - DEVPKEY_Device_Parent, DEVPKEY_Device_Service, DEVPKEY_NAME, + DEVPKEY_Device_Address, DEVPKEY_Device_BusNumber, DEVPKEY_Device_Children, + DEVPKEY_Device_FriendlyName, DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, + DEVPKEY_Device_Manufacturer, DEVPKEY_Device_Parent, DEVPKEY_Device_Service, }, Usb::{GUID_DEVINTERFACE_USB_DEVICE, USB_DEVICE_SPEED}, }; @@ -18,13 +18,12 @@ use super::{ }; pub fn list_devices() -> Result, Error> { - let dset = - DeviceInfoSet::get_by_setup_class(GUID_DEVINTERFACE_USB_DEVICE, None).map_err(|_| { - Error::new( - ErrorKind::UnexpectedEof, - String::from("failed to list devices"), - ) - })?; + let dset = DeviceInfoSet::get(Some(GUID_DEVINTERFACE_USB_DEVICE), None).map_err(|_| { + Error::new( + ErrorKind::UnexpectedEof, + String::from("failed to list devices"), + ) + })?; let devs: Vec<_> = dset.iter_devices().flat_map(probe_device).collect(); Ok(devs.into_iter()) @@ -66,13 +65,31 @@ pub fn probe_device(dev: setupapi::DeviceInfo) -> Option { let driver = dev .get_string_property(DEVPKEY_Device_Service) - .and_then(|s| s.into_string().ok()); + .and_then(|s| s.into_string().ok()) + .unwrap_or_default(); + + let mut interfaces = HashMap::new(); + if driver.eq_ignore_ascii_case("usbccgp") { + let children = dev + .get_string_list_property(DEVPKEY_Device_Children) + .unwrap_or_default(); + interfaces.extend(children.into_iter().flat_map(probe_interface)); + } else if driver.eq_ignore_ascii_case("winusb") { + let intf_dev = dev.interfaces(GUID_DEVINTERFACE_USB_DEVICE).next(); + + if let Some(path) = intf_dev.and_then(|i| i.get_path()) { + interfaces.insert(0, path); + } else { + error!("Failed to find path for winusb device"); + } + } Some(DeviceInfo { instance_id, parent_instance_id, + interfaces, port_number, - driver, + driver: Some(driver).filter(|s| !s.is_empty()), bus_number: bus_number as u8, device_address: info.DeviceAddress as u8, vendor_id: info.DeviceDescriptor.idVendor, @@ -88,6 +105,50 @@ pub fn probe_device(dev: setupapi::DeviceInfo) -> Option { }) } +fn probe_interface(c_id: OsString) -> Option<(u8, OsString)> { + debug!("Probing interface {c_id:?} of composite device"); + let iset = DeviceInfoSet::get(None, Some(&c_id)).ok()?; + + let Some(intf_dev) = iset.iter_devices().next() else { + debug!("Interface not found in SetupAPI"); + return None; + }; + + let driver = intf_dev.get_string_property(DEVPKEY_Device_Service); + if !driver + .as_ref() + .is_some_and(|d| d.eq_ignore_ascii_case("winusb")) + { + return None; + } + + let hw_ids = intf_dev.get_string_list_property(DEVPKEY_Device_HardwareIds); + let Some(intf_num) = hw_ids + .as_deref() + .unwrap_or_default() + .iter() + .find_map(|id| id.to_str()?.rsplit_once("&MI_")?.1.parse::().ok()) + else { + error!("Failed to parse interface number in hardware IDs: {hw_ids:?}"); + return None; + }; + + let reg_key = intf_dev.registry_key().unwrap(); + let guid = reg_key.query_value_guid("DeviceInterfaceGUIDs").unwrap(); + + let Some(intf) = intf_dev.interfaces(guid).next() else { + error!("Failed to find interface"); + return None; + }; + + let Some(path) = intf.get_path() else { + error!("Failed to find interface path"); + return None; + }; + + Some((intf_num, path)) +} + fn map_speed(speed: u8) -> Option { #![allow(non_upper_case_globals)] use windows_sys::Win32::Devices::Usb::{ diff --git a/src/platform/windows_winusb/events.rs b/src/platform/windows_winusb/events.rs new file mode 100644 index 0000000..2dee170 --- /dev/null +++ b/src/platform/windows_winusb/events.rs @@ -0,0 +1,103 @@ +use log::error; +use once_cell::sync::OnceCell; +use std::{ + os::windows::{ + io::HandleOrNull, + prelude::{OwnedHandle, RawHandle}, + }, + thread, +}; +use windows_sys::Win32::{ + Foundation::{GetLastError, FALSE, INVALID_HANDLE_VALUE}, + System::IO::{CreateIoCompletionPort, GetQueuedCompletionStatusEx, OVERLAPPED_ENTRY}, +}; + +use crate::Error; + +use super::util::raw_handle; + +struct IoCompletionPort(OwnedHandle); + +impl IoCompletionPort { + fn new() -> Result { + unsafe { + let port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); + match HandleOrNull::from_raw_handle(port as RawHandle).try_into() { + Ok(handle) => Ok(IoCompletionPort(handle)), + Err(_) => { + let err = GetLastError(); + error!("CreateIoCompletionPort (create) failed: {err:?}"); + Err(std::io::Error::from_raw_os_error(err as i32)) + } + } + } + } + + fn register(&self, handle: &OwnedHandle) -> Result<(), Error> { + unsafe { + let r = CreateIoCompletionPort(raw_handle(handle), raw_handle(&self.0), 0, 0); + if r == 0 { + let err = std::io::Error::last_os_error(); + error!("CreateIoCompletionPort (register) failed: {err:?}"); + Err(err) + } else { + Ok(()) + } + } + } + + fn wait(&self, events: &mut Vec) -> Result<(), Error> { + unsafe { + let mut event_count = 0; + let r = GetQueuedCompletionStatusEx( + raw_handle(&self.0), + events.as_mut_ptr(), + events + .capacity() + .try_into() + .expect("events capacity should fit in u32"), + &mut event_count, + u32::MAX, + 0, + ); + + if r == FALSE { + let err = std::io::Error::last_os_error(); + error!("GetQueuedCompletionStatusEx failed: {err:?}"); + Err(err) + } else { + events.set_len(event_count as usize); + Ok(()) + } + } + } +} + +static IOCP_HANDLE: OnceCell = OnceCell::new(); + +pub(super) fn register(usb_fd: &OwnedHandle) -> Result<(), Error> { + let mut start_thread = false; + let iocp = IOCP_HANDLE.get_or_try_init(|| { + start_thread = true; + IoCompletionPort::new() + })?; + + if start_thread { + thread::spawn(event_loop); + } + + iocp.register(usb_fd) +} + +fn event_loop() { + let iocp = IOCP_HANDLE.get().unwrap(); + let mut event_list = Vec::with_capacity(8); + loop { + event_list.clear(); + iocp.wait(&mut event_list).unwrap(); + + for event in &event_list { + super::transfer::handle_event(event.lpOverlapped); + } + } +} diff --git a/src/platform/windows_winusb/hub.rs b/src/platform/windows_winusb/hub.rs index 1440574..01a6228 100644 --- a/src/platform/windows_winusb/hub.rs +++ b/src/platform/windows_winusb/hub.rs @@ -5,7 +5,7 @@ use std::{ ptr::null_mut, }; -use log::{debug, error}; +use log::error; use windows_sys::Win32::{ Devices::Usb::{ GUID_DEVINTERFACE_USB_HUB, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, @@ -25,8 +25,7 @@ pub struct HubHandle(OwnedHandle); impl HubHandle { pub fn by_instance_id(instance_id: &OsStr) -> Option { - let devs = - DeviceInfoSet::get_by_setup_class(GUID_DEVINTERFACE_USB_HUB, Some(instance_id)).ok()?; + let devs = DeviceInfoSet::get(Some(GUID_DEVINTERFACE_USB_HUB), Some(instance_id)).ok()?; let Some(hub_interface) = devs.iter_interfaces(GUID_DEVINTERFACE_USB_HUB).next() else { error!("Failed to find hub interface"); return None; diff --git a/src/platform/windows_winusb/mod.rs b/src/platform/windows_winusb/mod.rs index 7c17717..e6bdd30 100644 --- a/src/platform/windows_winusb/mod.rs +++ b/src/platform/windows_winusb/mod.rs @@ -1,6 +1,8 @@ mod enumeration; pub use enumeration::list_devices; +mod events; + mod device; pub(crate) use device::WindowsDevice as Device; pub(crate) use device::WindowsInterface as Interface; diff --git a/src/platform/windows_winusb/setupapi.rs b/src/platform/windows_winusb/setupapi.rs index 4b19225..bc17116 100644 --- a/src/platform/windows_winusb/setupapi.rs +++ b/src/platform/windows_winusb/setupapi.rs @@ -4,13 +4,14 @@ use std::{ alloc, alloc::Layout, ffi::{OsStr, OsString}, + io::{self, ErrorKind}, mem::{self, size_of}, - os::windows::prelude::OsStrExt, + os::windows::prelude::{OsStrExt, OsStringExt}, ptr::{addr_of_mut, null, null_mut}, slice, }; -use log::{debug, error}; +use log::error; use windows_sys::{ core::GUID, Win32::{ @@ -18,12 +19,20 @@ use windows_sys::{ DeviceAndDriverInstallation::{ SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInfo, SetupDiEnumDeviceInterfaces, SetupDiGetClassDevsW, SetupDiGetDeviceInterfaceDetailW, SetupDiGetDevicePropertyW, - DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, SP_DEVICE_INTERFACE_DATA, + SetupDiOpenDevRegKey, DICS_FLAG_GLOBAL, DIGCF_ALLCLASSES, DIGCF_DEVICEINTERFACE, + DIGCF_PRESENT, DIREG_DEV, SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_W, SP_DEVINFO_DATA, }, - Properties::{DEVPROPKEY, DEVPROPTYPE, DEVPROP_TYPE_STRING, DEVPROP_TYPE_UINT32}, + Properties::{ + DEVPROPKEY, DEVPROPTYPE, DEVPROP_TYPE_STRING, DEVPROP_TYPE_STRING_LIST, + DEVPROP_TYPE_UINT32, + }, + }, + Foundation::{GetLastError, ERROR_SUCCESS, FALSE, INVALID_HANDLE_VALUE, S_OK, TRUE}, + System::{ + Com::IIDFromString, + Registry::{RegCloseKey, RegQueryValueExW, HKEY, KEY_READ, REG_MULTI_SZ, REG_SZ}, }, - Foundation::{GetLastError, FALSE, INVALID_HANDLE_VALUE, TRUE}, }, }; @@ -35,20 +44,26 @@ pub struct DeviceInfoSet { } impl DeviceInfoSet { - pub fn get_by_setup_class(guid: GUID, enumerator: Option<&OsStr>) -> Result { + pub fn get( + class: Option, + enumerator: Option<&OsStr>, + ) -> Result { let enumerator: Option> = enumerator.map(|e| e.encode_wide().chain(Some(0)).collect()); let handle = unsafe { SetupDiGetClassDevsW( - &guid, + class.as_ref().map_or(null(), |g| g as *const _), enumerator.as_ref().map_or(null(), |s| s.as_ptr()), 0, - DIGCF_DEVICEINTERFACE | DIGCF_PRESENT, + if class.is_some() { 0 } else { DIGCF_ALLCLASSES } + | DIGCF_DEVICEINTERFACE + | DIGCF_PRESENT, ) }; if handle == INVALID_HANDLE_VALUE { - error!("SetupDiGetClassDevsW failed: {}", unsafe { GetLastError() }); - Err(()) + let err = io::Error::last_os_error(); + error!("SetupDiGetClassDevsW failed: {}", err); + Err(err) } else { Ok(DeviceInfoSet { handle }) } @@ -137,6 +152,38 @@ impl<'a> DeviceInfo<'a> { } } + pub fn get_string_list_property(&self, pkey: DEVPROPKEY) -> Option> { + let mut property_type: DEVPROPTYPE = unsafe { mem::zeroed() }; + let mut buffer = [0u16; 4096]; + let mut size: u32 = 0; // in bytes + + let r = unsafe { + SetupDiGetDevicePropertyW( + self.set.handle, + &self.device_info, + &pkey, + &mut property_type, + buffer.as_mut_ptr() as *mut u8, + (buffer.len() * mem::size_of::()) as u32, + &mut size, + 0, + ) + }; + + if r == TRUE && property_type == DEVPROP_TYPE_STRING_LIST { + let buffer = &buffer[..(size as usize / mem::size_of::())]; + Some( + buffer + .split(|&c| c == 0) + .filter(|e| e.len() > 0) + .map(|s| OsString::from_wide(s)) + .collect(), + ) + } else { + None + } + } + pub fn get_u32_property(&self, pkey: DEVPROPKEY) -> Option { let mut property_type: DEVPROPTYPE = unsafe { mem::zeroed() }; let mut buffer: u32 = 0; @@ -154,13 +201,32 @@ impl<'a> DeviceInfo<'a> { ) }; - if r == 1 && property_type == DEVPROP_TYPE_UINT32 { + if r == TRUE && property_type == DEVPROP_TYPE_UINT32 { Some(buffer) } else { None } } + pub fn registry_key(&self) -> Result { + unsafe { + let key = SetupDiOpenDevRegKey( + self.set.handle, + &self.device_info, + DICS_FLAG_GLOBAL, + 0, + DIREG_DEV, + KEY_READ, + ); + + if key == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(RegKey(key)) + } + } + } + pub fn interfaces(&self, interface_class_guid: GUID) -> DeviceInfoSetInterfaceIter { DeviceInfoSetInterfaceIter { set: self.set, @@ -269,3 +335,69 @@ impl<'a> InterfaceInfo<'a> { } } } + +pub struct RegKey(HKEY); + +impl RegKey { + pub fn query_value_guid(&self, value_name: &str) -> Result { + unsafe { + let value_name: Vec = OsStr::new(value_name) + .encode_wide() + .chain(Some(0)) + .collect(); + let mut ty = 0; + let mut size = 0; + + // get size + let r = RegQueryValueExW( + self.0, + value_name.as_ptr(), + null_mut(), + &mut ty, + null_mut(), + &mut size, + ); + + if r != ERROR_SUCCESS { + return Err(io::Error::from_raw_os_error(r as i32)); + } + + if ty != REG_MULTI_SZ && ty != REG_SZ { + return Err(io::Error::new( + ErrorKind::InvalidInput, + "registry value type not string", + )); + } + + let layout = Layout::from_size_align(size as usize, mem::align_of::()).unwrap(); + + let buf = alloc::alloc(layout); + + let r = RegQueryValueExW(self.0, value_name.as_ptr(), null(), &mut ty, buf, &mut size); + + if r != ERROR_SUCCESS { + alloc::dealloc(buf, layout); + return Err(io::Error::from_raw_os_error(r as i32)); + } + + let mut guid = GUID::from_u128(0); + let r = IIDFromString(buf as *mut u16, &mut guid); + + alloc::dealloc(buf, layout); + + if r == S_OK { + Ok(guid) + } else { + Err(io::Error::new(ErrorKind::InvalidData, "invalid UUID")) + } + } + } +} + +impl Drop for RegKey { + fn drop(&mut self) { + unsafe { + RegCloseKey(self.0); + } + } +} diff --git a/src/platform/windows_winusb/transfer.rs b/src/platform/windows_winusb/transfer.rs index 9cc9457..a08375d 100644 --- a/src/platform/windows_winusb/transfer.rs +++ b/src/platform/windows_winusb/transfer.rs @@ -1,86 +1,333 @@ use std::{ ffi::c_void, + io, mem::{self, ManuallyDrop}, - ptr::null_mut, + ptr::{addr_of_mut, null_mut}, + sync::Arc, +}; + +use log::{debug, error}; +use windows_sys::Win32::{ + Devices::Usb::{ + WinUsb_ControlTransfer, WinUsb_GetOverlappedResult, WinUsb_ReadPipe, WinUsb_WritePipe, + WINUSB_SETUP_PACKET, + }, + Foundation::{ + GetLastError, ERROR_DEVICE_NOT_CONNECTED, ERROR_FILE_NOT_FOUND, ERROR_GEN_FAILURE, + ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_SUCH_DEVICE, ERROR_REQUEST_ABORTED, FALSE, + TRUE, WIN32_ERROR, + }, + System::IO::{CancelIoEx, OVERLAPPED}, }; use crate::transfer::{ - Completion, ControlIn, ControlOut, EndpointType, PlatformSubmit, PlatformTransfer, - RequestBuffer, ResponseBuffer, TransferStatus, SETUP_PACKET_SIZE, + notify_completion, Completion, ControlIn, ControlOut, EndpointType, PlatformSubmit, + PlatformTransfer, RequestBuffer, ResponseBuffer, TransferStatus, }; +use super::util::raw_handle; + #[repr(C)] -pub struct TransferData {} +pub(crate) struct EventNotify { + // first member of repr(C) struct; can cast pointer between types + overlapped: OVERLAPPED, + ptr: *mut c_void, +} + +pub struct TransferData { + interface: Arc, + event: *mut EventNotify, + buf: *mut u8, + capacity: usize, + endpoint: u8, + ep_type: EndpointType, + submit_error: Option, +} unsafe impl Send for TransferData {} impl TransferData { pub(crate) fn new( - clone: std::sync::Arc, + interface: std::sync::Arc, endpoint: u8, ep_type: EndpointType, ) -> TransferData { - todo!() - } - - fn fill(&mut self, v: Vec, len: usize, user_data: *mut c_void) { - todo!() + TransferData { + interface, + event: Box::into_raw(Box::new(unsafe { mem::zeroed() })), + buf: null_mut(), + capacity: 0, + endpoint, + ep_type, + submit_error: None, + } } /// SAFETY: requires that the transfer has completed and `length` bytes are initialized unsafe fn take_buf(&mut self, length: usize) -> Vec { - todo!() + let v = Vec::from_raw_parts(self.buf, length, self.capacity); + self.buf = null_mut(); + self.capacity = 0; + v + } + + /// SAFETY: user_data must be the callback pointer passed to `submit` + unsafe fn post_submit(&mut self, r: i32, func: &str, user_data: *mut c_void) { + if r == TRUE { + error!("{func} completed synchronously") + } + + let err = GetLastError(); + + if err != ERROR_IO_PENDING { + self.submit_error = Some(err); + error!("{func} failed: {}", io::Error::from_raw_os_error(err as _)); + + // Safety: Transfer was not submitted, so we still own it + // and must complete it in place of the event thread. + notify_completion::(user_data); + } else { + self.submit_error = None; + } + } + + /// SAFETY: transfer must be completed + unsafe fn get_status(&mut self) -> (usize, TransferStatus) { + if let Some(err) = self.submit_error { + debug!( + "Transfer {:?} on endpoint {:02x} failed on submit: {}", + self.event, self.endpoint, err + ); + return (0, map_error(err)); + } + + let mut actual_len = 0; + let r = WinUsb_GetOverlappedResult( + self.interface.winusb_handle, + self.event as *mut OVERLAPPED, + &mut actual_len, + FALSE, + ); + + let status = if r != 0 { + debug!( + "Transfer {:?} on endpoint {:02x} complete: {} bytes transferred", + self.event, self.endpoint, actual_len + ); + TransferStatus::Complete + } else { + let err = GetLastError(); + debug!( + "Transfer {:?} on endpoint {:02x} failed: {}, {} bytes transferred", + self.event, self.endpoint, err, actual_len + ); + map_error(err) + }; + + (actual_len as usize, status) } } impl Drop for TransferData { fn drop(&mut self) { - todo!() + if !self.buf.is_null() { + unsafe { drop(Vec::from_raw_parts(self.buf, 0, self.capacity)) } + } + unsafe { drop(Box::from_raw(self.event)) } } } impl PlatformTransfer for TransferData { fn cancel(&self) { - todo!() + debug!("Cancelling transfer {:?}", self.event); + unsafe { + let r = CancelIoEx( + raw_handle(&self.interface.handle), + self.event as *mut OVERLAPPED, + ); + if r == 0 { + let err = GetLastError(); + if err != ERROR_NOT_FOUND { + error!( + "CancelIoEx failed: {}", + io::Error::from_raw_os_error(err as i32) + ); + } + } + } } } impl PlatformSubmit> for TransferData { unsafe fn submit(&mut self, data: Vec, user_data: *mut c_void) { - todo!() + addr_of_mut!((*self.event).ptr).write(user_data); + + let mut data = ManuallyDrop::new(data); + self.buf = data.as_mut_ptr(); + self.capacity = data.capacity(); + let len = data.len(); + + debug!( + "Submit transfer {:?} on endpoint {:02X} for {} bytes OUT", + self.event, self.endpoint, len + ); + + let r = WinUsb_WritePipe( + self.interface.winusb_handle, + self.endpoint, + self.buf, + len.try_into().expect("transfer size should fit in u32"), + null_mut(), + self.event as *mut OVERLAPPED, + ); + self.post_submit(r, "WinUsb_WritePipe", user_data); } unsafe fn take_completed(&mut self) -> Completion { - todo!() + let (actual_len, status) = self.get_status(); + let data = ResponseBuffer::from_vec(self.take_buf(0), actual_len); + Completion { data, status } } } impl PlatformSubmit for TransferData { unsafe fn submit(&mut self, data: RequestBuffer, user_data: *mut c_void) { - todo!() + addr_of_mut!((*self.event).ptr).write(user_data); + + let (buf, request_len) = data.into_vec(); + let mut buf = ManuallyDrop::new(buf); + self.buf = buf.as_mut_ptr(); + self.capacity = buf.capacity(); + + debug!( + "Submit transfer {:?} on endpoint {:02X} for {} bytes IN", + self.event, self.endpoint, request_len + ); + + let r = WinUsb_ReadPipe( + self.interface.winusb_handle, + self.endpoint, + self.buf, + request_len + .try_into() + .expect("transfer size should fit in u32"), + null_mut(), + self.event as *mut OVERLAPPED, + ); + self.post_submit(r, "WinUsb_ReadPipe", user_data); } unsafe fn take_completed(&mut self) -> Completion> { - todo!() + let (actual_len, status) = self.get_status(); + let data = self.take_buf(actual_len); + Completion { data, status } } } impl PlatformSubmit for TransferData { unsafe fn submit(&mut self, data: ControlIn, user_data: *mut c_void) { - todo!() + assert_eq!(self.endpoint, 0); + assert_eq!(self.ep_type, EndpointType::Control); + addr_of_mut!((*self.event).ptr).write(user_data); + + let mut buf = ManuallyDrop::new(Vec::with_capacity(data.length as usize)); + self.buf = buf.as_mut_ptr(); + self.capacity = buf.capacity(); + + debug!( + "Submit transfer {:?} on endpoint {:02X} for {} bytes ControlIN", + self.event, self.endpoint, data.length + ); + + let pkt = WINUSB_SETUP_PACKET { + RequestType: data.request_type(), + Request: data.request, + Value: data.value, + Index: data.index, + Length: data.length, + }; + + let r = WinUsb_ControlTransfer( + self.interface.winusb_handle, + pkt, + self.buf, + data.length as u32, + null_mut(), + self.event as *mut OVERLAPPED, + ); + + self.post_submit(r, "WinUsb_ControlTransfer", user_data); } unsafe fn take_completed(&mut self) -> Completion> { - todo!() + let (actual_len, status) = self.get_status(); + let data = self.take_buf(actual_len); + Completion { data, status } } } impl PlatformSubmit> for TransferData { unsafe fn submit(&mut self, data: ControlOut, user_data: *mut c_void) { - todo!() + assert_eq!(self.endpoint, 0); + assert_eq!(self.ep_type, EndpointType::Control); + addr_of_mut!((*self.event).ptr).write(user_data); + + let mut buf = ManuallyDrop::new(data.data.to_vec()); + self.buf = buf.as_mut_ptr(); + self.capacity = buf.capacity(); + let len: u16 = buf + .len() + .try_into() + .expect("transfer size should fit in u16"); + + debug!( + "Submit transfer {:?} on endpoint {:02X} for {} bytes ControlOUT", + self.event, self.endpoint, len + ); + + let pkt = WINUSB_SETUP_PACKET { + RequestType: data.request_type(), + Request: data.request, + Value: data.value, + Index: data.index, + Length: len as u16, + }; + + let r = WinUsb_ControlTransfer( + self.interface.winusb_handle, + pkt, + self.buf, + len as u32, + null_mut(), + self.event as *mut OVERLAPPED, + ); + + self.post_submit(r, "WinUsb_ControlTransfer", user_data); } unsafe fn take_completed(&mut self) -> Completion { - todo!() + let (actual_len, status) = self.get_status(); + let data = ResponseBuffer::from_vec(self.take_buf(0), actual_len); + Completion { data, status } + } +} + +pub(super) fn handle_event(completion: *mut OVERLAPPED) { + let completion = completion as *mut EventNotify; + debug!("Handling completion for transfer {completion:?}"); + unsafe { + let p = addr_of_mut!((*completion).ptr).read(); + notify_completion::(p) + } +} + +fn map_error(err: WIN32_ERROR) -> TransferStatus { + match err { + ERROR_GEN_FAILURE => TransferStatus::Stall, + ERROR_REQUEST_ABORTED => TransferStatus::Cancelled, + ERROR_FILE_NOT_FOUND | ERROR_DEVICE_NOT_CONNECTED | ERROR_NO_SUCH_DEVICE => { + TransferStatus::Disconnected + } + _ => TransferStatus::UnknownError, } } diff --git a/src/platform/windows_winusb/util.rs b/src/platform/windows_winusb/util.rs index f7d3341..90fd901 100644 --- a/src/platform/windows_winusb/util.rs +++ b/src/platform/windows_winusb/util.rs @@ -1,5 +1,6 @@ use std::{ ffi::{OsStr, OsString}, + io, os::windows::prelude::{ AsHandle, AsRawHandle, HandleOrInvalid, OsStrExt, OsStringExt, OwnedHandle, RawHandle, }, @@ -7,19 +8,21 @@ use std::{ }; use windows_sys::Win32::{ - Foundation::{GetLastError, GENERIC_WRITE, HANDLE, WIN32_ERROR}, - Storage::FileSystem::{CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_WRITE, OPEN_EXISTING}, + Foundation::{GENERIC_READ, GENERIC_WRITE, HANDLE}, + Storage::FileSystem::{ + CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + }, }; /// Wrapper around `CreateFile` -pub fn create_file(path: &OsStr) -> Result { +pub fn create_file(path: &OsStr) -> Result { let wide_name: Vec = path.encode_wide().chain(Some(0)).collect(); unsafe { let r = CreateFileW( wide_name.as_ptr(), - GENERIC_WRITE, - FILE_SHARE_WRITE, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, null(), OPEN_EXISTING, FILE_FLAG_OVERLAPPED, @@ -27,7 +30,7 @@ pub fn create_file(path: &OsStr) -> Result { ); HandleOrInvalid::from_raw_handle(r as RawHandle) .try_into() - .map_err(|_| GetLastError()) + .map_err(|_| io::Error::last_os_error()) } } diff --git a/src/transfer/control.rs b/src/transfer/control.rs index bcaf05a..23c3df4 100644 --- a/src/transfer/control.rs +++ b/src/transfer/control.rs @@ -48,6 +48,7 @@ pub struct ControlOut<'a> { } impl<'a> ControlOut<'a> { + #[allow(unused)] pub(crate) fn setup_packet(&self) -> Result<[u8; SETUP_PACKET_SIZE], ()> { Ok(pack_setup( Direction::Out, @@ -59,6 +60,10 @@ impl<'a> ControlOut<'a> { self.data.len().try_into().map_err(|_| ())?, )) } + + pub(crate) fn request_type(&self) -> u8 { + request_type(Direction::Out, self.control_type, self.recipient) + } } impl TransferRequest for ControlOut<'_> { @@ -86,6 +91,7 @@ pub struct ControlIn { } impl ControlIn { + #[allow(unused)] pub(crate) fn setup_packet(&self) -> [u8; SETUP_PACKET_SIZE] { pack_setup( Direction::In, @@ -97,6 +103,10 @@ impl ControlIn { self.length, ) } + + pub(crate) fn request_type(&self) -> u8 { + request_type(Direction::In, self.control_type, self.recipient) + } } pub(crate) const SETUP_PACKET_SIZE: usize = 8; @@ -110,7 +120,7 @@ fn pack_setup( index: u16, length: u16, ) -> [u8; SETUP_PACKET_SIZE] { - let bmrequesttype = ((direction as u8) << 7) | ((control_type as u8) << 5) | (recipient as u8); + let bmrequesttype = request_type(direction, control_type, recipient); [ bmrequesttype, @@ -124,6 +134,10 @@ fn pack_setup( ] } +fn request_type(direction: Direction, control_type: ControlType, recipient: Recipient) -> u8 { + ((direction as u8) << 7) | ((control_type as u8) << 5) | (recipient as u8) +} + impl TransferRequest for ControlIn { type Response = Vec; }