Initial commit: bulk transfers on Linux usbfs

This commit is contained in:
Kevin Mehall 2023-09-30 16:51:13 -06:00
commit 85551a9779
16 changed files with 1025 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

25
Cargo.toml Normal file
View file

@ -0,0 +1,25 @@
[package]
name = "nusb"
version = "0.1.0"
description = "Low-level access to USB devices in pure Rust"
authors = ["Kevin Mehall <km@kevinmehall.net>"]
edition = "2021"
license = "Apache-2.0 OR MIT"
[dependencies]
atomic-waker = "1.1.2"
futures-lite = "1.13.0"
log = "0.4.20"
once_cell = "1.18.0"
slab = "0.4.9"
[dev-dependencies]
env_logger = "0.10.0"
[target.'cfg(target_os="linux")'.dependencies]
rustix = { version = "0.38.15", features = ["fs", "event"] }
[patch.crates-io]
rustix = { path = "../rustix" }

4
README.md Normal file
View file

@ -0,0 +1,4 @@
nusb
----
A new pure-Rust library for cross-platform low-level access to USB devices.

23
examples/bulk.rs Normal file
View file

@ -0,0 +1,23 @@
use futures_lite::future::block_on;
use nusb::TransferStatus;
fn main() {
env_logger::init();
let di = nusb::list_devices()
.unwrap()
.find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23)
.expect("device should be connected");
println!("Device info: {di:?}");
let device = di.open().unwrap();
let interface = device.claim_interface(0).unwrap();
loop {
let result = block_on(interface.bulk_transfer(0x81, Vec::with_capacity(256)));
println!("{result:?}");
if result.status != TransferStatus::Complete {
break;
}
}
}

6
examples/list.rs Normal file
View file

@ -0,0 +1,6 @@
fn main() {
env_logger::init();
for dev in nusb::list_devices().unwrap() {
println!("{:#?}", dev);
}
}

105
src/device.rs Normal file
View file

@ -0,0 +1,105 @@
use std::{collections::VecDeque, sync::Arc, time::Duration};
use crate::{transfer::EndpointType, Completion, DeviceInfo, Error, Transfer};
type TransferError = Error;
type Buffer = Vec<u8>;
#[derive(Clone)]
pub struct Device {
backend: Arc<crate::platform::Device>,
}
impl Device {
pub(crate) fn open(d: &DeviceInfo) -> Result<Device, std::io::Error> {
let backend = crate::platform::Device::from_device_info(d)?;
Ok(Device { backend })
}
pub fn set_configuration(&self, configuration: u8) -> Result<(), Error> {
self.backend.set_configuration(configuration)
}
pub fn reset(&self) -> Result<(), Error> {
self.backend.reset()
}
pub fn claim_interface(&self, interface: u8) -> Result<Interface, Error> {
let backend = self.backend.claim_interface(interface)?;
Ok(Interface { backend })
}
}
pub struct Interface {
backend: Arc<crate::platform::Interface>,
}
impl Interface {
pub fn set_alt_setting(&self) {
todo!()
}
pub fn bulk_transfer(&self, endpoint: u8, buf: Vec<u8>) -> Transfer {
let mut t = Transfer::new(self.backend.clone(), endpoint, EndpointType::Bulk);
t.submit(buf);
t
}
pub fn interrupt_transfer(&self, endpoint: u8, buf: Vec<u8>) -> Transfer {
let mut t = Transfer::new(self.backend.clone(), endpoint, EndpointType::Interrupt);
t.submit(buf);
t
}
}
struct Queue {
pending: VecDeque<Transfer>,
cached: Option<Transfer>,
}
impl Queue {
/// Submit a new transfer on the endpoint.
///
/// For an IN endpoint, the transfer size is set by the *capacity* of
/// the buffer, and the length and current contents are ignored. The
/// buffer is returned from a later call to `complete` filled with
/// the data read from the endpoint.
///
/// For an OUT endpoint, the contents of the buffer are written to
/// the endpoint.
pub fn submit(&mut self, buf: Buffer) -> Result<(), TransferError> {
todo!()
}
/// Block waiting for the next pending transfer to complete, and return
/// its buffer or an error status.
///
/// For an IN endpoint, the returned buffer contains the data
/// read from the device.
///
/// For an OUT endpoint, the buffer is unmodified, but can be
/// reused for another transfer.
pub fn complete(&mut self, timeout: Option<Duration>) -> Option<Completion> {
todo!()
}
/// Get the number of transfers that have been submitted with
/// `submit` that have not yet been returned from `complete`.
pub fn pending_transfers(&self) -> usize {
todo!()
}
/// Get the number of transfers that have completed and are
/// ready to be returned from `complete` without blocking.
pub fn ready_transfers(&self) -> usize {
todo!()
}
/// Cancel all pending transfers on the endpoint pipe.
/// TODO: maybe this should be on the `Device` or an object separable from the `Pipe`
/// so it can be called from another thread, and cause a blocking `complete` call to
//// immediately return.
fn cancel_all(&mut self) -> Result<(), TransferError> {
todo!()
}
}

