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
73pub 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}