Skip to content

Commit

Permalink
USB: delay opening USB device until enumeration in one of the USB loo…
Browse files Browse the repository at this point in the history
…kup functions

Refactor USB code not to open USB devices as soon as they
register, but when needed for one of the lookup functions
(usb_list_devices, usb_device_for_name, usb_device_for_address).
This way, when a device open fails, it can be retried at
a later time, when USB enumeration happens again.

This is specifically useful in Linux when new devices are
owned by `root:root` and need a chmod to be usable by a
non-root user.
  • Loading branch information
arteme committed Oct 27, 2024
1 parent 303fdbd commit 37ace4d
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 56 deletions.
127 changes: 88 additions & 39 deletions usb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use anyhow::Context as _;
use core::result::Result::Ok;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, OnceLock};
use std::sync::atomic::{AtomicBool, Ordering};
use once_cell::sync::Lazy;

Expand Down Expand Up @@ -87,14 +87,21 @@ impl<T: UsbContext> Hotplug<T> for HotplugHandler {
}
}

enum UsbFoundDevice {
Found(DeviceAddedEvent),
Open(Device)
}

static mut INIT_DONE: AtomicBool = AtomicBool::new(false);
static INIT_DONE_NOTIFY: Lazy<Arc<Notify>> = Lazy::new(|| {
Arc::new(Notify::new())
});
static DEVICES: Lazy<Arc<Mutex<HashMap<String, Device>>>> = Lazy::new(|| {
static DEVICES: Lazy<Arc<Mutex<HashMap<String, UsbFoundDevice>>>> = Lazy::new(|| {
Arc::new(Mutex::new(HashMap::new()))
});

static USB: OnceLock<Arc<Mutex<Usb>>> = OnceLock::new();

pub fn usb_start() -> Result<()> {
if !rusb::has_hotplug() {
bail!("Libusb hotplug API not supported");
Expand All @@ -111,8 +118,7 @@ pub fn usb_start() -> Result<()> {
hh.device_init_notify(0);

let usb = Usb::new(Box::new(hh))?;

let devices = DEVICES.clone();
USB.set(Arc::new(Mutex::new(usb))).map_err(|_| anyhow!("Failed to set global USB var"))?;

tokio::spawn(async move {
info!("USB event RX thread start");
Expand All @@ -121,7 +127,7 @@ pub fn usb_start() -> Result<()> {
Ok(msg) => { msg }
Err(RecvError::Closed) => {
info!("Event bus closed");
return;
break;
}
Err(RecvError::Lagged(n)) => {
error!("Event bus lagged: {}", n);
Expand All @@ -130,24 +136,9 @@ pub fn usb_start() -> Result<()> {
};

match msg {
UsbEvent::DeviceAdded(DeviceAddedEvent{ vid, pid, bus, address }) => {
let usb_dev = find_device(vid, pid).unwrap();
let h = match usb.open(vid, pid) {
Ok(h) => { h }
Err(e) => {
error!("Failed to open device {:04x}{:04x}: {}", vid, pid, e);
continue
}
};
let handler = match Device::new(h, usb_dev) {
Ok(h) => { h }
Err(e) => {
error!("Filed to initialize device {:?}: {}", usb_dev.name, e);
continue
}
};
UsbEvent::DeviceAdded(e @ DeviceAddedEvent{ bus, address, .. }) => {
let address = usb_address_string(bus, address);
usb_add_device(address, handler);
usb_add_device(address, e);
}
UsbEvent::DeviceRemoved(DeviceRemovedEvent{ bus, address, .. }) => {
let address = usb_address_string(bus, address);
Expand Down Expand Up @@ -186,14 +177,61 @@ pub async fn usb_init_wait() {
debug!("Waiting for USB init over");
}

fn usb_enumerate_devices(devices: &mut HashMap<String, UsbFoundDevice>) -> Vec<&mut Device> {
let Some(usb) = USB.get() else {
error!("Cannot enumerate USB: usb not ready!");
return vec![];
};
let usb = usb.lock().unwrap();

info!("Enumerating USB devices...");
let mut update = HashMap::new();
for (key, value) in devices.iter() {
match value {
&UsbFoundDevice::Found(DeviceAddedEvent { vid, pid, bus, address }) => {
let usb_dev = find_device(vid, pid).unwrap();
let h = match usb.open(vid, pid, bus, address) {
Ok(h) => { h }
Err(e) => {
error!("Failed to open device: {}", e);
continue
}
};
let handler = match Device::new(h, usb_dev) {
Ok(h) => { h }
Err(e) => {
error!("Filed to initialize device {:?}: {}", usb_dev.name, e);
continue
}
};

update.insert(key.clone(), UsbFoundDevice::Open(handler));
}
UsbFoundDevice::Open(_) => {}
}
}
let updated = update.len();
for (key, value) in update {
devices.insert(key, value);
}
info!("Enumerating USB devices finished: {updated} entries updated");

devices.values_mut().flat_map(|v| match v {
UsbFoundDevice::Found(_) => { None }
UsbFoundDevice::Open(dev) => { Some(dev) }
})
.collect()
}


pub fn usb_list_devices() -> Vec<String> {
let devices = DEVICES.lock().unwrap();
devices.values().map(|i| i.name.clone()).collect()
let mut devices = DEVICES.lock().unwrap();
usb_enumerate_devices(&mut devices).iter().map(|i| i.name.clone()).collect()
}

fn usb_add_device(key: String, device: Device) {
fn usb_add_device(key: String, event: DeviceAddedEvent) {
let mut devices = DEVICES.lock().unwrap();
devices.insert(key, device);
devices.insert(key, UsbFoundDevice::Found(event));
}

fn usb_remove_device(key: String) {
Expand All @@ -203,11 +241,12 @@ fn usb_remove_device(key: String) {

pub fn usb_device_for_address(dev_addr: &str) -> Result<(impl MidiIn, impl MidiOut)> {
let mut devices = DEVICES.lock().unwrap();
let _ = usb_enumerate_devices(&mut devices);

let port_n_re = Regex::new(r"\d+").unwrap();
let port_id_re = Regex::new(r"\d+:\d+").unwrap();

let mut found = None;
let mut found;
if port_id_re.is_match(dev_addr) {
found = devices.get_mut(dev_addr);
} else if port_n_re.is_match(dev_addr) {
Expand All @@ -218,22 +257,32 @@ pub fn usb_device_for_address(dev_addr: &str) -> Result<(impl MidiIn, impl MidiO
bail!("Unrecognized USB device address {:?}", dev_addr);
}

let Some(dev) = found.take() else {
bail!("USB device for address {:?} not found!", dev_addr);
};

dev.open()
match found {
Some(UsbFoundDevice::Open(dev)) => { dev.open() }
Some(_) => {
bail!("USB device for address {:?} found, but couldn't be opened", dev_addr);
}
None => {
bail!("USB device for address {:?} not found!", dev_addr);
}
}
}

pub fn usb_device_for_name(dev_name: &str) -> Result<(impl MidiIn, impl MidiOut)> {
let mut devices = DEVICES.lock().unwrap();
let _ = usb_enumerate_devices(&mut devices);

let mut found = devices.values_mut().find(|dev| {
dev.name == dev_name
});
let Some(dev) = found.take() else {
bail!("USB device for name {:?} not found!", dev_name);
};
let found = devices.values_mut().find(|dev|
match dev {
UsbFoundDevice::Open(dev) if dev.name == dev_name => { true }
_ => { false }
}
);

dev.open()
match found {
Some(UsbFoundDevice::Open(dev)) => { dev.open() }
_ => {
bail!("USB device for name {:?} not found!", dev_name);
}
}
}
32 changes: 15 additions & 17 deletions usb/src/usb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ use std::mem::align_of;
use std::ptr::{NonNull, null_mut};
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
use std::{ptr, thread};
use std::thread;
use std::time::Duration;
use log::{debug, error, info};
use rusb::{Context, Hotplug, HotplugBuilder, Registration, UsbContext};
use rusb::constants::{LIBUSB_ENDPOINT_DIR_MASK, LIBUSB_ENDPOINT_IN, LIBUSB_ENDPOINT_OUT, LIBUSB_ERROR_INTERRUPTED, LIBUSB_TRANSFER_CANCELLED, LIBUSB_TRANSFER_TYPE_BULK};
use rusb::ffi::{libusb_alloc_transfer, libusb_cancel_transfer, libusb_free_transfer, libusb_submit_transfer, libusb_transfer};
use crate::check;
use crate::util::usb_address_string;

pub type Device = rusb::Device<Context>;
pub type DeviceHandle = rusb::DeviceHandle<Context>;
Expand Down Expand Up @@ -44,21 +45,18 @@ impl Usb {
self.thread.take().map(thread::JoinHandle::join);
}

/// Convenience function for opening the first device with the matching
/// Vendor/Product ID.
pub fn open(&self, vid: u16, pid: u16) -> Result<DeviceHandle> {
info!("Opening {:04X}:{:04X}", vid, pid);
// For now skip the full reset...
/*
let hdl = libusb::reset(
self.ctx.open_device_with_vid_pid(vid, pid)
.ok_or(rusb::Error::NotFound)?,
)?;
*/
let hdl = self.ctx.open_device_with_vid_pid(vid, pid)
.ok_or(rusb::Error::NotFound)?;

Ok(hdl)
pub fn open(&self, vid: u16, pid: u16, bus: u8, address: u8) -> Result<DeviceHandle> {
let addr_str = usb_address_string(bus, address);
info!("Opening {:04X}:{:04X} at {}", vid, pid, addr_str);

for dev in self.ctx.devices()?.iter() {
if dev.bus_number() != bus || dev.address() != address { continue }
return dev.open().map_err(|e| {
anyhow!("Failed to open USB device {:04X}:{:04X} at {}: {}", vid, pid, addr_str, e)
});
}

bail!("USB device not found!");
}

/// Dedicated thread for async transfer and hotplug events.
Expand Down Expand Up @@ -168,7 +166,7 @@ impl Transfer
status, callback: None
};

let mut inner = transfer.inner.as_mut();
let inner = transfer.inner.as_mut();
inner.endpoint = endpoint;
inner.transfer_type = transfer_type;
inner.callback = Self::callback;
Expand Down

0 comments on commit 37ace4d

Please sign in to comment.