Supporting USB Monitor Control in the Linux kernel - Weekend 3: Bindings
The work for binding the Linux kernel HID interface in C is taking shape. I have two commits related to the work in my tree. The reason the work is split in two was figuring out how to handle the HID device id table as a complex problem on its own compared to the rest of the binding work.
- https://github.com/Binary-Eater/linux/commit/f6dc91a0eb7eaa6eca08bc246da3372e59ef022d
- https://github.com/Binary-Eater/linux/commit/383a97f7a68fe083013e5f8fc11878361b73a022
DISCLAIMER: The work for this happened on Feb. 22-23, 2025, but I did not have time to write the related blog that weekend.
Initial bindings and macro design
The kernel has done a lot to work to simplify the C ffi in Rust signficantly.
Specifically, the Opaque
struct under rust/kernel/types.rs
is very helpful.
#[repr(transparent)]
pub struct Device(Opaque<bindings::hid_device>);
#[repr(transparent)]
pub struct DeviceId(Opaque<bindings::hid_device_id>);
What we are doing here is defining two single element "tuple structs" where the
first element is FFI object that is allowed to make guarantees Rust normally
would not permit. This will be very useful for passing data in and out of the C
api layer. We use the #[repr(transparent)]
directive to ensure the Rust types
we are declaring will have the same memory layout as the C ffi object types
being used in the single element tuple structs.
By guaranteeing the memory layout is the same, we can implement static methods for the type as follows.
impl Device {
unsafe fn from_ptr<'a>(ptr: *mut bindings::hid_device) -> &'a mut Self {
let ptr = ptr.cast::<Self>();
unsafe { &mut *ptr };
}
pub fn vendor(&self) -> u32 {
let hdev = self.0.get();
unsafe { (*hdev).vendor }
}
// Omitted the rest
}
impl DeviceId {
unsafe fn from_ptr<'a>(ptr: *mut bindings::hid_device_id) -> &'a mut Self {
let ptr = ptr.cast::<Self>();
unsafe { &mut *ptr };
}
pub fn vendor(&self) -> u32 {
let hdev_id = self.0.get();
unsafe { (*hdev_id).vendor }
}
// Omitted the rest
}
We define from_ptr
methods that enable the Rust layer to represent the C types
using the memory layout equivalent Rust types. We then define some Rust getters
to "safely" access the underlying C data. I need to go back later and add the
SAFETY
comments in the code.
Driver adapters and vtables
The Linux kernel module registration interface is well-designed with regards to C semantics. Bridging that over to Rust takes quite a bit of massaging. The rust binding code had a habit of utilizing a "adapter" and a "vtable". Lets first look at the way HID drivers are instantiated in C before delving into the Rust code.
/**
* struct hid_driver
* @name: driver name (e.g. "Footech_bar-wheel")
* @id_table: which devices is this driver for (must be non-NULL for probe
* to be called)
* @dyn_list: list of dynamically added device ids
* @dyn_lock: lock protecting @dyn_list
* @match: check if the given device is handled by this driver
* @probe: new device inserted
* @remove: device removed (NULL if not a hot-plug capable driver)
* @report_table: on which reports to call raw_event (NULL means all)
* @raw_event: if report in report_table, this hook is called (NULL means nop)
* @usage_table: on which events to call event (NULL means all)
* @event: if usage in usage_table, this hook is called (NULL means nop)
* @report: this hook is called after parsing a report (NULL means nop)
* @report_fixup: called before report descriptor parsing (NULL means nop)
* @input_mapping: invoked on input registering before mapping an usage
* @input_mapped: invoked on input registering after mapping an usage
* @input_configured: invoked just before the device is registered
* @feature_mapping: invoked on feature registering
* @suspend: invoked on suspend (NULL means nop)
* @resume: invoked on resume if device was not reset (NULL means nop)
* @reset_resume: invoked on resume if device was reset (NULL means nop)
*/
static struct hid_driver shield_driver = {
.name = "shield",
.id_table = shield_devices,
.input_mapping = android_input_mapping,
.probe = shield_probe,
.remove = shield_remove,
.raw_event = shield_raw_event,
.driver = {
.dev_groups = shield_device_groups,
},
};
We see that the hid_driver
struct instance used for the hid-nvidia-shield
driver does not instantiate all members of the struct. This is common practice
for structs used to bind driver functionality. However, this does not play well
with Rust, which essentially requires all members of a struct to be initialized.
We will need some involved logic to work around this.
struct Adapter<T: Driver> {
_p: PhantomData<T>,
}
impl<T: Driver> Adapter<T> {
unsafe extern "C" fn probe_callback(
hdev: *mut bindings::hid_device,
hdev_id: *mut bindings::hid_device_id,
) -> crate::ffi::c_int {
from_result(|| {
let dev = unsafe { Device::from_ptr(hdev) };
let dev_id = unsafe { DeviceId::from_ptr(hdev_id) };
T::probe(dev, dev_id)?;
Ok(0)
})
}
unsafe extern "C" fn remove_callback(hdev: *mut bindings::hid_device,) -> crate::ffi::c_int {
from_result(|| {
let dev = unsafe { Device::from_ptr(hdev) };
T::remove(dev)?;
Ok(0)
})
}
}
We define an Adapter
struct capable of providing C linkage functions for
feedback to C HID API. We make these C linkage functions call the native Rust
HID Driver
trait methods and provide C style return codes.
We enabled the Rust HID Driver
trait to have a vtable
implementation, which
means trait implementers do not have to define all the trait methods. This means
that we need to handle the case when HID methods methods are missing. We create
a DriverVTable
type that is essentially mapped to hid_driver
. We also a
Driver
trair generic create_hid_driver
function to instantiate the wrapped
hid_driver
type.
#[repr(transparent)]
pub struct DriverVTable(Opaque<bindings::hid_driver>);
// SAFETY: `DriverVTable` doesn't expose any &self method to access internal data, so it's safe to
// share `&DriverVTable` across execution context boundaries.
unsafe impl Sync for DriverVTable {}
pub const fn create_hid_driver<T: Driver>(name: &'static CStr) -> DriverVTable {
DriverVTable(Opaque::new(bindings::hid_driver {
name: name.as_char_ptr(),
id_table: /* TODO */,
probe: if T::HAS_PROBE {
Some(Adapter::<T>::probe_callback)
} else {
None
},
remove: if T::HAS_REMOVE {
Some(Adapter::<T>::remove_callback)
} else {
None
},
// SAFETY: The rest is zeroed out to initialize `struct hid_driver`,
// sets `Option<&F>` to be `None`.
..unsafe { core::mem::MaybeUninit::<bindings::hid_driver>::zeroed().assume_init() }
}))
}
Registering the driver
Normally for a C HID driver, we would call the module_hid_driver
macro to
instantiate the HID driver. Calling C-style macros from Rust is not an option.
We can traverse that the macro eventually calls the __hid_register_driver
function, which we will want to call from Rust. Similarly, we will want to call
hid_unregister_driver
when unbinding the driver. Both functions can be found
in drivers/hid/hid-core.c
.
We will have to utilize a a new struct and macro in Rust to provide the
equivalent functionality for a Rust HID driver. Lets first explore the
Registration
struct and implementation.
pub struct Registration {
driver: Pin<&'static mut DriverVTable>,
}
unsafe impl Send for Registration {}
impl Registration {
pub fn register(
module: &'static crate::ThisModule,
driver: Pin<&'static mut DriverVTable>,
name: &'static CStr,
) -> Result<Self> {
to_result(unsafe {
bindings::__hid_register_driver(driver.0.get(), module.0, name.as_char_ptr())
})?;
Ok(Registration { driver })
}
}
impl Drop for Registration {
fn drop(&mut self) {
unsafe {
bindings::hid_unregister_driver(self.driver.0.get())
};
}
}
The register
function is the safe Rust wrapper for the __hid_register_driver
C call. We essentially use the Register
struct for unregistering the driver
when the driver is no longer binded. We do this by implementing the Drop
trait
and calling hid_unregister_driver
in drop
.
Working with the Register
struct directly in a Rust HID driver would be
clunky, so we can instead provide a Rust macro to mask the complexity.
#[macro_export]
macro_rules! module_hid_driver {
(driver: $($driver:ident), name: $($name:expr), $(f:tt)*) => {
struct Module {
_reg: $crate::hid::Registration,
}
$crate::prelude::module! {
type: Module,
name: $($name),
$($f)*
}
const _: () = {
static mut DRIVER: $crate::hid::DriverVTable = $($crate::hid::create_hid_driver::<$driver>(NAME, /* TODO pass ID_TABLE */));
};
}
}
The macro_rules!
for module_hid_driver
here is very simple. Also, I need to
make updates to the signature of create_hid_driver
due to my changes to device
ID table handling. We call the prelude module!
macro to make the newly define
Module
struct implement what is needed to be a Rust kernel module type.
Dealing with the HID device ID table
Handling the HID device ID table was one of the most architecturally challenging aspects of this project for me. I have lost count of the number of design patterns I have scratched while trying to settle on how to handle this. To begin with, lets take a look at how this is handled in C.
static const struct hid_device_id shield_devices[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NVIDIA,
USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NVIDIA,
USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER) },
{ }
};
We have a C style array where we do not explicitly state the size on the left-hand side (lhs) and have a "terminating" element in the right-hand side (rhs). This design pattern is completely not compatible with Rust. Rust arrays must declare their explicit size in the left-hand side and do not trivially support the terminator pattern. This made the design tricky. I eventually converged to using macros to assist with the arbitrary-sized structure.
The first step was to make the HID_USB_DEVICE
equivalent in Rust.
#[macro_export]
macro_rules! usb_device {
(vendor: $($vendor:expr), product: $($product:expr)) => {
DeviceId(Opaque::new(bindings::hid_device_id {
bus: 0x3, /* BUS_USB */
vendor: $($vendor),
product: $($product),
// SAFETY: The rest is zeroed out to initialize `struct hid_device_id`,
// sets `Option<&F>` to be `None`.
..unsafe { core::mem::MaybeUninit::<bindings::hid_device_id>::zeroed().assume_init() }
}))
}
}
The usb_device
macro serves as the Rust equivalent. To deal with terminating
the table, I made the following macro for internal use only in the hid
Rust
module.
macro_rules! term {
() => {
DeviceId(Opaque::new(bindings::hid_device_id {
// SAFETY: The rest is zeroed out to initialize `struct hid_device_id`,
// sets `Option<&F>` to be `None`.
..unsafe { $crate::core::mem::MaybeUninit::<bindings::hid_device_id>::zeroed().assume_init() }
}))
}
}
I realize now that this term
macro is useless and will be getting rid of it.
It's a lot of back and forth on the design. Here is an updated variant of
module_hid_driver
macro for creating a id table structure.
#[macro_export]
macro_rules! module_hid_driver {
(@replace_expr $_t:tt $sub:expr) => {$sub};
(@count_devices $($x:expr),*) => {
0usize $(+ $crate::module_hid_driver!(@replace_expr $x 1usize))*
};
(driver: $($driver:ident), id_table: [$($dev_id:expr),+ $(,)?], name: $($name:expr), $(f:tt)*) => {
struct Module {
_reg: $crate::hid::Registration,
}
$crate::prelude::module! {
type: Module,
name: $($name),
$($f)*
}
const _: () = {
const NAME: &'static Cstr = $crate::c_str!($($name));
static ID_TABLE: [$crate::bindings::hid_device_id;
$crate::module_hid_driver!(@count_devices $($dev_id),+) + 1] = [
$($dev_id.as_ptr()),+,
$crate::bindings::hid_device_id {
// SAFETY: All is zeroed out to initialize `struct hid_device_id`,
// sets `Option<&F>` to be `None`.
..unsafe { $crate::core::mem::MaybeUninit::<$crate::bindings::hid_device_id>::zeroed().assume_init() }
},
];
static mut DRIVER: $crate::hid::DriverVTable = $($crate::hid::create_hid_driver::<$driver>(NAME, /* TODO pass ID_TABLE */));
};
}
}
Truthfully, exposing an array of bindings::hid_device_id
is broken. Instead, I
should build a DeviceId
array and pass the raw pointer of the underlying
Opaque
type to the first element to the C API.
Next step: probing
With all this work in place, it should be trivial to start probing HID devices and exercise the correctness of the bindings.