Skip to content

Commit

Permalink
WIP: Simple Rust driver that touches real hardware
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 11 changed files with 550 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ jobs:
- run: make ${{ env.MAKE_ARCH }} ${{ env.MAKE_CROSS_COMPILE }} ${{ env.MAKE_TOOLCHAIN }} ${{ env.MAKE_OUTPUT }} ${{ env.MAKE_SYSROOT }} -j3 CLIPPY=1

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

# Formatting
- run: make rustfmtcheck
48 changes: 23 additions & 25 deletions arch/arm/configs/bcm2835_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
CONFIG_SYSVIPC=y
CONFIG_NO_HZ=y
CONFIG_HIGH_RES_TIMERS=y
CONFIG_PREEMPT_VOLUNTARY=y
CONFIG_BSD_PROCESS_ACCT=y
CONFIG_BSD_PROCESS_ACCT_V3=y
CONFIG_LOG_BUF_SHIFT=18
CONFIG_CFS_BANDWIDTH=y
CONFIG_RT_GROUP_SCHED=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CPUSETS=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_PERF=y
Expand All @@ -21,19 +21,10 @@ CONFIG_KALLSYMS_ALL=y
CONFIG_EMBEDDED=y
# CONFIG_COMPAT_BRK is not set
CONFIG_PROFILING=y
CONFIG_JUMP_LABEL=y
CONFIG_CC_STACKPROTECTOR_REGULAR=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_RUST=y
CONFIG_ARCH_MULTI_V6=y
CONFIG_ARCH_BCM=y
CONFIG_ARCH_BCM2835=y
CONFIG_PREEMPT_VOLUNTARY=y
CONFIG_AEABI=y
CONFIG_KSM=y
CONFIG_CLEANCACHE=y
CONFIG_CMA=y
CONFIG_SECCOMP=y
CONFIG_KEXEC=y
CONFIG_CRASH_DUMP=y
CONFIG_CPU_FREQ=y
Expand All @@ -45,9 +36,16 @@ CONFIG_CPU_FREQ_GOV_ONDEMAND=y
CONFIG_CPUFREQ_DT=y
CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
CONFIG_VFP=y
# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
# CONFIG_SUSPEND is not set
CONFIG_PM=y
CONFIG_RASPBERRYPI_FIRMWARE=y
CONFIG_JUMP_LABEL=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
CONFIG_KSM=y
CONFIG_CLEANCACHE=y
CONFIG_CMA=y
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_UNIX=y
Expand All @@ -64,8 +62,6 @@ CONFIG_MAC80211=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
# CONFIG_STANDALONE is not set
CONFIG_DMA_CMA=y
CONFIG_CMA_SIZE_MBYTES=32
CONFIG_SCSI=y
CONFIG_BLK_DEV_SD=y
CONFIG_SCSI_CONSTANTS=y
Expand All @@ -88,6 +84,7 @@ CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_DEV_BUS=y
CONFIG_TTY_PRINTK=y
# CONFIG_HW_RANDOM is not set
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_BCM2835=y
CONFIG_SPI=y
Expand All @@ -105,7 +102,6 @@ CONFIG_REGULATOR=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_REGULATOR_GPIO=y
CONFIG_MEDIA_SUPPORT=y
CONFIG_MEDIA_CAMERA_SUPPORT=y
CONFIG_DRM=y
CONFIG_DRM_VC4=y
CONFIG_FB_SIMPLE=y
Expand Down Expand Up @@ -152,7 +148,6 @@ CONFIG_BCM2835_MBOX=y
CONFIG_RASPBERRYPI_POWER=y
CONFIG_PWM=y
CONFIG_PWM_BCM2835=y
CONFIG_RASPBERRYPI_FIRMWARE=y
CONFIG_EXT2_FS=y
CONFIG_EXT2_FS_XATTR=y
CONFIG_EXT2_FS_POSIX_ACL=y
Expand All @@ -171,20 +166,23 @@ CONFIG_NLS_CODEPAGE_437=y
CONFIG_NLS_ASCII=y
CONFIG_NLS_ISO8859_1=y
CONFIG_NLS_UTF8=y
# CONFIG_XZ_DEC_ARM is not set
# CONFIG_XZ_DEC_ARMTHUMB is not set
CONFIG_DMA_CMA=y
CONFIG_CMA_SIZE_MBYTES=32
CONFIG_PRINTK_TIME=y
CONFIG_BOOT_PRINTK_DELAY=y
CONFIG_DYNAMIC_DEBUG=y
CONFIG_DEBUG_INFO=y
# CONFIG_ENABLE_MUST_CHECK is not set
CONFIG_DEBUG_MEMORY_INIT=y
CONFIG_LOCKUP_DETECTOR=y
CONFIG_SCHED_TRACER=y
CONFIG_STACK_TRACER=y
CONFIG_FUNCTION_PROFILER=y
CONFIG_TEST_KSTRTOX=y
CONFIG_DEBUG_FS=y
CONFIG_KGDB=y
CONFIG_KGDB_KDB=y
CONFIG_DEBUG_MEMORY_INIT=y
CONFIG_FUNCTION_PROFILER=y
CONFIG_STACK_TRACER=y
CONFIG_SCHED_TRACER=y
CONFIG_SAMPLES=y
CONFIG_SAMPLES_RUST=y
CONFIG_SAMPLE_RUST_PLATDEV=y
CONFIG_STRICT_DEVMEM=y
# CONFIG_XZ_DEC_ARM is not set
# CONFIG_XZ_DEC_ARMTHUMB is not set
CONFIG_TEST_KSTRTOX=y
40 changes: 40 additions & 0 deletions rust/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <linux/gfp.h>
#include <linux/highmem.h>
#include <linux/uio.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>

