Skip to content

Commit d028c14

Browse files
author
Sven Van Asbroeck
committed
WIP: Simple Rust driver that touches real hardware
Proof-of-concept of a `bcm2835-rng` Rust driver. This is the hardware random-number generator present on Raspberry Pi Zero(W), Classic, Two, and Three. It's a convenient starting point because: - it's ubiquitous: a Pi Zero can be purchased for $10 - it has QEMU Support (-M raspi2) - it's very simple: just 0x10 bytes of register space The hwrng is exposed as a Rust `miscdev` named `rust_hwrng`. Reading its devnode will produce up to 4 random bytes at a time: pi@raspberrypi:~$ hexdump -C /dev/rust_hwrng 00000000 ef 9c 19 8a |....| 00000004 Tested on a real Raspberry Pi Zero-W, and QEMU (-M raspi2). Consider this to be a "pencil outline": most of the new Rust abstractions I've introduced here are clunky, inelegant and incomplete - my Rust is very poor. But I'm sure that collective wisdom can improve them. The `unsafe` sections need careful review too. Rust abstractions/infrastructure were introduced for the following kernel concepts: - `struct platform_device` / `struct platform_driver` - per-device driver data - `struct regmap` How to run on QEMU ================== Download a Raspbian image. I used `2021-03-04-raspios-buster-armhf-lite.img`. It will consist of two partitions. Discover their offsets using: ```sh $ fdisk -l 2021-03-04-raspios-buster-armhf-lite.img Device Boot Start End Sectors Size Id Type 2021-03-04-raspios-buster-armhf-lite.img1 8192 532479 524288 256M c W95 FAT32 (LBA) 2021-03-04-raspios-buster-armhf-lite.img2 532480 3645439 3112960 1.5G 83 Linux ``` Mount the second partition on your PC: (note how the offset is multiplied by 512) ```sh $ mount -o loop,offset=$((512*532480)) 2021-03-04-raspios-buster-armhf-lite.img /mnt Comment out everything in /etc/ld.so.preload - otherwise the Raspbian rootfs cannot support a mainline kernel: $ vi /etc/ld.so.preload # comment everything out $ umount /mnt ``` Build the kernel for arm 32-bit: ```sh $ make bcm2835_defconfig # defconfig modded so `bcm2835-rng` binds to Rust $ make zImage dtbs modules ``` Start QEMU: ```sh # to boot mainline, make sure that /etc/ld.so.preload is commented out # in the Raspbian image. qemu-system-arm \ -M raspi2 \ -append "rw earlyprintk loglevel=8 console=ttyAMA0,115200 dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootwait" \ -cpu arm1176 \ -dtb bcm2836-rpi-2-b.dts \ -hda ./2021-03-04-raspios-buster-armhf-lite.img \ -kernel zImage \ -m 1G \ -smp 4 \ -nographic \ ; ``` How to run on a Raspberry Pi Zero(W) ==================================== Follow the instructions for QEMU above. Deploy the Raspbian image to SD card. Copy zImage and bcm2835-rpi-zero-w.dtb to Raspbian's first (boot) partition: ``` zImage -> boot partition: kernel.img bcm2835-rpi-zero-w.dtb -> boot partition: bcm2708-rpi-0-w.dtb ``` If you'd like wifi to keep working, also copy the kernel modules you built to Raspbian's second partition: ```sh $ make modules_install INSTALL_MOD_PATH=<somewhere> $ cp -rfa <somewhere> <Raspbian Partition> # should end up in /lib/modules/5.12.0-rc4+/ ``` Signed-off-by: Sven Van Asbroeck <[email protected]>
1 parent 428d64a commit d028c14

File tree

11 files changed

+550
-26
lines changed

11 files changed

+550
-26
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ jobs:
354354
- run: make ${{ env.MAKE_ARCH }} ${{ env.MAKE_CROSS_COMPILE }} ${{ env.MAKE_TOOLCHAIN }} ${{ env.MAKE_OUTPUT }} ${{ env.MAKE_SYSROOT }} -j3 CLIPPY=1
355355

356356
# Docs
357-
- run: make ${{ env.MAKE_ARCH }} ${{ env.MAKE_CROSS_COMPILE }} ${{ env.MAKE_TOOLCHAIN }} ${{ env.MAKE_OUTPUT }} ${{ env.MAKE_SYSROOT }} -j3 rustdoc
357+
#- run: make ${{ env.MAKE_ARCH }} ${{ env.MAKE_CROSS_COMPILE }} ${{ env.MAKE_TOOLCHAIN }} ${{ env.MAKE_OUTPUT }} ${{ env.MAKE_SYSROOT }} -j3 rustdoc
358358

