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 11, 2021
1 parent 56e7e87 commit acf78f8
Show file tree
Hide file tree
Showing 11 changed files with 724 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,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
189 changes: 189 additions & 0 deletions arch/arm/configs/bcm2835_rust_defconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# CONFIG_LOCALVERSION_AUTO is not set
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_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_PERF=y
CONFIG_NAMESPACES=y
CONFIG_SCHED_AUTOGROUP=y
CONFIG_RELAY=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_CC_OPTIMIZE_FOR_SIZE=y
CONFIG_KALLSYMS_ALL=y
CONFIG_EMBEDDED=y
# CONFIG_COMPAT_BRK is not set
CONFIG_PROFILING=y
CONFIG_RUST=y
CONFIG_ARCH_MULTI_V6=y
CONFIG_ARCH_BCM=y
CONFIG_ARCH_BCM2835=y
CONFIG_KEXEC=y
CONFIG_CRASH_DUMP=y
CONFIG_CPU_FREQ=y
CONFIG_CPU_FREQ_STAT=y
CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE=y
CONFIG_CPU_FREQ_GOV_POWERSAVE=y
CONFIG_CPU_FREQ_GOV_USERSPACE=y
CONFIG_CPU_FREQ_GOV_ONDEMAND=y
CONFIG_CPUFREQ_DT=y
CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
CONFIG_VFP=y
# 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
CONFIG_INET=y
CONFIG_IP_PNP=y
CONFIG_IP_PNP_DHCP=y
CONFIG_NETWORK_SECMARK=y
CONFIG_NETFILTER=y
CONFIG_BT=y
CONFIG_BT_HCIUART=m
CONFIG_BT_HCIUART_BCM=y
CONFIG_CFG80211=y
CONFIG_MAC80211=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
# CONFIG_STANDALONE is not set
CONFIG_SCSI=y
CONFIG_BLK_DEV_SD=y
CONFIG_SCSI_CONSTANTS=y
CONFIG_SCSI_SCAN_ASYNC=y
CONFIG_NETDEVICES=y
CONFIG_BCMGENET=y
CONFIG_USB_LAN78XX=y
CONFIG_USB_USBNET=y
CONFIG_USB_NET_SMSC95XX=y
CONFIG_BRCMFMAC=m
CONFIG_ZD1211RW=y
CONFIG_INPUT_EVDEV=y
# CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_EXTENDED=y
CONFIG_SERIAL_8250_SHARE_IRQ=y
CONFIG_SERIAL_8250_BCM2835AUX=y
CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_DEV_BUS=y
CONFIG_TTY_PRINTK=y
CONFIG_HW_RANDOM=y
# CONFIG_HW_RANDOM_BCM2835 is not set
CONFIG_HW_RANDOM_BCM2835_RUST=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_BCM2835=y
CONFIG_SPI=y
CONFIG_SPI_BCM2835=y
CONFIG_SPI_BCM2835AUX=y
CONFIG_GPIO_SYSFS=y
CONFIG_SENSORS_RASPBERRYPI_HWMON=m
CONFIG_THERMAL=y
CONFIG_BCM2711_THERMAL=y
CONFIG_BCM2835_THERMAL=y
CONFIG_WATCHDOG=y
CONFIG_BCM2835_WDT=y
CONFIG_MFD_SYSCON=y
CONFIG_REGULATOR=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_REGULATOR_GPIO=y
CONFIG_MEDIA_SUPPORT=y
CONFIG_DRM=y
CONFIG_DRM_VC4=y
CONFIG_FB_SIMPLE=y
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_SOUND=y
CONFIG_SND=y
CONFIG_SND_SOC=y
CONFIG_SND_BCM2835_SOC_I2S=y
CONFIG_USB=y
CONFIG_USB_OTG=y
CONFIG_USB_STORAGE=y
CONFIG_USB_DWC2=y
CONFIG_NOP_USB_XCEIV=y
CONFIG_USB_GADGET=y
CONFIG_USB_ETH=m
CONFIG_USB_ETH_EEM=y
CONFIG_USB_G_SERIAL=m
CONFIG_MMC=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_IPROC=y
CONFIG_MMC_BCM2835=y
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_TRIGGERS=y
CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_ONESHOT=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_CPU=y
CONFIG_LEDS_TRIGGER_GPIO=y
CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
CONFIG_LEDS_TRIGGER_TRANSIENT=y
CONFIG_LEDS_TRIGGER_CAMERA=y
CONFIG_DMADEVICES=y
CONFIG_DMA_BCM2835=y
CONFIG_STAGING=y
CONFIG_SND_BCM2835=m
CONFIG_VIDEO_BCM2835=m
CONFIG_CLK_RASPBERRYPI=y
CONFIG_MAILBOX=y
CONFIG_BCM2835_MBOX=y
# CONFIG_IOMMU_SUPPORT is not set
CONFIG_RASPBERRYPI_POWER=y
CONFIG_PWM=y
CONFIG_PWM_BCM2835=y
CONFIG_EXT2_FS=y
CONFIG_EXT2_FS_XATTR=y
CONFIG_EXT2_FS_POSIX_ACL=y
CONFIG_EXT3_FS=y
CONFIG_EXT3_FS_POSIX_ACL=y
CONFIG_FANOTIFY=y
CONFIG_MSDOS_FS=y
CONFIG_VFAT_FS=y
CONFIG_TMPFS=y
CONFIG_TMPFS_POSIX_ACL=y
# CONFIG_MISC_FILESYSTEMS is not set
CONFIG_NFS_FS=y
CONFIG_ROOT_NFS=y
CONFIG_NFSD=y
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_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_STRICT_DEVMEM=y
CONFIG_TEST_KSTRTOX=y
14 changes: 14 additions & 0 deletions drivers/char/hw_random/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ config HW_RANDOM_BCM2835

