mirror of
https://github.com/hexagonal-sun/moss-kernel.git
synced 2026-04-18 14:19:01 -04:00
libkernel: add documentation and cargo metadata
Fix up missing documentaiton and add metadata to the cargo definition.
This commit is contained in:
committed by
Ashwin Naren
parent
3f87c1f3d9
commit
8ec17724d9
@@ -2,6 +2,12 @@
|
||||
name = "libkernel"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "Architecture-independent kernel building blocks for operating systems"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/hexagonal-sun/moss-kernel/"
|
||||
readme = "README.md"
|
||||
keywords = ["kernel", "os", "no-std", "operating-system", "embedded"]
|
||||
categories = ["no-std", "os", "embedded"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -43,3 +49,6 @@ tokio = { version = "1.47.1", features = ["full"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
41
libkernel/README.md
Normal file
41
libkernel/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# libkernel
|
||||
|
||||
Architecture-independent kernel building blocks for operating systems.
|
||||
|
||||
`libkernel` provides the core abstractions that a kernel needs to manage memory,
|
||||
processes, filesystems, and synchronisation, agnostic of the an underlying CPU
|
||||
architecture. It is designed to run in a `no_std` environment and uses feature
|
||||
gates to keep the dependency footprint minimal.
|
||||
|
||||
## Feature gates
|
||||
|
||||
| Feature | Enables | Implies |
|
||||
|-----------|-------------------------------------------------------|------------------|
|
||||
| `sync` | Synchronisation primitives (spinlock, mutex, rwlock…) | — |
|
||||
| `alloc` | Memory allocators (buddy, slab) and collection types | `sync` |
|
||||
| `paging` | Page tables, address-space management, PTE helpers | `alloc` |
|
||||
| `proc` | Process identity types (UID/GID, capabilities) | — |
|
||||
| `fs` | VFS traits, path manipulation, block I/O | `proc`, `sync` |
|
||||
| `proc_vm` | Process virtual-memory management (mmap, brk, CoW) | `paging`, `fs` |
|
||||
| `kbuf` | Async-aware circular kernel buffers | `sync` |
|
||||
| `all` | Everything above | all of the above |
|
||||
|
||||
## Quick start
|
||||
|
||||
Add `libkernel` to your `Cargo.toml` with only the features you need:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
libkernel = { version = "0.1", features = ["sync", "proc"] }
|
||||
```
|
||||
|
||||
## The `CpuOps` trait
|
||||
|
||||
Most synchronisation and memory primitives are generic over a
|
||||
[`CpuOps`](https://docs.rs/libkernel/latest/libkernel/trait.CpuOps.html)
|
||||
implementation. This trait abstracts the handful of arch-specific operations
|
||||
(core ID, interrupt masking, halt) that the portable code depends on.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the MIT license. See [LICENSE](../LICENSE) for details.
|
||||
@@ -1,3 +1,5 @@
|
||||
//! AArch64 memory management types and page table support.
|
||||
|
||||
pub mod pg_descriptors;
|
||||
pub mod pg_tables;
|
||||
pub mod pg_tear_down;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! AArch64 page table entry (PTE) descriptor types and traits.
|
||||
|
||||
use paste::paste;
|
||||
use tock_registers::interfaces::{ReadWriteable, Readable};
|
||||
use tock_registers::{register_bitfields, registers::InMemoryRegister};
|
||||
@@ -61,9 +63,12 @@ impl TableAddr {
|
||||
}
|
||||
}
|
||||
|
||||
/// The memory type attribute applied to a page table mapping.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum MemoryType {
|
||||
/// Device (non-cacheable, non-reorderable) memory.
|
||||
Device,
|
||||
/// Normal (cacheable) memory.
|
||||
Normal,
|
||||
}
|
||||
|
||||
@@ -163,6 +168,7 @@ macro_rules! define_descriptor {
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns a new descriptor with the given permissions applied.
|
||||
pub fn set_permissions(self, perms: PtePermissions) -> Self {
|
||||
let reg = InMemoryRegister::new(self.0);
|
||||
use [<$name Fields>]::BlockPageFields;
|
||||
@@ -287,9 +293,13 @@ define_descriptor!(
|
||||
},
|
||||
);
|
||||
|
||||
/// The decoded state of an L3 page descriptor.
|
||||
pub enum L3DescriptorState {
|
||||
/// The entry is not present.
|
||||
Invalid,
|
||||
/// The entry has been swapped out but retains address information.
|
||||
Swapped,
|
||||
/// The entry is a valid page mapping.
|
||||
Valid,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! AArch64 page table structures, levels, and mapping logic.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::{
|
||||
@@ -17,7 +19,9 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
/// Number of page table descriptors that fit in a single 4 KiB page.
|
||||
pub const DESCRIPTORS_PER_PAGE: usize = PAGE_SIZE / core::mem::size_of::<u64>();
|
||||
/// Bitmask used to extract the page table index from a shifted virtual address.
|
||||
pub const LEVEL_MASK: usize = DESCRIPTORS_PER_PAGE - 1;
|
||||
|
||||
/// Trait representing a single level of the page table hierarchy.
|
||||
@@ -47,8 +51,10 @@ pub trait PgTable: Clone + Copy {
|
||||
/// The descriptor (page table entry) type for this level.
|
||||
type Descriptor: PageTableEntry;
|
||||
|
||||
/// Constructs this table handle from a typed virtual pointer to its backing array.
|
||||
fn from_ptr(ptr: TVA<PgTableArray<Self>>) -> Self;
|
||||
|
||||
/// Returns the raw mutable pointer to the underlying descriptor array.
|
||||
fn to_raw_ptr(self) -> *mut u64;
|
||||
|
||||
/// Compute the index into this page table from a virtual address.
|
||||
@@ -81,6 +87,7 @@ pub(super) trait TableMapperTable: PgTable<Descriptor: TableMapper> + Clone + Co
|
||||
}
|
||||
}
|
||||
|
||||
/// A page-aligned array of raw page table entries for a given table level.
|
||||
#[derive(Clone)]
|
||||
#[repr(C, align(4096))]
|
||||
pub struct PgTableArray<K: PgTable> {
|
||||
@@ -89,6 +96,7 @@ pub struct PgTableArray<K: PgTable> {
|
||||
}
|
||||
|
||||
impl<K: PgTable> PgTableArray<K> {
|
||||
/// Creates a zeroed page table array (all entries invalid).
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
pages: [0; DESCRIPTORS_PER_PAGE],
|
||||
@@ -104,8 +112,9 @@ impl<K: PgTable> Default for PgTableArray<K> {
|
||||
}
|
||||
|
||||
macro_rules! impl_pgtable {
|
||||
($table:ident, $shift:expr, $desc_type:ident) => {
|
||||
($(#[$outer:meta])* $table:ident, $shift:expr, $desc_type:ident) => {
|
||||
#[derive(Clone, Copy)]
|
||||
$(#[$outer])*
|
||||
pub struct $table {
|
||||
base: *mut u64,
|
||||
}
|
||||
@@ -145,22 +154,26 @@ macro_rules! impl_pgtable {
|
||||
};
|
||||
}
|
||||
|
||||
impl_pgtable!(L0Table, 39, L0Descriptor);
|
||||
impl_pgtable!(/// Level 0 page table (512 GiB per entry).
|
||||
L0Table, 39, L0Descriptor);
|
||||
impl TableMapperTable for L0Table {
|
||||
type NextLevel = L1Table;
|
||||
}
|
||||
|
||||
impl_pgtable!(L1Table, 30, L1Descriptor);
|
||||
impl_pgtable!(/// Level 1 page table (1 GiB per entry).
|
||||
L1Table, 30, L1Descriptor);
|
||||
impl TableMapperTable for L1Table {
|
||||
type NextLevel = L2Table;
|
||||
}
|
||||
|
||||
impl_pgtable!(L2Table, 21, L2Descriptor);
|
||||
impl_pgtable!(/// Level 2 page table (2 MiB per entry).
|
||||
L2Table, 21, L2Descriptor);
|
||||
impl TableMapperTable for L2Table {
|
||||
type NextLevel = L3Table;
|
||||
}
|
||||
|
||||
impl_pgtable!(L3Table, 12, L3Descriptor);
|
||||
impl_pgtable!(/// Level 3 page table (4 KiB per entry).
|
||||
L3Table, 12, L3Descriptor);
|
||||
|
||||
/// Trait for temporarily mapping and modifying a page table located at a
|
||||
/// physical address.
|
||||
@@ -459,6 +472,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(missing_docs)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Utilities for tearing down and freeing page table hierarchies.
|
||||
|
||||
use super::pg_descriptors::{PaMapper, TableMapper};
|
||||
use super::pg_tables::L0Table;
|
||||
use super::{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Page table walking and per-entry modification.
|
||||
|
||||
use super::{
|
||||
pg_descriptors::{L3Descriptor, PageTableEntry, TableMapper},
|
||||
pg_tables::{L0Table, L3Table, PageTableMapper, PgTable, PgTableArray, TableMapperTable},
|
||||
@@ -17,7 +19,9 @@ pub struct WalkContext<'a, PM>
|
||||
where
|
||||
PM: PageTableMapper + 'a,
|
||||
{
|
||||
/// The mapper used to temporarily access page tables by physical address.
|
||||
pub mapper: &'a mut PM,
|
||||
/// The TLB invalidator invoked after modifying page table entries.
|
||||
pub invalidator: &'a dyn TLBInvalidator,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! TLB invalidation helpers.
|
||||
|
||||
/// Trait for invalidating TLB entries after page table modifications.
|
||||
pub trait TLBInvalidator {}
|
||||
|
||||
/// A no-op TLB invalidator used when invalidation is unnecessary.
|
||||
pub struct NullTlbInvalidator {}
|
||||
|
||||
impl TLBInvalidator for NullTlbInvalidator {}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
//! AArch64 (ARM64) specific implementations.
|
||||
|
||||
pub mod memory;
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
//! Architecture-specific support code.
|
||||
|
||||
pub mod arm64;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
//! Device descriptor types.
|
||||
|
||||
/// A major/minor pair identifying a character or block device.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct CharDevDescriptor {
|
||||
/// The major device number (identifies the driver).
|
||||
pub major: u64,
|
||||
/// The minor device number (identifies the device instance).
|
||||
pub minor: u64,
|
||||
}
|
||||
|
||||
@@ -1,217 +1,295 @@
|
||||
//! Unified kernel error types.
|
||||
//!
|
||||
//! This module defines the error hierarchy used throughout the kernel.
|
||||
//! [`KernelError`] is the top-level enum and wraps domain-specific errors such
|
||||
//! as [`MapError`], [`FsError`], [`IoError`], and others. A [`Result<T>`] type
|
||||
//! alias is provided for convenience.
|
||||
//!
|
||||
//! The [`syscall_error`] submodule maps [`KernelError`] variants to their POSIX
|
||||
//! `errno` equivalents for returning values to user space.
|
||||
|
||||
use core::convert::Infallible;
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod syscall_error;
|
||||
|
||||
/// Errors that can occur during device probing.
|
||||
#[derive(Debug, Error, PartialEq, Eq, Clone)]
|
||||
pub enum ProbeError {
|
||||
/// No registers present in the Flattened Device Tree.
|
||||
#[error("No registers present in FDT")]
|
||||
NoReg,
|
||||
|
||||
/// No register bank size found in the Flattened Device Tree.
|
||||
#[error("No register bank size in FDT")]
|
||||
NoRegSize,
|
||||
|
||||
/// No interrupts declared in the Flattened Device Tree.
|
||||
#[error("No interrupts in FDT")]
|
||||
NoInterrupts,
|
||||
|
||||
/// No parent interrupt controller found in the Flattened Device Tree.
|
||||
#[error("No parent interrupt controller in FDT")]
|
||||
NoParentInterrupt,
|
||||
|
||||
/// The specified interrupt parent is not an interrupt controller.
|
||||
#[error("The specified interrupt parent isn't an interrupt controller")]
|
||||
NotInterruptController,
|
||||
|
||||
// Driver probing should be tried again after other probes have succeeded.
|
||||
/// Driver probing should be tried again after other probes have succeeded.
|
||||
#[error("Driver probing deferred for other dependencies")]
|
||||
Deferred,
|
||||
|
||||
// Device inspected but not a match for this driver; skip silently.
|
||||
/// Device inspected but not a match for this driver; skip silently.
|
||||
#[error("Device not matched by driver")]
|
||||
NoMatch,
|
||||
}
|
||||
|
||||
/// Errors that can occur during page table mapping.
|
||||
#[derive(Debug, Error, PartialEq, Eq, Clone)]
|
||||
pub enum MapError {
|
||||
/// Physical address is not page aligned.
|
||||
#[error("Physical address not page aligned")]
|
||||
PhysNotAligned,
|
||||
|
||||
#[error("Physical address not page aligned")]
|
||||
/// Virtual address is not page aligned.
|
||||
#[error("Virtual address not page aligned")]
|
||||
VirtNotAligned,
|
||||
|
||||
/// Physical and virtual range sizes do not match.
|
||||
#[error("Physical and virtual range sizes do not match")]
|
||||
SizeMismatch,
|
||||
|
||||
/// Failed to walk to the next level page table.
|
||||
#[error("Failed to walk to the next level page table")]
|
||||
WalkFailed,
|
||||
|
||||
/// Invalid page table descriptor encountered.
|
||||
#[error("Invalid page table descriptor encountered")]
|
||||
InvalidDescriptor,
|
||||
|
||||
/// The region to be mapped is smaller than `PAGE_SIZE`.
|
||||
#[error("The region to be mapped is smaller than PAGE_SIZE")]
|
||||
TooSmall,
|
||||
|
||||
/// The virtual address range has already been mapped.
|
||||
#[error("The VA range is has already been mapped")]
|
||||
AlreadyMapped,
|
||||
|
||||
/// Page table does not contain an L3 mapping.
|
||||
#[error("Page table does not contain an L3 mapping")]
|
||||
NotL3Mapped,
|
||||
}
|
||||
|
||||
/// Errors from block-level I/O operations.
|
||||
#[derive(Error, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum IoError {
|
||||
/// The requested I/O operation was out of bounds for the block device.
|
||||
#[error("The requested I/O operation was out of bounds for the block device")]
|
||||
OutOfBounds,
|
||||
|
||||
/// Corruption found in the filesystem metadata.
|
||||
#[error("Corruption found in the filesystem metadata")]
|
||||
MetadataCorruption,
|
||||
}
|
||||
|
||||
/// Errors from filesystem operations.
|
||||
#[derive(Error, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum FsError {
|
||||
/// The path or file was not found.
|
||||
#[error("The path or file was not found")]
|
||||
NotFound,
|
||||
|
||||
/// The path component is not a directory.
|
||||
#[error("The path component is not a directory.")]
|
||||
NotADirectory,
|
||||
|
||||
/// The path component is a directory.
|
||||
#[error("The path component is a directory.")]
|
||||
IsADirectory,
|
||||
|
||||
/// The file or directory already exists.
|
||||
#[error("The file or directory already exists.")]
|
||||
AlreadyExists,
|
||||
|
||||
/// The directory is not empty.
|
||||
#[error("The directory is not empty.")]
|
||||
DirectoryNotEmpty,
|
||||
|
||||
/// Invalid input parameters.
|
||||
#[error("Invalid input parameters.")]
|
||||
InvalidInput,
|
||||
|
||||
/// The filesystem is corrupted or has an invalid format.
|
||||
#[error("The filesystem is corrupted or has an invalid format.")]
|
||||
InvalidFs,
|
||||
|
||||
/// Attempted to access data out of bounds.
|
||||
#[error("Attempted to access data out of bounds.")]
|
||||
OutOfBounds,
|
||||
|
||||
/// The operation is not permitted.
|
||||
#[error("The operation is not permitted.")]
|
||||
PermissionDenied,
|
||||
|
||||
/// Could not find the specified filesystem driver.
|
||||
#[error("Could not find the specified FS driver")]
|
||||
DriverNotFound,
|
||||
|
||||
/// Too many open files.
|
||||
#[error("Too many open files")]
|
||||
TooManyFiles,
|
||||
|
||||
/// The device could not be found.
|
||||
#[error("The device could not be found")]
|
||||
NoDevice,
|
||||
|
||||
/// Too many symbolic links encountered.
|
||||
#[error("Too many symbolic links encountered")]
|
||||
Loop,
|
||||
|
||||
/// Attempted to rename across devices.
|
||||
#[error("Attempted to rename from cross device")]
|
||||
CrossDevice,
|
||||
}
|
||||
|
||||
/// Errors that occur when loading or parsing an executable.
|
||||
#[derive(Error, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ExecError {
|
||||
/// Invalid ELF format.
|
||||
#[error("Invalid ELF Format")]
|
||||
InvalidElfFormat,
|
||||
|
||||
/// Invalid script format.
|
||||
#[error("Invalid Script Format")]
|
||||
InvalidScriptFormat,
|
||||
|
||||
/// Invalid program header format.
|
||||
#[error("Invalid Program Header Format")]
|
||||
InvalidPHdrFormat,
|
||||
}
|
||||
|
||||
/// Top-level kernel error type wrapping all domain-specific errors.
|
||||
#[derive(Error, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum KernelError {
|
||||
/// Cannot allocate memory.
|
||||
#[error("Cannot allocate memory")]
|
||||
NoMemory,
|
||||
|
||||
/// Memory region not found.
|
||||
#[error("Memory region not found")]
|
||||
NoMemRegion,
|
||||
|
||||
/// Invalid value.
|
||||
#[error("Invalid value")]
|
||||
InvalidValue,
|
||||
|
||||
/// The current resource is already in use.
|
||||
#[error("The current resource is already in use")]
|
||||
InUse,
|
||||
|
||||
/// Page table mapping failed.
|
||||
#[error("Page table mapping failed: {0}")]
|
||||
MappingError(#[from] MapError),
|
||||
|
||||
/// Provided object is too large.
|
||||
#[error("Provided object is too large")]
|
||||
TooLarge,
|
||||
|
||||
/// Operation not supported.
|
||||
#[error("Operation not supported")]
|
||||
NotSupported,
|
||||
|
||||
/// Address family not supported.
|
||||
#[error("Address family not supported")]
|
||||
AddressFamilyNotSupported,
|
||||
|
||||
/// Device probe failed.
|
||||
#[error("Device probe failed: {0}")]
|
||||
Probe(#[from] ProbeError),
|
||||
|
||||
/// I/O operation failed.
|
||||
#[error("I/O operation failed: {0}")]
|
||||
Io(#[from] IoError),
|
||||
|
||||
/// Filesystem operation failed.
|
||||
#[error("Filesystem operation failed: {0}")]
|
||||
Fs(#[from] FsError),
|
||||
|
||||
/// Exec error during executable loading.
|
||||
#[error("Exec error: {0}")]
|
||||
Exec(#[from] ExecError),
|
||||
|
||||
/// Not a tty.
|
||||
#[error("Not a tty")]
|
||||
NotATty,
|
||||
|
||||
/// Fault error during syscall.
|
||||
#[error("Fault error during syscall")]
|
||||
Fault,
|
||||
|
||||
/// Not an open file descriptor.
|
||||
#[error("Not an open file descriptor")]
|
||||
BadFd,
|
||||
|
||||
/// Cannot seek on a pipe.
|
||||
#[error("Cannot seek on a pipe")]
|
||||
SeekPipe,
|
||||
|
||||
/// Broken pipe.
|
||||
#[error("Broken pipe")]
|
||||
BrokenPipe,
|
||||
|
||||
/// Operation not permitted.
|
||||
#[error("Operation not permitted")]
|
||||
NotPermitted,
|
||||
|
||||
/// Buffer is full.
|
||||
#[error("Buffer is full")]
|
||||
BufferFull,
|
||||
|
||||
/// Operation would block.
|
||||
#[error("Operation would block")]
|
||||
TryAgain,
|
||||
|
||||
/// No such process.
|
||||
#[error("No such process")]
|
||||
NoProcess,
|
||||
|
||||
/// No child process.
|
||||
#[error("No child process")]
|
||||
NoChildProcess,
|
||||
|
||||
/// Operation timed out.
|
||||
#[error("Operation timed out")]
|
||||
TimedOut,
|
||||
|
||||
/// Value out of range.
|
||||
#[error("Value out of range")]
|
||||
RangeError,
|
||||
|
||||
/// Operation not supported on transport endpoint.
|
||||
#[error("Operation not supported on transport endpoint")]
|
||||
OpNotSupported,
|
||||
|
||||
/// Interrupted system call.
|
||||
#[error("Interrupted system call")]
|
||||
Interrupted,
|
||||
|
||||
/// Name too long.
|
||||
#[error("Name too long")]
|
||||
NameTooLong,
|
||||
|
||||
/// Not a socket.
|
||||
#[error("Not a socket")]
|
||||
NotASocket,
|
||||
|
||||
/// Other error with a static description.
|
||||
#[error("{0}")]
|
||||
Other(&'static str),
|
||||
}
|
||||
|
||||
/// Convenience alias for a [`core::result::Result`] with [`KernelError`].
|
||||
pub type Result<T> = core::result::Result<T, KernelError>;
|
||||
|
||||
impl From<Infallible> for KernelError {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! Maps [`KernelError`] variants to POSIX `errno` values
|
||||
//! for returning results to user-space system calls.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::error::FsError;
|
||||
|
||||
use super::KernelError;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! File attribute types (permissions, modes, and metadata).
|
||||
|
||||
use crate::{
|
||||
error::{KernelError, Result},
|
||||
proc::{
|
||||
@@ -7,10 +9,10 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{FileType, InodeId};
|
||||
use bitflags::bitflags;
|
||||
use core::time::Duration;
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// POSIX access-mode flags for permission checks (`R_OK`, `W_OK`, `X_OK`).
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AccessMode: i32 {
|
||||
/// Execution is permitted
|
||||
@@ -22,58 +24,70 @@ bitflags::bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FilePermissions: u16 {
|
||||
const S_IXOTH = 0x0001;
|
||||
const S_IWOTH = 0x0002;
|
||||
const S_IROTH = 0x0004;
|
||||
mod _file_permissions {
|
||||
#![allow(missing_docs)]
|
||||
use bitflags::bitflags;
|
||||
bitflags! {
|
||||
/// POSIX file permission bits (owner/group/other read/write/execute and setuid/setgid/sticky).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FilePermissions: u16 {
|
||||
const S_IXOTH = 0x0001;
|
||||
const S_IWOTH = 0x0002;
|
||||
const S_IROTH = 0x0004;
|
||||
|
||||
const S_IXGRP = 0x0008;
|
||||
const S_IWGRP = 0x0010;
|
||||
const S_IRGRP = 0x0020;
|
||||
const S_IXGRP = 0x0008;
|
||||
const S_IWGRP = 0x0010;
|
||||
const S_IRGRP = 0x0020;
|
||||
|
||||
const S_IXUSR = 0x0040;
|
||||
const S_IWUSR = 0x0080;
|
||||
const S_IRUSR = 0x0100;
|
||||
const S_IXUSR = 0x0040;
|
||||
const S_IWUSR = 0x0080;
|
||||
const S_IRUSR = 0x0100;
|
||||
|
||||
const S_ISVTX = 0x0200;
|
||||
const S_ISVTX = 0x0200;
|
||||
|
||||
const S_ISGID = 0x0400;
|
||||
const S_ISUID = 0x0800;
|
||||
const S_ISGID = 0x0400;
|
||||
const S_ISUID = 0x0800;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use _file_permissions::FilePermissions;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FileMode: u16 {
|
||||
const S_IXOTH = 0x0001;
|
||||
const S_IWOTH = 0x0002;
|
||||
const S_IROTH = 0x0004;
|
||||
mod _file_mode {
|
||||
#![allow(missing_docs)]
|
||||
use bitflags::bitflags;
|
||||
bitflags! {
|
||||
/// Combined file type and permission bits, as returned by `stat`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FileMode: u16 {
|
||||
const S_IXOTH = 0x0001;
|
||||
const S_IWOTH = 0x0002;
|
||||
const S_IROTH = 0x0004;
|
||||
|
||||
const S_IXGRP = 0x0008;
|
||||
const S_IWGRP = 0x0010;
|
||||
const S_IRGRP = 0x0020;
|
||||
const S_IXGRP = 0x0008;
|
||||
const S_IWGRP = 0x0010;
|
||||
const S_IRGRP = 0x0020;
|
||||
|
||||
const S_IXUSR = 0x0040;
|
||||
const S_IWUSR = 0x0080;
|
||||
const S_IRUSR = 0x0100;
|
||||
const S_IXUSR = 0x0040;
|
||||
const S_IWUSR = 0x0080;
|
||||
const S_IRUSR = 0x0100;
|
||||
|
||||
const S_ISVTX = 0x0200;
|
||||
const S_ISVTX = 0x0200;
|
||||
|
||||
const S_ISGID = 0x0400;
|
||||
const S_ISUID = 0x0800;
|
||||
const S_ISGID = 0x0400;
|
||||
const S_ISUID = 0x0800;
|
||||
|
||||
// Mutually-exclusive file types:
|
||||
const S_IFIFO = 0x1000;
|
||||
const S_IFCHR = 0x2000;
|
||||
const S_IFDIR = 0x4000;
|
||||
const S_IFBLK = 0x6000;
|
||||
const S_IFREG = 0x8000;
|
||||
const S_IFLNK = 0xA000;
|
||||
const S_IFSOCK = 0xC000;
|
||||
// Mutually-exclusive file types:
|
||||
const S_IFIFO = 0x1000;
|
||||
const S_IFCHR = 0x2000;
|
||||
const S_IFDIR = 0x4000;
|
||||
const S_IFBLK = 0x6000;
|
||||
const S_IFREG = 0x8000;
|
||||
const S_IFLNK = 0xA000;
|
||||
const S_IFSOCK = 0xC000;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use _file_mode::FileMode;
|
||||
|
||||
impl From<FileMode> for FilePermissions {
|
||||
fn from(mode: FileMode) -> Self {
|
||||
@@ -82,6 +96,7 @@ impl From<FileMode> for FilePermissions {
|
||||
}
|
||||
|
||||
impl FileMode {
|
||||
/// Constructs a `FileMode` from a file type and permission bits.
|
||||
pub fn new(file_type: FileType, permissions: FilePermissions) -> Self {
|
||||
let mut mode = FileMode::from_bits_truncate(permissions.bits());
|
||||
mode |= match file_type {
|
||||
@@ -98,6 +113,7 @@ impl FileMode {
|
||||
}
|
||||
|
||||
/// Represents file metadata, similar to `stat`.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileAttr {
|
||||
pub id: InodeId,
|
||||
@@ -116,6 +132,7 @@ pub struct FileAttr {
|
||||
}
|
||||
|
||||
impl FileAttr {
|
||||
/// Returns the combined file type and permission bits as a `FileMode`.
|
||||
pub fn mode(&self) -> FileMode {
|
||||
FileMode::new(self.file_type, self.permissions)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Block I/O buffer management.
|
||||
|
||||
use core::{mem, slice};
|
||||
|
||||
use crate::{error::Result, fs::BlockDevice, pod::Pod};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Block device layer.
|
||||
|
||||
pub mod buffer;
|
||||
#[cfg(feature = "paging")]
|
||||
pub mod ramdisk;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! RAM-backed block device implementation.
|
||||
|
||||
use crate::{
|
||||
KernAddressSpace,
|
||||
error::{IoError, KernelError, Result},
|
||||
@@ -13,6 +15,7 @@ use alloc::boxed::Box;
|
||||
use async_trait::async_trait;
|
||||
use core::ptr;
|
||||
|
||||
/// A block device backed by a region of RAM.
|
||||
pub struct RamdiskBlkDev {
|
||||
base: TVA<u8>,
|
||||
num_blocks: u64,
|
||||
|
||||
@@ -110,6 +110,7 @@ impl From<Metadata> for FileAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps an ext4 directory iterator to produce VFS [`Dirent`] entries.
|
||||
pub struct ReadDirWrapper {
|
||||
inner: AsyncSkip<ReadDir>,
|
||||
fs_id: u64,
|
||||
@@ -117,6 +118,7 @@ pub struct ReadDirWrapper {
|
||||
}
|
||||
|
||||
impl ReadDirWrapper {
|
||||
/// Creates a new `ReadDirWrapper` starting at the given offset.
|
||||
pub fn new(inner: ReadDir, fs_id: u64, start_offset: u64) -> Self {
|
||||
Self {
|
||||
inner: inner.skip(start_offset as usize),
|
||||
@@ -187,6 +189,7 @@ impl DerefMut for InodeInner {
|
||||
}
|
||||
}
|
||||
|
||||
/// An inode within an ext4 filesystem.
|
||||
pub struct Ext4Inode<CPU: CpuOps> {
|
||||
fs_ref: Weak<Ext4Filesystem<CPU>>,
|
||||
id: NonZeroU32,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! FAT32 filesystem driver.
|
||||
|
||||
use crate::{
|
||||
error::{FsError, Result},
|
||||
fs::{FileType, Filesystem, Inode, InodeId, attr::FileAttr, blk::buffer::BlockBuffer},
|
||||
@@ -23,6 +25,7 @@ mod fat;
|
||||
mod file;
|
||||
mod reader;
|
||||
|
||||
/// A logical sector number on a FAT32 volume.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Sector(u32);
|
||||
|
||||
@@ -43,23 +46,28 @@ impl Add<Sector> for Sector {
|
||||
}
|
||||
|
||||
impl Sector {
|
||||
/// Returns an iterator over sectors from `self` (inclusive) to `other` (exclusive).
|
||||
pub fn sectors_until(self, other: Self) -> impl Iterator<Item = Self> {
|
||||
(self.0..other.0).map(Sector)
|
||||
}
|
||||
}
|
||||
|
||||
/// A FAT32 cluster number.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct Cluster(u32);
|
||||
|
||||
impl Cluster {
|
||||
/// Returns the raw cluster number as a `usize`.
|
||||
pub fn value(self) -> usize {
|
||||
self.0 as _
|
||||
}
|
||||
|
||||
/// Constructs a cluster number from its high and low 16-bit halves.
|
||||
pub fn from_high_low(clust_high: u16, clust_low: u16) -> Cluster {
|
||||
Cluster((clust_high as u32) << 16 | clust_low as u32)
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a valid data cluster (number >= 2).
|
||||
pub fn is_valid(self) -> bool {
|
||||
self.0 >= 2
|
||||
}
|
||||
@@ -71,6 +79,7 @@ impl Display for Cluster {
|
||||
}
|
||||
}
|
||||
|
||||
/// A mounted FAT32 filesystem instance.
|
||||
pub struct Fat32Filesystem {
|
||||
dev: BlockBuffer,
|
||||
bpb: BiosParameterBlock,
|
||||
@@ -80,6 +89,7 @@ pub struct Fat32Filesystem {
|
||||
}
|
||||
|
||||
impl Fat32Filesystem {
|
||||
/// Creates a new FAT32 filesystem from the given block device buffer.
|
||||
pub async fn new(dev: BlockBuffer, id: u64) -> Result<Arc<Self>> {
|
||||
let bpb = BiosParameterBlock::new(&dev).await?;
|
||||
let fat = Fat::read_fat(&dev, &bpb, 0).await?;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Concrete filesystem implementations.
|
||||
|
||||
pub mod ext4;
|
||||
pub mod fat32;
|
||||
#[cfg(feature = "alloc")]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! In-memory temporary filesystem (tmpfs).
|
||||
|
||||
use crate::{
|
||||
CpuOps,
|
||||
error::{FsError, KernelError, Result},
|
||||
@@ -783,6 +785,7 @@ impl<C: CpuOps> TmpFsSymlinkInode<C> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An in-memory temporary filesystem backed by page allocations.
|
||||
pub struct TmpFs<C, G, T>
|
||||
where
|
||||
C: CpuOps,
|
||||
@@ -802,6 +805,7 @@ where
|
||||
G: PageAllocGetter<C>,
|
||||
T: AddressTranslator<()>,
|
||||
{
|
||||
/// Creates a new tmpfs instance with the given filesystem ID.
|
||||
pub fn new(fs_id: u64) -> Arc<Self> {
|
||||
Arc::new_cyclic(|weak_fs| {
|
||||
let root =
|
||||
@@ -817,6 +821,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// Allocates the next unique inode ID for this filesystem.
|
||||
pub fn alloc_inode_id(&self) -> u64 {
|
||||
self.next_inode_id.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@@ -32,28 +32,38 @@ use async_trait::async_trait;
|
||||
use attr::{FileAttr, FilePermissions};
|
||||
use core::time::Duration;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct OpenFlags: u32 {
|
||||
const O_RDONLY = 0b000;
|
||||
const O_WRONLY = 0b001;
|
||||
const O_RDWR = 0b010;
|
||||
const O_ACCMODE = 0b011;
|
||||
const O_CREAT = 0o100;
|
||||
const O_EXCL = 0o200;
|
||||
const O_TRUNC = 0o1000;
|
||||
const O_DIRECTORY = 0o200000;
|
||||
const O_APPEND = 0o2000;
|
||||
const O_NONBLOCK = 0o4000;
|
||||
const O_CLOEXEC = 0o2000000;
|
||||
mod _open_flags {
|
||||
#![allow(missing_docs)]
|
||||
bitflags::bitflags! {
|
||||
/// Flags used when opening a file, corresponding to POSIX `O_*` constants.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct OpenFlags: u32 {
|
||||
const O_RDONLY = 0b000;
|
||||
const O_WRONLY = 0b001;
|
||||
const O_RDWR = 0b010;
|
||||
const O_ACCMODE = 0b011;
|
||||
const O_CREAT = 0o100;
|
||||
const O_EXCL = 0o200;
|
||||
const O_TRUNC = 0o1000;
|
||||
const O_DIRECTORY = 0o200000;
|
||||
const O_APPEND = 0o2000;
|
||||
const O_NONBLOCK = 0o4000;
|
||||
const O_CLOEXEC = 0o2000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use _open_flags::OpenFlags;
|
||||
|
||||
// Reserved psuedo filesystem instances created internally in the kernel.
|
||||
// Reserved pseudo filesystem instances created internally in the kernel.
|
||||
/// Filesystem instance ID for the device filesystem.
|
||||
pub const DEVFS_ID: u64 = 1;
|
||||
/// Filesystem instance ID for the proc filesystem.
|
||||
pub const PROCFS_ID: u64 = 2;
|
||||
/// Filesystem instance ID for the sys filesystem.
|
||||
pub const SYSFS_ID: u64 = 3;
|
||||
/// Filesystem instance ID for the cgroup filesystem.
|
||||
pub const CGROUPFS_ID: u64 = 4;
|
||||
/// Starting ID for user-mounted filesystem instances.
|
||||
pub const FS_ID_START: u64 = 10;
|
||||
|
||||
/// Trait for a mounted filesystem instance. Its main role is to act as a
|
||||
@@ -78,30 +88,34 @@ pub trait Filesystem: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
// A unique identifier for an inode across the entire VFS. A tuple of
|
||||
// (filesystem_id, inode_number).
|
||||
/// A unique identifier for an inode across the entire VFS, combining a filesystem ID and inode number.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct InodeId(u64, u64);
|
||||
|
||||
impl InodeId {
|
||||
/// Creates an `InodeId` from a filesystem ID and an inode number.
|
||||
pub fn from_fsid_and_inodeid(fs_id: u64, inode_id: u64) -> Self {
|
||||
Self(fs_id, inode_id)
|
||||
}
|
||||
|
||||
/// Returns a sentinel `InodeId` used as a placeholder.
|
||||
pub fn dummy() -> Self {
|
||||
Self(u64::MAX, u64::MAX)
|
||||
}
|
||||
|
||||
/// Returns the filesystem ID component.
|
||||
pub fn fs_id(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the inode number component.
|
||||
pub fn inode_id(self) -> u64 {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard POSIX file types.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum FileType {
|
||||
File,
|
||||
@@ -138,13 +152,18 @@ pub trait DirStream: Send + Sync {
|
||||
/// Represents a single directory entry.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dirent {
|
||||
/// The inode identifier of this entry.
|
||||
pub id: InodeId,
|
||||
/// The name of this directory entry.
|
||||
pub name: String,
|
||||
/// The type of file this entry represents.
|
||||
pub file_type: FileType,
|
||||
/// The byte offset of this entry within the directory.
|
||||
pub offset: u64,
|
||||
}
|
||||
|
||||
impl Dirent {
|
||||
/// Creates a new directory entry.
|
||||
pub fn new(name: String, id: InodeId, file_type: FileType, offset: u64) -> Self {
|
||||
Self {
|
||||
id,
|
||||
@@ -158,8 +177,11 @@ impl Dirent {
|
||||
/// Specifies how to seek within a file, mirroring `std::io::SeekFrom`.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum SeekFrom {
|
||||
/// Seek from the beginning of the file.
|
||||
Start(u64),
|
||||
/// Seek from the end of the file.
|
||||
End(i64),
|
||||
/// Seek relative to the current position.
|
||||
Current(i64),
|
||||
}
|
||||
|
||||
@@ -329,6 +351,7 @@ pub trait Inode: Send + Sync + Any {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return this inode as an `Any` object, suitable for downcasting.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
@@ -336,14 +359,19 @@ pub trait Inode: Send + Sync + Any {
|
||||
/// for common inode operations.
|
||||
#[async_trait]
|
||||
pub trait SimpleFile {
|
||||
/// Returns the inode ID of this file.
|
||||
fn id(&self) -> InodeId;
|
||||
/// Returns the file metadata.
|
||||
async fn getattr(&self) -> Result<FileAttr>;
|
||||
/// Reads the entire file contents into a byte vector.
|
||||
async fn read(&self) -> Result<Vec<u8>>;
|
||||
/// Reads the target of a symbolic link, if applicable.
|
||||
async fn readlink(&self) -> Result<PathBuf> {
|
||||
Err(KernelError::NotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[async_trait]
|
||||
impl<T> Inode for T
|
||||
where
|
||||
@@ -385,12 +413,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple in-memory directory stream backed by a `Vec` of entries.
|
||||
pub struct SimpleDirStream {
|
||||
entries: Vec<Dirent>,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl SimpleDirStream {
|
||||
/// Creates a new `SimpleDirStream` starting at the given offset.
|
||||
pub fn new(entries: Vec<Dirent>, start_offset: u64) -> Self {
|
||||
Self {
|
||||
entries,
|
||||
|
||||
@@ -1,10 +1,59 @@
|
||||
//! # libkernel
|
||||
//!
|
||||
//! Architecture-independent kernel building blocks for operating systems.
|
||||
//!
|
||||
//!
|
||||
//! `libkernel` provides the core abstractions that a kernel needs to manage
|
||||
//! memory, processes, filesystems, and synchronisation — all without tying the
|
||||
//! implementation to a particular CPU architecture. It is designed to run in a
|
||||
//! `no_std` environment and relies on feature gates to keep the dependency
|
||||
//! footprint minimal.
|
||||
//!
|
||||
//! ## Feature gates
|
||||
//!
|
||||
//! Most of the crate is hidden behind Cargo features so that consumers only pay
|
||||
//! for the subsystems they need:
|
||||
//!
|
||||
//! | Feature | Enables | Implies |
|
||||
//! |-----------|-------------------------------------------------------|------------------|
|
||||
//! | `sync` | Synchronisation primitives (spinlock, mutex, rwlock…) | — |
|
||||
//! | `alloc` | Memory allocators (buddy, slab) and collection types | `sync` |
|
||||
//! | `paging` | Page tables, address-space management, PTE helpers | `alloc` |
|
||||
//! | `proc` | Process identity types (UID/GID, capabilities) | — |
|
||||
//! | `fs` | VFS traits, path manipulation, block I/O | `proc`, `sync` |
|
||||
//! | `proc_vm` | Process virtual-memory management (mmap, brk, CoW) | `paging`, `fs` |
|
||||
//! | `kbuf` | Async-aware circular kernel buffers | `sync` |
|
||||
//! | `all` | Everything above | all of the above |
|
||||
//!
|
||||
//! ## The `CpuOps` trait
|
||||
//!
|
||||
//! Nearly every synchronisation and memory primitive in the crate is generic
|
||||
//! over a [`CpuOps`] implementation. This trait abstracts the handful of
|
||||
//! arch-specific operations (core ID, interrupt masking, halt) that the
|
||||
//! arch-independent code depends on, making the library portable while still
|
||||
//! fully testable on the host.
|
||||
//!
|
||||
//! ## Crate layout
|
||||
//!
|
||||
//! - [`error`] — Unified kernel error types and POSIX errno mapping.
|
||||
//! - [`memory`] — Typed addresses, memory regions, page allocators, and
|
||||
//! address-space management.
|
||||
//! - [`sync`] — Async-aware spinlocks, mutexes, rwlocks, condvars, channels,
|
||||
//! and per-CPU storage *(feature `sync`)*.
|
||||
//! - [`fs`] — VFS traits (`Filesystem`, `Inode`, `BlockDevice`), path
|
||||
//! manipulation, and filesystem driver scaffolding *(feature `fs`)*.
|
||||
//! - [`proc`] — Process identity types and Linux-compatible capabilities
|
||||
//! *(feature `proc`)*.
|
||||
//! - [`arch`] — Architecture-specific support code *(feature `paging`)*.
|
||||
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[cfg(feature = "paging")]
|
||||
pub mod arch;
|
||||
pub mod error;
|
||||
#[cfg(feature = "fs")]
|
||||
pub mod driver;
|
||||
pub mod error;
|
||||
#[cfg(feature = "fs")]
|
||||
pub mod fs;
|
||||
pub mod memory;
|
||||
@@ -18,10 +67,32 @@ pub mod sync;
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "paging")]
|
||||
pub use memory::address_space::{
|
||||
KernAddressSpace, PageInfo, UserAddressSpace, VirtualMemory,
|
||||
};
|
||||
pub use memory::address_space::{KernAddressSpace, PageInfo, UserAddressSpace, VirtualMemory};
|
||||
|
||||
/// Trait abstracting the small set of CPU operations that the
|
||||
/// architecture-independent kernel code requires.
|
||||
///
|
||||
/// Every concrete kernel target must provide an implementation of this trait.
|
||||
/// The synchronisation primitives in [`sync`] and the memory subsystem in
|
||||
/// [`memory`] are generic over `CpuOps`, which keeps this crate portable while
|
||||
/// allowing the real kernel — and unit tests — to supply their own
|
||||
/// implementations.
|
||||
///
|
||||
/// # Example (test mock)
|
||||
///
|
||||
/// ```
|
||||
/// use libkernel::CpuOps;
|
||||
///
|
||||
/// struct MockCpu;
|
||||
///
|
||||
/// impl CpuOps for MockCpu {
|
||||
/// fn id() -> usize { 0 }
|
||||
/// fn halt() -> ! { loop { core::hint::spin_loop() } }
|
||||
/// fn disable_interrupts() -> usize { 0 }
|
||||
/// fn restore_interrupt_state(_flags: usize) {}
|
||||
/// fn enable_interrupts() {}
|
||||
/// }
|
||||
/// ```
|
||||
pub trait CpuOps: 'static {
|
||||
/// Returns the ID of the currently executing core.
|
||||
fn id() -> usize;
|
||||
@@ -41,6 +112,7 @@ pub trait CpuOps: 'static {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(missing_docs)]
|
||||
pub mod test {
|
||||
use core::hint::spin_loop;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! `address` module: Type-safe handling of virtual and physical addresses.
|
||||
//! Type-safe handling of virtual and physical addresses.
|
||||
//!
|
||||
//! This module defines strongly-typed address representations for both physical
|
||||
//! and virtual memory. It provides abstractions to ensure correct usage and
|
||||
@@ -147,20 +147,24 @@ impl<K: MemKind, T> Address<K, T> {
|
||||
self.inner & PAGE_MASK
|
||||
}
|
||||
|
||||
/// Returns the null (zero) address.
|
||||
pub const fn null() -> Self {
|
||||
Self::from_value(0)
|
||||
}
|
||||
|
||||
/// Returns a new address offset forward by `n` bytes.
|
||||
#[must_use]
|
||||
pub const fn add_bytes(self, n: usize) -> Self {
|
||||
Self::from_value(self.value() + n)
|
||||
}
|
||||
|
||||
/// Returns a new address offset backward by `n` bytes.
|
||||
#[must_use]
|
||||
pub fn sub_bytes(self, n: usize) -> Self {
|
||||
Self::from_value(self.value() - n)
|
||||
}
|
||||
|
||||
/// Returns `true` if this is the null (zero) address.
|
||||
pub fn is_null(self) -> bool {
|
||||
self.inner == 0
|
||||
}
|
||||
@@ -291,6 +295,7 @@ impl PA {
|
||||
TPA::from_value(self.value())
|
||||
}
|
||||
|
||||
/// Converts this physical address to a page frame number.
|
||||
pub fn to_pfn(&self) -> PageFrame {
|
||||
PageFrame::from_pfn(self.inner >> PAGE_SHIFT)
|
||||
}
|
||||
@@ -298,7 +303,9 @@ impl PA {
|
||||
|
||||
/// Trait for translating between physical and virtual addresses.
|
||||
pub trait AddressTranslator<T>: 'static + Send + Sync {
|
||||
/// Translates a virtual address to its corresponding physical address.
|
||||
fn virt_to_phys(va: TVA<T>) -> TPA<T>;
|
||||
/// Translates a physical address to its corresponding virtual address.
|
||||
fn phys_to_virt(pa: TPA<T>) -> TVA<T>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Kernel and user address-space management.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::{
|
||||
@@ -14,7 +16,9 @@ use crate::{
|
||||
|
||||
/// An architecture-independent representation of a page table entry (PTE).
|
||||
pub struct PageInfo {
|
||||
/// The page frame number identifying the physical page.
|
||||
pub pfn: PageFrame,
|
||||
/// The permission bits for this page table entry.
|
||||
pub perms: PtePermissions,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Memory allocators.
|
||||
|
||||
mod frame;
|
||||
pub mod phys;
|
||||
pub mod slab;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Physical page-frame allocator (buddy allocator).
|
||||
|
||||
use crate::{
|
||||
CpuOps,
|
||||
error::{KernelError, Result},
|
||||
@@ -155,22 +157,28 @@ impl FrameAllocatorInner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe wrapper around the buddy frame allocator.
|
||||
pub struct FrameAllocator<CPU: CpuOps> {
|
||||
pub(super) inner: SpinLockIrq<FrameAllocatorInner, CPU>,
|
||||
}
|
||||
|
||||
/// An RAII guard for a contiguous allocation of physical page frames.
|
||||
///
|
||||
/// When dropped, the pages are automatically returned to the allocator.
|
||||
pub struct PageAllocation<'a, CPU: CpuOps> {
|
||||
region: PhysMemoryRegion,
|
||||
inner: &'a SpinLockIrq<FrameAllocatorInner, CPU>,
|
||||
}
|
||||
|
||||
impl<CPU: CpuOps> PageAllocation<'_, CPU> {
|
||||
/// Consumes the allocation without freeing it, returning the underlying region.
|
||||
pub fn leak(self) -> PhysMemoryRegion {
|
||||
let region = self.region;
|
||||
core::mem::forget(self);
|
||||
region
|
||||
}
|
||||
|
||||
/// Returns a reference to the physical memory region backing this allocation.
|
||||
pub fn region(&self) -> &PhysMemoryRegion {
|
||||
&self.region
|
||||
}
|
||||
@@ -434,11 +442,14 @@ impl<CPU: CpuOps> FrameAllocator<CPU> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides access to the global page-frame allocator.
|
||||
pub trait PageAllocGetter<C: CpuOps>: Send + Sync + 'static {
|
||||
/// Returns a reference to the global [`FrameAllocator`].
|
||||
fn global_page_alloc() -> &'static FrameAllocator<C>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(missing_docs)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// A slab allocator for Moss.
|
||||
//! A slab memory allocator.
|
||||
use super::{
|
||||
SLAB_FRAME_ALLOC_ORDER, SLAB_MAX_OBJ_SHIFT, alloc_order,
|
||||
slab::{Slab, SlabState},
|
||||
@@ -36,18 +36,18 @@ const MAX_FREE_SLABS: usize = 32;
|
||||
///
|
||||
/// There is no 'full' list. Full slabs are unlinked from both the 'partial' and
|
||||
/// 'free' lists. They are allowed to float "in the ether" (referenced only by
|
||||
/// the global [FrameList]). When freeing an object from a 'full' slab, the
|
||||
/// the global `FrameList`). When freeing an object from a 'full' slab, the
|
||||
/// allocator detects the state transition and re-links the frame into the
|
||||
/// 'partial'/'free' list.
|
||||
///
|
||||
/// # Safety and Ownership
|
||||
///
|
||||
/// The FA and the `SlabManager` share a list of frame metadata via [FrameList]. To
|
||||
/// The FA and the `SlabManager` share a list of frame metadata via `FrameList`. To
|
||||
/// share this list safely, we implement an implicit ownership model:
|
||||
///
|
||||
/// 1. When the FA allocates a frame, it initializes the metadata.
|
||||
/// 2. We convert this frame into a slab allocation via
|
||||
/// [PageAllocation::as_slab].
|
||||
/// `PageAllocation::as_slab`.
|
||||
/// 3. Once that function returns, this `SlabManager` is considered the
|
||||
/// exclusive owner of that frame's metadata.
|
||||
///
|
||||
@@ -55,7 +55,7 @@ const MAX_FREE_SLABS: usize = 32;
|
||||
/// the metadata of any frame in its possession, as the `SpinLock` protecting
|
||||
/// this struct guarantees exclusive access to the specific size class that
|
||||
/// "owns" the frame. Ownership is eventually returned to the FA via
|
||||
/// [FrameAllocatorInner::free_slab].
|
||||
/// `FrameAllocatorInner::free_slab`.
|
||||
pub struct SlabManager<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> {
|
||||
pub(super) free: LinkedList<FrameAdapter>,
|
||||
pub(super) partial: LinkedList<FrameAdapter>,
|
||||
@@ -253,6 +253,7 @@ impl<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> SlabManager
|
||||
}
|
||||
}
|
||||
|
||||
/// The top-level slab allocator, dispatching allocations to size-appropriate [`SlabManager`]s.
|
||||
pub struct SlabAllocator<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> {
|
||||
pub(super) managers:
|
||||
[SpinLockIrq<SlabManager<CPU, A, T>, CPU>; SLAB_MAX_OBJ_SHIFT as usize + 1],
|
||||
@@ -268,6 +269,7 @@ unsafe impl<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> Sync
|
||||
}
|
||||
|
||||
impl<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> SlabAllocator<CPU, A, T> {
|
||||
/// Creates a new slab allocator backed by the given frame list.
|
||||
pub fn new(frame_list: FrameList) -> Self {
|
||||
Self {
|
||||
managers: core::array::from_fn(|n| {
|
||||
@@ -276,6 +278,7 @@ impl<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> SlabAllocat
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the slab manager responsible for the given layout, if one exists.
|
||||
pub fn allocator_for_layout(
|
||||
&self,
|
||||
layout: core::alloc::Layout,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Per-object-size slab cache management.
|
||||
|
||||
use super::{
|
||||
alloc_order,
|
||||
allocator::{SlabAllocator, SlabManager},
|
||||
@@ -20,6 +22,7 @@ const NUM_PTR_CACHES: usize = SLAB_MAX_OBJ_SHIFT as usize + 1;
|
||||
// Ensure that our cache fits in a single page.
|
||||
const _: () = assert!(core::mem::size_of::<SlabCache>() <= PAGE_SIZE);
|
||||
|
||||
/// A fixed-size cache of recently freed pointers for a single size class.
|
||||
#[repr(C)]
|
||||
pub struct PtrCache {
|
||||
next_free: usize,
|
||||
@@ -27,6 +30,7 @@ pub struct PtrCache {
|
||||
}
|
||||
|
||||
impl PtrCache {
|
||||
/// Creates an empty pointer cache.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
next_free: 0,
|
||||
@@ -34,10 +38,12 @@ impl PtrCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if no pointers are cached.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.next_free == 0
|
||||
}
|
||||
|
||||
/// Returns `true` if the cache has no remaining capacity.
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.next_free == PTRS_PER_SZ_CLASS
|
||||
}
|
||||
@@ -66,6 +72,7 @@ impl PtrCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a cached pointer, or `None` if the cache is empty.
|
||||
pub fn alloc(&mut self) -> Option<*mut u8> {
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Kernel heap built on top of the slab allocator.
|
||||
|
||||
use super::{allocator::SlabAllocator, cache::SlabCache};
|
||||
use crate::{
|
||||
CpuOps,
|
||||
@@ -11,15 +13,21 @@ use crate::{
|
||||
};
|
||||
use core::{alloc::GlobalAlloc, marker::PhantomData, ops::DerefMut};
|
||||
|
||||
/// Provides access to the global slab allocator instance.
|
||||
pub trait SlabGetter<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> {
|
||||
/// Returns a reference to the global slab allocator.
|
||||
fn global_slab_alloc() -> &'static SlabAllocator<CPU, A, T>;
|
||||
}
|
||||
|
||||
/// Per-CPU storage backend for a [`SlabCache`] pointer.
|
||||
pub trait SlabCacheStorage {
|
||||
/// Stores the slab cache pointer (e.g. into per-CPU data).
|
||||
fn store(ptr: *mut SlabCache);
|
||||
/// Retrieves the slab cache for the current CPU.
|
||||
fn get() -> impl DerefMut<Target = SlabCache>;
|
||||
}
|
||||
|
||||
/// The kernel heap allocator backed by the slab allocator and frame allocator.
|
||||
pub struct KHeap<CPU, S, PG, T, SG>
|
||||
where
|
||||
CPU: CpuOps,
|
||||
@@ -56,6 +64,7 @@ where
|
||||
T: AddressTranslator<()>,
|
||||
SG: SlabGetter<CPU, PG, T>,
|
||||
{
|
||||
/// Creates a new kernel heap instance.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
phantom1: PhantomData,
|
||||
@@ -74,6 +83,7 @@ where
|
||||
pages_needed.next_power_of_two().ilog2() as usize
|
||||
}
|
||||
|
||||
/// Initializes the per-CPU slab cache for the current CPU.
|
||||
pub fn init_for_this_cpu() {
|
||||
let page: ClaimedPage<CPU, PG, T> =
|
||||
ClaimedPage::alloc_zeroed().expect("Cannot allocate heap page");
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Slab allocator for small, fixed-size kernel objects.
|
||||
|
||||
use crate::memory::PAGE_SIZE;
|
||||
|
||||
// Allocations of order 2 (4 pages) from the FA for slabs.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! `smalloc` module: A simple physical memory allocator for early boot and
|
||||
//! kernel use.
|
||||
//! A simple physical memory allocator for early boot and kernel use.
|
||||
//!
|
||||
//! This allocator manages a fixed number of memory and reservation regions and
|
||||
//! supports basic allocation and freeing of physical memory blocks.
|
||||
@@ -34,6 +33,7 @@ use core::{
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
/// A fixed-capacity list of non-overlapping physical memory regions.
|
||||
#[derive(Clone)]
|
||||
pub struct RegionList {
|
||||
count: usize,
|
||||
@@ -42,10 +42,12 @@ pub struct RegionList {
|
||||
}
|
||||
|
||||
impl RegionList {
|
||||
/// Returns `true` if the list contains no regions.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.count == 0
|
||||
}
|
||||
|
||||
/// Creates a new region list backed by a pre-allocated buffer at `region_ptr`.
|
||||
pub const fn new(max: usize, region_ptr: *mut PhysMemoryRegion) -> Self {
|
||||
Self {
|
||||
count: 0,
|
||||
@@ -68,6 +70,7 @@ impl RegionList {
|
||||
self.count -= 1;
|
||||
}
|
||||
|
||||
/// Inserts a region, merging with adjacent neighbours when possible.
|
||||
pub fn insert_region(&mut self, mut new_region: PhysMemoryRegion) {
|
||||
if self.count == self.max {
|
||||
panic!("Cannot insert into full region list");
|
||||
@@ -108,6 +111,7 @@ impl RegionList {
|
||||
self[insert_idx] = new_region;
|
||||
}
|
||||
|
||||
/// Returns an iterator over the regions in this list.
|
||||
pub fn iter(&self) -> impl Iterator<Item = PhysMemoryRegion> {
|
||||
(0..self.count).map(|x| self[x])
|
||||
}
|
||||
@@ -156,8 +160,11 @@ impl IndexMut<usize> for RegionList {
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple physical memory allocator that tracks free and reserved regions.
|
||||
pub struct Smalloc<T: AddressTranslator<()>> {
|
||||
/// Available memory regions.
|
||||
pub memory: RegionList,
|
||||
/// Reserved memory regions (e.g. ACPI tables, device memory).
|
||||
pub res: RegionList,
|
||||
permit_region_realloc: bool,
|
||||
_phantom: PhantomData<T>,
|
||||
@@ -171,6 +178,7 @@ enum RegionListType {
|
||||
unsafe impl<T: AddressTranslator<()>> Send for Smalloc<T> {}
|
||||
|
||||
impl<T: AddressTranslator<()>> Smalloc<T> {
|
||||
/// Creates a new allocator with the given memory and reservation lists.
|
||||
pub const fn new(memory: RegionList, reserved_list: RegionList) -> Self {
|
||||
Self {
|
||||
memory,
|
||||
@@ -230,6 +238,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Allocates a region of the given `size` and alignment. Returns the base physical address.
|
||||
pub fn alloc(&mut self, size: usize, align: usize) -> Result<PA> {
|
||||
if self.res.requires_reallocation() {
|
||||
self.grow_region_list(RegionListType::Res)?;
|
||||
@@ -245,6 +254,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
/// Frees a previously allocated region at `addr` with the given `size`.
|
||||
pub fn free(&mut self, addr: PA, size: usize) -> Result<()> {
|
||||
let region_to_remove = PhysMemoryRegion::new(addr, size);
|
||||
|
||||
@@ -344,6 +354,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks a physical region as reserved without allocating from free memory.
|
||||
pub fn add_reservation(&mut self, region: PhysMemoryRegion) -> Result<()> {
|
||||
if self.res.requires_reallocation() {
|
||||
self.grow_region_list(RegionListType::Res)?;
|
||||
@@ -354,6 +365,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the base address of the first memory region, if any.
|
||||
pub fn base_ram_base_address(&self) -> Option<PA> {
|
||||
if self.memory.is_empty() {
|
||||
None
|
||||
@@ -362,6 +374,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new physical memory region to the allocator.
|
||||
pub fn add_memory(&mut self, region: PhysMemoryRegion) -> Result<()> {
|
||||
if self.memory.requires_reallocation() {
|
||||
self.grow_region_list(RegionListType::Mem)?;
|
||||
@@ -372,16 +385,19 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allocates a single page-aligned page and returns its frame number.
|
||||
pub fn alloc_page(&mut self) -> Result<PageFrame> {
|
||||
let pa = self.alloc(PAGE_SIZE, PAGE_SIZE)?;
|
||||
|
||||
Ok(PageFrame::from_pfn(pa.value() >> PAGE_SHIFT))
|
||||
}
|
||||
|
||||
/// Returns an iterator over all memory regions known to the allocator.
|
||||
pub fn iter_memory(&self) -> impl Iterator<Item = PhysMemoryRegion> {
|
||||
self.memory.iter()
|
||||
}
|
||||
|
||||
/// Returns a copy of the memory region list.
|
||||
pub fn get_memory_list(&self) -> RegionList {
|
||||
self.memory.clone()
|
||||
}
|
||||
@@ -399,6 +415,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over free (unreserved) physical memory regions.
|
||||
pub struct FreeRegionsIter<M, R>
|
||||
where
|
||||
M: Iterator<Item = PhysMemoryRegion>,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! RAII wrapper for a page frame claimed from the physical page allocator.
|
||||
|
||||
use super::{
|
||||
PAGE_SIZE,
|
||||
address::AddressTranslator,
|
||||
@@ -55,6 +57,7 @@ impl<A: CpuOps, G: PageAllocGetter<A>, T: AddressTranslator<()>> ClaimedPage<A,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the physical address of this claimed page.
|
||||
#[inline(always)]
|
||||
pub fn pa(&self) -> PA {
|
||||
self.0.region().start_address()
|
||||
@@ -98,6 +101,7 @@ impl<A: CpuOps, G: PageAllocGetter<A>, T: AddressTranslator<()>> ClaimedPage<A,
|
||||
unsafe { slice::from_raw_parts_mut(self.as_ptr_mut(), PAGE_SIZE) }
|
||||
}
|
||||
|
||||
/// Consumes the claimed page, returning the underlying page frame without freeing it.
|
||||
pub fn leak(self) -> PageFrame {
|
||||
self.0.leak().start_address().to_pfn()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ struct KBufInner<T, S: Storage<Item = T>> {
|
||||
write_waiters: WakerSet,
|
||||
}
|
||||
|
||||
/// A page-backed, async-aware circular kernel buffer.
|
||||
pub struct KBufCore<T, S: Storage<Item = T>, C: CpuOps> {
|
||||
inner: Arc<SpinLockIrq<KBufInner<T, S>, C>>,
|
||||
}
|
||||
@@ -35,6 +36,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> Clone for KBufCore<T, S, C> {
|
||||
}
|
||||
|
||||
impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
/// Creates a new kernel buffer backed by the given storage.
|
||||
pub fn new(storage: S) -> Self {
|
||||
let rb = unsafe { SharedRb::from_raw_parts(storage, 0, 0) };
|
||||
|
||||
@@ -47,6 +49,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a future that resolves when data is available for reading.
|
||||
pub fn read_ready(&self) -> impl Future<Output = ()> + use<T, S, C> {
|
||||
let lock = self.inner.clone();
|
||||
|
||||
@@ -59,6 +62,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Waits until at least one slot is available for writing.
|
||||
pub async fn write_ready(&self) {
|
||||
wait_until(
|
||||
self.inner.clone(),
|
||||
@@ -81,6 +85,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to push `obj` into the buffer without waiting. Returns `Err(obj)` if full.
|
||||
pub fn try_push(&self, obj: T) -> core::result::Result<(), T> {
|
||||
let mut inner = self.inner.lock_save_irq();
|
||||
|
||||
@@ -93,6 +98,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
res
|
||||
}
|
||||
|
||||
/// Pops a value from the buffer, waiting asynchronously if it is empty.
|
||||
pub async fn pop(&self) -> T {
|
||||
loop {
|
||||
self.read_ready().await;
|
||||
@@ -103,6 +109,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to pop a value without blocking, returning `None` if the buffer is empty.
|
||||
pub fn try_pop(&self) -> Option<T> {
|
||||
let mut inner = self.inner.lock_save_irq();
|
||||
|
||||
@@ -115,12 +122,14 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
res
|
||||
}
|
||||
|
||||
/// Returns the capacity of the underlying ring buffer.
|
||||
pub fn capacity(&self) -> NonZeroUsize {
|
||||
self.inner.lock_save_irq().buf.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
/// Asynchronously pops up to `buf.len()` items into `buf`, blocking until at least one is available.
|
||||
pub async fn pop_slice(&self, buf: &mut [T]) -> usize {
|
||||
wait_until(
|
||||
self.inner.clone(),
|
||||
@@ -141,6 +150,7 @@ impl<T: Copy, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Attempts to pop up to `buf.len()` items without blocking.
|
||||
pub fn try_pop_slice(&self, buf: &mut [T]) -> usize {
|
||||
let mut guard = self.inner.lock_save_irq();
|
||||
let size = guard.buf.pop_slice(buf);
|
||||
@@ -150,6 +160,7 @@ impl<T: Copy, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
size
|
||||
}
|
||||
|
||||
/// Asynchronously pushes items from `buf`, blocking until space is available.
|
||||
pub async fn push_slice(&self, buf: &[T]) -> usize {
|
||||
wait_until(
|
||||
self.inner.clone(),
|
||||
@@ -176,6 +187,7 @@ impl<T: Copy, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Attempts to push items from `buf` without blocking.
|
||||
pub fn try_push_slice(&self, buf: &[T]) -> usize {
|
||||
let mut guard = self.inner.lock_save_irq();
|
||||
let size = guard.buf.push_slice(buf);
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
//! Memory management primitives.
|
||||
//!
|
||||
//! This module contains the core building blocks for kernel memory management:
|
||||
//! typed addresses, memory regions, page frame tracking, page allocators,
|
||||
//! address-space abstractions, and kernel buffers.
|
||||
//!
|
||||
//! The always-available submodules ([`address`], [`page`], [`region`]) require
|
||||
//! no features. Higher-level subsystems are gated behind their respective
|
||||
//! feature flags.
|
||||
|
||||
pub mod address;
|
||||
#[cfg(feature = "paging")]
|
||||
pub mod address_space;
|
||||
@@ -16,6 +26,9 @@ pub mod pg_offset;
|
||||
pub mod proc_vm;
|
||||
pub mod region;
|
||||
|
||||
/// The system page size in bytes (4 KiB).
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
/// The number of bits to shift to convert between byte offsets and page numbers.
|
||||
pub const PAGE_SHIFT: usize = PAGE_SIZE.trailing_zeros() as usize;
|
||||
/// Bitmask for extracting the within-page offset from an address.
|
||||
pub const PAGE_MASK: usize = PAGE_SIZE - 1;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use super::{
|
||||
PAGE_SHIFT,
|
||||
address::PA,
|
||||
region::PhysMemoryRegion,
|
||||
};
|
||||
//! Page frame numbers.
|
||||
//!
|
||||
//! A [`PageFrame`] is a lightweight handle for a physical page, identified by
|
||||
//! its page frame number (PFN).
|
||||
|
||||
use super::{PAGE_SHIFT, address::PA, region::PhysMemoryRegion};
|
||||
use crate::memory::PAGE_SIZE;
|
||||
use core::fmt::Display;
|
||||
|
||||
/// A page frame number (PFN) — an index into physical memory in units of
|
||||
/// [`PAGE_SIZE`].
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct PageFrame {
|
||||
n: usize,
|
||||
@@ -18,18 +21,22 @@ impl Display for PageFrame {
|
||||
}
|
||||
|
||||
impl PageFrame {
|
||||
/// Creates a `PageFrame` from a raw page frame number.
|
||||
pub fn from_pfn(n: usize) -> Self {
|
||||
Self { n }
|
||||
}
|
||||
|
||||
/// Returns the physical address of the start of this page frame.
|
||||
pub fn pa(&self) -> PA {
|
||||
PA::from_value(self.n << PAGE_SHIFT)
|
||||
}
|
||||
|
||||
/// Returns this page frame as a single-page physical memory region.
|
||||
pub fn as_phys_range(&self) -> PhysMemoryRegion {
|
||||
PhysMemoryRegion::new(self.pa(), PAGE_SIZE)
|
||||
}
|
||||
|
||||
/// Returns the raw page frame number.
|
||||
pub fn value(&self) -> usize {
|
||||
self.n
|
||||
}
|
||||
@@ -41,6 +48,7 @@ impl PageFrame {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new `PageFrame` offset by `n` pages.
|
||||
#[must_use]
|
||||
pub fn add_pages(self, n: usize) -> Self {
|
||||
Self { n: self.n + n }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Page-table entry permission flags.
|
||||
|
||||
use core::fmt;
|
||||
|
||||
#[cfg(feature = "proc_vm")]
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
//! Page-offset arithmetic helpers.
|
||||
|
||||
use super::address::{AddressTranslator, TPA, TVA};
|
||||
use crate::VirtualMemory;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Translates between physical and virtual addresses using a fixed page-offset mapping.
|
||||
pub struct PageOffsetTranslator<VM: VirtualMemory> {
|
||||
_phantom: PhantomData<VM>,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Memory map management for a process address space.
|
||||
|
||||
use super::vmarea::{VMAPermissions, VMArea, VMAreaKind};
|
||||
use crate::{
|
||||
UserAddressSpace,
|
||||
@@ -17,11 +19,20 @@ pub struct MemoryMap<AS: UserAddressSpace> {
|
||||
address_space: AS,
|
||||
}
|
||||
|
||||
/// Specifies how the kernel should choose the virtual address for a mapping.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AddressRequest {
|
||||
/// Let the kernel pick any suitable address.
|
||||
Any,
|
||||
/// Prefer the given address but fall back to any free region.
|
||||
Hint(VA),
|
||||
Fixed { address: VA, permit_overlap: bool },
|
||||
/// Map at exactly the given address.
|
||||
Fixed {
|
||||
/// The exact virtual address to map at.
|
||||
address: VA,
|
||||
/// If `true`, existing mappings in the range may be replaced.
|
||||
permit_overlap: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl<AS: UserAddressSpace> MemoryMap<AS> {
|
||||
@@ -168,6 +179,7 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
|
||||
self.unmap_region(range.align_to_page_boundary(), None)
|
||||
}
|
||||
|
||||
/// Changes the memory protection flags for a page-aligned region.
|
||||
pub fn mprotect(
|
||||
&mut self,
|
||||
protect_region: VirtMemoryRegion,
|
||||
@@ -498,18 +510,22 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying address space.
|
||||
pub fn address_space_mut(&mut self) -> &mut AS {
|
||||
&mut self.address_space
|
||||
}
|
||||
|
||||
/// Returns the number of VMAs in this memory map.
|
||||
pub fn vma_count(&self) -> usize {
|
||||
self.vmas.len()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all VMAs in address order.
|
||||
pub fn iter_vmas(&self) -> impl Iterator<Item = &VMArea> {
|
||||
self.vmas.values()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(missing_docs)]
|
||||
pub mod tests;
|
||||
|
||||
@@ -15,6 +15,7 @@ pub mod vmarea;
|
||||
|
||||
const BRK_PERMISSIONS: VMAPermissions = VMAPermissions::rw();
|
||||
|
||||
/// The virtual memory state of a user-space process.
|
||||
pub struct ProcessVM<AS: UserAddressSpace> {
|
||||
mm: MemoryMap<AS>,
|
||||
brk: VirtMemoryRegion,
|
||||
@@ -49,6 +50,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
|
||||
Ok(Self { mm, brk })
|
||||
}
|
||||
|
||||
/// Constructs a `ProcessVM` from an existing memory map.
|
||||
pub fn from_map(map: MemoryMap<AS>) -> Self {
|
||||
// Last entry will be the VMA with the highest address.
|
||||
let brk = map
|
||||
@@ -67,6 +69,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an empty `ProcessVM` with no mappings and a zero-sized heap.
|
||||
pub fn empty() -> Result<Self> {
|
||||
Ok(Self {
|
||||
mm: MemoryMap::new()?,
|
||||
@@ -74,6 +77,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Finds the VMA covering `addr` if the given access type is permitted.
|
||||
pub fn find_vma_for_fault(&self, addr: VA, access_type: AccessKind) -> Option<&VMArea> {
|
||||
let vma = self.mm.find_vma(addr)?;
|
||||
|
||||
@@ -84,18 +88,22 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a non-mutable reference to the underlying memory map.
|
||||
pub fn mm(&self) -> &MemoryMap<AS> {
|
||||
&self.mm
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying memory map.
|
||||
pub fn mm_mut(&mut self) -> &mut MemoryMap<AS> {
|
||||
&mut self.mm
|
||||
}
|
||||
|
||||
/// Returns the current start address of the program break (heap).
|
||||
pub fn start_brk(&self) -> VA {
|
||||
self.brk.start_address()
|
||||
}
|
||||
|
||||
/// Returns the current end address of the program break (heap).
|
||||
pub fn current_brk(&self) -> VA {
|
||||
self.brk.end_address()
|
||||
}
|
||||
@@ -165,6 +173,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
|
||||
Ok(new_end_addr)
|
||||
}
|
||||
|
||||
/// Clones this process VM, marking all writable pages as copy-on-write.
|
||||
pub fn clone_as_cow(&mut self) -> Result<Self> {
|
||||
Ok(Self {
|
||||
mm: self.mm.clone_as_cow()?,
|
||||
|
||||
@@ -26,12 +26,16 @@ use object::{
|
||||
/// Describes the permissions assigned for this VMA.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct VMAPermissions {
|
||||
/// Whether reads are allowed.
|
||||
pub read: bool,
|
||||
/// Whether writes are allowed.
|
||||
pub write: bool,
|
||||
/// Whether execution is allowed.
|
||||
pub execute: bool,
|
||||
}
|
||||
|
||||
impl VMAPermissions {
|
||||
/// Read-write permissions.
|
||||
pub const fn rw() -> Self {
|
||||
Self {
|
||||
read: true,
|
||||
@@ -40,6 +44,7 @@ impl VMAPermissions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-execute permissions.
|
||||
pub const fn rx() -> Self {
|
||||
Self {
|
||||
read: true,
|
||||
@@ -48,6 +53,7 @@ impl VMAPermissions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only permissions.
|
||||
pub const fn ro() -> Self {
|
||||
Self {
|
||||
read: true,
|
||||
@@ -156,10 +162,12 @@ pub enum VMAreaKind {
|
||||
}
|
||||
|
||||
impl VMAreaKind {
|
||||
/// Creates a new anonymous VMA kind.
|
||||
pub fn new_anon() -> Self {
|
||||
Self::Anon
|
||||
}
|
||||
|
||||
/// Creates a new file-backed VMA kind.
|
||||
pub fn new_file(file: Arc<dyn Inode>, offset: u64, len: u64) -> Self {
|
||||
Self::File(VMFileMapping { file, offset, len })
|
||||
}
|
||||
@@ -173,6 +181,7 @@ impl VMAreaKind {
|
||||
/// managing a process's memory layout.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct VMArea {
|
||||
/// The virtual address range of this VMA.
|
||||
pub region: VirtMemoryRegion,
|
||||
pub(super) name: String,
|
||||
pub(super) kind: VMAreaKind,
|
||||
@@ -195,6 +204,7 @@ impl VMArea {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a human-readable name for this VMA (e.g. `[stack]`, `[heap]`).
|
||||
pub fn set_name<S: AsRef<str>>(&mut self, s: S) {
|
||||
self.name = s.as_ref().to_string();
|
||||
}
|
||||
@@ -266,9 +276,9 @@ impl VMArea {
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - [`AccessValidation::Valid`]: If the address and permissions are valid.
|
||||
/// - [`AccessValidation::NotPresent`]: If the address is outside this VMA.
|
||||
/// - [`AccessValidation::PermissionDenied`]: If the address is inside this
|
||||
/// - [`FaultValidation::Valid`]: If the address and permissions are valid.
|
||||
/// - [`FaultValidation::NotPresent`]: If the address is outside this VMA.
|
||||
/// - [`FaultValidation::PermissionDenied`]: If the address is inside this
|
||||
/// VMA but the access is not allowed. This allows the caller to immediately
|
||||
/// identify a segmentation fault without checking other VMAs.
|
||||
pub fn validate_fault(&self, addr: VA, kind: AccessKind) -> FaultValidation {
|
||||
@@ -371,10 +381,12 @@ impl VMArea {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the memory permissions for this VMA.
|
||||
pub fn permissions(&self) -> VMAPermissions {
|
||||
self.permissions
|
||||
}
|
||||
|
||||
/// Returns `true` if the given virtual address falls within this VMA.
|
||||
pub fn contains_address(&self, addr: VA) -> bool {
|
||||
self.region.contains_address(addr)
|
||||
}
|
||||
@@ -464,6 +476,7 @@ impl VMArea {
|
||||
self.region
|
||||
}
|
||||
|
||||
/// Returns the file offset for a file-backed VMA, or `None` for anonymous mappings.
|
||||
pub fn file_offset(&self) -> Option<u64> {
|
||||
match self.kind {
|
||||
VMAreaKind::File(ref vmfile_mapping) => Some(vmfile_mapping.offset()),
|
||||
@@ -471,6 +484,7 @@ impl VMArea {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the inode ID of the backing file, or `None` for anonymous mappings.
|
||||
pub fn inode_id(&self) -> Option<InodeId> {
|
||||
match self.kind {
|
||||
VMAreaKind::File(ref vmfile_mapping) => Some(vmfile_mapping.file().id()),
|
||||
@@ -478,12 +492,14 @@ impl VMArea {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the human-readable name of this VMA.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(missing_docs)]
|
||||
pub mod tests {
|
||||
use crate::fs::InodeId;
|
||||
use core::any::Any;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! `region` module: Contiguous memory regions.
|
||||
//! Contiguous memory regions.
|
||||
//!
|
||||
//! This module defines `MemoryRegion<T>`, a generic abstraction for handling
|
||||
//! ranges of memory in both physical and virtual address spaces.
|
||||
@@ -382,6 +382,7 @@ impl PhysMemoryRegion {
|
||||
VirtMemoryRegion::new(self.address.to_va::<T>(), self.size)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the page frame numbers in this region.
|
||||
pub fn iter_pfns(self) -> impl Iterator<Item = PageFrame> {
|
||||
let mut count = 0;
|
||||
let pages_count = self.size >> PAGE_SHIFT;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
//! Plain Old Data trait and blanket implementations.
|
||||
//!
|
||||
//! Types that implement [`Pod`] can be safely created by copying their raw byte
|
||||
//! representation. This is useful for reading on-disk structures from block
|
||||
//! devices.
|
||||
|
||||
/// An unsafe trait indicating that a type is "Plain Old Data".
|
||||
///
|
||||
/// A type is `Pod` if it is a simple collection of bytes with no invalid bit
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
//! Linux-compatible process capabilities.
|
||||
//!
|
||||
//! This module implements the five capability sets (`effective`, `permitted`,
|
||||
//! `inheritable`, `ambient`, `bounding`) used by the kernel for
|
||||
//! fine-grained privilege checks.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::error::{KernelError, Result};
|
||||
|
||||
// Linux-compatible capability flags.
|
||||
//
|
||||
// Each flag corresponds to a single Linux capability as defined in
|
||||
// `include/uapi/linux/capability.h`.
|
||||
bitflags::bitflags! {
|
||||
/// Linux-compatible capability flags.
|
||||
///
|
||||
/// Each flag corresponds to a single Linux capability as defined in
|
||||
/// `include/uapi/linux/capability.h`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct CapabilitiesFlags: u64 {
|
||||
const CAP_CHOWN = 1 << 0;
|
||||
@@ -47,6 +63,9 @@ bitflags::bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
/// The five capability sets associated with a process.
|
||||
///
|
||||
/// See `capabilities(7)` for the full semantics of each set.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Capabilities {
|
||||
effective: CapabilitiesFlags,
|
||||
@@ -57,6 +76,7 @@ pub struct Capabilities {
|
||||
}
|
||||
|
||||
impl Capabilities {
|
||||
/// Creates a new `Capabilities` with the given sets.
|
||||
pub fn new(
|
||||
effective: CapabilitiesFlags,
|
||||
permitted: CapabilitiesFlags,
|
||||
@@ -73,6 +93,7 @@ impl Capabilities {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a root (all-capable) set.
|
||||
pub fn new_root() -> Self {
|
||||
Self {
|
||||
effective: CapabilitiesFlags::all(),
|
||||
@@ -83,6 +104,7 @@ impl Capabilities {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an empty (no capabilities) set.
|
||||
pub fn new_empty() -> Self {
|
||||
Self {
|
||||
effective: CapabilitiesFlags::empty(),
|
||||
@@ -129,30 +151,37 @@ impl Capabilities {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the effective capability flags.
|
||||
pub fn effective(&self) -> CapabilitiesFlags {
|
||||
self.effective
|
||||
}
|
||||
|
||||
/// Returns the permitted capability flags.
|
||||
pub fn permitted(&self) -> CapabilitiesFlags {
|
||||
self.permitted
|
||||
}
|
||||
|
||||
/// Returns the inheritable capability flags.
|
||||
pub fn inheritable(&self) -> CapabilitiesFlags {
|
||||
self.inheritable
|
||||
}
|
||||
|
||||
/// Returns the ambient capability flags.
|
||||
pub fn ambient(&self) -> CapabilitiesFlags {
|
||||
self.ambient
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the ambient capability flags.
|
||||
pub fn ambient_mut(&mut self) -> &mut CapabilitiesFlags {
|
||||
&mut self.ambient
|
||||
}
|
||||
|
||||
/// Returns the bounding capability flags.
|
||||
pub fn bounding(&self) -> CapabilitiesFlags {
|
||||
self.bounding
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the bounding capability flags.
|
||||
pub fn bounding_mut(&mut self) -> &mut CapabilitiesFlags {
|
||||
&mut self.bounding
|
||||
}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
//! User and group identity types.
|
||||
//!
|
||||
//! [`Uid`] and [`Gid`] are thin wrappers around `u32` that prevent accidental
|
||||
//! mixing of user IDs and group IDs at the type level.
|
||||
|
||||
/// A user identity (UID).
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Uid(u32);
|
||||
|
||||
impl Uid {
|
||||
/// Creates a new `Uid` with the given numeric value.
|
||||
pub const fn new(id: u32) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
/// Returns `true` if this is the root (UID 0) user.
|
||||
pub fn is_root(self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Returns the root UID (0).
|
||||
pub fn new_root() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
@@ -29,15 +38,18 @@ impl From<Uid> for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// A group identity (GID).
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Gid(u32);
|
||||
|
||||
impl Gid {
|
||||
/// Creates a new `Gid` with the given numeric value.
|
||||
pub const fn new(id: u32) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
/// Returns the root group GID (0).
|
||||
pub fn new_root_group() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
//! Process identity and capability types.
|
||||
//!
|
||||
//! This module provides the fundamental types for process credentials:
|
||||
//! user/group identifiers ([`ids`]) and Linux-compatible capabilities
|
||||
//! ([`caps`]).
|
||||
|
||||
pub mod caps;
|
||||
pub mod ids;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Async-aware condition variable.
|
||||
|
||||
use super::spinlock::SpinLockIrq;
|
||||
use super::waker_set::WakerSet;
|
||||
use crate::CpuOps;
|
||||
@@ -5,8 +7,11 @@ use alloc::sync::Arc;
|
||||
|
||||
/// The type of wakeup that should occur after a state update.
|
||||
pub enum WakeupType {
|
||||
/// Do not wake any waiting task.
|
||||
None,
|
||||
/// Wake exactly one waiting task.
|
||||
One,
|
||||
/// Wake all waiting tasks.
|
||||
All,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! Synchronisation primitives for `no_std` kernel environments.
|
||||
//!
|
||||
//! All primitives are generic over [`CpuOps`](crate::CpuOps) so they can
|
||||
//! disable/restore interrupts on the local core.
|
||||
|
||||
pub mod condvar;
|
||||
pub mod mpsc;
|
||||
pub mod mutex;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Async-aware mutual-exclusion lock.
|
||||
|
||||
use alloc::collections::VecDeque;
|
||||
use core::cell::UnsafeCell;
|
||||
use core::future::Future;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! A thread-safe cell that is initialized exactly once.
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use crate::CpuOps;
|
||||
|
||||
@@ -65,6 +65,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A mutable borrow of per-CPU data that restores interrupts on drop.
|
||||
pub struct IrqSafeRefMut<'a, T, CPU: CpuOps> {
|
||||
borrow: ManuallyDrop<RefMut<'a, T>>,
|
||||
flags: usize,
|
||||
@@ -198,6 +199,7 @@ impl<T: Send, CPU: CpuOps> PerCpu<RefCell<T>, CPU> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to mutably borrow the per-CPU data, returning `None` if already borrowed.
|
||||
#[track_caller]
|
||||
pub fn try_borrow_mut(&self) -> Option<IrqGuard<RefMut<'_, T>, CPU>> {
|
||||
let flags = CPU::disable_interrupts();
|
||||
@@ -225,6 +227,7 @@ impl<T: Send, CPU: CpuOps> PerCpu<RefCell<T>, CPU> {
|
||||
}
|
||||
|
||||
impl<T: Send + Sync, CPU: CpuOps> PerCpu<T, CPU> {
|
||||
/// Returns a reference to the data for the given CPU.
|
||||
pub fn get_by_cpu(&self, cpu_id: usize) -> &T {
|
||||
unsafe { self.get_for_cpu(cpu_id) }
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Async-aware readers–writer lock.
|
||||
|
||||
use super::spinlock::SpinLockIrq;
|
||||
use crate::CpuOps;
|
||||
use crate::sync::mutex::Mutex;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Low-level spin lock primitives.
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::hint::spin_loop;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Waker registration and notification set.
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::sync::Arc;
|
||||
use core::{
|
||||
@@ -9,6 +11,7 @@ use crate::CpuOps;
|
||||
|
||||
use super::spinlock::SpinLockIrq;
|
||||
|
||||
/// A set of registered [`Waker`]s that can be selectively or collectively woken.
|
||||
pub struct WakerSet<T = ()> {
|
||||
waiters: BTreeMap<u64, (Waker, T)>,
|
||||
next_id: u64,
|
||||
@@ -21,6 +24,7 @@ impl Default for WakerSet {
|
||||
}
|
||||
|
||||
impl<T> WakerSet<T> {
|
||||
/// Creates a new, empty waker set.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
waiters: BTreeMap::new(),
|
||||
@@ -38,6 +42,7 @@ impl<T> WakerSet<T> {
|
||||
id
|
||||
}
|
||||
|
||||
/// Returns `true` if the given token is still registered in the set.
|
||||
pub fn contains_token(&self, token: u64) -> bool {
|
||||
self.waiters.contains_key(&token)
|
||||
}
|
||||
@@ -84,6 +89,7 @@ impl<T> WakerSet<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a waker together with associated data, returning its token.
|
||||
pub fn register_with_data(&mut self, waker: &Waker, data: T) -> u64 {
|
||||
let id = self.allocate_id();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user