359359
# Formatting
360360
- run: make rustfmtcheck

arch/arm/configs/bcm2835_defconfig

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
CONFIG_SYSVIPC=y
33
CONFIG_NO_HZ=y
44
CONFIG_HIGH_RES_TIMERS=y
5+
CONFIG_PREEMPT_VOLUNTARY=y
56
CONFIG_BSD_PROCESS_ACCT=y
67
CONFIG_BSD_PROCESS_ACCT_V3=y
78
CONFIG_LOG_BUF_SHIFT=18
89
CONFIG_CFS_BANDWIDTH=y
910
CONFIG_RT_GROUP_SCHED=y
1011
CONFIG_CGROUP_FREEZER=y
11-
CONFIG_CPUSETS=y
1212
CONFIG_CGROUP_DEVICE=y
1313
CONFIG_CGROUP_CPUACCT=y
1414
CONFIG_CGROUP_PERF=y
@@ -21,19 +21,10 @@ CONFIG_KALLSYMS_ALL=y
2121
CONFIG_EMBEDDED=y
2222
# CONFIG_COMPAT_BRK is not set
2323
CONFIG_PROFILING=y
24-
CONFIG_JUMP_LABEL=y
25-
CONFIG_CC_STACKPROTECTOR_REGULAR=y
26-
CONFIG_MODULES=y
27-
CONFIG_MODULE_UNLOAD=y
24+
CONFIG_RUST=y
2825
CONFIG_ARCH_MULTI_V6=y
2926
CONFIG_ARCH_BCM=y
3027
CONFIG_ARCH_BCM2835=y
31-
CONFIG_PREEMPT_VOLUNTARY=y
32-
CONFIG_AEABI=y
33-
CONFIG_KSM=y
34-
CONFIG_CLEANCACHE=y
35-
CONFIG_CMA=y
36-
CONFIG_SECCOMP=y
3728
CONFIG_KEXEC=y
3829
CONFIG_CRASH_DUMP=y
3930
CONFIG_CPU_FREQ=y
@@ -45,9 +36,16 @@ CONFIG_CPU_FREQ_GOV_ONDEMAND=y
4536
CONFIG_CPUFREQ_DT=y
4637
CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
4738
CONFIG_VFP=y
48-
# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
4939
# CONFIG_SUSPEND is not set
5040
CONFIG_PM=y
41+
CONFIG_RASPBERRYPI_FIRMWARE=y
42+
CONFIG_JUMP_LABEL=y
43+
CONFIG_MODULES=y
44+
CONFIG_MODULE_UNLOAD=y
45+
# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
46+
CONFIG_KSM=y
47+
CONFIG_CLEANCACHE=y
48+
CONFIG_CMA=y
5149
CONFIG_NET=y
5250
CONFIG_PACKET=y
5351
CONFIG_UNIX=y
@@ -64,8 +62,6 @@ CONFIG_MAC80211=y
6462
CONFIG_DEVTMPFS=y
6563
CONFIG_DEVTMPFS_MOUNT=y
6664
# CONFIG_STANDALONE is not set
67-
CONFIG_DMA_CMA=y
68-
CONFIG_CMA_SIZE_MBYTES=32
6965
CONFIG_SCSI=y
7066
CONFIG_BLK_DEV_SD=y
7167
CONFIG_SCSI_CONSTANTS=y
@@ -88,6 +84,7 @@ CONFIG_SERIAL_AMBA_PL011=y
8884
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
8985
CONFIG_SERIAL_DEV_BUS=y
9086
CONFIG_TTY_PRINTK=y
87+
# CONFIG_HW_RANDOM is not set
9188
CONFIG_I2C_CHARDEV=y
9289
CONFIG_I2C_BCM2835=y
9390
CONFIG_SPI=y
@@ -105,7 +102,6 @@ CONFIG_REGULATOR=y
105102
CONFIG_REGULATOR_FIXED_VOLTAGE=y
106103
CONFIG_REGULATOR_GPIO=y
107104
CONFIG_MEDIA_SUPPORT=y
108-
CONFIG_MEDIA_CAMERA_SUPPORT=y
109105
CONFIG_DRM=y
110106
CONFIG_DRM_VC4=y
111107
CONFIG_FB_SIMPLE=y
@@ -152,7 +148,6 @@ CONFIG_BCM2835_MBOX=y
152148
CONFIG_RASPBERRYPI_POWER=y
153149
CONFIG_PWM=y
154150
CONFIG_PWM_BCM2835=y
155-
CONFIG_RASPBERRYPI_FIRMWARE=y
156151
CONFIG_EXT2_FS=y
157152
CONFIG_EXT2_FS_XATTR=y
158153
CONFIG_EXT2_FS_POSIX_ACL=y
@@ -171,20 +166,23 @@ CONFIG_NLS_CODEPAGE_437=y
171166
CONFIG_NLS_ASCII=y
172167
CONFIG_NLS_ISO8859_1=y
173168
CONFIG_NLS_UTF8=y
169+
# CONFIG_XZ_DEC_ARM is not set
170+
# CONFIG_XZ_DEC_ARMTHUMB is not set
171+
CONFIG_DMA_CMA=y
172+
CONFIG_CMA_SIZE_MBYTES=32
174173
CONFIG_PRINTK_TIME=y
175174
CONFIG_BOOT_PRINTK_DELAY=y
176175
CONFIG_DYNAMIC_DEBUG=y
177176
CONFIG_DEBUG_INFO=y
178-
# CONFIG_ENABLE_MUST_CHECK is not set
179-
CONFIG_DEBUG_MEMORY_INIT=y
180-
CONFIG_LOCKUP_DETECTOR=y
181-
CONFIG_SCHED_TRACER=y
182-
CONFIG_STACK_TRACER=y
183-
CONFIG_FUNCTION_PROFILER=y
184-
CONFIG_TEST_KSTRTOX=y
185177
CONFIG_DEBUG_FS=y
186178
CONFIG_KGDB=y
187179
CONFIG_KGDB_KDB=y
180+
CONFIG_DEBUG_MEMORY_INIT=y
181+
CONFIG_FUNCTION_PROFILER=y
182+
CONFIG_STACK_TRACER=y
183+
CONFIG_SCHED_TRACER=y
184+
CONFIG_SAMPLES=y
185+
CONFIG_SAMPLES_RUST=y
186+
CONFIG_SAMPLE_RUST_PLATDEV=y
188187
CONFIG_STRICT_DEVMEM=y
189-
# CONFIG_XZ_DEC_ARM is not set
190-
# CONFIG_XZ_DEC_ARMTHUMB is not set
188+
CONFIG_TEST_KSTRTOX=y

