windows: implement transfers
This commit is contained in:
parent
5de605874e
commit
d4f322fe18
12 changed files with 682 additions and 82 deletions
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) interfaces: HashMap<u8, OsString>,
|
||||
|
||||
pub(crate) bus_number: u8,
|
||||
pub(crate) device_address: u8,
|
||||
|
||||
|
|
|
|||
|
|
@ -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<u8, OsString>,
|
||||
}
|
||||
|
||||
impl WindowsDevice {
|
||||
pub(crate) fn from_device_info(d: &DeviceInfo) -> Result<Arc<WindowsDevice>, 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<Self>,
|
||||
interface: u8,
|
||||
) -> Result<Arc<WindowsInterface>, 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<WindowsDevice>,
|
||||
pub(crate) handle: OwnedHandle,
|
||||
pub(crate) winusb_handle: WINUSB_INTERFACE_HANDLE,
|
||||
}
|
||||
|
||||
impl WindowsInterface {
|
||||
|
|
@ -47,18 +87,12 @@ impl WindowsInterface {
|
|||
) -> TransferHandle<super::TransferData> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<impl Iterator<Item = DeviceInfo>, 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<DeviceInfo> {
|
|||
|
||||
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<DeviceInfo> {
|
|||
})
|
||||
}
|
||||
|
||||
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::<u8>().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<Speed> {
|
||||
#![allow(non_upper_case_globals)]
|
||||
use windows_sys::Win32::Devices::Usb::{
|
||||
|
|
|
|||
103
src/platform/windows_winusb/events.rs
Normal file
103
src/platform/windows_winusb/events.rs
Normal file
|
|
@ -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<IoCompletionPort, Error> {
|
||||
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<OVERLAPPED_ENTRY>) -> 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<IoCompletionPort> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<HubHandle> {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<DeviceInfoSet, ()> {
|
||||
pub fn get(
|
||||
class: Option<GUID>,
|
||||
enumerator: Option<&OsStr>,
|
||||
) -> Result<DeviceInfoSet, io::Error> {
|
||||
let enumerator: Option<Vec<u16>> =
|
||||
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<Vec<OsString>> {
|
||||
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::<u16>()) as u32,
|
||||
&mut size,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if r == TRUE && property_type == DEVPROP_TYPE_STRING_LIST {
|
||||
let buffer = &buffer[..(size as usize / mem::size_of::<u16>())];
|
||||
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<u32> {
|
||||
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<RegKey, io::Error> {
|
||||
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<GUID, io::Error> {
|
||||
unsafe {
|
||||
let value_name: Vec<u16> = 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::<u16>()).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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<super::Interface>,
|
||||
event: *mut EventNotify,
|
||||
buf: *mut u8,
|
||||
capacity: usize,
|
||||
endpoint: u8,
|
||||
ep_type: EndpointType,
|
||||
submit_error: Option<WIN32_ERROR>,
|
||||
}
|
||||
|
||||
unsafe impl Send for TransferData {}
|
||||
|
||||
impl TransferData {
|
||||
pub(crate) fn new(
|
||||
clone: std::sync::Arc<super::Interface>,
|
||||
interface: std::sync::Arc<super::Interface>,
|
||||
endpoint: u8,
|
||||
ep_type: EndpointType,
|
||||
) -> TransferData {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn fill(&mut self, v: Vec<u8>, 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<u8> {
|
||||
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::<TransferData>(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<Vec<u8>> for TransferData {
|
||||
unsafe fn submit(&mut self, data: Vec<u8>, 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<ResponseBuffer> {
|
||||
todo!()
|
||||
let (actual_len, status) = self.get_status();
|
||||
let data = ResponseBuffer::from_vec(self.take_buf(0), actual_len);
|
||||
Completion { data, status }
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformSubmit<RequestBuffer> 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<Vec<u8>> {
|
||||
todo!()
|
||||
let (actual_len, status) = self.get_status();
|
||||
let data = self.take_buf(actual_len);
|
||||
Completion { data, status }
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformSubmit<ControlIn> 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<Vec<u8>> {
|
||||
todo!()
|
||||
let (actual_len, status) = self.get_status();
|
||||
let data = self.take_buf(actual_len);
|
||||
Completion { data, status }
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformSubmit<ControlOut<'_>> 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<ResponseBuffer> {
|
||||
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::<TransferData>(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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OwnedHandle, WIN32_ERROR> {
|
||||
pub fn create_file(path: &OsStr) -> Result<OwnedHandle, io::Error> {
|
||||
let wide_name: Vec<u16> = 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<OwnedHandle, WIN32_ERROR> {
|
|||
);
|
||||
HandleOrInvalid::from_raw_handle(r as RawHandle)
|
||||
.try_into()
|
||||
.map_err(|_| GetLastError())
|
||||
.map_err(|_| io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<u8>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue