From 1912dd8eb78ec51cb4876b03059f424e0fce0147 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 15 May 2024 03:41:08 +0100 Subject: [PATCH] Update to new analyzer USB API. --- src/backend/cynthion.rs | 110 +++++++++++++++++++++++++++++----------- src/ui.rs | 4 +- 2 files changed, 81 insertions(+), 33 deletions(-) diff --git a/src/backend/cynthion.rs b/src/backend/cynthion.rs index 1e14a004..4840936b 100644 --- a/src/backend/cynthion.rs +++ b/src/backend/cynthion.rs @@ -24,8 +24,9 @@ use nusb::{ const VID: u16 = 0x1d50; const PID: u16 = 0x615b; -const MIN_SUPPORTED: u16 = 0x0002; -const NOT_SUPPORTED: u16 = 0x0003; +const CLASS: u8 = 0xff; +const SUBCLASS: u8 = 0x10; +const PROTOCOL: u8 = 0x01; const ENDPOINT: u8 = 0x81; @@ -80,10 +81,15 @@ impl State { } } +pub struct InterfaceSelection { + interface_number: u8, + alt_setting_number: u8, +} + /// Whether a Cynthion device is ready for use as an analyzer. pub enum CynthionUsability { - /// Device is usable at supported speeds. - Usable(Vec), + /// Device is usable via the given interface, at supported speeds. + Usable(InterfaceSelection, Vec), /// Device not usable, with a string explaining why. Unusable(String), } @@ -111,33 +117,72 @@ pub struct CynthionStop { worker: JoinHandle::<()>, } -/// Check whether a Cynthion device is usable as an analyzer. -fn check_device(device_info: &DeviceInfo) -> Result, Error> +/// Check whether a Cynthion device has an accessible analyzer interface. +fn check_device(device_info: &DeviceInfo) + -> Result<(InterfaceSelection, Vec), Error> { - // Check version is correct. - let version = device_info.device_version(); - if !(MIN_SUPPORTED..=NOT_SUPPORTED).contains(&version) { - bail!("Device version not supported"); - } - // Check we can open the device. let device = device_info .open() .context("Failed to open device")?; - // Try to claim the interface. - let interface = device - .claim_interface(0) - .context("Failed to claim interface")?; + // Read the active configuration. + let config = device + .active_configuration() + .context("Failed to retrieve active configuration")?; + + // Iterate over the interfaces... + for interface in config.interfaces() { + let interface_number = interface.interface_number(); + + // ...and alternate settings... + for alt_setting in interface.alt_settings() { + let alt_setting_number = alt_setting.alternate_setting(); + + // Ignore if this is not our supported target. + if alt_setting.class() != CLASS || + alt_setting.subclass() != SUBCLASS + { + continue; + } + + // Check protocol version. + let protocol = alt_setting.protocol(); + if protocol != PROTOCOL { + bail!("Wrong protocol version: {} supported, {} found", + PROTOCOL, protocol); + } + + // Try to claim the interface. + let interface = device + .claim_interface(interface_number) + .context("Failed to claim interface")?; + + // Select the required alternate, if not the default. + if alt_setting_number != 0 { + interface + .set_alt_setting(alt_setting_number) + .context("Failed to select alternate setting")?; + } - // Fetch the available speeds. - let handle = CynthionHandle { interface }; - let speeds = handle - .speeds() - .context("Failed to fetch available speeds")?; + // Fetch the available speeds. + let handle = CynthionHandle { interface }; + let speeds = handle + .speeds() + .context("Failed to fetch available speeds")?; + + // Now we have a usable device. + return Ok(( + InterfaceSelection { + interface_number, + alt_setting_number, + }, + speeds + )) + } + } - // Now we have a usable device. - return Ok(speeds); + bail!("No supported analyzer interface found"); } impl CynthionDevice { @@ -147,9 +192,9 @@ impl CynthionDevice { .filter(|info| info.product_id() == PID) .map(|device_info| match check_device(&device_info) { - Ok(speeds) => CynthionDevice { + Ok((iface, speeds)) => CynthionDevice { device_info, - usability: Usable(speeds) + usability: Usable(iface, speeds) }, Err(err) => CynthionDevice { device_info, @@ -162,9 +207,12 @@ impl CynthionDevice { pub fn open(&self) -> Result { match &self.usability { - Usable(..) => { + Usable(iface, _) => { let device = self.device_info.open()?; - let interface = device.claim_interface(0)?; + let interface = device.claim_interface(iface.interface_number)?; + if iface.alt_setting_number != 0 { + interface.set_alt_setting(iface.alt_setting_number)?; + } Ok(CynthionHandle { interface }) }, Unusable(reason) => bail!("Device not usable: {}", reason), @@ -178,10 +226,10 @@ impl CynthionHandle { use Speed::*; let control = Control { control_type: ControlType::Vendor, - recipient: Recipient::Device, + recipient: Recipient::Interface, request: 2, value: 0, - index: 0, + index: self.interface.interface_number() as u16, }; let mut buf = [0; 64]; let timeout = Duration::from_secs(1); @@ -285,10 +333,10 @@ impl CynthionHandle { fn write_state(&mut self, state: State) -> Result<(), Error> { let control = Control { control_type: ControlType::Vendor, - recipient: Recipient::Device, + recipient: Recipient::Interface, request: 1, value: u16::from(state.0), - index: 0, + index: self.interface.interface_number() as u16, }; let data = &[]; let timeout = Duration::from_secs(1); diff --git a/src/ui.rs b/src/ui.rs index 3f204c87..e27f5fbf 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -189,7 +189,7 @@ impl DeviceSelector { } } ); - if let Usable(speeds) = &device.usability { + if let Usable(_, speeds) = &device.usability { self.dev_speeds.push( speeds.iter().map(Speed::description).collect() ) @@ -220,7 +220,7 @@ impl DeviceSelector { let device_id = self.dev_dropdown.selected(); let device = &self.devices[device_id as usize]; match &device.usability { - Usable(speeds) => { + Usable(_, speeds) => { let speed_id = self.speed_dropdown.selected() as usize; let speed = speeds[speed_id]; let cynthion = device.open()?;