Supporting USB Monitor Control in the Linux kernel - Weekend 2: Prototyping
Continuing with my weekend series on my Rust driver development work, I have
committed some outline code for what a HID driver in Rust should look like and
an outline for the Driver
trait for the hid
module.
https://github.com/Binary-Eater/linux/commit/5072473328d3448e20a1cab43f6c23808af75a82
It is really small and seems to nothing. What's the point?
The number one concern I have when building Rust interfaces for the linux kernel
is having C layers penitrate into the Rust driver. I have seen this with network
phy drivers in the linux kernel. Lets take a look at this snippet from
rust/kernel/net/phy.rs
.
/// Driver implementation for a particular PHY type.
///
/// This trait is used to create a [`DriverVTable`].
#[vtable]
pub trait Driver {
/// Defines certain other features this PHY supports.
/// It is a combination of the flags in the [`flags`] module.
const FLAGS: u32 = 0;
/// The friendly name of this PHY type.
const NAME: &'static CStr;
// Code omitted...
}
The Rust phy Driver
trait exposes a CStr
variable for phy Driver
trait
implementers to define. Lets look at the CStr
type declaration under
rust/kernel/str.rs
.
/// A string that is guaranteed to have exactly one `NUL` byte, which is at the
/// end.
///
/// Used for interoperability with kernel APIs that take C strings.
#[repr(transparent)]
pub struct CStr([u8]);
This type is purely meant for C string interop and should not be exposed in Rust
API utilizers in the kernel. Yet here we have the phy Driver
implementation
exposing CStr
directly in drivers. Drivers drivers/net/phy/qt2025.rs
and
drivers/net/phy/ax88796b_rust.rs
end up needing to utilize the c_str!
macro
in high-level Rust driver code because of this design choice.
Surprisingly, I had to put a lot of thought into the short amount of code I
wrote. One of the most time consuming aspects was the implementation of the HID
device id table used for deciding what devices will be probed against the
driver. I prototype IdTable
as a static lifetime vector of hid::DeviceId
under the kernel::hid::Driver
trait.
const IdTable: &'static Vec<DeviceId>;
Now in the hid-monitor-control.rs
driver implementation, the definition looks
like below.
/*
* TODO figure out type. If type is too flexible, might make sense to make
* the id table a part of the driver macro
*/
const IdTable: &'static Vec<hid::DeviceId> = vec![
hid::usb_device! {
vendor_id: /* TODO fill in */,
device_id: /* TODO fill in */,
},
];
This seems obvious. Well, it does after you have seem it. Currently, I am
noticing an overuse of Rust macro_rules!
in the kernel tree for handling
arbitrary sized structures in the driver module declaration. module_phy_driver
has quite a bit of macro logic just for traversing through arbitrary sized
structures at compile time.
#[macro_export]
macro_rules! module_phy_driver {
(@replace_expr $_t:tt $sub:expr) => {$sub};
(@count_devices $($x:expr),*) => {
0usize $(+ $crate::module_phy_driver!(@replace_expr $x 1usize))*
};
(@device_table [$($dev:expr),+]) => {
// SAFETY: C will not read off the end of this constant since the last element is zero.
const _DEVICE_TABLE: [$crate::bindings::mdio_device_id;
$crate::module_phy_driver!(@count_devices $($dev),+) + 1] = [
$($dev.mdio_device_id()),+,
$crate::bindings::mdio_device_id {
phy_id: 0,
phy_id_mask: 0
}
];
#[cfg(MODULE)]
#[no_mangle]
static __mod_device_table__mdio__phydev: [$crate::bindings::mdio_device_id;
$crate::module_phy_driver!(@count_devices $($dev),+) + 1] = _DEVICE_TABLE;
};
(drivers: [$($driver:ident),+ $(,)?], device_table: [$($dev:expr),+ $(,)?], $($f:tt)*) => {
struct Module {
_reg: $crate::net::phy::Registration,
}
$crate::prelude::module! {
type: Module,
$($f)*
}
const _: () = {
static mut DRIVERS: [$crate::net::phy::DriverVTable;
$crate::module_phy_driver!(@count_devices $($driver),+)] =
[$($crate::net::phy::create_phy_driver::<$driver>()),+];
impl $crate::Module for Module {
fn init(module: &'static $crate::ThisModule) -> Result<Self> {
// SAFETY: The anonymous constant guarantees that nobody else can access
// the `DRIVERS` static. The array is used only in the C side.
let drivers = unsafe { &mut DRIVERS };
let mut reg = $crate::net::phy::Registration::register(
module,
::core::pin::Pin::static_mut(drivers),
)?;
Ok(Module { _reg: reg })
}
}
};
$crate::module_phy_driver!(@device_table [$($dev),+]);
}
}
If you need some help understanding the above macro, I recommend taking a look
at the macro_rules!
section in the official Rust book and the doc comments in
rust/kernel/net/phy.rs
.
By using a Vec
in the Driver
trait, I would needing such complexity in the
macro_rules!
. Since it has a const static lifetime, hopefully rustc
will be
able to compile time optimize unrolling the vector even if we do not explicitly
do the list unrolling at compile time with a macro. If not, there is room for
improvement with rustc
.
A vector has to be used instead of an array because Rust does not permit
declaring an array of arbitrary length that can then be inferred by the right
hand side. In C, we can do the following with arrays (snippet taken from
drivers/hid/hid-nvidia-shield.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) },
{ }
};
MODULE_DEVICE_TABLE(hid, shield_devices);
The lhs declares a hid_device_id[]
type where the size is defined by the rhs.
In this case, shield_devices
has a size of 3, including the terminating record
at the end. This pattern that is common in C cannot be replicated in Rust. Rust
requires an explicit size for the array declaration. Therefore, a Vec
seems
like the most comfortable substitute to use given the complexity of list
unrolling with macro_rules!
.