From d4552cdb02ee5ac0995d76607ea6263a102cb825 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_rust_defconfig | 189 +++++++++++++++++++++ drivers/char/hw_random/Kconfig | 14 ++ drivers/char/hw_random/Makefile | 1 + drivers/char/hw_random/bcm2835_rng_rust.rs | 120 +++++++++++++ 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 | 180 ++++++++++++++++++++ rust/kernel/regmap.rs | 154 +++++++++++++++++ 11 files changed, 724 insertions(+), 1 deletion(-) create mode 100644 arch/arm/configs/bcm2835_rust_defconfig create mode 100644 drivers/char/hw_random/bcm2835_rng_rust.rs create mode 100644 rust/kernel/platform_driver.rs create mode 100644 rust/kernel/regmap.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c0e74541ee49a1..a9b4582811885e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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 diff --git a/arch/arm/configs/bcm2835_rust_defconfig b/arch/arm/configs/bcm2835_rust_defconfig new file mode 100644 index 00000000000000..ca50cfafa9071f --- /dev/null +++ b/arch/arm/configs/bcm2835_rust_defconfig @@ -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 diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index 1fe006f3f12fab..6e59ad61f5e5c1 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -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_rust + + If unsure, say N. + config HW_RANDOM_IPROC_RNG200 tristate "Broadcom iProc/STB RNG200 support" depends on ARCH_BCM_IPROC || ARCH_BCM2835 || ARCH_BRCMSTB diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index 8933fada74f2fb..3e14586f2b3f29 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -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 diff --git a/drivers/char/hw_random/bcm2835_rng_rust.rs b/drivers/char/hw_random/bcm2835_rng_rust.rs new file mode 100644 index 00000000000000..e0bcca1136240b --- /dev/null +++ b/drivers/char/hw_random/bcm2835_rng_rust.rs @@ -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> { + Ok(Arc::try_new(SharedState { regmap })?) + } +} + +struct RngDevice { + state: Arc, +} + +impl FileOpener> for RngDevice { + fn open(state: &Arc) -> Result { + Ok(Box::try_new(RngDevice { + state: state.clone(), + })?) + } +} + +impl FileOperations for RngDevice { + kernel::declare_file_operations!(read); + + fn read(&self, _: &File, data: &mut T, offset: u64) -> Result { + // 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: 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>>>; + + fn probe(pdev: &mut PlatformDevice) -> Result { + // 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::(cstr!("rust_hwrng"), None, state)?; + Ok(dev) + } +} + +struct RngModule { + _pdev: Pin>, +} + +impl KernelModule for RngModule { + fn init() -> Result { + let pdev = platform_driver::Registration::new_pinned::( + 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 }) + } +} 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 27043dc8778d75..9941de89ef6494 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) -> Result<*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 da488d67776ae2..5ed3ef621372d4 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, Result}; 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..2e499423e31810 --- /dev/null +++ b/rust/kernel/platform_driver.rs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 + +use crate::{ + bindings, c_types, + error::{Error, Result}, + types::PointerWrapper, + CStr, +}; +use alloc::boxed::Box; +use core::{marker::PhantomPinned, mem::transmute, 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 = drv_data.into_pointer() as *mut c_types::c_void; + Ok(drv_data) as Result<_> + }; + 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>) -> Result { + // 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); + } + buf.get_mut(..compatible.len()) + .ok_or(Error::EINVAL)? + .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); + // SAFETY: + // - we allocated this pointer using `T::DrvData::into_pointer`, + // so it is safe to turn back into a `T::DrvData`. + // - the allocation happened in `probe`, no-one freed the memory, and + // `remove` is the canonical kernel location to free driver data. so OK + // to free the pointer here. + let drv_data = T::DrvData::from_pointer(ptr); + match T::remove(&mut PlatformDevice::new(pdev), drv_data) { + Err(e) => e.to_kernel_errno(), + Ok(_) => 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, + ) -> Result { + // 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, + ) -> Result>> { + 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 {} + +/// Rust abstraction of a kernel `struct platform_device`. +pub struct PlatformDevice(*mut bindings::platform_device); + +impl PlatformDevice { + fn new(ptr: *mut bindings::platform_device) -> Self { + Self(ptr) + } + + pub(crate) fn to_ptr(&self) -> *mut bindings::platform_device { + self.0 + } +} + +/// 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: PointerWrapper; + + fn probe(pdev: &mut PlatformDevice) -> Result; + + // TODO: do drivers ever need to override this? + fn remove(_pdev: &mut PlatformDevice, drv_data: Self::DrvData) -> Result { + drop(drv_data); + Ok(()) + } +} diff --git a/rust/kernel/regmap.rs b/rust/kernel/regmap.rs new file mode 100644 index 00000000000000..8e8aaa1f0420e9 --- /dev/null +++ b/rust/kernel/regmap.rs @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 + +use crate::error::{ptr_err_check, Error, Result}; +use crate::platform_driver::{Device, PlatformDevice}; +use crate::{bindings, c_types}; + +// TODO: investigate lifetime management for Regmap. +// +// The `struct regmap` lifetime (and that of its `void __iomem *` dependency) is +// managed by the kernel using the `devm_` mechanism. The kernel will keep `devm_` +// objects around for as long as the device exists. On device removal, the `devm_` +// objects are automatically released by the kernel. +// +// Theoretically, a `devm_` based object could 'leak' out of the Rust driver. If +// it gets used/dereferenced **after** the device has been removed, that'll result +// in a use-after-free. +// +// Investigate if we can to leverage Rust lifetimes to ensure build-time correctness. + +/// 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: u32, val: u32) -> Result { + // 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: u32) -> Result { + // Initialize `val` here to eliminate `unsafe` code when returning it. + let mut val = 0_u32; + // SAFETY: FFI call. + // OK to coerce a temporary `u32` to a mut pointer, + // as that pointer has to be valid only for the lifetime of the + // `regmap_read` call. + let res = unsafe { bindings::regmap_read(self.0, reg, &mut val) }; + if res != 0 { + return Err(Error::from_kernel_errno(res)); + } + Ok(val) + } + + pub fn init_mmio_platform_resource( + pdev: &mut PlatformDevice, + index: u32, + cfg: &RegmapConfig, + ) -> Result { + let iomem = Self::devm_platform_ioremap_resource(pdev, index)?; + Self::devm_regmap_init_mmio(pdev, iomem, cfg) + } + + fn devm_regmap_init_mmio( + dev: &mut impl Device, + regs: *mut c_types::c_void, + cfg: &RegmapConfig, + ) -> Result { + 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, + ) -> Result<*mut c_types::c_void> { + // SAFETY: FFI call. + unsafe { + ptr_err_check(bindings::devm_platform_ioremap_resource( + pdev.to_ptr(), + index, + )) + } + } +} + +#[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: u8) -> Self { + self.reg_stride = Some(reg_stride.into()); + self + } + + pub fn max_register(mut self, max_register: u32) -> Self { + self.max_register = Some(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 + } +}