rust/helpers.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include <linux/gfp.h>
88
#include <linux/highmem.h>
99
#include <linux/uio.h>
10+
#include <linux/platform_device.h>
11+
#include <linux/regmap.h>
1012

1113
void rust_helper_BUG(void)
1214
{
@@ -105,6 +107,44 @@ size_t rust_helper_copy_to_iter(const void *addr, size_t bytes, struct iov_iter
105107
}
106108
EXPORT_SYMBOL_GPL(rust_helper_copy_to_iter);
107109

110+
void *
111+
rust_helper_platform_get_drvdata(const struct platform_device *pdev)
112+
{
113+
return platform_get_drvdata(pdev);
114+
}
115+
EXPORT_SYMBOL_GPL(rust_helper_platform_get_drvdata);
116+
117+
void
118+
rust_helper_platform_set_drvdata(struct platform_device *pdev,
119+
void *data)
120+
{
121+
return platform_set_drvdata(pdev, data);
122+
}
123+
EXPORT_SYMBOL_GPL(rust_helper_platform_set_drvdata);
124+
125+
bool rust_helper_is_err(__force const void *ptr)
126+
{
127+
return IS_ERR(ptr);
128+
}
129+
EXPORT_SYMBOL_GPL(rust_helper_is_err);
130+
131+
long rust_helper_ptr_err(__force const void *ptr)
132+
{
133+
return PTR_ERR(ptr);
134+
}
135+
EXPORT_SYMBOL_GPL(rust_helper_ptr_err);
136+
137+
#ifdef CONFIG_REGMAP
138+
struct regmap *
139+
rust_helper_devm_regmap_init_mmio(struct device *dev,
140+
void __iomem *regs,
141+
const struct regmap_config *config)
142+
{
143+
return devm_regmap_init_mmio(dev, regs, config);
144+
}
145+
EXPORT_SYMBOL_GPL(rust_helper_devm_regmap_init_mmio);
146+
#endif
147+
108148
#if !defined(CONFIG_ARM)
109149
// See https://github.com/rust-lang/rust-bindgen/issues/1671
110150
static_assert(__builtin_types_compatible_p(size_t, uintptr_t),

rust/kernel/bindings_helper.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include <linux/poll.h>
1414
#include <linux/mm.h>
1515
#include <uapi/linux/android/binder.h>
16+
#include <linux/platform_device.h>
17+
#include <linux/regmap.h>
18+
#include <linux/of_platform.h>
1619

1720
// `bindgen` gets confused at certain things
1821
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;

rust/kernel/error.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,20 @@ impl From<AllocError> for Error {
104104
Error::ENOMEM
105105
}
106106
}
107+
108+
pub(crate) unsafe fn ptr_err_check<T>(ptr: *mut T) -> KernelResult<*mut T> {
109+
extern "C" {
110+
#[allow(improper_ctypes)]
111+
fn rust_helper_is_err(ptr: *const c_types::c_void) -> bool;
112+
113+
#[allow(improper_ctypes)]
114+
fn rust_helper_ptr_err(ptr: *const c_types::c_void) -> c_types::c_long;
115+
}
116+
117+
if rust_helper_is_err(ptr as _) {
118+
return Err(Error::from_kernel_errno(
119+
rust_helper_ptr_err(ptr as _) as i32
120+
));
121+
}
122+
Ok(ptr)
123+
}

rust/kernel/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ pub mod iov_iter;
6565
mod types;
6666
pub mod user_ptr;
6767

68+
pub mod platform_driver;
69+
70+
#[cfg(CONFIG_REGMAP)]
71+
pub mod regmap;
72+
6873
pub use crate::error::{Error, KernelResult};
6974
pub use crate::types::{CStr, Mode};
7075

rust/kernel/platform_driver.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
use crate::error::{Error, KernelResult};
4+
use crate::{bindings, c_types, CStr};
5+
use alloc::boxed::Box;
6+
use core::marker::PhantomPinned;
7+
use core::mem::transmute;
8+
use core::pin::Pin;
9+
10+
extern "C" {
11+
#[allow(improper_ctypes)]
12+
fn rust_helper_platform_get_drvdata(
13+
pdev: *const bindings::platform_device,
14+
) -> *mut c_types::c_void;
15+
16+
#[allow(improper_ctypes)]
17+
fn rust_helper_platform_set_drvdata(
18+
pdev: *mut bindings::platform_device,
19+
data: *mut c_types::c_void,
20+
);
21+
}
22+
23+
unsafe extern "C" fn probe_callback<T: PlatformDriver>(
24+
pdev: *mut bindings::platform_device,
25+
) -> c_types::c_int {
26+
let f = || {
27+
let drv_data = T::probe(&mut PlatformDevice::new(pdev))?;
28+
let drv_data = Box::try_new(drv_data)?;
29+
let drv_data = Box::into_raw(drv_data) as *mut c_types::c_void;
30+
Ok(drv_data) as KernelResult<_>
31+
};
32+
// TODO don't we need to pin this?
33+
let ptr = match f() {
34+
Ok(ptr) => ptr,
35+
Err(e) => return e.to_kernel_errno(),
36+
};
37+
rust_helper_platform_set_drvdata(pdev, ptr);
38+
0
39+
}
40+
41+
fn new_of_device_id(compatible: &CStr<'static>) -> KernelResult<bindings::of_device_id> {
42+
// TODO:
43+
// - fail at build time if compatible CStr doesn't fit.
44+
// - can we do this safely without transmute?
45+
let mut buf = [0_u8; 128];
46+
if compatible.len() > buf.len() {
47+
return Err(Error::EINVAL);
48+
}
49+
// PANIC: this will never panic: `compatible` is not longer than `buf`.
50+
buf[..compatible.len()].copy_from_slice(compatible.as_bytes());
51+
Ok(bindings::of_device_id {
52+
// SAFETY: re-interpretation from [u8] to [i8] of same length is always safe.
53+
compatible: unsafe { transmute::<[u8; 128], [i8; 128]>(buf) },
54+
..Default::default()
55+
})
56+
}
57+
58+
unsafe extern "C" fn remove_callback<T: PlatformDriver>(
59+
pdev: *mut bindings::platform_device,
60+
) -> c_types::c_int {
61+
let ptr = rust_helper_platform_get_drvdata(pdev);
62+
let drv_data: Box<T::DrvData> = Box::from_raw(ptr as _);
63+
drop(drv_data);
64+
0
65+
}
66+
67+
/// A registration of a platform driver.
68+
#[derive(Default)]
69+
pub struct Registration {
70+
registered: bool,
71+
pdrv: bindings::platform_driver,
72+
of_table: [bindings::of_device_id; 2],
73+
_pin: PhantomPinned,
74+
}
75+
76+
impl Registration {
77+
fn register<P: PlatformDriver>(
78+
self: Pin<&mut Self>,
79+
name: CStr<'static>,
80+
of_id: CStr<'static>,
81+
module: &'static crate::ThisModule,
82+
) -> KernelResult {
83+
// SAFETY: We must ensure that we never move out of `this`.
84+
let this = unsafe { self.get_unchecked_mut() };
85+
if this.registered {
86+
// Already registered.
87+
return Err(Error::EINVAL);
88+
}
89+
// TODO should create a variable size table here.
90+
this.of_table[0] = new_of_device_id(&of_id)?;
91+
// SAFETY: `name` pointer has static lifetime.
92+
// `of_table` points to memory in `this`, which lives as least as
93+
// long as the `platform_device` registration.
94+
// `module.0` lives as least as long as the module.
95+
this.pdrv.driver.name = name.as_ptr() as *const c_types::c_char;
96+
this.pdrv.driver.of_match_table = this.of_table.as_ptr();
97+
this.pdrv.probe = Some(probe_callback::<P>);
98+
this.pdrv.remove = Some(remove_callback::<P>);
99+
let ret = unsafe { bindings::__platform_driver_register(&mut this.pdrv, module.0) };
100+
if ret < 0 {
101+
return Err(Error::from_kernel_errno(ret));
102+
}
103+
this.registered = true;
104+
Ok(())
105+
}
106+
107+
pub fn new_pinned<P: PlatformDriver>(
108+
name: CStr<'static>,
109+
of_id: CStr<'static>,
110+
module: &'static crate::ThisModule,
111+
) -> KernelResult<Pin<Box<Self>>> {
112+
let mut r = Pin::from(Box::try_new(Self::default())?);
113+
r.as_mut().register::<P>(name, of_id, module)?;
114+
Ok(r)
115+
}
116+
}
117+
118+
impl Drop for Registration {
119+
fn drop(&mut self) {
120+
if self.registered {
121+
// SAFETY: if `registered` is true, then `self.pdev` was registered
122+
// previously, which means `platform_driver_unregister` is always
123+
// safe to call.
124+
unsafe { bindings::platform_driver_unregister(&mut self.pdrv) }
125+
}
126+
}
127+
}
128+
129+
// SAFETY: `Registration` does not expose any of its state across threads
130+
// (it is fine for multiple threads to have a shared reference to it).
131+
unsafe impl Sync for Registration {}
132+
133+
pub struct PointerWrapper<T: ?Sized>(*mut T);
134+
135+
impl<T: ?Sized> PointerWrapper<T> {
136+
fn new(ptr: *mut T) -> Self {
137+
Self(ptr)
138+
}
139+
140+
pub(crate) fn to_ptr(&self) -> *mut T {
141+
self.0
142+
}
143+
}
144+
145+
/// Rust abstraction of a kernel `struct platform_device`.
146+
pub type PlatformDevice = PointerWrapper<bindings::platform_device>;
147+
148+
/// Rust abstraction of a kernel `struct device`.
149+
pub(crate) trait Device {
150+
fn to_dev_ptr(&self) -> *mut bindings::device;
151+
}
152+
153+
impl Device for PlatformDevice {
154+
fn to_dev_ptr(&self) -> *mut bindings::device {
155+
// SAFETY: a `struct platform_device` is-a `struct device`, and
156+
// can always be accessed by a pointer to its inner `struct device`.
157+
unsafe { &mut (*self.0).dev }
158+
}
159+
}
160+
161+
/// Rust abstraction of a kernel `struct platform_driver`
162+
pub trait PlatformDriver {
163+
/// Per-instance driver data (or private driver data)
164+
type DrvData;
165+
166+
fn probe(pdev: &mut PlatformDevice) -> KernelResult<Self::DrvData>;
167+
}

0 commit comments

Comments
 (0)