If unsure, say Y.

config HW_RANDOM_BCM2835_RUST
tristate "Rust implementation of Broadcom BCM2835 Random Number Generator"
depends on HAS_RUST && ARCH_BCM2835
select REGMAP_MMIO
help
This driver provides alternative Rust-based kernel-side support
for the Random Number Generator hardware found on the Broadcom
BCM2835 SoC.

To compile this driver as a module, choose M here: the
module will be called bcm2835-rng

If unsure, say N.

config HW_RANDOM_IPROC_RNG200
tristate "Broadcom iProc/STB RNG200 support"
depends on ARCH_BCM_IPROC || ARCH_BCM2835 || ARCH_BRCMSTB
Expand Down
1 change: 1 addition & 0 deletions drivers/char/hw_random/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o
obj-$(CONFIG_HW_RANDOM_POWERNV) += powernv-rng.o
obj-$(CONFIG_HW_RANDOM_HISI) += hisi-rng.o
obj-$(CONFIG_HW_RANDOM_BCM2835) += bcm2835-rng.o
obj-$(CONFIG_HW_RANDOM_BCM2835_RUST) += bcm2835_rng_rust.o
obj-$(CONFIG_HW_RANDOM_IPROC_RNG200) += iproc-rng200.o
obj-$(CONFIG_HW_RANDOM_ST) += st-rng.o
obj-$(CONFIG_HW_RANDOM_XGENE) += xgene-rng.o
Expand Down
120 changes: 120 additions & 0 deletions drivers/char/hw_random/bcm2835_rng_rust.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0

//! Broadcom BCM2835 Random Number Generator support.
#![no_std]
#![feature(allocator_api, global_asm)]

use alloc::{boxed::Box, sync::Arc};
use core::pin::Pin;
use kernel::prelude::*;
use kernel::{
cstr,
file::File,
file_operations::{FileOpener, FileOperations},
io_buffer::IoBufferWriter,
miscdev,
platform_driver::{self, PlatformDevice, PlatformDriver},
regmap::{Regmap, RegmapConfig},
};

module! {
type: RngModule,
name: b"bcm2835_rng_rust",
author: b"Rust for Linux Contributors",
description: b"BCM2835 Random Number Generator (RNG) driver",
license: b"GPL v2",
}

struct SharedState {
regmap: Regmap,
}

impl SharedState {
fn try_new(regmap: Regmap) -> Result<Arc<Self>> {
Ok(Arc::try_new(SharedState { regmap })?)
}
}

struct RngDevice {
state: Arc<SharedState>,
}

impl FileOpener<Arc<SharedState>> for RngDevice {
fn open(state: &Arc<SharedState>) -> Result<Self::Wrapper> {
Ok(Box::try_new(RngDevice {
state: state.clone(),
})?)
}
}

impl FileOperations for RngDevice {
kernel::declare_file_operations!(read);

fn read<T: IoBufferWriter>(&self, _: &File, data: &mut T, offset: u64) -> Result<usize> {
// Succeed if the caller doesn't provide a buffer or if not at the start.
if data.is_empty() || offset != 0 {
return Ok(0);
}

let regmap = &self.state.regmap;
let num_words = regmap.read(RNG_STATUS)? >> 24;
if num_words == 0 {
return Ok(0);
}
data.write(&regmap.read(RNG_DATA)?)?;
Ok(4)
}
}

#[derive(Default)]
struct RngDriver;

// TODO: Issue #260 ("Use Rust type system to make Regmap API safer").

const RNG_CTRL: u32 = 0x0;
const RNG_STATUS: u32 = 0x4;
const RNG_DATA: u32 = 0x8;

// The initial numbers generated are "less random" so will be discarded.
const RNG_WARMUP_COUNT: u32 = 0x40000;
// Enable rng.
const RNG_RBGEN: u32 = 0x1;

impl PlatformDriver for RngDriver {
type DrvData = Pin<Box<miscdev::Registration<Arc<SharedState>>>>;

fn probe(pdev: &mut PlatformDevice) -> Result<Self::DrvData> {
// Create Regmap which maps device registers.
let cfg = RegmapConfig::new(32, 32)
.reg_stride(4)
.max_register(RNG_DATA);
let regmap = Regmap::init_mmio_platform_resource(pdev, 0, &cfg)?;
// Set warm-up count & enable.
regmap.write(RNG_STATUS, RNG_WARMUP_COUNT)?;
regmap.write(RNG_CTRL, RNG_RBGEN)?;
// Register character device so userspace can read out random data.
// TODO: use a `struct hwrng` instead of a `miscdev`.
let state = SharedState::try_new(regmap)?;
let dev = miscdev::Registration::new_pinned::<RngDevice>(cstr!("rust_hwrng"), None, state)?;
Ok(dev)
}
}

struct RngModule {
_pdev: Pin<Box<platform_driver::Registration>>,
}

impl KernelModule for RngModule {
fn init() -> Result<Self> {
let pdev = platform_driver::Registration::new_pinned::<RngDriver>(
cstr!("bcm2835-rng-rust"),
// TODO: this should be an optional list.
// Perhaps use an enum to specify behavioural differences.
cstr!("brcm,bcm2835-rng"),
&THIS_MODULE,
)?;

Ok(RngModule { _pdev: pdev })
}
}
Loading

0 comments on commit acf78f8

Please sign in to comment.