void rust_helper_BUG(void)
{
Expand Down Expand Up @@ -105,6 +107,44 @@ size_t rust_helper_copy_to_iter(const void *addr, size_t bytes, struct iov_iter
}
EXPORT_SYMBOL_GPL(rust_helper_copy_to_iter);

void *
rust_helper_platform_get_drvdata(const struct platform_device *pdev)
{
return platform_get_drvdata(pdev);
}
EXPORT_SYMBOL_GPL(rust_helper_platform_get_drvdata);

void
rust_helper_platform_set_drvdata(struct platform_device *pdev,
void *data)
{
return platform_set_drvdata(pdev, data);
}
EXPORT_SYMBOL_GPL(rust_helper_platform_set_drvdata);

bool rust_helper_is_err(__force const void *ptr)
{
return IS_ERR(ptr);
}
EXPORT_SYMBOL_GPL(rust_helper_is_err);

long rust_helper_ptr_err(__force const void *ptr)
{
return PTR_ERR(ptr);
}
EXPORT_SYMBOL_GPL(rust_helper_ptr_err);

#ifdef CONFIG_REGMAP
struct regmap *
rust_helper_devm_regmap_init_mmio(struct device *dev,
void __iomem *regs,
const struct regmap_config *config)
{
return devm_regmap_init_mmio(dev, regs, config);
}
EXPORT_SYMBOL_GPL(rust_helper_devm_regmap_init_mmio);
#endif

#if !defined(CONFIG_ARM)
// See https://github.com/rust-lang/rust-bindgen/issues/1671
static_assert(__builtin_types_compatible_p(size_t, uintptr_t),
Expand Down
3 changes: 3 additions & 0 deletions rust/kernel/bindings_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#include <linux/poll.h>
#include <linux/mm.h>
#include <uapi/linux/android/binder.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/of_platform.h>

// `bindgen` gets confused at certain things
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;
Expand Down
17 changes: 17 additions & 0 deletions rust/kernel/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,20 @@ impl From<AllocError> for Error {
Error::ENOMEM
}
}

pub(crate) unsafe fn ptr_err_check<T>(ptr: *mut T) -> KernelResult<*mut T> {
extern "C" {
#[allow(improper_ctypes)]
fn rust_helper_is_err(ptr: *const c_types::c_void) -> bool;

#[allow(improper_ctypes)]
fn rust_helper_ptr_err(ptr: *const c_types::c_void) -> c_types::c_long;
}

if rust_helper_is_err(ptr as _) {
return Err(Error::from_kernel_errno(
rust_helper_ptr_err(ptr as _) as i32
));
}
Ok(ptr)
}
5 changes: 5 additions & 0 deletions rust/kernel/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ pub mod iov_iter;
mod types;
pub mod user_ptr;

pub mod platform_driver;

#[cfg(CONFIG_REGMAP)]
pub mod regmap;

pub use crate::error::{Error, KernelResult};
pub use crate::types::{CStr, Mode};

Expand Down
167 changes: 167 additions & 0 deletions rust/kernel/platform_driver.rs
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>;
}
Loading

0 comments on commit d028c14

Please sign in to comment.