115
src/enumeration.rs Normal file
View file

@ -0,0 +1,115 @@
use std::{fmt::Display, str::FromStr};
use crate::{platform, Device, Error};
#[derive(Debug, Clone)]
pub struct DeviceInfo {
#[cfg(target_os = "linux")]
pub(crate) path: crate::platform::SysfsPath,
pub(crate) bus_number: u8,
pub(crate) device_address: u8,
pub(crate) vendor_id: u16,
pub(crate) product_id: u16,
pub(crate) device_version: u16,
pub(crate) class: u8,
pub(crate) subclass: u8,
pub(crate) protocol: u8,
pub(crate) speed: Speed,
pub(crate) manufacturer_string: Option<String>,
pub(crate) product_string: Option<String>,
pub(crate) serial_number: Option<String>,
}
impl DeviceInfo {
#[cfg(target_os = "linux")]
pub fn path(&self) -> &platform::SysfsPath {
&self.path
}
pub fn bus_number(&self) -> u8 {
self.bus_number
}
pub fn device_address(&self) -> u8 {
self.device_address
}
pub fn vendor_id(&self) -> u16 {
self.vendor_id
}
pub fn product_id(&self) -> u16 {
self.product_id
}
pub fn device_version(&self) -> u16 {
self.device_version
}
pub fn class(&self) -> u8 {
self.class
}
pub fn subclass(&self) -> u8 {
self.subclass
}
pub fn protocol(&self) -> u8 {
self.protocol
}
pub fn speed(&self) -> Speed {
self.speed
}
pub fn manufacturer_string(&self) -> Option<&str> {
self.manufacturer_string.as_deref()
}
pub fn product_string(&self) -> Option<&str> {
self.product_string.as_deref()
}
pub fn serial_number(&self) -> Option<&str> {
self.serial_number.as_deref()
}
pub fn open(&self) -> Result<Device, Error> {
Device::open(self)
}
}
#[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)]
#[non_exhaustive]
pub enum Speed {
Low,
Full,
High,
Super,
SuperPlus,
}
#[derive(Copy, Clone, Debug)]
pub struct UnknownValue;
impl Display for UnknownValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown value")
}
}
impl std::error::Error for UnknownValue {}
impl FromStr for Speed {
type Err = UnknownValue; //TODO
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"low" | "1.5" => Ok(Speed::Low),
"full" | "12" => Ok(Speed::Full),
"high" | "480" => Ok(Speed::High),
"super" | "5000" => Ok(Speed::Super),
"super+" | "10000" => Ok(Speed::SuperPlus),
_ => Err(UnknownValue),
}
}
}

16
src/lib.rs Normal file
View file

