Skip to content

Commit

Permalink
Merge pull request torvalds#462 from wedsonaf/io_mem
Browse files Browse the repository at this point in the history
rust: add `IoMem` abstraction.
  • Loading branch information
wedsonaf authored Aug 20, 2021
2 parents 5f44006 + f9e2f7e commit 718f222
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 0 deletions.
59 changes: 59 additions & 0 deletions rust/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/security.h>
#include <asm/io.h>

void rust_helper_BUG(void)
{
Expand All @@ -32,6 +33,64 @@ unsigned long rust_helper_clear_user(void __user *to, unsigned long n)
return clear_user(to, n);
}

void __iomem *rust_helper_ioremap(resource_size_t offset, unsigned long size)
{
return ioremap(offset, size);
}
EXPORT_SYMBOL_GPL(rust_helper_ioremap);

u8 rust_helper_readb(const volatile void __iomem *addr)
{
return readb(addr);
}
EXPORT_SYMBOL_GPL(rust_helper_readb);

u16 rust_helper_readw(const volatile void __iomem *addr)
{
return readw(addr);
}
EXPORT_SYMBOL_GPL(rust_helper_readw);

u32 rust_helper_readl(const volatile void __iomem *addr)
{
return readl(addr);
}
EXPORT_SYMBOL_GPL(rust_helper_readl);

#ifdef CONFIG_64BIT
u64 rust_helper_readq(const volatile void __iomem *addr)
{
return readq(addr);
}
EXPORT_SYMBOL_GPL(rust_helper_readq);
#endif

void rust_helper_writeb(u8 value, volatile void __iomem *addr)
{
writeb(value, addr);
}
EXPORT_SYMBOL_GPL(rust_helper_writeb);

void rust_helper_writew(u16 value, volatile void __iomem *addr)
{
writew(value, addr);
}
EXPORT_SYMBOL_GPL(rust_helper_writew);

void rust_helper_writel(u32 value, volatile void __iomem *addr)
{
writel(value, addr);
}
EXPORT_SYMBOL_GPL(rust_helper_writel);

#ifdef CONFIG_64BIT
void rust_helper_writeq(u64 value, volatile void __iomem *addr)
{
writeq(value, addr);
}
EXPORT_SYMBOL_GPL(rust_helper_writeq);
#endif

