vlfd_rs/
usb.rs

1use crate::error::{Error, Result};
2use rusb::{
3    self, Context, Device, DeviceHandle, Hotplug, HotplugBuilder, Registration, UsbContext,
4};
5use std::{
6    sync::{
7        Arc,
8        atomic::{AtomicBool, Ordering},
9    },
10    thread,
11    time::Duration,
12};
13
14const INTERFACE: u8 = 0;
15const DEFAULT_TIMEOUT: Duration = Duration::from_millis(1_000);
16
17#[derive(Debug, Clone, Copy)]
18pub enum Endpoint {
19    FifoWrite = 0x02,
20    Command = 0x04,
21    FifoRead = 0x86,
22    Sync = 0x88,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum HotplugEventKind {
27    Arrived,
28    Left,
29}
30
31#[derive(Debug, Clone)]
32pub struct HotplugDeviceInfo {
33    pub bus_number: u8,
34    pub address: u8,
35    pub port_numbers: Vec<u8>,
36    pub vendor_id: Option<u16>,
37    pub product_id: Option<u16>,
38    pub class_code: Option<u8>,
39    pub sub_class_code: Option<u8>,
40    pub protocol_code: Option<u8>,
41}
42
43impl HotplugDeviceInfo {
44    fn from_device(device: &Device<Context>) -> Self {
45        let descriptor = device.device_descriptor().ok();
46        Self {
47            bus_number: device.bus_number(),
48            address: device.address(),
49            port_numbers: device.port_numbers().unwrap_or_default(),
50            vendor_id: descriptor.as_ref().map(|desc| desc.vendor_id()),
51            product_id: descriptor.as_ref().map(|desc| desc.product_id()),
52            class_code: descriptor.as_ref().map(|desc| desc.class_code()),
53            sub_class_code: descriptor.as_ref().map(|desc| desc.sub_class_code()),
54            protocol_code: descriptor.as_ref().map(|desc| desc.protocol_code()),
55        }
56    }
57}
58
59#[derive(Debug, Clone)]
60pub struct HotplugEvent {
61    pub kind: HotplugEventKind,
62    pub device: HotplugDeviceInfo,
63}
64
65#[derive(Debug, Clone, Copy, Default)]
66pub struct HotplugOptions {
67    pub vendor_id: Option<u16>,
68    pub product_id: Option<u16>,
69    pub class_code: Option<u8>,
70    pub enumerate: bool,
71}
72
73/// Thin wrapper around a `rusb` device handle that offers higher level helpers
74/// for bulk transfers and automatic cleanup.
75pub struct UsbDevice {
76    context: Context,
77    handle: Option<DeviceHandle<Context>>,
78}
79
80impl UsbDevice {
81    pub fn new() -> Result<Self> {
82        let context = Context::new().map_err(|err| usb_error(err, "libusb_init"))?;
83        Ok(Self {
84            context,
85            handle: None,
86        })
87    }
88
89    pub fn is_open(&self) -> bool {
90        self.handle.is_some()
91    }
92
93    pub fn open(&mut self, vid: u16, pid: u16) -> Result<()> {
94        if self.is_open() {
95            return Ok(());
96        }
97
98        let handle = self
99            .context
100            .open_device_with_vid_pid(vid, pid)
101            .ok_or(Error::DeviceNotFound { vid, pid })?;
102
103        handle
104            .reset()
105            .map_err(|err| usb_error(err, "libusb_reset_device"))?;
106        handle
107            .claim_interface(INTERFACE)
108            .map_err(|err| usb_error(err, "libusb_claim_interface"))?;
109
110        for endpoint in [
111            Endpoint::FifoWrite,
112            Endpoint::Command,
113            Endpoint::FifoRead,
114            Endpoint::Sync,
115        ] {
116            handle
117                .clear_halt(endpoint as u8)
118                .map_err(|err| usb_error(err, "libusb_clear_halt"))?;
119        }
120
121        self.handle = Some(handle);
122        Ok(())
123    }
124
125    pub fn close(&mut self) -> Result<()> {
126        if let Some(handle) = self.handle.take() {
127            match handle.release_interface(INTERFACE) {
128                Ok(_) | Err(rusb::Error::NoDevice) => {}
129                Err(err) => return Err(usb_error(err, "libusb_release_interface")),
130            }
131        }
132        Ok(())
133    }
134
135    pub fn read_bytes(&self, endpoint: Endpoint, buffer: &mut [u8]) -> Result<()> {
136        let handle = self.handle.as_ref().ok_or(Error::DeviceNotOpen)?;
137        bulk_read(handle, endpoint, buffer)
138    }
139
140    pub fn read_words(&self, endpoint: Endpoint, buffer: &mut [u16]) -> Result<()> {
141        let raw = words_as_bytes_mut(buffer);
142        self.read_bytes(endpoint, raw)
143    }
144
145    pub fn write_bytes(&self, endpoint: Endpoint, buffer: &[u8]) -> Result<()> {
146        let handle = self.handle.as_ref().ok_or(Error::DeviceNotOpen)?;
147        bulk_write(handle, endpoint, buffer)
148    }
149
150    pub fn write_words(&self, endpoint: Endpoint, buffer: &[u16]) -> Result<()> {
151        let raw = words_as_bytes(buffer);
152        self.write_bytes(endpoint, raw)
153    }
154
155    pub fn register_hotplug_callback<F>(
156        &self,
157        options: HotplugOptions,
158        callback: F,
159    ) -> Result<HotplugRegistration>
160    where
161        F: FnMut(HotplugEvent) + Send + 'static,
162    {
163        if !rusb::has_hotplug() {
164            return Err(Error::FeatureUnavailable("usb_hotplug"));
165        }
166
167        let mut builder = HotplugBuilder::new();
168        if let Some(vendor) = options.vendor_id {
169            builder.vendor_id(vendor);
170        }
171        if let Some(product) = options.product_id {
172            builder.product_id(product);
173        }
174        if let Some(class_code) = options.class_code {
175            builder.class(class_code);
176        }
177        builder.enumerate(options.enumerate);
178
179        let handler = CallbackHotplug { callback };
180
181        let registration = builder
182            .register(&self.context, Box::new(handler))
183            .map_err(|err| usb_error(err, "libusb_hotplug_register_callback"))?;
184
185        HotplugRegistration::new(self.context.clone(), registration)
186    }
187}
188
189impl Drop for UsbDevice {
190    fn drop(&mut self) {
191        let _ = self.close();
192    }
193}
194
195#[derive(Debug)]
196pub struct HotplugRegistration {
197    registration: Option<Registration<Context>>,
198    running: Arc<AtomicBool>,
199    thread: Option<thread::JoinHandle<()>>,
200}
201
202impl HotplugRegistration {
203    fn new(context: Context, registration: Registration<Context>) -> Result<Self> {
204        let running = Arc::new(AtomicBool::new(true));
205        let thread_running = Arc::clone(&running);
206
207        let thread = thread::Builder::new()
208            .name("vlfd-usb-hotplug".into())
209            .spawn(move || {
210                while thread_running.load(Ordering::Relaxed) {
211                    match context.handle_events(Some(Duration::from_millis(100))) {
212                        Ok(_) => {}
213                        Err(rusb::Error::Interrupted) | Err(rusb::Error::Timeout) => continue,
214                        Err(_) => break,
215                    }
216                }
217            })
218            .map_err(Error::Io)?;
219
220        Ok(Self {
221            registration: Some(registration),
222            running,
223            thread: Some(thread),
224        })
225    }
226}
227
228impl Drop for HotplugRegistration {
229    fn drop(&mut self) {
230        let _ = self.registration.take();
231        self.running.store(false, Ordering::SeqCst);
232        if let Some(handle) = self.thread.take() {
233            let _ = handle.join();
234        }
235    }
236}
237
238struct CallbackHotplug<F>
239where
240    F: FnMut(HotplugEvent) + Send + 'static,
241{
242    callback: F,
243}
244
245impl<F> Hotplug<Context> for CallbackHotplug<F>
246where
247    F: FnMut(HotplugEvent) + Send + 'static,
248{
249    fn device_arrived(&mut self, device: Device<Context>) {
250        (self.callback)(HotplugEvent {
251            kind: HotplugEventKind::Arrived,
252            device: HotplugDeviceInfo::from_device(&device),
253        });
254    }
255
256    fn device_left(&mut self, device: Device<Context>) {
257        (self.callback)(HotplugEvent {
258            kind: HotplugEventKind::Left,
259            device: HotplugDeviceInfo::from_device(&device),
260        });
261    }
262}
263
264fn bulk_read<T: UsbContext>(
265    handle: &DeviceHandle<T>,
266    endpoint: Endpoint,
267    buffer: &mut [u8],
268) -> Result<()> {
269    let mut offset = 0;
270    while offset < buffer.len() {
271        let chunk = &mut buffer[offset..];
272        let bytes_read = handle
273            .read_bulk(endpoint as u8, chunk, DEFAULT_TIMEOUT)
274            .map_err(|err| usb_error(err, "libusb_bulk_transfer"))?;
275
276        if bytes_read == 0 {
277            return Err(Error::UnexpectedResponse("bulk read returned zero bytes"));
278        }
279
280        offset += bytes_read;
281    }
282    Ok(())
283}
284
285fn bulk_write<T: UsbContext>(
286    handle: &DeviceHandle<T>,
287    endpoint: Endpoint,
288    buffer: &[u8],
289) -> Result<()> {
290    let mut offset = 0;
291    while offset < buffer.len() {
292        let chunk = &buffer[offset..];
293        let bytes_written = handle
294            .write_bulk(endpoint as u8, chunk, DEFAULT_TIMEOUT)
295            .map_err(|err| usb_error(err, "libusb_bulk_transfer"))?;
296
297        if bytes_written == 0 {
298            return Err(Error::UnexpectedResponse("bulk write returned zero bytes"));
299        }
300
301        offset += bytes_written;
302    }
303    Ok(())
304}
305
306fn words_as_bytes(words: &[u16]) -> &[u8] {
307    unsafe { std::slice::from_raw_parts(words.as_ptr() as *const u8, std::mem::size_of_val(words)) }
308}
309
310fn words_as_bytes_mut(words: &mut [u16]) -> &mut [u8] {
311    unsafe {
312        std::slice::from_raw_parts_mut(words.as_mut_ptr() as *mut u8, std::mem::size_of_val(words))
313    }
314}
315
316fn usb_error(err: rusb::Error, context: &'static str) -> Error {
317    Error::Usb {
318        source: err,
319        context,
320    }
321}