Skip to content

Commit bb09582

Browse files
committed
content: usb-monitor-control weekend 3
Describe the HID binding layer work. Signed-off-by: Rahul Rameshbabu <[email protected]>
1 parent 1a30074 commit bb09582

File tree

1 file changed

+387
-0
lines changed

1 file changed

+387
-0
lines changed
+387
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
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

Comments
 (0)