//! 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 = 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)] 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, /// A region of memory that does not implement caching. Writes to these regions are immediate. Uncacheable, /// 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, /// Memory stored in an intermediate Write Combine Buffer and released later /// Memory-Mapped I/O. This is an aarch64-specific term. DeviceMemory, } 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 { 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 { Ok(Self { fd: memory_root_fd().openat("translation", O_CLOEXEC, 0)?, }) } /// Translate physical => virtual. pub fn translate(&self, physical: usize) -> Result { 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)) } }