diff --git a/Cargo.lock b/Cargo.lock index 8298300e00..55ddc6e20e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1957,7 +1957,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rehid" version = "0.1.0" -source = "git+https://gitlab.redox-os.org/redox-os/rehid.git#39d7ef05c97fd4e5e2cfd38b1a21f20d888c5e00" +source = "git+https://gitlab.redox-os.org/redox-os/rehid.git#da987bc6d0f043bb025ece0d35804da2b250fab3" dependencies = [ "hidreport", "log", diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs index dd5a7f2f62..9522dd0d0b 100644 --- a/drivers/input/usbhidd/src/main.rs +++ b/drivers/input/usbhidd/src/main.rs @@ -1,9 +1,15 @@ use anyhow::{Context, Result}; -use std::{env, thread, time}; +use std::{ + collections::{BTreeMap, HashMap}, + env, + sync::mpsc, + thread, time, +}; use inputd::ProducerHandle; use orbclient::KeyEvent as OrbKeyEvent; use rehid::{ + hidreport::{Report, Usage}, report_desc::{ReportTy, REPORT_DESC_TY}, report_handler::ReportHandler, usage_tables::{GenericDesktopUsage, UsagePage}, @@ -16,6 +22,7 @@ use xhcid_interface::{ use crate::descs::HidDescriptor; mod descs; +mod overrides; mod reqs; fn send_key_event(display: &mut ProducerHandle, usage_page: u16, usage: u16, pressed: bool) { @@ -173,6 +180,15 @@ fn main() -> Result<()> { .expect(USAGE) .parse::() .expect("Expected integer as input of interface"); + let mut report_desc_override = None; + if let Some(override_name) = args.next() { + for (name, bytes) in overrides::OVERRIDES.iter() { + if name == &override_name { + report_desc_override = Some(bytes.to_vec()); + break; + } + } + } let name = format!("{}_{}_{}_hid", scheme, port, interface_num); common::setup_logging( @@ -204,7 +220,7 @@ fn main() -> Result<()> { log::debug!("{:X?}", desc); let mut endp_count = 0; - let (conf_desc, (if_desc, endp_desc_opt, hid_desc)) = desc + let (conf_desc, (if_desc, endp_desc_opt, hid_desc_opt)) = desc .config_descs .iter() .find_map(|conf_desc| { @@ -215,16 +231,28 @@ fn main() -> Result<()> { if endp_desc.ty() == EndpointTy::Interrupt && endp_desc.direction() == EndpDirection::In { + log::warn!( + "using endpoint 0x{:x} {:?} {:?}", + endp_desc.address, + endp_desc.ty(), + endp_desc.direction() + ); Some((endp_count, endp_desc.clone())) } else { + log::warn!( + "ignoring endpoint 0x{:x} {:?} {:?}", + endp_desc.address, + endp_desc.ty(), + endp_desc.direction() + ); None } }); - let hid_desc = if_desc.unknown_descs.iter().find_map(|unknown_desc| { + let hid_desc_opt = if_desc.unknown_descs.iter().find_map(|unknown_desc| { //TODO: should we do any filtering? HidDescriptor::from_bytes(&unknown_desc.all_bytes).ok() - })?; - Some((if_desc.clone(), endp_desc_opt, hid_desc)) + }); + Some((if_desc.clone(), endp_desc_opt, hid_desc_opt)) } else { endp_count += if_desc.endpoints.len(); None @@ -247,21 +275,29 @@ fn main() -> Result<()> { //TODO: dynamically create good values, fix xhcid so it does not block on each request // This sets all reports to a duration of 4ms - reqs::set_idle(&handle, 1, 0, interface_num as u16).context("Failed to set idle")?; + if let Err(err) = reqs::set_idle(&handle, 1, 0, interface_num as u16) { + log::warn!("failed to set idle: {}", err); + } - let report_desc_len = hid_desc.get_report_desc()?.desc_len; + let report_desc_bytes = if let Some(hid_desc) = hid_desc_opt { + let report_desc_len = hid_desc.get_report_desc()?.desc_len; - let mut report_desc_bytes = vec![0u8; report_desc_len as usize]; - handle - .get_descriptor( - PortReqRecipient::Interface, - REPORT_DESC_TY, - 0, - //TODO: should this be an index into interface_descs? - interface_num as u16, - &mut report_desc_bytes, - ) - .context("Failed to retrieve report descriptor")?; + let mut report_desc_bytes = vec![0u8; report_desc_len as usize]; + handle + .get_descriptor( + PortReqRecipient::Interface, + REPORT_DESC_TY, + 0, + //TODO: should this be an index into interface_descs? + interface_num as u16, + &mut report_desc_bytes, + ) + .context("Failed to retrieve report descriptor")?; + + report_desc_bytes + } else { + report_desc_override.expect("failed to find report descriptor") + }; let mut handler = ReportHandler::new(&report_desc_bytes).expect("failed to parse report descriptor"); @@ -270,9 +306,23 @@ fn main() -> Result<()> { Some((_endp_num, endp_desc)) => endp_desc.max_packet_size as usize, None => handler.total_byte_length as usize, }; - let mut report_buffer = vec![0u8; report_len]; - let report_ty = ReportTy::Input; - let report_id = 0; + + let mut report_collections = HashMap::new(); + for report in handler.descriptor.input_reports() { + for field in report.fields() { + let key = (report.report_id().map(u8::from), u32::from(field.id())); + if let Some(old) = report_collections.get(&key) { + log::warn!( + "key {:?} has old collections {:?} and new collections {:?}", + key, + old, + field.collections() + ); + continue; + } + report_collections.insert(key, field.collections().to_vec()); + } + } let mut display = ProducerHandle::new().context("Failed to open input socket")?; let mut endpoint_opt = match endp_desc_opt { @@ -285,107 +335,318 @@ fn main() -> Result<()> { }, None => None, }; + + let (tx, rx) = mpsc::channel(); + thread::spawn(move || -> Result<()> { + let mut report_buffer = vec![0u8; report_len]; + let report_ty = ReportTy::Input; + let report_id = 0; + loop { + //TODO: get frequency from device + //TODO: use sleeps when accuracy is better: thread::sleep(time::Duration::from_millis(10)); + let timer = time::Instant::now(); + while timer.elapsed() < time::Duration::from_millis(1) { + thread::yield_now(); + } + + if let Some(endpoint) = &mut endpoint_opt { + // interrupt transfer + endpoint + .transfer_read(&mut report_buffer) + .context("failed to get report")?; + } else { + // control transfer + reqs::get_report( + &handle, + report_ty, + report_id, + //TODO: should this be an index into interface_descs? + interface_num as u16, + &mut report_buffer, + ) + .context("failed to get report")?; + } + + tx.send(report_buffer.clone()) + .context("failed to send report to main thread")?; + } + }); + let mut left_shift = false; let mut right_shift = false; let mut last_mouse_pos = (0, 0); let mut last_buttons = [false, false, false]; + let mut gamepad_state = HashMap::new(); loop { - //TODO: get frequency from device - //TODO: use sleeps when accuracy is better: thread::sleep(time::Duration::from_millis(10)); - let timer = time::Instant::now(); - while timer.elapsed() < time::Duration::from_millis(1) { - thread::yield_now(); - } - - if let Some(endpoint) = &mut endpoint_opt { - // interrupt transfer - endpoint - .transfer_read(&mut report_buffer) - .context("failed to get report")?; - } else { - // control transfer - reqs::get_report( - &handle, - report_ty, - report_id, - //TODO: should this be an index into interface_descs? - interface_num as u16, - &mut report_buffer, - ) - .context("failed to get report")?; - } - let mut mouse_pos = last_mouse_pos; let mut mouse_dx = 0i32; let mut mouse_dy = 0i32; let mut scroll_y = 0i32; let mut buttons = last_buttons; - for event in handler - .handle(&report_buffer) - .expect("failed to parse report") - { - log::debug!("{}", event); - if event.usage_page == UsagePage::GenericDesktop as u16 { - if event.usage == GenericDesktopUsage::X as u16 { - if event.relative { - mouse_dx += event.value as i32; + match rx.try_recv() { + Ok(report_buffer) => { + for event in handler + .handle(&report_buffer) + .expect("failed to parse report") + { + let mut gamepad = false; + if let Some(collections) = + report_collections.get(&(event.report_id, event.field_id)) + { + for collection in collections { + for usage in collection.usages() { + match UsagePage::from_repr(usage.usage_page.into()) { + Some(UsagePage::GenericDesktop) => { + match GenericDesktopUsage::from_repr(usage.usage_id.into()) + { + Some(GenericDesktopUsage::GamePad) => { + gamepad = true; + } + _ => {} + } + } + _ => {} + } + } + } } else { - mouse_pos.0 = event.value as i32; + log::warn!( + "collections missing for report {:?} field {}", + event.report_id, + event.field_id + ); } - } else if event.usage == GenericDesktopUsage::Y as u16 { - if event.relative { - mouse_dy += event.value as i32; - } else { - mouse_pos.1 = event.value as i32; + + // Special handling for gamepad + //TODO: make this more generic + if gamepad { + let last = gamepad_state + .get(&(event.usage_page, event.usage)) + .map_or(0, |x: &(i64, i64)| x.1); + gamepad_state.insert((event.usage_page, event.usage), (event.value, last)); + continue; } - } else if event.usage == GenericDesktopUsage::Wheel as u16 { - //TODO: what is X scroll? - if event.relative { - scroll_y += event.value as i32; - } else { - log::warn!("absolute mouse wheel not supported"); + + match UsagePage::from_repr(event.usage_page) { + Some(UsagePage::GenericDesktop) => { + match GenericDesktopUsage::from_repr(event.usage) { + Some(GenericDesktopUsage::X) => { + if event.relative { + mouse_dx += event.value as i32; + } else { + mouse_pos.0 = event.value as i32; + } + } + Some(GenericDesktopUsage::Y) => { + if event.relative { + mouse_dy += event.value as i32; + } else { + mouse_pos.1 = event.value as i32; + } + } + Some(GenericDesktopUsage::Wheel) => { + //TODO: what is X scroll? + if event.relative { + scroll_y += event.value as i32; + } else { + log::warn!("absolute mouse wheel not supported"); + } + } + unsupported => { + log::info!( + "unsupported generic desktop usage 0x{:X}:0x{:X} ({:?}) value {}", + event.usage_page, + event.usage, + unsupported, + event.value + ); + } + } + } + Some(UsagePage::KeyboardOrKeypad) => { + let (pressed, shift_opt) = if event.value != 0 { + (true, Some(left_shift | right_shift)) + } else { + (false, None) + }; + if event.usage == 0xE1 { + left_shift = pressed; + } else if event.usage == 0xE5 { + right_shift = pressed; + } + send_key_event(&mut display, event.usage_page, event.usage, pressed); + } + Some(UsagePage::Button) => { + if event.usage > 0 && event.usage as usize <= buttons.len() { + buttons[event.usage as usize - 1] = event.value != 0; + } else { + log::info!( + "unsupported buttons usage 0x{:X}:0x{:X} value {}", + event.usage_page, + event.usage, + event.value + ); + } + } + _ => { + if event.usage_page >= 0xFF00 { + // Ignore vendor defined event + } else { + log::info!( + "unsupported usage 0x{:X}:0x{:X} value {}", + event.usage_page, + event.usage, + event.value + ); + } + } } - } else { - log::info!( - "unsupported generic desktop usage 0x{:X}:0x{:X} value {}", - event.usage_page, - event.usage, - event.value - ); } - } else if event.usage_page == UsagePage::KeyboardOrKeypad as u16 { - let (pressed, shift_opt) = if event.value != 0 { - (true, Some(left_shift | right_shift)) - } else { - (false, None) - }; - if event.usage == 0xE1 { - left_shift = pressed; - } else if event.usage == 0xE5 { - right_shift = pressed; - } - send_key_event(&mut display, event.usage_page, event.usage, pressed); - } else if event.usage_page == UsagePage::Button as u16 { - if event.usage > 0 && event.usage as usize <= buttons.len() { - buttons[event.usage as usize - 1] = event.value != 0; - } else { - log::info!( - "unsupported buttons usage 0x{:X}:0x{:X} value {}", - event.usage_page, - event.usage, - event.value - ); - } - } else if event.usage_page >= 0xFF00 { - // Ignore vendor defined event - } else { - log::info!( - "unsupported usage 0x{:X}:0x{:X} value {}", - event.usage_page, - event.usage, - event.value - ); } + Err(mpsc::TryRecvError::Empty) => { + //TODO: get frequency from device + //TODO: use sleeps when accuracy is better: thread::sleep(time::Duration::from_millis(10)); + let timer = time::Instant::now(); + while timer.elapsed() < time::Duration::from_millis(1) { + thread::yield_now(); + } + } + Err(err) => { + anyhow::bail!("failed to recv report buffer from thread: {:?}", err); + } + } + + for (&(usage_page, usage), &(value, last)) in gamepad_state.iter() { + let gamepad_axis = |value| { + let deadzone = 8096; + if value < -deadzone { + value + deadzone + } else if value > deadzone { + value - deadzone + } else { + 0 + } + }; + let mut gamepad_key = |scancode, pressed| { + let key_event = OrbKeyEvent { + character: '\0', + scancode, + pressed, + }; + + match display.write_event(key_event.to_event()) { + Ok(_) => (), + Err(err) => { + log::warn!("failed to send key event to orbital: {}", err); + } + } + }; + let mut gamepad_axis_keys = |value, last, k_neg, k_pos| { + let axis = gamepad_axis(value); + let press_neg = axis < 0; + let press_pos = axis > 0; + let last_axis = gamepad_axis(last); + if press_neg != (last_axis < 0) { + gamepad_key(k_neg, press_neg); + } + if press_pos != (last_axis > 0) { + gamepad_key(k_pos, press_pos); + } + }; + match UsagePage::from_repr(usage_page) { + Some(UsagePage::GenericDesktop) => match GenericDesktopUsage::from_repr(usage) { + Some(GenericDesktopUsage::X) => { + gamepad_axis_keys(value, last, orbclient::K_A, orbclient::K_D); + } + Some(GenericDesktopUsage::Y) => { + gamepad_axis_keys(value, last, orbclient::K_S, orbclient::K_W); + } + Some(GenericDesktopUsage::Rx) => { + mouse_dx += (gamepad_axis(value) / 4096) as i32; + } + Some(GenericDesktopUsage::Ry) => { + mouse_dy -= (gamepad_axis(value) / 4096) as i32; + } + Some(GenericDesktopUsage::DpadLeft) => { + if value != last { + gamepad_key(orbclient::K_A, value != 0); + } + } + Some(GenericDesktopUsage::DpadRight) => { + if value != last { + gamepad_key(orbclient::K_D, value != 0); + } + } + Some(GenericDesktopUsage::DpadUp) => { + if value != last { + gamepad_key(orbclient::K_W, value != 0); + } + } + Some(GenericDesktopUsage::DpadDown) => { + if value != last { + gamepad_key(orbclient::K_S, value != 0); + } + } + _ => {} + }, + Some(UsagePage::Button) => match usage { + // A + 1 => { + if value != last { + gamepad_key(orbclient::K_SEMICOLON, value != 0) + } + } + // B + 2 => { + if value != last { + gamepad_key(orbclient::K_L, value != 0) + } + } + // X + 3 => { + if value != last { + gamepad_key(orbclient::K_O, value != 0) + } + } + // Y + 4 => { + if value != last { + gamepad_key(orbclient::K_K, value != 0) + } + } + // LB + 5 => { + if value != last { + gamepad_key(orbclient::K_I, value != 0) + } + } + // RB + 6 => { + if value != last { + gamepad_key(orbclient::K_P, value != 0) + } + } + // Select + 7 => { + if value != last { + gamepad_key(orbclient::K_SPACE, value != 0) + } + } + // Start + 8 => { + if value != last { + gamepad_key(orbclient::K_ENTER, value != 0) + } + } + _ => { + //log::warn!("unknown gamepad button {}", usage) + } + }, + _ => {} + } + } + for (_, (value, last)) in gamepad_state.iter_mut() { + *last = *value; } if mouse_pos != last_mouse_pos { diff --git a/drivers/input/usbhidd/src/overrides.rs b/drivers/input/usbhidd/src/overrides.rs new file mode 100644 index 0000000000..5c8395f456 --- /dev/null +++ b/drivers/input/usbhidd/src/overrides.rs @@ -0,0 +1,90 @@ +#[rustfmt::skip] +pub const OVERRIDES: &'static [(&'static str, &'static [u8])] = &[ + // Adapted from https://marc.info/?l=openbsd-tech&m=138227720105805&w=2 + ( + "xbox-360", + &[ + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x05, // Usage (Game Pad) + 0xA1, 0x01, // Collection (Application) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x3B, // Usage (Byte Count) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + 0x75, 0x01, // Report Size (1) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x35, 0x00, // Physical Minimum (0) + 0x45, 0x01, // Physical Maximum (1) + 0x95, 0x04, // Report Count (4) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x90, // Usage (D-pad Up) + 0x09, 0x91, // Usage (D-pad Down) + 0x09, 0x93, // Usage (D-pad Left) + 0x09, 0x92, // Usage (D-pad Right) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0x75, 0x01, // Report Size (1) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x35, 0x00, // Physical Minimum (0) + 0x45, 0x01, // Physical Maximum (1) + 0x95, 0x07, // Report Count (7) + 0x05, 0x09, // Usage Page (Button) + 0x09, 0x08, // Usage (0x08) + 0x09, 0x07, // Usage (0x07) + 0x09, 0x09, // Usage (0x09) + 0x09, 0x0A, // Usage (0x0A) + 0x09, 0x05, // Usage (0x05) + 0x09, 0x06, // Usage (0x06) + 0x09, 0x0B, // Usage (0x0B) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x01, // Report Size (1) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x35, 0x00, // Physical Minimum (0) + 0x45, 0x01, // Physical Maximum (1) + 0x95, 0x04, // Report Count (4) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x04, // Usage Maximum (0x04) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x95, 0x02, // Report Count (2) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x10, // Report Size (16) + 0x16, 0x00, 0x80, // Logical Minimum (-32768) + 0x26, 0xFF, 0x7F, // Logical Maximum (32767) + 0x36, 0x00, 0x80, // Physical Minimum (-32768) + 0x46, 0xFF, 0x7F, // Physical Maximum (32767) + 0x95, 0x04, // Report Count (4) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x33, // Usage (Rx) + 0x09, 0x34, // Usage (Ry) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x30, // Report Size (48) + 0x95, 0x01, // Report Count (1) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + ], + ), +]; diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml index 83c90e2385..837db82cbc 100644 --- a/drivers/usb/xhcid/drivers.toml +++ b/drivers/usb/xhcid/drivers.toml @@ -16,3 +16,13 @@ name = "USB HID" class = 3 # HID class subclass = -1 command = ["usbhidd", "$SCHEME", "$PORT", "$IF_NUM"] + +[[drivers]] +name = "USB HID Xbox 360" +class = 255 # Vendor specific class +subclass = 93 +protocol = 1 +ids = { 0x045e = [ + 0x028e # Xbox 360 controller +] } +command = ["usbhidd", "$SCHEME", "$PORT", "$IF_NUM", "xbox-360"] diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs index 73d9d90b2a..d9b0c2cf95 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ -1251,19 +1251,14 @@ impl Xhci { trace!("Spawning driver on port: {}", port); //TODO: support choosing config? - let config_desc = &ps - .dev_desc - .as_ref() - .ok_or_else(|| { - log::warn!("Missing device descriptor"); - Error::new(EBADF) - })? - .config_descs - .first() - .ok_or_else(|| { - log::warn!("Missing config descriptor"); - Error::new(EBADF) - })?; + let dev_desc = ps.dev_desc.as_ref().ok_or_else(|| { + log::warn!("Missing device descriptor"); + Error::new(EBADF) + })?; + let config_desc = dev_desc.config_descs.first().ok_or_else(|| { + log::warn!("Missing config descriptor"); + Error::new(EBADF) + })?; trace!("Got config and device descriptors on port {}", port); @@ -1287,11 +1282,36 @@ impl Xhci { } if let Some(driver) = self.drivers_config.drivers.iter().find(|driver| { + if let Some(ref ids) = driver.ids { + let mut device_found = false; + for (vendor, devices) in ids { + let vendor_without_prefix = vendor.trim_start_matches("0x"); + let vendor = i64::from_str_radix(vendor_without_prefix, 16).unwrap() as u16; + + if vendor != dev_desc.vendor { + continue; + } + + for device in devices { + if *device == dev_desc.product { + device_found = true; + break; + } + } + } + if !device_found { + return false; + } + } driver.class == ifdesc.class && driver .subclass() .map(|subclass| subclass == ifdesc.sub_class) .unwrap_or(true) + && driver + .protocol + .map(|protocol| protocol == ifdesc.protocol) + .unwrap_or(true) }) { info!( "Loading subdriver \"{}\" for port {} iface {} alternate {} class {}.{} proto {}", @@ -1337,7 +1357,7 @@ impl Xhci { ifdesc.alternate_setting, ifdesc.class, ifdesc.sub_class, - ifdesc.protocol + ifdesc.protocol, ); } } @@ -1485,6 +1505,8 @@ struct DriverConfig { name: String, class: u8, subclass: i16, // The subclass may be meaningless for some drivers, hence negative values (and values above 255) mean "undefined". + protocol: Option, + ids: Option>>, command: Vec, } impl DriverConfig {