@ -0,0 +1,16 @@
use std::{fmt::Display, io, str::FromStr};
pub mod platform;
use device::Device;
pub use platform::list_devices;
mod enumeration;
pub use enumeration::{DeviceInfo, Speed, UnknownValue};
mod device;
mod transfer;
pub use transfer::{Completion, Transfer, TransferStatus};
pub type Error = io::Error;

View file

@ -0,0 +1,158 @@
use std::{path::PathBuf, sync::Arc};
use log::{debug, error};
use rustix::{
fd::OwnedFd,
fs::{Mode, OFlags},
io::Errno,
};
use super::{events, usbfs};
use crate::{
platform::linux_usbfs::transfer::{Transfer, TransferInner},
DeviceInfo, Error,
};
pub(crate) struct LinuxDevice {
fd: OwnedFd,
events_id: usize,
}
impl LinuxDevice {
pub(crate) fn from_device_info(d: &DeviceInfo) -> Result<Arc<LinuxDevice>, Error> {
Self::open(d.bus_number(), d.device_address())
}
pub(crate) fn open(busnum: u8, devnum: u8) -> Result<Arc<LinuxDevice>, Error> {
let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}"));
debug!("Opening usbfs device {}", path.display());
let fd = rustix::fs::open(path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())?;
// because there's no Arc::try_new_cyclic
let mut events_err = None;
let arc = Arc::new_cyclic(|weak| {
let res = events::register(&fd, weak.clone());
let events_id = *res.as_ref().unwrap_or(&usize::MAX);
events_err = res.err();
LinuxDevice { fd, events_id }
});
if let Some(err) = events_err {
error!("Failed to initialize event loop: {err}");
Err(err)
} else {
debug!(
"Opened device bus={busnum} addr={devnum} with id {}",
arc.events_id
);
Ok(arc)
}
}
pub(crate) fn handle_events(&self) {
debug!("Handling events for device {}", self.events_id);
match usbfs::reap_urb_ndelay(&self.fd) {
Ok(urb_ptr) => {
{
let urb = unsafe { &*urb_ptr };
debug!(
"URB {:?} for ep {:x} completed, status={} actual_length={}",
urb_ptr, urb.endpoint, urb.status, urb.actual_length
);
}
unsafe { Transfer::notify_completion(urb_ptr as *mut TransferInner) }
}
Err(Errno::AGAIN) => {}
Err(Errno::NODEV) => {
debug!("Device {} disconnected", self.events_id);
// epoll returns events continuously on a disconnected device, and REAPURB
// only returns ENODEV after all events are received, so unregister to
// keep the event thread from spinning because we won't receive further events.
// The drop impl will try to unregister again, but that's ok.
events::unregister_fd(&self.fd);
}
Err(e) => {
error!("Unexpected error {e} from REAPURBNDELAY");
}
}
}
pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), Error> {
usbfs::set_configuration(&self.fd, configuration)?;
Ok(())
}
pub(crate) fn reset(&self) -> Result<(), Error> {
usbfs::reset(&self.fd)?;
Ok(())
}
pub(crate) fn claim_interface(
self: &Arc<Self>,
interface: u8,
) -> Result<Arc<LinuxInterface>, Error> {
usbfs::claim_interface(&self.fd, interface)?;
debug!(
"Claimed interface {interface} on device id {dev}",
dev = self.events_id
);
Ok(Arc::new(LinuxInterface {
device: self.clone(),
interface,
}))
}
}
impl Drop for LinuxDevice {
fn drop(&mut self) {
debug!("Closing device {}", self.events_id);
events::unregister(&self.fd, self.events_id)
}
}
pub(crate) struct LinuxInterface {
pub(crate) interface: u8,
pub(crate) device: Arc<LinuxDevice>,
}
impl LinuxInterface {
pub(crate) unsafe fn submit_transfer(&self, transfer: *mut TransferInner) {
let urb = transfer as *mut usbfs::Urb;
let ep = unsafe { (&mut *urb).endpoint };
if let Err(e) = usbfs::submit_urb(&self.device.fd, urb) {
// SAFETY: Transfer was not submitted. We still own the transfer
// and can write to the URB and complete it in place of the handler.
unsafe {
{
let u = &mut *urb;
debug!("Failed to submit URB {urb:?} on ep {ep:x}: {e} {u:?}");
u.actual_length = 0;
u.status = e.raw_os_error();
}
Transfer::notify_completion(transfer)
}
} else {
debug!("Submitted URB {urb:?} on ep {ep:x}");
}
}
pub(crate) unsafe fn cancel_transfer(&self, transfer: *mut TransferInner) {
unsafe {
let urb = transfer as *mut usbfs::Urb;
if let Err(e) = usbfs::discard_urb(&self.device.fd, urb) {
debug!("Failed to cancel URB {urb:?}: {e}");
}
}
}
}
impl Drop for LinuxInterface {
fn drop(&mut self) {
let res = usbfs::release_interface(&self.device.fd, self.interface);
debug!(
"Released interface {} on device {}: {res:?}",
self.interface, self.device.events_id
);
}
}