void rust_helper_spin_lock_init(spinlock_t *lock, const char *name,
struct lock_class_key *key)
{
Expand Down
1 change: 1 addition & 0 deletions rust/kernel/bindings_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/security.h>
#include <asm/io.h>

// `bindgen` gets confused at certain things
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;
Expand Down
210 changes: 210 additions & 0 deletions rust/kernel/io_mem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-2.0

//! Memory-mapped IO.
//!
//! C header: [`include/asm-generic/io.h`](../../../../include/asm-generic/io.h)
use crate::{bindings, c_types, Error, Result};
use core::convert::TryInto;

extern "C" {
fn rust_helper_ioremap(
offset: bindings::resource_size_t,
size: c_types::c_ulong,
) -> *mut c_types::c_void;

fn rust_helper_readb(addr: *const c_types::c_void) -> u8;
fn rust_helper_readw(addr: *const c_types::c_void) -> u16;
fn rust_helper_readl(addr: *const c_types::c_void) -> u32;
fn rust_helper_readq(addr: *const c_types::c_void) -> u64;

fn rust_helper_writeb(value: u8, addr: *mut c_types::c_void);
fn rust_helper_writew(value: u16, addr: *mut c_types::c_void);
fn rust_helper_writel(value: u32, addr: *mut c_types::c_void);
fn rust_helper_writeq(value: u64, addr: *mut c_types::c_void);
}

/// Represents a memory resource.
pub struct Resource {
offset: bindings::resource_size_t,
size: bindings::resource_size_t,
}

impl Resource {
pub(crate) fn new(
start: bindings::resource_size_t,
end: bindings::resource_size_t,
) -> Option<Self> {
if start == 0 {
return None;
}
Some(Self {
offset: start,
size: end.checked_sub(start)?.checked_add(1)?,
})
}
}

/// Represents a memory block of at least `SIZE` bytes.
///
/// # Invariants
///
/// `ptr` is a non-null and valid address of at least `SIZE` bytes and returned by an `ioremap`
/// variant. `ptr` is also 8-byte aligned.
///
/// # Examples
///
/// ```
/// # use kernel::prelude::*;
/// use kernel::io_mem::{IoMem, Resource};
///
/// fn test(res: Resource) -> Result {
/// // Create an io mem block of at least 100 bytes.
/// // SAFETY: No DMA operations are initiated through `mem`.
/// let mem = unsafe { IoMem::<100>::try_new(res) }?;
///
/// // Read one byte from offset 10.
/// let v = mem.readb(10);
///
/// // Write value to offset 20.
/// mem.writeb(v, 20);
///
/// Ok(())
/// }
///
/// ```
pub struct IoMem<const SIZE: usize> {
ptr: usize,
}

macro_rules! define_read {
($name:ident, $try_name:ident, $type_name:ty) => {
/// Reads IO data from the given offset known, at compile time.
///
/// If the offset is not known at compile time, the build will fail.
pub fn $name(&self, offset: usize) -> $type_name {
Self::check_offset::<$type_name>(offset);
let ptr = self.ptr.wrapping_add(offset);
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
// guarantees that the code won't build if `offset` makes the read go out of bounds
// (including the type size).
unsafe { concat_idents!(rust_helper_, $name)(ptr as _) }
}

/// Reads IO data from the given offset.
///
/// It fails if/when the offset (plus the type size) is out of bounds.
pub fn $try_name(&self, offset: usize) -> Result<$type_name> {
if !Self::offset_ok::<$type_name>(offset) {
return Err(Error::EINVAL);
}
let ptr = self.ptr.wrapping_add(offset);
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
// returns an error if `offset` would make the read go out of bounds (including the
// type size).
Ok(unsafe { concat_idents!(rust_helper_, $name)(ptr as _) })
}
};
}

macro_rules! define_write {
($name:ident, $try_name:ident, $type_name:ty) => {
/// Writes IO data to the given offset, known at compile time.
///
/// If the offset is not known at compile time, the build will fail.
pub fn $name(&self, value: $type_name, offset: usize) {
Self::check_offset::<$type_name>(offset);
let ptr = self.ptr.wrapping_add(offset);
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
// guarantees that the code won't link if `offset` makes the write go out of bounds
// (including the type size).
unsafe { concat_idents!(rust_helper_, $name)(value, ptr as _) }
}

/// Writes IO data to the given offset.
///
/// It fails if/when the offset (plus the type size) is out of bounds.
pub fn $try_name(&self, value: $type_name, offset: usize) -> Result {
if !Self::offset_ok::<$type_name>(offset) {
return Err(Error::EINVAL);
}
let ptr = self.ptr.wrapping_add(offset);
// SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
// returns an error if `offset` would make the write go out of bounds (including the
// type size).
unsafe { concat_idents!(rust_helper_, $name)(value, ptr as _) };
Ok(())
}
};
}

impl<const SIZE: usize> IoMem<SIZE> {
/// Tries to create a new instance of a memory block.
///
/// The resource described by `res` is mapped into the CPU's address space so that it can be
/// accessed directly. It is also consumed by this function so that it can't be mapped again
/// to a different address.
///
/// # Safety
///
/// Callers must ensure that either (a) the resulting interface cannot be used to initiate DMA
/// operations, or (b) that DMA operations initiated via the returned interface use DMA handles
/// allocated through the `dma` module.
pub unsafe fn try_new(res: Resource) -> Result<Self> {
// Check that the resource has at least `SIZE` bytes in it.
if res.size < SIZE.try_into()? {
return Err(Error::EINVAL);
}

// To be able to check pointers at compile time based only on offsets, we need to guarantee
// that the base pointer is minimally aligned. So we conservatively expect at least 8 bytes.
if res.offset % 8 != 0 {
crate::pr_err!("Physical address is not 64-bit aligned: {:x}", res.offset);
return Err(Error::EDOM);
}

// Try to map the resource.
// SAFETY: Just mapping the memory range.
// TODO: Remove `into` call below (and disabling of clippy warning) once #465 is fixed.
#[allow(clippy::complexity)]
let addr = unsafe { rust_helper_ioremap(res.offset, res.size.into()) };
if addr.is_null() {
Err(Error::ENOMEM)
} else {
// INVARIANT: `addr` is non-null and was returned by `ioremap`, so it is valid. It is
// also 8-byte aligned because we checked it above.
Ok(Self { ptr: addr as usize })
}
}

const fn offset_ok<T>(offset: usize) -> bool {
let type_size = core::mem::size_of::<T>();
if let Some(end) = offset.checked_add(type_size) {
end <= SIZE && offset % type_size == 0
} else {
false
}
}

const fn check_offset<T>(offset: usize) {
crate::build_assert!(Self::offset_ok::<T>(offset), "IoMem offset overflow");
}

define_read!(readb, try_readb, u8);
define_read!(readw, try_readw, u16);
define_read!(readl, try_readl, u32);
define_read!(readq, try_readq, u64);

define_write!(writeb, try_writeb, u8);
define_write!(writew, try_writew, u16);
define_write!(writel, try_writel, u32);
define_write!(writeq, try_writeq, u64);
}

impl<const SIZE: usize> Drop for IoMem<SIZE> {
fn drop(&mut self) {
// SAFETY: By the type invariant, `self.ptr` is a value returned by a previous successful
// call to `ioremap`.
unsafe { bindings::iounmap(self.ptr as _) };
}
}
2 changes: 2 additions & 0 deletions rust/kernel/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#![feature(
allocator_api,
associated_type_defaults,
concat_idents,
const_fn_trait_bound,
const_mut_refs,
const_panic,
Expand Down Expand Up @@ -74,6 +75,7 @@ pub mod sync;
pub mod sysctl;

pub mod io_buffer;
pub mod io_mem;
pub mod iov_iter;
pub mod of;
pub mod platdev;
Expand Down

0 comments on commit 718f222

Please sign in to comment.