e9fd66c78e
This way PCI drivers don't need to use the privileged physmap interface, but only need access to a pcid handle. This is not yet enough for running drivers as unprivileged processes. Interrupts also need privileges and we need IOMMU support in the kernel.
333 lines
11 KiB
Rust
333 lines
11 KiB
Rust
//! This crate provides various abstractions for use by all drivers in the Redox drivers repo.
|
|
//!
|
|
//! This includes direct memory access via [dma], and Scatter-Gather List support via [sgl]. It also
|
|
//! provides various memory management structures for use with drivers, and some logging support.
|
|
|
|
use libredox::call::MmapArgs;
|
|
use libredox::flag::{self, O_CLOEXEC, O_RDONLY, O_RDWR, O_WRONLY};
|
|
use libredox::{
|
|
errno::EINVAL,
|
|
error::{Error, Result},
|
|
Fd,
|
|
};
|
|
use syscall::{ProcSchemeVerb, PAGE_SIZE};
|
|
|
|
/// The Direct Memory Access (DMA) API for drivers
|
|
pub mod dma;
|
|
/// MMIO utilities
|
|
pub mod io;
|
|
mod logger;
|
|
/// The Scatter Gather List (SGL) API for drivers.
|
|
pub mod sgl;
|
|
/// Low latency timeout for driver loops
|
|
pub mod timeout;
|
|
|
|
pub use logger::{file_level, output_level, setup_logging};
|
|
|
|
use std::sync::OnceLock;
|
|
|
|
static MEMORY_ROOT_FD: OnceLock<libredox::Fd> = OnceLock::new();
|
|
|
|
/// Initializes a file descriptor to be used as the root memory for a driver.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function will panic if:
|
|
/// - `libredox` is unable to open a file descriptor.
|
|
/// - The memory root file descriptor has already been set (this function has already been called).
|
|
pub fn init() {
|
|
if MEMORY_ROOT_FD
|
|
.set(
|
|
libredox::Fd::open("/scheme/memory/scheme-root", 0, 0)
|
|
.expect("drivers common: failed to open memory root fd"),
|
|
)
|
|
.is_err()
|
|
{
|
|
panic!("drivers common: failed to set memory root fd");
|
|
}
|
|
}
|
|
|
|
/// Gets the memory root file descriptor.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function will panic if `init` has not already been called first.
|
|
pub fn memory_root_fd() -> &'static libredox::Fd {
|
|
MEMORY_ROOT_FD
|
|
.get()
|
|
.expect("drivers common: memory root fd not initialized. Please call `common::init` in your main function.")
|
|
}
|
|
|
|
/// Specifies the write behavior for a specific region of memory
|
|
///
|
|
/// These types indicate to the driver how writes to a specific memory region are handled by the
|
|
/// system. This usually refers to the caching behavior that the processor or I/O device responsible
|
|
/// for that memory implements.
|
|
///
|
|
/// aarch64 and x86 have very different cache-coherency rules, so this API as written is likely
|
|
/// not sufficient to describe the memory caching behavior in a cross-platform manner. As such,
|
|
/// consider this API unstable.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
#[repr(u8)] // Make sure the discriminants match mmap_prep in pcid/src/scheme.rs
|
|
pub enum MemoryType {
|
|
/// A region of memory that implements Write-back caching.
|
|
///
|
|
/// In write-back caching, the processor will first store data in its local cache, and then
|
|
/// flush it to the actual storage location at regular intervals, or as applications access
|
|
/// the data.
|
|
Writeback = 0b00,
|
|
/// A region of memory that does not implement caching. Writes to these regions are immediate.
|
|
Uncacheable = 0b01,
|
|
/// A region of memory that implements write combining.
|
|
///
|
|
/// Write combining memory regions store all writes in a temporary buffer called a Write
|
|
/// Combine Buffer. Multiple writes to the location are stored in a single buffer, and then
|
|
/// released to the memory location in an unspecified order. Write-Combine memory does not
|
|
/// guarantee that the order at which you write to it is the order at which those writes are
|
|
/// committed to memory.
|
|
WriteCombining = 0b10,
|
|
/// Memory stored in an intermediate Write Combine Buffer and released later
|
|
/// Memory-Mapped I/O. This is an aarch64-specific term.
|
|
DeviceMemory = 0b11,
|
|
}
|
|
impl Default for MemoryType {
|
|
fn default() -> Self {
|
|
Self::Writeback
|
|
}
|
|
}
|
|
|
|
/// Represents the protection level of an area of memory.
|
|
///
|
|
/// This structure shouldn't be used directly -- instead, use the [`Prot::RO`] (Read-Only),
|
|
/// [`Prot::WO`] (Write-Only) and [`Prot::RW`] (Read-Write) constants to specify the memory's protection
|
|
/// level.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct Prot {
|
|
/// The memory is readable
|
|
pub read: bool,
|
|
/// The memory is writeable
|
|
pub write: bool,
|
|
}
|
|
|
|
/// Implements the memory protection level constants
|
|
impl Prot {
|
|
/// A constant representing Read-Only memory.
|
|
pub const RO: Self = Self {
|
|
read: true,
|
|
write: false,
|
|
};
|
|
|
|
/// A constant representing Write-Only memory
|
|
pub const WO: Self = Self {
|
|
read: false,
|
|
write: true,
|
|
};
|
|
|
|
/// A constant representing Read-Write memory
|
|
pub const RW: Self = Self {
|
|
read: true,
|
|
write: true,
|
|
};
|
|
}
|
|
|
|
/// Maps physical memory to virtual memory
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * '`base_phys`: [usize]' - The base address of the physical memory to map.
|
|
/// * 'len: [usize]' - The length of the physical memory to map (Should be a multiple of [`PAGE_SIZE`]
|
|
/// * '_: [Prot]' - The memory protection level of the mapping.
|
|
/// * 'type: [`MemoryType`]' - The caching behavior specification of the memory.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A '[Result]<*mut ()>' which is:
|
|
/// - '[Ok]' containing a raw pointer to the mapped memory.
|
|
/// - '[Err]' which contains an error on failure.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This function will return an error if:
|
|
/// - An invalid value is provided to 'read' or 'write'
|
|
/// - The system could not open a file descriptor to the memory scheme for the specified [`MemoryType`].
|
|
/// - The system failed to map the physical address to a virtual address. See [`libredox::call::mmap`]
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Safe, as the kernel ensures it doesn't conflict with any other memory described in the memory
|
|
/// map for regular RAM.
|
|
///
|
|
/// # Notes
|
|
/// - This function is unsafe, and upon using it you will be responsible for freeing the memory with
|
|
/// [`libredox::call::munmap`]. If you want a safe accessor, use [`PhysBorrowed`] instead.
|
|
/// - The `MemoryType` specified is used to tell the function which memory scheme to access. (i.e
|
|
/// /scheme/memory/physical@wb, /scheme/memory/physical@uc, etc).
|
|
pub unsafe fn physmap(
|
|
base_phys: usize,
|
|
len: usize,
|
|
Prot { read, write }: Prot,
|
|
ty: MemoryType,
|
|
) -> Result<*mut ()> {
|
|
// TODO: arraystring?
|
|
|
|
//Return an error rather than potentially crash the kernel.
|
|
if base_phys == 0 {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
|
|
let path = format!(
|
|
"physical@{}",
|
|
match ty {
|
|
MemoryType::Writeback => "wb",
|
|
MemoryType::Uncacheable => "uc",
|
|
MemoryType::WriteCombining => "wc",
|
|
MemoryType::DeviceMemory => "dev",
|
|
}
|
|
);
|
|
let mode = match (read, write) {
|
|
(true, true) => O_RDWR,
|
|
(true, false) => O_RDONLY,
|
|
(false, true) => O_WRONLY,
|
|
(false, false) => return Err(Error::new(EINVAL)),
|
|
};
|
|
let mut prot = 0;
|
|
if read {
|
|
prot |= flag::PROT_READ;
|
|
}
|
|
if write {
|
|
prot |= flag::PROT_WRITE;
|
|
}
|
|
|
|
let fd = memory_root_fd().openat(&path, O_CLOEXEC | mode, 0)?;
|
|
Ok(libredox::call::mmap(MmapArgs {
|
|
fd: fd.raw(),
|
|
offset: base_phys as u64,
|
|
length: len.next_multiple_of(PAGE_SIZE),
|
|
flags: flag::MAP_SHARED,
|
|
prot,
|
|
addr: core::ptr::null_mut(),
|
|
})? as *mut ())
|
|
}
|
|
|
|
impl std::fmt::Display for MemoryType {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}",
|
|
match self {
|
|
Self::Writeback => "wb",
|
|
Self::Uncacheable => "uc",
|
|
Self::WriteCombining => "wc",
|
|
Self::DeviceMemory => "dev",
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
/// A safe virtual mapping to physical memory that unmaps the memory when the structure goes out
|
|
/// of scope.
|
|
///
|
|
/// This function provides a safe binding to [physmap]. It implements Drop to free the mapped memory
|
|
/// when the structure goes out of scope.
|
|
pub struct PhysBorrowed {
|
|
mem: *mut (),
|
|
len: usize,
|
|
}
|
|
impl PhysBorrowed {
|
|
/// Constructs a `PhysBorrowed` instance.
|
|
///
|
|
/// # Arguments
|
|
/// See [physmap] for a description of the parameters.
|
|
///
|
|
/// # Returns
|
|
/// A '[Result]' which contains the following:
|
|
/// - A '[`PhysBorrowed`]' which represents the newly mapped region.
|
|
/// - An 'Err' if a memory mapping error occurs.
|
|
///
|
|
/// # Errors
|
|
/// See [physmap] for a description of the error cases.
|
|
pub fn map(base_phys: usize, len: usize, prot: Prot, ty: MemoryType) -> Result<Self> {
|
|
let mem = unsafe { physmap(base_phys, len, prot, ty)? };
|
|
Ok(Self {
|
|
mem,
|
|
len: len.next_multiple_of(PAGE_SIZE),
|
|
})
|
|
}
|
|
|
|
/// Gets a raw pointer to the borrowed region.
|
|
///
|
|
/// # Returns
|
|
/// - self.mem - A pointer to the mapped region in virtual memory.
|
|
///
|
|
/// # Notes
|
|
/// - The pointer may live beyond the lifetime of [`PhysBorrowed`], so dereferences to the pointer
|
|
/// must be treated as unsafe.
|
|
///
|
|
pub fn as_ptr(&self) -> *mut () {
|
|
self.mem
|
|
}
|
|
|
|
/// Gets the length of the mapped region.
|
|
///
|
|
/// # Returns
|
|
/// - self.len - The length of the mapped region. It should be a multiple of [`PAGE_SIZE`]
|
|
pub fn mapped_len(&self) -> usize {
|
|
self.len
|
|
}
|
|
}
|
|
|
|
impl Drop for PhysBorrowed {
|
|
/// Frees the mapped memory region.
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
let _ = libredox::call::munmap(self.mem, self.len);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Instructs the kernel to enable I/O ports for this (usermode) process (x86-specific).
|
|
///
|
|
/// On Redox, x86 privilege ring 3 represents userspace. Most Redox drivers run in userspace to
|
|
/// prevent system instability caused by a faulty driver. Processes with (bitmap-enabled) IO port
|
|
/// rights can use the IN/OUT instructions. This is not the same as IOPL 3; the CLI instruction is
|
|
/// still not allowed.
|
|
pub fn acquire_port_io_rights() -> Result<()> {
|
|
extern "C" {
|
|
fn redox_cur_thrfd_v0() -> usize;
|
|
}
|
|
let kernel_fd = syscall::dup(unsafe { redox_cur_thrfd_v0() }, b"open_via_dup")?;
|
|
let res = libredox::call::call_wo(
|
|
kernel_fd,
|
|
&[],
|
|
syscall::CallFlags::empty(),
|
|
&[ProcSchemeVerb::Iopl as u64],
|
|
);
|
|
let _ = syscall::close(kernel_fd);
|
|
res?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Kernel handle for translating virtual addresses in the current address space, to their
|
|
/// underlying physical addresses.
|
|
///
|
|
/// It is currently unspecified whether this handle is specific to the address space at the time it
|
|
/// was created, or whether all calls reference the currently active address space.
|
|
pub struct VirtaddrTranslationHandle {
|
|
fd: Fd,
|
|
}
|
|
|
|
impl VirtaddrTranslationHandle {
|
|
/// Create a new handle, requires uid=0 but this may change.
|
|
pub fn new() -> Result<Self> {
|
|
Ok(Self {
|
|
fd: memory_root_fd().openat("translation", O_CLOEXEC, 0)?,
|
|
})
|
|
}
|
|
/// Translate physical => virtual.
|
|
pub fn translate(&self, physical: usize) -> Result<usize> {
|
|
let mut buf = physical.to_ne_bytes();
|
|
libredox::call::call_ro(self.fd.raw(), &mut buf, syscall::CallFlags::empty(), &[])?;
|
|
Ok(usize::from_ne_bytes(buf))
|
|
}
|
|
}
|