View file

@ -0,0 +1,87 @@
use std::fs;
use std::io;
use std::num::ParseIntError;
use std::path::PathBuf;
use std::str::FromStr;
use log::debug;
use crate::DeviceInfo;
use crate::Error;
#[derive(Debug, Clone)]
pub struct SysfsPath(PathBuf);
impl SysfsPath {
fn read_attr<T: FromStr>(&self, attr: &str) -> Result<T, io::Error>
where
T: FromStr,
T::Err: std::error::Error + Send + Sync + 'static,
{
let attr_path = self.0.join(attr);
let read_res = fs::read_to_string(&attr_path);
debug!("sysfs read {attr_path:?}: {read_res:?}");
read_res?
.trim()
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
fn read_attr_hex<T: FromHexStr>(&self, attr: &str) -> Result<T, io::Error> {
let s = self.read_attr::<String>(attr)?;
T::from_hex_str(&s)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid hex str"))
}
}
trait FromHexStr: Sized {
fn from_hex_str(s: &str) -> Result<Self, ParseIntError>;
}
impl FromHexStr for u8 {
fn from_hex_str(s: &str) -> Result<Self, ParseIntError> {
u8::from_str_radix(s, 16)
}
}
impl FromHexStr for u16 {
fn from_hex_str(s: &str) -> Result<Self, ParseIntError> {
u16::from_str_radix(s, 16)
}
}
const SYSFS_PREFIX: &'static str = "/sys/bus/usb/devices/";
pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
Ok(fs::read_dir(SYSFS_PREFIX)?.flat_map(|entry| {
let res = probe_device(SysfsPath(entry.ok()?.path()));
if let Err(x) = &res {
debug!("failed to probe, skipping: {x}")
}
res.ok()
}))
}
pub fn probe_device(path: SysfsPath) -> Result<DeviceInfo, Error> {
debug!("probe device {path:?}");
Ok(DeviceInfo {
bus_number: path.read_attr("busnum")?,
device_address: path.read_attr("devnum")?,
vendor_id: path.read_attr_hex("idVendor")?,
product_id: path.read_attr_hex("idProduct")?,
device_version: path.read_attr_hex("bcdDevice")?,
class: path.read_attr_hex("bDeviceClass")?,
subclass: path.read_attr_hex("bDeviceSubClass")?,
protocol: path.read_attr_hex("bDeviceProtocol")?,
speed: path.read_attr("speed")?,
manufacturer_string: path.read_attr("manufacturer").ok(),
product_string: path.read_attr("product").ok(),
serial_number: path.read_attr("serial").ok(),
path: path,
})
}
/// Returns the path of a device in usbfs
fn usb_devfs_path(busnum: u8, devnum: u8) -> PathBuf {
PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}"))
}

View file

