Skip to content

Commit a7aa9ff

Browse files
committed
content: usb-monitor-control weekend 2
Describe my challenges with interface design. Signed-off-by: Rahul Rameshbabu <[email protected]>
1 parent f674537 commit a7aa9ff

File tree

1 file changed

+180
-0
lines changed

1 file changed

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

Comments
 (0)