diff --git a/usb/src/lib.rs b/usb/src/lib.rs index 565e05b..73737b8 100644 --- a/usb/src/lib.rs +++ b/usb/src/lib.rs @@ -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; @@ -87,14 +87,21 @@ impl Hotplug for HotplugHandler { } } +enum UsbFoundDevice { + Found(DeviceAddedEvent), + Open(Device) +} + static mut INIT_DONE: AtomicBool = AtomicBool::new(false); static INIT_DONE_NOTIFY: Lazy> = Lazy::new(|| { Arc::new(Notify::new()) }); -static DEVICES: Lazy>>> = Lazy::new(|| { +static DEVICES: Lazy>>> = Lazy::new(|| { Arc::new(Mutex::new(HashMap::new())) }); +static USB: OnceLock>> = OnceLock::new(); + pub fn usb_start() -> Result<()> { if !rusb::has_hotplug() { bail!("Libusb hotplug API not supported"); @@ -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"); @@ -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); @@ -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); @@ -186,14 +177,61 @@ pub async fn usb_init_wait() { debug!("Waiting for USB init over"); } +fn usb_enumerate_devices(devices: &mut HashMap) -> 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 { - 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) { @@ -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) { @@ -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); + } + } } diff --git a/usb/src/usb.rs b/usb/src/usb.rs index 2e6b8e3..9a023a1 100644 --- a/usb/src/usb.rs +++ b/usb/src/usb.rs @@ -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; pub type DeviceHandle = rusb::DeviceHandle; @@ -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 { - 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 { + 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. @@ -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;