|
| 1 | +--- |
| 2 | +title: "Supporting USB Monitor Control in the Linux kernel - Weekend 2: Prototyping" |
| 3 | +tags: ["kernel", "display", "hid", "usb monitor control"] |
| 4 | +date: 2025-02-16T17:56:38-08:00 |
| 5 | +draft: false |
| 6 | +--- |
| 7 | + |
| 8 | +Continuing with my weekend series on my Rust driver development work, I have |
| 9 | +committed some outline code for what a HID driver in Rust should look like and |
| 10 | +an outline for the ~Driver~ trait for the ~hid~ module. |
| 11 | + |
| 12 | +[[https://github.com/Binary-Eater/linux/commit/5072473328d3448e20a1cab43f6c23808af75a82]] |
| 13 | + |
| 14 | +* It is really small and seems to nothing. What's the point? |
| 15 | + |
| 16 | +The number one concern I have when building Rust interfaces for the linux kernel |
| 17 | +is having C layers penitrate into the Rust driver. I have seen this with network |
| 18 | +phy drivers in the linux kernel. Lets take a look at this snippet from |
| 19 | +~rust/kernel/net/phy.rs~. |
| 20 | + |
| 21 | +#+BEGIN_SRC rust |
| 22 | + /// Driver implementation for a particular PHY type. |
| 23 | + /// |
| 24 | + /// This trait is used to create a [`DriverVTable`]. |
| 25 | + #[vtable] |
| 26 | + pub trait Driver { |
| 27 | + /// Defines certain other features this PHY supports. |
| 28 | + /// It is a combination of the flags in the [`flags`] module. |
| 29 | + const FLAGS: u32 = 0; |
| 30 | + |
| 31 | + /// The friendly name of this PHY type. |
| 32 | + const NAME: &'static CStr; |
| 33 | + |
| 34 | + // Code omitted... |
| 35 | + } |
| 36 | +#+END_SRC |
| 37 | + |
| 38 | +The Rust phy ~Driver~ trait exposes a ~CStr~ variable for phy ~Driver~ trait |
| 39 | +implementers to define. Lets look at the ~CStr~ type declaration under |
| 40 | +~rust/kernel/str.rs~. |
| 41 | + |
| 42 | +#+BEGIN_SRC rust |
| 43 | + /// A string that is guaranteed to have exactly one `NUL` byte, which is at the |
| 44 | + /// end. |
| 45 | + /// |
| 46 | + /// Used for interoperability with kernel APIs that take C strings. |
| 47 | + #[repr(transparent)] |
| 48 | + pub struct CStr([u8]); |
| 49 | +#+END_SRC |
| 50 | + |
| 51 | +This type is purely meant for C string interop and should not be exposed in Rust |
| 52 | +API utilizers in the kernel. Yet here we have the phy ~Driver~ implementation |
| 53 | +exposing ~CStr~ directly in drivers. Drivers ~drivers/net/phy/qt2025.rs~ and |
| 54 | +~drivers/net/phy/ax88796b_rust.rs~ end up needing to utilize the ~c_str!~ macro |
| 55 | +in high-level Rust driver code because of this design choice. |
| 56 | + |
| 57 | +Surprisingly, I had to put a lot of thought into the short amount of code I |
| 58 | +wrote. One of the most time consuming aspects was the implementation of the HID |
| 59 | +device id table used for deciding what devices will be probed against the |
| 60 | +driver. I prototype ~IdTable~ as a static lifetime vector of ~hid::DeviceId~ |
| 61 | +under the ~kernel::hid::Driver~ trait. |
| 62 | + |
| 63 | +#+BEGIN_SRC rust |
| 64 | + const IdTable: &'static Vec<DeviceId>; |
| 65 | +#+END_SRC |
| 66 | + |
| 67 | +Now in the ~hid-monitor-control.rs~ driver implementation, the definition looks |
| 68 | +like below. |
| 69 | + |
| 70 | +#+BEGIN_SRC rust |
| 71 | + /* |
| 72 | + * TODO figure out type. If type is too flexible, might make sense to make |
| 73 | + * the id table a part of the driver macro |
| 74 | + */ |
| 75 | + const IdTable: &'static Vec<hid::DeviceId> = vec![ |
| 76 | + hid::usb_device! { |
| 77 | + vendor_id: /* TODO fill in */, |
| 78 | + device_id: /* TODO fill in */, |
| 79 | + }, |
| 80 | + ]; |
| 81 | +#+END_SRC |
| 82 | + |
| 83 | +This seems obvious. Well, it does after you have see it. Currently, I am |
| 84 | +noticing an overuse of Rust ~macro_rules!~ in the kernel tree for handling |
| 85 | +arbitrary sized structures in the driver module declaration. ~module_phy_driver~ |
| 86 | +has quite a bit of macro logic just for traversing through arbitrary sized |
| 87 | +structures at compile time. |
| 88 | + |
| 89 | +#+BEGIN_SRC rust |
| 90 | + #[macro_export] |
| 91 | + macro_rules! module_phy_driver { |
| 92 | + (@replace_expr $_t:tt $sub:expr) => {$sub}; |
| 93 | + |
| 94 | + (@count_devices $($x:expr),*) => { |
| 95 | + 0usize $(+ $crate::module_phy_driver!(@replace_expr $x 1usize))* |
| 96 | + }; |
| 97 | + |
| 98 | + (@device_table [$($dev:expr),+]) => { |
| 99 | + // SAFETY: C will not read off the end of this constant since the last element is zero. |
| 100 | + const _DEVICE_TABLE: [$crate::bindings::mdio_device_id; |
| 101 | + $crate::module_phy_driver!(@count_devices $($dev),+) + 1] = [ |
| 102 | + $($dev.mdio_device_id()),+, |
| 103 | + $crate::bindings::mdio_device_id { |
| 104 | + phy_id: 0, |
| 105 | + phy_id_mask: 0 |
| 106 | + } |
| 107 | + ]; |
| 108 | + |
| 109 | + #[cfg(MODULE)] |
| 110 | + #[no_mangle] |
| 111 | + static __mod_device_table__mdio__phydev: [$crate::bindings::mdio_device_id; |
| 112 | + $crate::module_phy_driver!(@count_devices $($dev),+) + 1] = _DEVICE_TABLE; |
| 113 | + }; |
| 114 | + |
| 115 | + (drivers: [$($driver:ident),+ $(,)?], device_table: [$($dev:expr),+ $(,)?], $($f:tt)*) => { |
| 116 | + struct Module { |
| 117 | + _reg: $crate::net::phy::Registration, |
| 118 | + } |
| 119 | + |
| 120 | + $crate::prelude::module! { |
| 121 | + type: Module, |
| 122 | + $($f)* |
| 123 | + } |
| 124 | + |
| 125 | + const _: () = { |
| 126 | + static mut DRIVERS: [$crate::net::phy::DriverVTable; |
| 127 | + $crate::module_phy_driver!(@count_devices $($driver),+)] = |
| 128 | + [$($crate::net::phy::create_phy_driver::<$driver>()),+]; |
| 129 | + |
| 130 | + impl $crate::Module for Module { |
| 131 | + fn init(module: &'static $crate::ThisModule) -> Result<Self> { |
| 132 | + // SAFETY: The anonymous constant guarantees that nobody else can access |
| 133 | + // the `DRIVERS` static. The array is used only in the C side. |
| 134 | + let drivers = unsafe { &mut DRIVERS }; |
| 135 | + let mut reg = $crate::net::phy::Registration::register( |
| 136 | + module, |
| 137 | + ::core::pin::Pin::static_mut(drivers), |
| 138 | + )?; |
| 139 | + Ok(Module { _reg: reg }) |
| 140 | + } |
| 141 | + } |
| 142 | + }; |
| 143 | + |
| 144 | + $crate::module_phy_driver!(@device_table [$($dev),+]); |
| 145 | + } |
| 146 | + } |
| 147 | +#+END_SRC |
| 148 | + |
| 149 | +If you need some help understanding the above macro, I recommend taking a look |
| 150 | +at the ~macro_rules!~ section in the official Rust book and the doc comments in |
| 151 | +~rust/kernel/net/phy.rs~. |
| 152 | + |
| 153 | +By using a ~Vec~ in the ~Driver~ trait, I would needing such complexity in the |
| 154 | +~macro_rules!~. Since it has a const static lifetime, hopefully ~rustc~ will be |
| 155 | +able to compile time optimize unrolling the vector even if we do not explicitly |
| 156 | +do the list unrolling at compile time with a macro. If not, there is room for |
| 157 | +improvement with ~rustc~. |
| 158 | + |
| 159 | +A vector has to be used instead of an array because Rust does not permit |
| 160 | +declaring an array of arbitrary length that can then be inferred by the right |
| 161 | +hand side. In C, we can do the following with arrays (snippet taken from |
| 162 | +~drivers/hid/hid-nvidia-shield.c~). |
| 163 | + |
| 164 | +#+BEGIN_SRC c |
| 165 | +static const struct hid_device_id shield_devices[] = { |
| 166 | + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NVIDIA, |
| 167 | + USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER) }, |
| 168 | + { HID_USB_DEVICE(USB_VENDOR_ID_NVIDIA, |
| 169 | + USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER) }, |
| 170 | + { } |
| 171 | +}; |
| 172 | +MODULE_DEVICE_TABLE(hid, shield_devices); |
| 173 | +#+END_SRC |
| 174 | + |
| 175 | +The lhs declares a ~hid_device_id[]~ type where the size is defined by the rhs. |
| 176 | +In this case, ~shield_devices~ has a size of 3, including the terminating record |
| 177 | +at the end. This pattern that is common in C cannot be replicated in Rust. Rust |
| 178 | +requires an explicit size for the array declaration. Therefore, a ~Vec~ seems |
| 179 | +like the most comfortable substitute to use given the complexity of list |
| 180 | +unrolling with ~macro_rules!~. |
0 commit comments