|
| 1 | +--- |
| 2 | +title: "Supporting USB Monitor Control in the Linux kernel - Weekend 3: Bindings" |
| 3 | +tags: ["kernel", "display", "hid", "usb monitor control"] |
| 4 | +date: 2025-03-02T10:49:38-08:00 |
| 5 | +draft: false |
| 6 | +--- |
| 7 | + |
| 8 | +The work for binding the Linux kernel HID interface in C is taking shape. I have |
| 9 | +two commits related to the work in my tree. The reason the work is split in two |
| 10 | +was figuring out how to handle the HID device id table as a complex problem on |
| 11 | +its own compared to the rest of the binding work. |
| 12 | + |
| 13 | ++ [[https://github.com/Binary-Eater/linux/commit/f6dc91a0eb7eaa6eca08bc246da3372e59ef022d]] |
| 14 | ++ [[https://github.com/Binary-Eater/linux/commit/383a97f7a68fe083013e5f8fc11878361b73a022]] |
| 15 | + |
| 16 | +*DISCLAIMER:* The work for this happened on Feb. 22-23, 2025, but I did not have |
| 17 | +time to write the related blog that weekend. |
| 18 | + |
| 19 | +* Initial bindings and macro design |
| 20 | + |
| 21 | +The kernel has done a lot to work to simplify the C ffi in Rust signficantly. |
| 22 | +Specifically, the ~Opaque~ struct under ~rust/kernel/types.rs~ is very helpful. |
| 23 | + |
| 24 | +#+BEGIN_SRC rust |
| 25 | + #[repr(transparent)] |
| 26 | + pub struct Device(Opaque<bindings::hid_device>); |
| 27 | + |
| 28 | + #[repr(transparent)] |
| 29 | + pub struct DeviceId(Opaque<bindings::hid_device_id>); |
| 30 | +#+END_SRC |
| 31 | + |
| 32 | +What we are doing here is defining two single element [[https://doc.rust-lang.org/book/ch05-01-defining-structs.html#using-tuple-structs-without-named-fields-to-create-different-types]["tuple structs"]] where the |
| 33 | +first element is FFI object that is allowed to make guarantees Rust normally |
| 34 | +would not permit. This will be very useful for passing data in and out of the C |
| 35 | +api layer. We use the ~#[repr(transparent)]~ directive to ensure the Rust types |
| 36 | +we are declaring will have the same memory layout as the C ffi object types |
| 37 | +being used in the single element tuple structs. |
| 38 | + |
| 39 | +By guaranteeing the memory layout is the same, we can implement static methods |
| 40 | +for the type as follows. |
| 41 | + |
| 42 | +#+BEGIN_SRC rust |
| 43 | + impl Device { |
| 44 | + unsafe fn from_ptr<'a>(ptr: *mut bindings::hid_device) -> &'a mut Self { |
| 45 | + let ptr = ptr.cast::<Self>(); |
| 46 | + |
| 47 | + unsafe { &mut *ptr }; |
| 48 | + } |
| 49 | + |
| 50 | + pub fn vendor(&self) -> u32 { |
| 51 | + let hdev = self.0.get(); |
| 52 | + |
| 53 | + unsafe { (*hdev).vendor } |
| 54 | + } |
| 55 | + |
| 56 | + // Omitted the rest |
| 57 | + } |
| 58 | + |
| 59 | + impl DeviceId { |
| 60 | + unsafe fn from_ptr<'a>(ptr: *mut bindings::hid_device_id) -> &'a mut Self { |
| 61 | + let ptr = ptr.cast::<Self>(); |
| 62 | + |
| 63 | + unsafe { &mut *ptr }; |
| 64 | + } |
| 65 | + |
| 66 | + pub fn vendor(&self) -> u32 { |
| 67 | + let hdev_id = self.0.get(); |
| 68 | + |
| 69 | + unsafe { (*hdev_id).vendor } |
| 70 | + } |
| 71 | + |
| 72 | + // Omitted the rest |
| 73 | + } |
| 74 | +#+END_SRC |
| 75 | + |
| 76 | +We define ~from_ptr~ methods that enable the Rust layer to represent the C types |
| 77 | +using the memory layout equivalent Rust types. We then define some Rust getters |
| 78 | +to "safely" access the underlying C data. I need to go back later and add the |
| 79 | +~SAFETY~ comments in the code. |
| 80 | + |
| 81 | +* Driver adapters and vtables |
| 82 | + |
| 83 | +The Linux kernel module registration interface is well-designed with regards to |
| 84 | +C semantics. Bridging that over to Rust takes quite a bit of massaging. The rust |
| 85 | +binding code had a habit of utilizing a "adapter" and a "vtable". Lets first |
| 86 | +look at the way HID drivers are instantiated in C before delving into the Rust |
| 87 | +code. |
| 88 | + |
| 89 | +#+BEGIN_SRC c |
| 90 | + /** |
| 91 | + * struct hid_driver |
| 92 | + * @name: driver name (e.g. "Footech_bar-wheel") |
| 93 | + * @id_table: which devices is this driver for (must be non-NULL for probe |
| 94 | + * to be called) |
| 95 | + * @dyn_list: list of dynamically added device ids |
| 96 | + * @dyn_lock: lock protecting @dyn_list |
| 97 | + * @match: check if the given device is handled by this driver |
| 98 | + * @probe: new device inserted |
| 99 | + * @remove: device removed (NULL if not a hot-plug capable driver) |
| 100 | + * @report_table: on which reports to call raw_event (NULL means all) |
| 101 | + * @raw_event: if report in report_table, this hook is called (NULL means nop) |
| 102 | + * @usage_table: on which events to call event (NULL means all) |
| 103 | + * @event: if usage in usage_table, this hook is called (NULL means nop) |
| 104 | + * @report: this hook is called after parsing a report (NULL means nop) |
| 105 | + * @report_fixup: called before report descriptor parsing (NULL means nop) |
| 106 | + * @input_mapping: invoked on input registering before mapping an usage |
| 107 | + * @input_mapped: invoked on input registering after mapping an usage |
| 108 | + * @input_configured: invoked just before the device is registered |
| 109 | + * @feature_mapping: invoked on feature registering |
| 110 | + * @suspend: invoked on suspend (NULL means nop) |
| 111 | + * @resume: invoked on resume if device was not reset (NULL means nop) |
| 112 | + * @reset_resume: invoked on resume if device was reset (NULL means nop) |
| 113 | + */ |
| 114 | + |
| 115 | + static struct hid_driver shield_driver = { |
| 116 | + .name = "shield", |
| 117 | + .id_table = shield_devices, |
| 118 | + .input_mapping = android_input_mapping, |
| 119 | + .probe = shield_probe, |
| 120 | + .remove = shield_remove, |
| 121 | + .raw_event = shield_raw_event, |
| 122 | + .driver = { |
| 123 | + .dev_groups = shield_device_groups, |
| 124 | + }, |
| 125 | + }; |
| 126 | +#+END_SRC |
| 127 | + |
| 128 | +We see that the ~hid_driver~ struct instance used for the ~hid-nvidia-shield~ |
| 129 | +driver does not instantiate all members of the struct. This is common practice |
| 130 | +for structs used to bind driver functionality. However, this does not play well |
| 131 | +with Rust, which essentially requires all members of a struct to be initialized. |
| 132 | +We will need some involved logic to work around this. |
| 133 | + |
| 134 | +#+BEGIN_SRC rust |
| 135 | + struct Adapter<T: Driver> { |
| 136 | + _p: PhantomData<T>, |
| 137 | + } |
| 138 | + |
| 139 | + impl<T: Driver> Adapter<T> { |
| 140 | + unsafe extern "C" fn probe_callback( |
| 141 | + hdev: *mut bindings::hid_device, |
| 142 | + hdev_id: *mut bindings::hid_device_id, |
| 143 | + ) -> crate::ffi::c_int { |
| 144 | + from_result(|| { |
| 145 | + let dev = unsafe { Device::from_ptr(hdev) }; |
| 146 | + let dev_id = unsafe { DeviceId::from_ptr(hdev_id) }; |
| 147 | + T::probe(dev, dev_id)?; |
| 148 | + Ok(0) |
| 149 | + }) |
| 150 | + } |
| 151 | + |
| 152 | + unsafe extern "C" fn remove_callback(hdev: *mut bindings::hid_device,) -> crate::ffi::c_int { |
| 153 | + from_result(|| { |
| 154 | + let dev = unsafe { Device::from_ptr(hdev) }; |
| 155 | + T::remove(dev)?; |
| 156 | + Ok(0) |
| 157 | + }) |
| 158 | + } |
| 159 | + } |
| 160 | +#+END_SRC |
| 161 | + |
| 162 | +We define an ~Adapter~ struct capable of providing C linkage functions for |
| 163 | +feedback to C HID API. We make these C linkage functions call the native Rust |
| 164 | +HID ~Driver~ trait methods and provide C style return codes. |
| 165 | + |
| 166 | +We enabled the Rust HID ~Driver~ trait to have a ~vtable~ implementation, which |
| 167 | +means trait implementers do not have to define all the trait methods. This means |
| 168 | +that we need to handle the case when HID methods methods are missing. We create |
| 169 | +a ~DriverVTable~ type that is essentially mapped to ~hid_driver~. We also a |
| 170 | +~Driver~ trair generic ~create_hid_driver~ function to instantiate the wrapped |
| 171 | +~hid_driver~ type. |
| 172 | + |
| 173 | +#+BEGIN_SRC rust |
| 174 | + #[repr(transparent)] |
| 175 | + pub struct DriverVTable(Opaque<bindings::hid_driver>); |
| 176 | + |
| 177 | + // SAFETY: `DriverVTable` doesn't expose any &self method to access internal data, so it's safe to |
| 178 | + // share `&DriverVTable` across execution context boundaries. |
| 179 | + unsafe impl Sync for DriverVTable {} |
| 180 | + |
| 181 | + pub const fn create_hid_driver<T: Driver>(name: &'static CStr) -> DriverVTable { |
| 182 | + DriverVTable(Opaque::new(bindings::hid_driver { |
| 183 | + name: name.as_char_ptr(), |
| 184 | + id_table: /* TODO */, |
| 185 | + probe: if T::HAS_PROBE { |
| 186 | + Some(Adapter::<T>::probe_callback) |
| 187 | + } else { |
| 188 | + None |
| 189 | + }, |
| 190 | + remove: if T::HAS_REMOVE { |
| 191 | + Some(Adapter::<T>::remove_callback) |
| 192 | + } else { |
| 193 | + None |
| 194 | + }, |
| 195 | + // SAFETY: The rest is zeroed out to initialize `struct hid_driver`, |
| 196 | + // sets `Option<&F>` to be `None`. |
| 197 | + ..unsafe { core::mem::MaybeUninit::<bindings::hid_driver>::zeroed().assume_init() } |
| 198 | + })) |
| 199 | + } |
| 200 | +#+END_SRC |
| 201 | + |
| 202 | +* Registering the driver |
| 203 | + |
| 204 | +Normally for a C HID driver, we would call the ~module_hid_driver~ macro to |
| 205 | +instantiate the HID driver. Calling C-style macros from Rust is not an option. |
| 206 | +We can traverse that the macro eventually calls the ~__hid_register_driver~ |
| 207 | +function, which we will want to call from Rust. Similarly, we will want to call |
| 208 | +~hid_unregister_driver~ when unbinding the driver. Both functions can be found |
| 209 | +in ~drivers/hid/hid-core.c~. |
| 210 | + |
| 211 | +We will have to utilize a a new struct and macro in Rust to provide the |
| 212 | +equivalent functionality for a Rust HID driver. Lets first explore the |
| 213 | +~Registration~ struct and implementation. |
| 214 | + |
| 215 | +#+BEGIN_SRC rust |
| 216 | + pub struct Registration { |
| 217 | + driver: Pin<&'static mut DriverVTable>, |
| 218 | + } |
| 219 | + |
| 220 | + unsafe impl Send for Registration {} |
| 221 | + |
| 222 | + impl Registration { |
| 223 | + pub fn register( |
| 224 | + module: &'static crate::ThisModule, |
| 225 | + driver: Pin<&'static mut DriverVTable>, |
| 226 | + name: &'static CStr, |
| 227 | + ) -> Result<Self> { |
| 228 | + to_result(unsafe { |
| 229 | + bindings::__hid_register_driver(driver.0.get(), module.0, name.as_char_ptr()) |
| 230 | + })?; |
| 231 | + |
| 232 | + Ok(Registration { driver }) |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + impl Drop for Registration { |
| 237 | + fn drop(&mut self) { |
| 238 | + unsafe { |
| 239 | + bindings::hid_unregister_driver(self.driver.0.get()) |
| 240 | + }; |
| 241 | + } |
| 242 | + } |
| 243 | +#+END_SRC |
| 244 | + |
| 245 | +The ~register~ function is the safe Rust wrapper for the ~__hid_register_driver~ |
| 246 | +C call. We essentially use the ~Register~ struct for unregistering the driver |
| 247 | +when the driver is no longer binded. We do this by implementing the ~Drop~ trait |
| 248 | +and calling ~hid_unregister_driver~ in ~drop~. |
| 249 | + |
| 250 | +Working with the ~Register~ struct directly in a Rust HID driver would be |
| 251 | +clunky, so we can instead provide a Rust macro to mask the complexity. |
| 252 | + |
| 253 | +#+BEGIN_SRC rust |
| 254 | + #[macro_export] |
| 255 | + macro_rules! module_hid_driver { |
| 256 | + (driver: $($driver:ident), name: $($name:expr), $(f:tt)*) => { |
| 257 | + struct Module { |
| 258 | + _reg: $crate::hid::Registration, |
| 259 | + } |
| 260 | + |
| 261 | + $crate::prelude::module! { |
| 262 | + type: Module, |
| 263 | + name: $($name), |
| 264 | + $($f)* |
| 265 | + } |
| 266 | + |
| 267 | + const _: () = { |
| 268 | + static mut DRIVER: $crate::hid::DriverVTable = $($crate::hid::create_hid_driver::<$driver>(NAME, /* TODO pass ID_TABLE */)); |
| 269 | + }; |
| 270 | + } |
| 271 | + } |
| 272 | +#+END_SRC |
| 273 | + |
| 274 | +The ~macro_rules!~ for ~module_hid_driver~ here is very simple. Also, I need to |
| 275 | +make updates to the signature of ~create_hid_driver~ due to my changes to device |
| 276 | +ID table handling. We call the prelude ~module!~ macro to make the newly define |
| 277 | +~Module~ struct implement what is needed to be a Rust kernel module type. |
| 278 | + |
| 279 | +* Dealing with the HID device ID table |
| 280 | + |
| 281 | +Handling the HID device ID table was one of the most architecturally challenging |
| 282 | +aspects of this project for me. I have lost count of the number of design |
| 283 | +patterns I have scratched while trying to settle on how to handle this. To begin |
| 284 | +with, lets take a look at how this is handled in C. |
| 285 | + |
| 286 | +#+BEGIN_SRC c |
| 287 | + static const struct hid_device_id shield_devices[] = { |
| 288 | + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NVIDIA, |
| 289 | + USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER) }, |
| 290 | + { HID_USB_DEVICE(USB_VENDOR_ID_NVIDIA, |
| 291 | + USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER) }, |
| 292 | + { } |
| 293 | + }; |
| 294 | +#+END_SRC |
| 295 | + |
| 296 | +We have a C style array where we do not explicitly state the size on the |
| 297 | +left-hand side (lhs) and have a "terminating" element in the right-hand side |
| 298 | +(rhs). This design pattern is completely not compatible with Rust. Rust arrays |
| 299 | +*must* declare their explicit size in the left-hand side and do not trivially |
| 300 | +support the terminator pattern. This made the design tricky. I eventually |
| 301 | +converged to using macros to assist with the arbitrary-sized structure. |
| 302 | + |
| 303 | +The first step was to make the ~HID_USB_DEVICE~ equivalent in Rust. |
| 304 | + |
| 305 | +#+BEGIN_SRC rust |
| 306 | + #[macro_export] |
| 307 | + macro_rules! usb_device { |
| 308 | + (vendor: $($vendor:expr), product: $($product:expr)) => { |
| 309 | + DeviceId(Opaque::new(bindings::hid_device_id { |
| 310 | + bus: 0x3, /* BUS_USB */ |
| 311 | + vendor: $($vendor), |
| 312 | + product: $($product), |
| 313 | + // SAFETY: The rest is zeroed out to initialize `struct hid_device_id`, |
| 314 | + // sets `Option<&F>` to be `None`. |
| 315 | + ..unsafe { core::mem::MaybeUninit::<bindings::hid_device_id>::zeroed().assume_init() } |
| 316 | + })) |
| 317 | + } |
| 318 | + } |
| 319 | +#+END_SRC |
| 320 | + |
| 321 | +The ~usb_device~ macro serves as the Rust equivalent. To deal with terminating |
| 322 | +the table, I made the following macro for internal use only in the ~hid~ Rust |
| 323 | +module. |
| 324 | + |
| 325 | +#+BEGIN_SRC rust |
| 326 | + macro_rules! term { |
| 327 | + () => { |
| 328 | + DeviceId(Opaque::new(bindings::hid_device_id { |
| 329 | + // SAFETY: The rest is zeroed out to initialize `struct hid_device_id`, |
| 330 | + // sets `Option<&F>` to be `None`. |
| 331 | + ..unsafe { $crate::core::mem::MaybeUninit::<bindings::hid_device_id>::zeroed().assume_init() } |
| 332 | + })) |
| 333 | + } |
| 334 | + } |
| 335 | +#+END_SRC |
| 336 | + |
| 337 | +I realize now that this ~term~ macro is useless and will be getting rid of it. |
| 338 | +It's a lot of back and forth on the design. Here is an updated variant of |
| 339 | +~module_hid_driver~ macro for creating a id table structure. |
| 340 | + |
| 341 | +#+BEGIN_SRC rust |
| 342 | + #[macro_export] |
| 343 | + macro_rules! module_hid_driver { |
| 344 | + (@replace_expr $_t:tt $sub:expr) => {$sub}; |
| 345 | + |
| 346 | + (@count_devices $($x:expr),*) => { |
| 347 | + 0usize $(+ $crate::module_hid_driver!(@replace_expr $x 1usize))* |
| 348 | + }; |
| 349 | + |
| 350 | + (driver: $($driver:ident), id_table: [$($dev_id:expr),+ $(,)?], name: $($name:expr), $(f:tt)*) => { |
| 351 | + struct Module { |
| 352 | + _reg: $crate::hid::Registration, |
| 353 | + } |
| 354 | + |
| 355 | + $crate::prelude::module! { |
| 356 | + type: Module, |
| 357 | + name: $($name), |
| 358 | + $($f)* |
| 359 | + } |
| 360 | + |
| 361 | + const _: () = { |
| 362 | + const NAME: &'static Cstr = $crate::c_str!($($name)); |
| 363 | + |
| 364 | + static ID_TABLE: [$crate::bindings::hid_device_id; |
| 365 | + $crate::module_hid_driver!(@count_devices $($dev_id),+) + 1] = [ |
| 366 | + $($dev_id.as_ptr()),+, |
| 367 | + $crate::bindings::hid_device_id { |
| 368 | + // SAFETY: All is zeroed out to initialize `struct hid_device_id`, |
| 369 | + // sets `Option<&F>` to be `None`. |
| 370 | + ..unsafe { $crate::core::mem::MaybeUninit::<$crate::bindings::hid_device_id>::zeroed().assume_init() } |
| 371 | + }, |
| 372 | + ]; |
| 373 | + |
| 374 | + static mut DRIVER: $crate::hid::DriverVTable = $($crate::hid::create_hid_driver::<$driver>(NAME, /* TODO pass ID_TABLE */)); |
| 375 | + }; |
| 376 | + } |
| 377 | + } |
| 378 | +#+END_SRC |
| 379 | + |
| 380 | +Truthfully, exposing an array of ~bindings::hid_device_id~ is broken. Instead, I |
| 381 | +should build a ~DeviceId~ array and pass the raw pointer of the underlying |
| 382 | +~Opaque~ type to the first element to the C API. |
| 383 | + |
| 384 | +* Next step: probing |
| 385 | + |
| 386 | +With all this work in place, it should be trivial to start probing HID devices |
| 387 | +and exercise the correctness of the bindings. |
0 commit comments