forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 438
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <TheSven73@gmail.com>
- Loading branch information
Sven Van Asbroeck
committed
May 7, 2021
1 parent
428d64a
commit d028c14
Showing
11 changed files
with
550 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
|
||
use crate::error::{Error, KernelResult}; | ||
use crate::{bindings, c_types, CStr}; | ||
use alloc::boxed::Box; | ||
use core::marker::PhantomPinned; | ||
use core::mem::transmute; | ||
use core::pin::Pin; | ||
|
||
extern "C" { | ||
#[allow(improper_ctypes)] | ||
fn rust_helper_platform_get_drvdata( | ||
pdev: *const bindings::platform_device, | ||
) -> *mut c_types::c_void; | ||
|
||
#[allow(improper_ctypes)] | ||
fn rust_helper_platform_set_drvdata( | ||
pdev: *mut bindings::platform_device, | ||
data: *mut c_types::c_void, | ||
); | ||
} | ||
|
||
unsafe extern "C" fn probe_callback<T: PlatformDriver>( | ||
pdev: *mut bindings::platform_device, | ||
) -> c_types::c_int { | ||
let f = || { | ||
let drv_data = T::probe(&mut PlatformDevice::new(pdev))?; | ||
let drv_data = Box::try_new(drv_data)?; | ||
let drv_data = Box::into_raw(drv_data) as *mut c_types::c_void; | ||
Ok(drv_data) as KernelResult<_> | ||
}; | ||
// TODO don't we need to pin this? | ||
let ptr = match f() { | ||
Ok(ptr) => ptr, | ||
Err(e) => return e.to_kernel_errno(), | ||
}; | ||
rust_helper_platform_set_drvdata(pdev, ptr); | ||
0 | ||
} | ||
|
||
fn new_of_device_id(compatible: &CStr<'static>) -> KernelResult<bindings::of_device_id> { | ||
// TODO: | ||
// - fail at build time if compatible CStr doesn't fit. | ||
// - can we do this safely without transmute? | ||
let mut buf = [0_u8; 128]; | ||
if compatible.len() > buf.len() { | ||
return Err(Error::EINVAL); | ||
} | ||
// PANIC: this will never panic: `compatible` is not longer than `buf`. | ||
buf[..compatible.len()].copy_from_slice(compatible.as_bytes()); | ||
Ok(bindings::of_device_id { | ||
// SAFETY: re-interpretation from [u8] to [i8] of same length is always safe. | ||
compatible: unsafe { transmute::<[u8; 128], [i8; 128]>(buf) }, | ||
..Default::default() | ||
}) | ||
} | ||
|
||
unsafe extern "C" fn remove_callback<T: PlatformDriver>( | ||
pdev: *mut bindings::platform_device, | ||
) -> c_types::c_int { | ||
let ptr = rust_helper_platform_get_drvdata(pdev); | ||
let drv_data: Box<T::DrvData> = Box::from_raw(ptr as _); | ||
drop(drv_data); | ||
0 | ||
} | ||
|
||
/// A registration of a platform driver. | ||
#[derive(Default)] | ||
pub struct Registration { | ||
registered: bool, | ||
pdrv: bindings::platform_driver, | ||
of_table: [bindings::of_device_id; 2], | ||
_pin: PhantomPinned, | ||
} | ||
|
||
impl Registration { | ||
fn register<P: PlatformDriver>( | ||
self: Pin<&mut Self>, | ||
name: CStr<'static>, | ||
of_id: CStr<'static>, | ||
module: &'static crate::ThisModule, | ||
) -> KernelResult { | ||
// SAFETY: We must ensure that we never move out of `this`. | ||
let this = unsafe { self.get_unchecked_mut() }; | ||
if this.registered { | ||
// Already registered. | ||
return Err(Error::EINVAL); | ||
} | ||
// TODO should create a variable size table here. | ||
this.of_table[0] = new_of_device_id(&of_id)?; | ||
// SAFETY: `name` pointer has static lifetime. | ||
// `of_table` points to memory in `this`, which lives as least as | ||
// long as the `platform_device` registration. | ||
// `module.0` lives as least as long as the module. | ||
this.pdrv.driver.name = name.as_ptr() as *const c_types::c_char; | ||
this.pdrv.driver.of_match_table = this.of_table.as_ptr(); | ||
this.pdrv.probe = Some(probe_callback::<P>); | ||
this.pdrv.remove = Some(remove_callback::<P>); | ||
let ret = unsafe { bindings::__platform_driver_register(&mut this.pdrv, module.0) }; | ||
if ret < 0 { | ||
return Err(Error::from_kernel_errno(ret)); | ||
} | ||
this.registered = true; | ||
Ok(()) | ||
} | ||
|
||
pub fn new_pinned<P: PlatformDriver>( | ||
name: CStr<'static>, | ||
of_id: CStr<'static>, | ||
module: &'static crate::ThisModule, | ||
) -> KernelResult<Pin<Box<Self>>> { | ||
let mut r = Pin::from(Box::try_new(Self::default())?); | ||
r.as_mut().register::<P>(name, of_id, module)?; | ||
Ok(r) | ||
} | ||
} | ||
|
||
impl Drop for Registration { | ||
fn drop(&mut self) { | ||
if self.registered { | ||
// SAFETY: if `registered` is true, then `self.pdev` was registered | ||
// previously, which means `platform_driver_unregister` is always | ||
// safe to call. | ||
unsafe { bindings::platform_driver_unregister(&mut self.pdrv) } | ||
} | ||
} | ||
} | ||
|
||
// SAFETY: `Registration` does not expose any of its state across threads | ||
// (it is fine for multiple threads to have a shared reference to it). | ||
unsafe impl Sync for Registration {} | ||
|
||
pub struct PointerWrapper<T: ?Sized>(*mut T); | ||
|
||
impl<T: ?Sized> PointerWrapper<T> { | ||
fn new(ptr: *mut T) -> Self { | ||
Self(ptr) | ||
} | ||
|
||
pub(crate) fn to_ptr(&self) -> *mut T { | ||
self.0 | ||
} | ||
} | ||
|
||
/// Rust abstraction of a kernel `struct platform_device`. | ||
pub type PlatformDevice = PointerWrapper<bindings::platform_device>; | ||
|
||
/// Rust abstraction of a kernel `struct device`. | ||
pub(crate) trait Device { | ||
fn to_dev_ptr(&self) -> *mut bindings::device; | ||
} | ||
|
||
impl Device for PlatformDevice { | ||
fn to_dev_ptr(&self) -> *mut bindings::device { | ||
// SAFETY: a `struct platform_device` is-a `struct device`, and | ||
// can always be accessed by a pointer to its inner `struct device`. | ||
unsafe { &mut (*self.0).dev } | ||
} | ||
} | ||
|
||
/// Rust abstraction of a kernel `struct platform_driver` | ||
pub trait PlatformDriver { | ||
/// Per-instance driver data (or private driver data) | ||
type DrvData; | ||
|
||
fn probe(pdev: &mut PlatformDevice) -> KernelResult<Self::DrvData>; | ||
} |
Oops, something went wrong.