@ -0,0 +1,66 @@
use once_cell::sync::OnceCell;
use rustix::{
event::epoll::{self, EventData},
fd::OwnedFd,
};
use slab::Slab;
use std::{
sync::{Mutex, Weak},
thread,
};
use crate::Error;
use super::Device;
static EPOLL_FD: OnceCell<OwnedFd> = OnceCell::new();
static DEVICES: Mutex<Slab<Weak<Device>>> = Mutex::new(Slab::new());
pub(super) fn register(usb_fd: &OwnedFd, weak_device: Weak<Device>) -> Result<usize, Error> {
let mut start_thread = false;
let epoll_fd = EPOLL_FD.get_or_try_init(|| {
start_thread = true;
epoll::create(epoll::CreateFlags::CLOEXEC)
})?;
let id = {
let mut devices = DEVICES.lock().unwrap();
devices.insert(weak_device)
};
if start_thread {
thread::spawn(event_loop);
}
let data = EventData::new_u64(id as u64);
epoll::add(epoll_fd, usb_fd, data, epoll::EventFlags::OUT)?;
Ok(id)
}
pub(super) fn unregister_fd(fd: &OwnedFd) {
let epoll_fd = EPOLL_FD.get().unwrap();
epoll::delete(epoll_fd, fd).ok();
}
pub(super) fn unregister(fd: &OwnedFd, events_id: usize) {
let epoll_fd = EPOLL_FD.get().unwrap();
epoll::delete(epoll_fd, fd).ok();
DEVICES.lock().unwrap().remove(events_id);
}
fn event_loop() {
let epoll_fd = EPOLL_FD.get().unwrap();
let mut event_list = epoll::EventVec::with_capacity(4);
loop {
epoll::wait(epoll_fd, &mut event_list, -1).unwrap();
let devices = DEVICES.lock().unwrap();
for event in &event_list {
let key = event.data.u64() as usize;
let device = devices.get(key).and_then(|w| w.upgrade());
if let Some(device) = device {
device.handle_events();
}
}
}
}

View file

@ -0,0 +1,13 @@
use std::path::PathBuf;
mod transfer;
mod usbfs;
pub use transfer::Transfer;
mod enumeration;
mod events;
pub use enumeration::{list_devices, SysfsPath};
mod device;
pub(crate) use device::LinuxDevice as Device;
pub(crate) use device::LinuxInterface as Interface;

View file

