From d028c1475fee8c5ea0491682c602bc3a09e48646 Mon Sep 17 00:00:00 2001 From: Sven Van Asbroeck Date: Sun, 2 May 2021 17:06:54 -0400 Subject: [PATCH] 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= $ cp -rfa # should end up in /lib/modules/5.12.0-rc4+/ ``` Signed-off-by: Sven Van Asbroeck --- .github/workflows/ci.yaml | 2 +- arch/arm/configs/bcm2835_defconfig | 48 ++++----- rust/helpers.c | 40 +++++++ rust/kernel/bindings_helper.h | 3 + rust/kernel/error.rs | 17 +++ rust/kernel/lib.rs | 5 + rust/kernel/platform_driver.rs | 167 +++++++++++++++++++++++++++++ rust/kernel/regmap.rs | 145 +++++++++++++++++++++++++ samples/rust/Kconfig | 11 ++ samples/rust/Makefile | 1 + samples/rust/rust_platdev.rs | 137 +++++++++++++++++++++++ 11 files changed, 550 insertions(+), 26 deletions(-) create mode 100644 rust/kernel/platform_driver.rs create mode 100644 rust/kernel/regmap.rs create mode 100644 samples/rust/rust_platdev.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0d5e8ae7fb2029..e6862efe492051 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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 diff --git a/arch/arm/configs/bcm2835_defconfig b/arch/arm/configs/bcm2835_defconfig index 383c632eba7bd7..6492c7e58c39a2 100644 --- a/arch/arm/configs/bcm2835_defconfig +++ b/arch/arm/configs/bcm2835_defconfig @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/rust/helpers.c b/rust/helpers.c index f38ed02438ae9b..0b6a6c74aa5dee 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include void rust_helper_BUG(void) { @@ -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), diff --git a/rust/kernel/bindings_helper.h b/rust/kernel/bindings_helper.h index ec052e54350fd9..99da190dff044d 100644 --- a/rust/kernel/bindings_helper.h +++ b/rust/kernel/bindings_helper.h @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include // `bindgen` gets confused at certain things const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 432d866232c13c..3a0bee82fdf514 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -104,3 +104,20 @@ impl From for Error { Error::ENOMEM } } + +pub(crate) unsafe fn ptr_err_check(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) +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index d34cb84eff50a0..2ef0ebff912050 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -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}; diff --git a/rust/kernel/platform_driver.rs b/rust/kernel/platform_driver.rs new file mode 100644 index 00000000000000..7609d0c6f95bdb --- /dev/null +++ b/rust/kernel/platform_driver.rs @@ -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( + 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 { + // 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( + pdev: *mut bindings::platform_device, +) -> c_types::c_int { + let ptr = rust_helper_platform_get_drvdata(pdev); + let drv_data: Box = 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( + 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::

); + this.pdrv.remove = Some(remove_callback::

); + 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( + name: CStr<'static>, + of_id: CStr<'static>, + module: &'static crate::ThisModule, + ) -> KernelResult>> { + let mut r = Pin::from(Box::try_new(Self::default())?); + r.as_mut().register::

(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(*mut T); + +impl PointerWrapper { + 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; + +/// 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; +} diff --git a/rust/kernel/regmap.rs b/rust/kernel/regmap.rs new file mode 100644 index 00000000000000..6cd03d543d3240 --- /dev/null +++ b/rust/kernel/regmap.rs @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0 + +use crate::error::{ptr_err_check, Error, KernelResult}; +use crate::platform_driver::{Device, PlatformDevice}; +use crate::{bindings, c_types}; +use core::mem::MaybeUninit; + +/// Abstraction wrapping a kernel `struct regmap`. +/// +/// # Invariants +/// +/// regmap locking is never disabled. +pub struct Regmap(*mut bindings::regmap); + +// SAFETY: we access the underlying `struct regmap` only through its helper functions, +// and we never disable locking by the type invariant, so: +// - `struct regmap *` can be safely sent between threads (Send) +// - `struct regmap`'s helper functions can be safely called from any thread (Sync) +unsafe impl Send for Regmap {} +unsafe impl Sync for Regmap {} + +impl Regmap { + pub fn write(&self, reg: c_types::c_uint, val: c_types::c_uint) -> KernelResult { + // SAFETY: FFI call. + // OK to coerce a shared reference to a mutable pointer, as + // we guarantee that a `struct regmap`'s `write` is fully synchronized. + let res = unsafe { bindings::regmap_write(self.0, reg, val) }; + if res != 0 { + return Err(Error::from_kernel_errno(res)); + } + Ok(()) + } + + pub fn read(&self, reg: c_types::c_uint) -> KernelResult { + let mut val = MaybeUninit::::uninit(); + // SAFETY: FFI call. + // OK to coerce a shared reference to a mutable pointer, as + // we guarantee that a `struct regmap`'s `read` is fully synchronized. + // OK to pass a pointer to an uninitialized `u32`, this is part of + // `struct regmap`'s `read` API. + let res = unsafe { bindings::regmap_read(self.0, reg, val.as_mut_ptr()) }; + if res != 0 { + return Err(Error::from_kernel_errno(res)); + } + // SAFETY: if `res` is zero, `val` is guaranteed initialized by the + // call to `regmap_read`. + Ok(unsafe { val.assume_init() }) + } + + #[cfg(CONFIG_REGMAP_MMIO)] + pub fn init_mmio_platform_resource( + pdev: &mut PlatformDevice, + index: u32, + cfg: &RegmapConfig, + ) -> KernelResult { + let iomem = devm_platform_ioremap_resource(pdev, index)?; + devm_regmap_init_mmio(pdev, iomem, cfg) + } +} + +#[derive(Default)] +pub struct RegmapConfig { + reg_bits: i32, + val_bits: i32, + reg_stride: Option, + max_register: Option, +} + +impl RegmapConfig { + pub fn new(reg_bits: u32, val_bits: u32) -> RegmapConfig { + RegmapConfig { + reg_bits: reg_bits as i32, + val_bits: val_bits as i32, + ..Default::default() + } + } + + pub fn reg_stride(mut self, reg_stride: u32) -> Self { + self.reg_stride.replace(reg_stride as i32); + self + } + + pub fn max_register(mut self, max_register: u32) -> Self { + self.max_register.replace(max_register); + self + } + + fn build(&self) -> bindings::regmap_config { + let mut cfg = bindings::regmap_config { + reg_bits: self.reg_bits, + val_bits: self.val_bits, + // INVARIANTS: `regmap` must be created with locking enabled. + disable_locking: false, + ..Default::default() + }; + if let Some(s) = self.reg_stride { + cfg.reg_stride = s; + } + if let Some(m) = self.max_register { + cfg.max_register = m; + } + cfg + } +} + +fn devm_regmap_init_mmio( + dev: &mut impl Device, + regs: *mut c_types::c_void, + cfg: &RegmapConfig, +) -> KernelResult { + extern "C" { + #[allow(improper_ctypes)] + fn rust_helper_devm_regmap_init_mmio( + dev: *mut bindings::device, + regs: *mut c_types::c_void, + config: *const bindings::regmap_config, + ) -> *mut bindings::regmap; + } + + // SAFETY: FFI call. + // OK to coerce a temporary `struct regmap_config` to a const pointer, + // as that pointer has to be valid only for the lifetime of the + // `regmap_init` call. + let rm = unsafe { + ptr_err_check(rust_helper_devm_regmap_init_mmio( + dev.to_dev_ptr(), + regs, + &cfg.build(), + ))? + }; + Ok(Regmap(rm)) +} + +fn devm_platform_ioremap_resource( + pdev: &mut PlatformDevice, + index: u32, +) -> KernelResult<*mut c_types::c_void> { + // SAFETY: FFI call. + unsafe { + ptr_err_check(bindings::devm_platform_ioremap_resource( + pdev.to_ptr(), + index, + )) + } +} diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 183a3c4dc80cd7..81918b3c3571eb 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -70,6 +70,17 @@ config SAMPLE_RUST_MISCDEV If unsure, say N. +config SAMPLE_RUST_PLATDEV + tristate "Platform device" + depends on REGMAP_MMIO + help + This option builds the Rust platform device sample. + + To compile this as a module, choose M here: + the module will be called rust_platdev. + + If unsure, say N. + config SAMPLE_RUST_STACK_PROBING tristate "Stack probing" help diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 48bc871ea1f8ff..bfa4e73081923d 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_SAMPLE_RUST_MODULE_PARAMETERS) += rust_module_parameters.o obj-$(CONFIG_SAMPLE_RUST_SYNC) += rust_sync.o obj-$(CONFIG_SAMPLE_RUST_CHRDEV) += rust_chrdev.o obj-$(CONFIG_SAMPLE_RUST_MISCDEV) += rust_miscdev.o +obj-$(CONFIG_SAMPLE_RUST_PLATDEV) += rust_platdev.o obj-$(CONFIG_SAMPLE_RUST_STACK_PROBING) += rust_stack_probing.o obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE) += rust_semaphore.o obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE_C) += rust_semaphore_c.o diff --git a/samples/rust/rust_platdev.rs b/samples/rust/rust_platdev.rs new file mode 100644 index 00000000000000..0fafaf257d32cd --- /dev/null +++ b/samples/rust/rust_platdev.rs @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust platform device sample + +#![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: RustPlatdev, + name: b"rust_platdev", + author: b"Rust for Linux Contributors", + description: b"Rust platform device sample", + license: b"GPL v2", + params: { + }, +} + +// The Shared State is simply a Regmap, which is Send + Sync. +struct SharedState { + regmap: Regmap, +} + +impl SharedState { + fn try_new(regmap: Regmap) -> KernelResult> { + Ok(Arc::try_new(SharedState { regmap })?) + } +} + +struct RngDevice { + state: Arc, +} + +impl FileOpener> for RngDevice { + fn open(state: &Arc) -> KernelResult { + Ok(Box::try_new(RngDevice { + state: state.clone(), + })?) + } +} + +impl FileOperations for RngDevice { + type Wrapper = Box; + + kernel::declare_file_operations!(read); + + fn read(&self, _: &File, data: &mut T, offset: u64) -> KernelResult { + // 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(®map.read(RNG_DATA)?)?; + Ok(4) + } +} + +#[derive(Default)] +struct RngDriver; + +// TODO maybe wrap register addresses into a type, so they can never +// be mixed/confused with register values? That's a common error. +// OR something more outrageous: wrap register values in a type linked +// to the register address type, so values cannot simply get written to +// the wrong address? That's another common error. + +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>>>; + + fn probe(pdev: &mut PlatformDevice) -> KernelResult { + pr_info!("probe!\n"); + // 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 + let state = SharedState::try_new(regmap)?; + let dev = miscdev::Registration::new_pinned::(cstr!("rust_hwrng"), None, state)?; + Ok(dev) + } +} + +struct RustPlatdev { + _pdev: Pin>, +} + +impl KernelModule for RustPlatdev { + fn init() -> KernelResult { + pr_info!("Rust platform device sample (init)\n"); + + let pdev = platform_driver::Registration::new_pinned::( + cstr!("bcm2835-rng"), + // TODO this should be an optional list. + // Perhaps use an enum to specify behavioural differences. + cstr!("brcm,bcm2835-rng"), + &THIS_MODULE, + )?; + + Ok(RustPlatdev { _pdev: pdev }) + } +} + +impl Drop for RustPlatdev { + fn drop(&mut self) { + pr_info!("Rust platform device sample (exit)\n"); + } +}