@ -0,0 +1,242 @@
use std::{
cell::UnsafeCell,
future::Future,
mem::{self, ManuallyDrop},
ptr::{null_mut, NonNull},
sync::{
atomic::{AtomicU8, Ordering},
Arc,
},
task::{Context, Poll},
};
use atomic_waker::AtomicWaker;
use rustix::io::Errno;
use crate::{transfer::EndpointType, Completion, TransferStatus};
use super::{
usbfs::{
Urb, USBDEVFS_URB_TYPE_BULK, USBDEVFS_URB_TYPE_CONTROL, USBDEVFS_URB_TYPE_INTERRUPT,
USBDEVFS_URB_TYPE_ISO,
},
Interface,
};
#[repr(C)]
pub(crate) struct TransferInner {
urb: UnsafeCell<Urb>,
state: AtomicU8,
waker: AtomicWaker,
interface: Arc<Interface>,
}
impl TransferInner {
/// Transfer ownership of `buf` into the transfer's `urb`.
/// SAFETY: requires that there is no concurrent access to `urb`
unsafe fn put_buffer(&self, buf: Vec<u8>) {
unsafe {
let mut buf = ManuallyDrop::new(buf);
let urb = &mut *self.urb.get();
urb.buffer = buf.as_mut_ptr();
assert!(buf.len() < i32::MAX as usize, "Buffer too large");
urb.actual_length = buf.len() as i32;
assert!(buf.capacity() < i32::MAX as usize, "Buffer too large");
urb.buffer_length = buf.capacity() as i32;
}
}
/// Transfer ownership of transfer's `urb` buffer back to a `Vec`.
/// SAFETY: requires that the the buffer is present and there is no concurrent
/// access to `urb`. Invalidates the buffer.
unsafe fn take_buffer(&self) -> Vec<u8> {
unsafe {
let urb = &mut *self.urb.get();
Vec::from_raw_parts(
mem::replace(&mut urb.buffer, null_mut()),
urb.actual_length as usize,
urb.buffer_length as usize,
)
}
}
/// Get the transfer status
/// SAFETY: requires that there is no concurrent access to `urb`
unsafe fn status(&self) -> TransferStatus {
let status = unsafe { (&*self.urb.get()).status };
if status == 0 {
return TransferStatus::Complete;
}
// It's sometimes positive, sometimes negative, but rustix panics if negative.
match Errno::from_raw_os_error(status.abs()) {
Errno::NODEV | Errno::SHUTDOWN => TransferStatus::Disconnected,
Errno::PIPE => TransferStatus::Stall,
Errno::NOENT | Errno::CONNRESET => TransferStatus::Cancelled,
Errno::PROTO | Errno::ILSEQ | Errno::OVERFLOW | Errno::COMM | Errno::TIME => {
TransferStatus::Fault
}
_ => TransferStatus::UnknownError,
}
}
}
pub struct Transfer {
ptr: NonNull<TransferInner>,
}
/// The transfer has not been submitted. The buffer is not valid.
const STATE_IDLE: u8 = 0;
/// The transfer has been submitted to the kernel and completion has not yet
/// been handled. The buffer points to valid memory but cannot be accessed by
/// userspace. There is a future or queue waiting for it completion.
const STATE_PENDING: u8 = 1;
/// Like PENDING, but there is no one waiting for completion. The completion
/// handler will drop the buffer and transfer.
const STATE_ABANDONED: u8 = 3;
/// The transfer completion has been handled. The buffer is valid and may
/// be accessed.
const STATE_COMPLETED: u8 = 3;
impl Transfer {
pub(crate) fn new(interface: Arc<Interface>, endpoint: u8, ep_type: EndpointType) -> Transfer {
let ep_type = match ep_type {
EndpointType::Control => USBDEVFS_URB_TYPE_CONTROL,
EndpointType::Interrupt => USBDEVFS_URB_TYPE_INTERRUPT,
EndpointType::Bulk => USBDEVFS_URB_TYPE_BULK,
EndpointType::Isochronous => USBDEVFS_URB_TYPE_ISO,
};
let b = Box::new(TransferInner {
urb: UnsafeCell::new(Urb {
ep_type,
endpoint,
status: 0,
flags: 0,
buffer: null_mut(),
buffer_length: 0,
actual_length: 0,
start_frame: 0,
number_of_packets_or_stream_id: 0,
error_count: 0,
signr: 0,
usercontext: null_mut(),
}),
state: AtomicU8::new(STATE_IDLE),
waker: AtomicWaker::new(),
interface,
});
Transfer {
ptr: Box::leak(b).into(),
}
}
fn inner(&self) -> &TransferInner {
// Safety: while Transfer is alive, its TransferInner is alive
unsafe { self.ptr.as_ref() }
}
/// Prepare the transfer for submission by filling the buffer fields
/// and setting the state to PENDING. Returns a `*mut TransferInner`
/// that must later be passed to `complete`.
///
/// Panics if the transfer has already been submitted.
pub(crate) fn submit(&mut self, data: Vec<u8>) {
let inner = self.inner();
assert_eq!(
inner.state.load(Ordering::Acquire),
STATE_IDLE,
"Transfer should be idle when submitted"
);
unsafe {
// SAFETY: invariants guaranteed by being in state IDLE
inner.put_buffer(data);
}
inner.state.store(STATE_PENDING, Ordering::Release);
unsafe {
inner.interface.submit_transfer(self.ptr.as_ptr());
}
}
pub(crate) fn cancel(&mut self) {
let inner = self.inner();
unsafe {
inner.interface.cancel_transfer(self.ptr.as_ptr());
}
}
pub fn poll_completion(&self, cx: &Context) -> Poll<Completion> {
let inner = self.inner();
inner.waker.register(cx.waker());
match inner.state.load(Ordering::Acquire) {
STATE_PENDING => Poll::Pending,
STATE_COMPLETED => {
// SAFETY: state means we have exclusive access
// and the buffer is valid.
inner.state.store(STATE_IDLE, Ordering::Relaxed);
unsafe {
let data = inner.take_buffer();
let status = inner.status();
Poll::Ready(Completion { data, status })
}
}
s => panic!("Polling transfer in unexpected state {s}"),
}
}
pub(crate) unsafe fn notify_completion(transfer: *mut TransferInner) {
unsafe {
let waker = (*transfer).waker.take();
match (*transfer).state.swap(STATE_COMPLETED, Ordering::Release) {
STATE_PENDING => {
if let Some(waker) = waker {
waker.wake()
}
}
STATE_ABANDONED => {
let b = Box::from_raw(transfer);
drop(b.take_buffer());
drop(b);
}
s => panic!("Completing transfer in unexpected state {s}"),
}
}
}
}
impl Drop for Transfer {
fn drop(&mut self) {
match self.inner().state.swap(STATE_ABANDONED, Ordering::Acquire) {
STATE_PENDING => {
self.cancel();
/* handler responsible for dropping */
}
STATE_IDLE => {
// SAFETY: state means there is no concurrent access
unsafe { drop(Box::from_raw(self.ptr.as_ptr())) }
}
STATE_COMPLETED => {
// SAFETY: state means buffer is valid and there is no concurrent access
unsafe {
let b = Box::from_raw(self.ptr.as_ptr());
drop(b.take_buffer());
drop(b);
}
}
s => panic!("Dropping transfer in unexpected state {s}"),
}
}
}
impl Future for Transfer {
type Output = Completion;
fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.as_mut().poll_completion(cx)
}
}

View file

@ -0,0 +1,133 @@
use std::{
ffi::{c_int, c_uchar, c_uint, c_void},
marker::PhantomData,
};
/// Wrappers for the [usbfs] character device ioctls, translated from the
/// [C structures and ioctl definitions][uapi].
///
/// [usbfs]: https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#the-usb-character-device-nodes
/// [uapi]: https://github.com/torvalds/linux/blob/master/tools/include/uapi/linux/usbdevice_fs.h
use rustix::{
fd::AsFd,
io,
ioctl::{self, CompileTimeOpcode, Ioctl, IoctlOutput},
};
pub fn set_configuration<Fd: AsFd>(fd: Fd, configuration: u8) -> io::Result<()> {
unsafe {
let ctl =
ioctl::Setter::<ioctl::ReadOpcode<b'U', 5, c_uint>, c_uint>::new(configuration.into());
ioctl::ioctl(fd, ctl)
}
}
pub fn claim_interface<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result<()> {
unsafe {
let ctl =
ioctl::Setter::<ioctl::ReadOpcode<b'U', 15, c_uint>, c_uint>::new(interface.into());
ioctl::ioctl(fd, ctl)
}
}
pub fn release_interface<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result<()> {
unsafe {
let ctl =
ioctl::Setter::<ioctl::ReadOpcode<b'U', 16, c_uint>, c_uint>::new(interface.into());
ioctl::ioctl(fd, ctl)
}
}
pub struct PassPtr<Opcode, Input> {
input: *mut Input,
_opcode: PhantomData<Opcode>,
}
impl<Opcode: CompileTimeOpcode, Input> PassPtr<Opcode, Input> {
/// Create a new pointer setter-style `ioctl` object.
///
/// # Safety
///
/// - `Opcode` must provide a valid opcode.
/// - For this opcode, `Input` must be the type that the kernel expects to
/// get.
#[inline]
pub unsafe fn new(input: *mut Input) -> Self {
Self {
input,
_opcode: PhantomData,
}
}
}
unsafe impl<Opcode: CompileTimeOpcode, Input> Ioctl for PassPtr<Opcode, Input> {
type Output = ();
const IS_MUTATING: bool = false;
const OPCODE: rustix::ioctl::Opcode = Opcode::OPCODE;
fn as_ptr(&mut self) -> *mut c_void {
self.input as *mut c_void
}
unsafe fn output_from_ptr(_: IoctlOutput, _: *mut c_void) -> rustix::io::Result<Self::Output> {
Ok(())
}
}
pub unsafe fn submit_urb<Fd: AsFd>(fd: Fd, urb: *mut Urb) -> io::Result<()> {
unsafe {
let ctl = PassPtr::<ioctl::ReadOpcode<b'U', 10, Urb>, Urb>::new(urb);
ioctl::ioctl(fd, ctl)
}
}
pub fn reap_urb_ndelay<Fd: AsFd>(fd: Fd) -> io::Result<*mut Urb> {
unsafe {
let ctl = ioctl::Getter::<ioctl::WriteOpcode<b'U', 13, *mut Urb>, *mut Urb>::new();
ioctl::ioctl(fd, ctl)
}
}
pub unsafe fn discard_urb<Fd: AsFd>(fd: Fd, urb: *mut Urb) -> io::Result<()> {
unsafe {
let ctl = PassPtr::<ioctl::NoneOpcode<b'U', 11, ()>, Urb>::new(urb);
ioctl::ioctl(fd, ctl)
}
}
pub fn reset<Fd: AsFd>(fd: Fd) -> io::Result<()> {
unsafe {
let ctl = ioctl::NoArg::<ioctl::NoneOpcode<b'U', 20, ()>>::new();
ioctl::ioctl(fd, ctl)
}
}
const USBDEVFS_URB_SHORT_NOT_OK: c_uint = 0x01;
const USBDEVFS_URB_ISO_ASAP: c_uint = 0x02;
const USBDEVFS_URB_BULK_CONTINUATION: c_uint = 0x04;
const USBDEVFS_URB_ZERO_PACKET: c_uint = 0x40;
const USBDEVFS_URB_NO_INTERRUPT: c_uint = 0x80;
pub const USBDEVFS_URB_TYPE_ISO: c_uchar = 0;
pub const USBDEVFS_URB_TYPE_INTERRUPT: c_uchar = 1;
pub const USBDEVFS_URB_TYPE_CONTROL: c_uchar = 2;
pub const USBDEVFS_URB_TYPE_BULK: c_uchar = 3;
#[repr(C)]
#[derive(Debug)]
pub struct Urb {
pub ep_type: c_uchar,
pub endpoint: c_uchar,
pub status: c_int,
pub flags: c_uint,
pub buffer: *mut u8,
pub buffer_length: c_int,
pub actual_length: c_int,
pub start_frame: c_int,
pub number_of_packets_or_stream_id: c_uint, // a union in C
pub error_count: c_int,
pub signr: c_uint,
pub usercontext: *mut c_void,
// + variable size array of iso_packet_desc
}

5
src/platform/mod.rs Normal file
View file

@ -0,0 +1,5 @@
#[cfg(target_os = "linux")]
mod linux_usbfs;
#[cfg(target_os = "linux")]
pub use linux_usbfs::*;

25
src/transfer.rs Normal file
View file

@ -0,0 +1,25 @@
pub use crate::platform::Transfer;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum EndpointType {
Control,
Interrupt,
Bulk,
Isochronous,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TransferStatus {
Complete,
Cancelled,
Stall,
Disconnected,
Fault,
UnknownError,
}
#[derive(Debug, Clone)]
pub struct Completion {
pub data: Vec<u8>,
pub status: TransferStatus,
}