Implement domain and endpoint modules with capability management, message passing, and scheduling features
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -45,6 +45,9 @@ version = "0.1.0"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "domain"
|
name = "domain"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gatekeeper"
|
name = "gatekeeper"
|
||||||
@@ -89,6 +92,10 @@ version = "0.1.0"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "platform"
|
name = "platform"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"domain",
|
||||||
|
"types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "power-server"
|
name = "power-server"
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
types = { workspace = true }
|
||||||
|
|||||||
33
crates/domain/src/capability/authority.rs
Normal file
33
crates/domain/src/capability/authority.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
use types::{
|
||||||
|
bounded::CapSlot,
|
||||||
|
id::ProcessId,
|
||||||
|
permission::{Permission, PermissionSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{capability::Capability, error::KernelError, resource::Resource};
|
||||||
|
|
||||||
|
pub trait CapabilityAuthority {
|
||||||
|
fn grant(
|
||||||
|
&mut self,
|
||||||
|
to: ProcessId,
|
||||||
|
resource: Resource,
|
||||||
|
permissions: PermissionSet,
|
||||||
|
) -> Result<CapSlot, KernelError>;
|
||||||
|
|
||||||
|
fn revoke(&mut self, owner: ProcessId, slot: CapSlot) -> Result<(), KernelError>;
|
||||||
|
|
||||||
|
fn delegate(
|
||||||
|
&mut self,
|
||||||
|
from: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
to: ProcessId,
|
||||||
|
restrict: PermissionSet,
|
||||||
|
) -> Result<CapSlot, KernelError>;
|
||||||
|
|
||||||
|
fn check(
|
||||||
|
&self,
|
||||||
|
caller: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
needed: Permission,
|
||||||
|
) -> Result<&Capability, KernelError>;
|
||||||
|
}
|
||||||
82
crates/domain/src/capability/mod.rs
Normal file
82
crates/domain/src/capability/mod.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use types::{bounded::CapSlot, generation::Generation, permission::PermissionSet};
|
||||||
|
|
||||||
|
pub mod authority;
|
||||||
|
|
||||||
|
use crate::{error::KernelError, resource::Resource};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "tests.rs"]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
const MAX_CAPS_PER_PROCESS: usize = 16;
|
||||||
|
|
||||||
|
pub struct Capability {
|
||||||
|
resource: Resource,
|
||||||
|
permissions: PermissionSet,
|
||||||
|
generation: Generation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Capability {
|
||||||
|
pub(crate) fn new(
|
||||||
|
resource: Resource,
|
||||||
|
permissions: PermissionSet,
|
||||||
|
generation: Generation,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
resource,
|
||||||
|
permissions,
|
||||||
|
generation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resource(&self) -> &Resource {
|
||||||
|
&self.resource
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn permissions(&self) -> &PermissionSet {
|
||||||
|
&self.permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generation(&self) -> &Generation {
|
||||||
|
&self.generation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CapabilityTable {
|
||||||
|
slots: [Option<Capability>; MAX_CAPS_PER_PROCESS],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CapabilityTable {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
slots: [const { None }; MAX_CAPS_PER_PROCESS],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, slot: CapSlot) -> Result<&Capability, KernelError> {
|
||||||
|
let idx = slot.value() as usize;
|
||||||
|
self.slots[idx]
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(KernelError::InvalidSlot(slot))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, cap: Capability) -> Result<CapSlot, KernelError> {
|
||||||
|
let idx = self
|
||||||
|
.slots
|
||||||
|
.iter()
|
||||||
|
.position(|s| s.is_none())
|
||||||
|
.ok_or(KernelError::NoFreeSlot)?;
|
||||||
|
|
||||||
|
self.slots[idx] = Some(cap);
|
||||||
|
CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, slot: CapSlot) -> Result<(), KernelError> {
|
||||||
|
let idx = slot.value() as usize;
|
||||||
|
if self.slots[idx].is_none() {
|
||||||
|
return Err(KernelError::InvalidSlot(slot));
|
||||||
|
}
|
||||||
|
self.slots[idx] = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
152
crates/domain/src/capability/tests.rs
Normal file
152
crates/domain/src/capability/tests.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
use super::*;
|
||||||
|
use types::generation::Generation;
|
||||||
|
use types::id::{EndpointId, RegionId};
|
||||||
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
|
fn perms(permissions: &[Permission]) -> PermissionSet {
|
||||||
|
permissions
|
||||||
|
.iter()
|
||||||
|
.fold(PermissionSet::empty(), |set, &p| set.with(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint_cap(id: u16, permissions: &[Permission]) -> Capability {
|
||||||
|
Capability::new(
|
||||||
|
Resource::Endpoint(EndpointId::from_raw(id)),
|
||||||
|
perms(permissions),
|
||||||
|
Generation::initial(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_cap(id: u16, permissions: &[Permission]) -> Capability {
|
||||||
|
Capability::new(
|
||||||
|
Resource::Memory(RegionId::from_raw(id)),
|
||||||
|
perms(permissions),
|
||||||
|
Generation::initial(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_table_is_empty() {
|
||||||
|
let table = CapabilityTable::new();
|
||||||
|
let slot = CapSlot::new(0).unwrap();
|
||||||
|
assert!(table.get(slot).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_and_get() {
|
||||||
|
let mut table = CapabilityTable::new();
|
||||||
|
let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap();
|
||||||
|
let retrieved = table.get(slot).unwrap();
|
||||||
|
|
||||||
|
assert!(retrieved.permissions().allows(Permission::Read));
|
||||||
|
assert!(!retrieved.permissions().allows(Permission::Write));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_then_get_fails() {
|
||||||
|
let mut table = CapabilityTable::new();
|
||||||
|
let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap();
|
||||||
|
table.remove(slot).unwrap();
|
||||||
|
assert!(table.get(slot).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_invalid_slot_fails() {
|
||||||
|
let mut table = CapabilityTable::new();
|
||||||
|
let slot = CapSlot::new(5).unwrap();
|
||||||
|
|
||||||
|
assert!(table.remove(slot).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_reuses_removed_slot() {
|
||||||
|
let mut table = CapabilityTable::new();
|
||||||
|
let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap();
|
||||||
|
table.remove(slot).unwrap();
|
||||||
|
let new_slot = table.insert(endpoint_cap(2, &[Permission::Write])).unwrap();
|
||||||
|
assert_eq!(slot, new_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_table_returns_no_free_slot() {
|
||||||
|
let mut table = CapabilityTable::new();
|
||||||
|
for i in 0..MAX_CAPS_PER_PROCESS {
|
||||||
|
assert!(
|
||||||
|
table
|
||||||
|
.insert(endpoint_cap(i as u16, &[Permission::Read]))
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert!(table.insert(endpoint_cap(999, &[])).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generation_mismatch_detected() {
|
||||||
|
let gen_v1 = Generation::initial();
|
||||||
|
let gen_v2 = gen_v1.next();
|
||||||
|
|
||||||
|
assert!(gen_v1.is_valid_against(&gen_v1));
|
||||||
|
assert!(!gen_v1.is_valid_against(&gen_v2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn restrict_to_never_escalates() {
|
||||||
|
let full = perms(&[Permission::Read, Permission::Write, Permission::Execute]);
|
||||||
|
let read_only = perms(&[Permission::Read]);
|
||||||
|
|
||||||
|
let restricted = full.restrict_to(&read_only);
|
||||||
|
|
||||||
|
assert!(restricted.allows(Permission::Read));
|
||||||
|
assert!(!restricted.allows(Permission::Write));
|
||||||
|
assert!(!restricted.allows(Permission::Execute));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn restrict_to_cannot_add_permissions() {
|
||||||
|
let read_only = perms(&[Permission::Read]);
|
||||||
|
let wants_everything = perms(&[
|
||||||
|
Permission::Read,
|
||||||
|
Permission::Write,
|
||||||
|
Permission::Execute,
|
||||||
|
Permission::Delegate,
|
||||||
|
Permission::Revoke,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let result = read_only.restrict_to(&wants_everything);
|
||||||
|
|
||||||
|
assert!(result.allows(Permission::Read));
|
||||||
|
assert!(!result.allows(Permission::Write));
|
||||||
|
assert!(!result.allows(Permission::Execute));
|
||||||
|
assert!(!result.allows(Permission::Delegate));
|
||||||
|
assert!(!result.allows(Permission::Revoke));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_subset_of() {
|
||||||
|
let rw = perms(&[Permission::Read, Permission::Write]);
|
||||||
|
let read_only = perms(&[Permission::Read]);
|
||||||
|
|
||||||
|
assert!(read_only.is_subset_of(&rw));
|
||||||
|
assert!(!rw.is_subset_of(&read_only));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_permission_set_allows_nothing() {
|
||||||
|
let empty = PermissionSet::empty();
|
||||||
|
|
||||||
|
assert!(!empty.allows(Permission::Read));
|
||||||
|
assert!(!empty.allows(Permission::Write));
|
||||||
|
assert!(!empty.allows(Permission::Execute));
|
||||||
|
assert!(!empty.allows(Permission::Delegate));
|
||||||
|
assert!(!empty.allows(Permission::Revoke));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn capability_preserves_resource_and_permissions() {
|
||||||
|
let permissions = [Permission::Read, Permission::Write];
|
||||||
|
let expected_permissions = perms(&permissions);
|
||||||
|
let cap = memory_cap(42, &permissions);
|
||||||
|
|
||||||
|
assert_eq!(cap.permissions(), &expected_permissions);
|
||||||
|
assert_eq!(cap.generation(), &Generation::initial());
|
||||||
|
}
|
||||||
78
crates/domain/src/endpoint/mod.rs
Normal file
78
crates/domain/src/endpoint/mod.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use types::{
|
||||||
|
bounded::CapSlot,
|
||||||
|
generation::Generation,
|
||||||
|
id::{EndpointId, ProcessId, ThreadId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{endpoint::wait_queue::WaitQueue, error::KernelError, message::Message};
|
||||||
|
|
||||||
|
pub mod wait_queue;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "tests.rs"]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
pub struct Endpoint {
|
||||||
|
id: EndpointId,
|
||||||
|
waiting_senders: WaitQueue,
|
||||||
|
waiting_receivers: WaitQueue,
|
||||||
|
generation: Generation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint {
|
||||||
|
pub fn new(id: EndpointId) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
waiting_senders: WaitQueue::new(),
|
||||||
|
waiting_receivers: WaitQueue::new(),
|
||||||
|
generation: Generation::initial(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> &EndpointId {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generation(&self) -> &Generation {
|
||||||
|
&self.generation
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enqueue_sender(&mut self, thread: ThreadId) -> Option<()> {
|
||||||
|
self.waiting_senders.push(thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dequeue_sender(&mut self) -> Option<ThreadId> {
|
||||||
|
self.waiting_senders.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enqueue_receiver(&mut self, thread: ThreadId) -> Option<()> {
|
||||||
|
self.waiting_receivers.push(thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dequeue_receiver(&mut self) -> Option<ThreadId> {
|
||||||
|
self.waiting_receivers.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_waiting_sender(&self) -> bool {
|
||||||
|
!self.waiting_senders.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_waiting_receiver(&self) -> bool {
|
||||||
|
!self.waiting_receivers.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke(&mut self) {
|
||||||
|
self.generation = self.generation.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MessagePassing {
|
||||||
|
fn send(
|
||||||
|
&mut self,
|
||||||
|
sender: ProcessId,
|
||||||
|
endpoint: CapSlot,
|
||||||
|
message: Message,
|
||||||
|
) -> Result<(), KernelError>;
|
||||||
|
|
||||||
|
fn receive(&mut self, receiver: ProcessId, endpoint: CapSlot) -> Result<Message, KernelError>;
|
||||||
|
}
|
||||||
137
crates/domain/src/endpoint/tests.rs
Normal file
137
crates/domain/src/endpoint/tests.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
use crate::endpoint::wait_queue::MAX_WAITERS;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use types::id::{EndpointId, ThreadId};
|
||||||
|
|
||||||
|
fn thread(id: u16) -> ThreadId {
|
||||||
|
ThreadId::from_raw(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_endpoint() -> Endpoint {
|
||||||
|
Endpoint::new(EndpointId::from_raw(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- WaitQueue tests ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_queue_pop_returns_none() {
|
||||||
|
let mut queue = WaitQueue::new();
|
||||||
|
assert!(queue.pop().is_none());
|
||||||
|
assert!(queue.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn push_then_pop_returns_same_thread() {
|
||||||
|
let mut queue = WaitQueue::new();
|
||||||
|
queue.push(thread(1)).unwrap();
|
||||||
|
assert_eq!(queue.pop(), Some(thread(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pop_order_is_fifo() {
|
||||||
|
let mut queue = WaitQueue::new();
|
||||||
|
queue.push(thread(1)).unwrap();
|
||||||
|
queue.push(thread(2)).unwrap();
|
||||||
|
queue.push(thread(3)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(queue.pop(), Some(thread(1)));
|
||||||
|
assert_eq!(queue.pop(), Some(thread(2)));
|
||||||
|
assert_eq!(queue.pop(), Some(thread(3)));
|
||||||
|
assert!(queue.pop().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_queue_rejects_push() {
|
||||||
|
let mut queue = WaitQueue::new();
|
||||||
|
for i in 0..MAX_WAITERS {
|
||||||
|
assert!(queue.push(thread(i as u16)).is_some());
|
||||||
|
}
|
||||||
|
assert!(queue.push(thread(999)).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_empty_after_drain() {
|
||||||
|
let mut queue = WaitQueue::new();
|
||||||
|
queue.push(thread(1)).unwrap();
|
||||||
|
queue.push(thread(2)).unwrap();
|
||||||
|
queue.pop();
|
||||||
|
queue.pop();
|
||||||
|
assert!(queue.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Endpoint tests ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_endpoint_has_no_waiters() {
|
||||||
|
let ep = test_endpoint();
|
||||||
|
assert!(!ep.has_waiting_sender());
|
||||||
|
assert!(!ep.has_waiting_receiver());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sender_waits_then_dequeues() {
|
||||||
|
let mut ep = test_endpoint();
|
||||||
|
ep.enqueue_sender(thread(1)).unwrap();
|
||||||
|
|
||||||
|
assert!(ep.has_waiting_sender());
|
||||||
|
assert_eq!(ep.dequeue_sender(), Some(thread(1)));
|
||||||
|
assert!(!ep.has_waiting_sender());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn receiver_waits_then_dequeues() {
|
||||||
|
let mut ep = test_endpoint();
|
||||||
|
ep.enqueue_receiver(thread(1)).unwrap();
|
||||||
|
|
||||||
|
assert!(ep.has_waiting_receiver());
|
||||||
|
assert_eq!(ep.dequeue_receiver(), Some(thread(1)));
|
||||||
|
assert!(!ep.has_waiting_receiver());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn senders_and_receivers_are_independent() {
|
||||||
|
let mut ep = test_endpoint();
|
||||||
|
ep.enqueue_sender(thread(1)).unwrap();
|
||||||
|
ep.enqueue_receiver(thread(2)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ep.dequeue_sender(), Some(thread(1)));
|
||||||
|
assert_eq!(ep.dequeue_receiver(), Some(thread(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn revoke_bumps_generation() {
|
||||||
|
let mut ep = test_endpoint();
|
||||||
|
let gen_before = *ep.generation();
|
||||||
|
ep.revoke();
|
||||||
|
let gen_after = *ep.generation();
|
||||||
|
|
||||||
|
assert!(!gen_before.is_valid_against(&gen_after));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rendezvous_sender_first() {
|
||||||
|
let mut ep = test_endpoint();
|
||||||
|
|
||||||
|
// sender arrives first, no receiver waiting
|
||||||
|
ep.enqueue_sender(thread(1)).unwrap();
|
||||||
|
assert!(!ep.has_waiting_receiver());
|
||||||
|
|
||||||
|
// receiver arrives, finds a sender
|
||||||
|
assert!(ep.has_waiting_sender());
|
||||||
|
let sender = ep.dequeue_sender().unwrap();
|
||||||
|
assert_eq!(sender, thread(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rendezvous_receiver_first() {
|
||||||
|
let mut ep = test_endpoint();
|
||||||
|
|
||||||
|
// receiver arrives first, no sender waiting
|
||||||
|
ep.enqueue_receiver(thread(1)).unwrap();
|
||||||
|
assert!(!ep.has_waiting_sender());
|
||||||
|
|
||||||
|
// sender arrives, finds a receiver
|
||||||
|
assert!(ep.has_waiting_receiver());
|
||||||
|
let receiver = ep.dequeue_receiver().unwrap();
|
||||||
|
assert_eq!(receiver, thread(1));
|
||||||
|
}
|
||||||
35
crates/domain/src/endpoint/wait_queue.rs
Normal file
35
crates/domain/src/endpoint/wait_queue.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use types::id::ThreadId;
|
||||||
|
|
||||||
|
pub const MAX_WAITERS: usize = 16;
|
||||||
|
|
||||||
|
pub struct WaitQueue {
|
||||||
|
entries: [Option<ThreadId>; MAX_WAITERS],
|
||||||
|
count: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WaitQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
entries: [None; MAX_WAITERS],
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, thread: ThreadId) -> Option<()> {
|
||||||
|
let slot = self.entries.iter_mut().find(|s| s.is_none())?;
|
||||||
|
*slot = Some(thread);
|
||||||
|
self.count += 1;
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Option<ThreadId> {
|
||||||
|
let slot = self.entries.iter_mut().find(|s| s.is_some())?;
|
||||||
|
let thread = slot.take();
|
||||||
|
self.count -= 1;
|
||||||
|
thread
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.count == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
11
crates/domain/src/error.rs
Normal file
11
crates/domain/src/error.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use types::{bounded::CapSlot, id::ProcessId, permission::Permission};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum KernelError {
|
||||||
|
InvalidSlot(CapSlot),
|
||||||
|
RevokedCapability(CapSlot),
|
||||||
|
PermissionDenied { slot: CapSlot, needed: Permission },
|
||||||
|
NoFreeSlot,
|
||||||
|
InvalidProcess(ProcessId),
|
||||||
|
WouldBlock,
|
||||||
|
}
|
||||||
10
crates/domain/src/fault.rs
Normal file
10
crates/domain/src/fault.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
use types::{address::VirtualAddress, bounded::CapSlot, permission::Permission};
|
||||||
|
|
||||||
|
pub enum Fault {
|
||||||
|
CapabilityViolation { slot: CapSlot, needed: Permission },
|
||||||
|
InvalidCapability { slot: CapSlot },
|
||||||
|
RevokedCapability { slot: CapSlot },
|
||||||
|
InvalidSyscall,
|
||||||
|
MemoryFault { address: VirtualAddress },
|
||||||
|
IllegalInstruction,
|
||||||
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
pub fn add(left: u64, right: u64) -> u64 {
|
#![no_std]
|
||||||
left + right
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
pub mod capability;
|
||||||
mod tests {
|
pub mod endpoint;
|
||||||
use super::*;
|
pub mod error;
|
||||||
|
pub mod fault;
|
||||||
#[test]
|
pub mod message;
|
||||||
fn it_works() {
|
pub mod process;
|
||||||
let result = add(2, 2);
|
pub mod resource;
|
||||||
assert_eq!(result, 4);
|
pub mod scheduler;
|
||||||
}
|
pub mod syscall;
|
||||||
}
|
pub mod thread;
|
||||||
|
|||||||
9
crates/domain/src/message.rs
Normal file
9
crates/domain/src/message.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use types::bounded::CapSlot;
|
||||||
|
|
||||||
|
pub const MAX_MESSAGE_WORDS: usize = 4;
|
||||||
|
pub const MAX_CAP_TRANSFERS: usize = 4;
|
||||||
|
|
||||||
|
pub struct Message {
|
||||||
|
words: [usize; MAX_MESSAGE_WORDS],
|
||||||
|
caps: [Option<CapSlot>; MAX_CAP_TRANSFERS],
|
||||||
|
}
|
||||||
15
crates/domain/src/process.rs
Normal file
15
crates/domain/src/process.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use types::{
|
||||||
|
bounded::CapSlot,
|
||||||
|
id::{ProcessId, ThreadId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::capability::CapabilityTable;
|
||||||
|
|
||||||
|
pub const MAX_THREADS_PER_PROCESS: usize = 4;
|
||||||
|
|
||||||
|
pub struct Process {
|
||||||
|
id: ProcessId,
|
||||||
|
capabilities: CapabilityTable,
|
||||||
|
threads: [Option<ThreadId>; MAX_THREADS_PER_PROCESS],
|
||||||
|
fault_handler: Option<CapSlot>,
|
||||||
|
}
|
||||||
22
crates/domain/src/resource.rs
Normal file
22
crates/domain/src/resource.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use types::{
|
||||||
|
address::VirtualAddress,
|
||||||
|
bounded::PageCount,
|
||||||
|
generation::Generation,
|
||||||
|
id::{EndpointId, IrqId, ProcessId, RegionId},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Resource {
|
||||||
|
Memory(RegionId),
|
||||||
|
Endpoint(EndpointId),
|
||||||
|
Process(ProcessId),
|
||||||
|
Interrupt(IrqId),
|
||||||
|
ProcessCreate,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemoryRegion {
|
||||||
|
id: RegionId,
|
||||||
|
base: VirtualAddress,
|
||||||
|
size: PageCount,
|
||||||
|
generation: Generation,
|
||||||
|
}
|
||||||
24
crates/domain/src/scheduler/mod.rs
Normal file
24
crates/domain/src/scheduler/mod.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use types::{
|
||||||
|
bounded::Priority,
|
||||||
|
id::{CoreId, ThreadId},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod per_core;
|
||||||
|
pub mod priority_queue;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "tests.rs"]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
pub enum SchedulerDecision {
|
||||||
|
Continue,
|
||||||
|
Preempt(ThreadId),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Scheduler {
|
||||||
|
fn add(&mut self, thread: ThreadId, priority: Priority, core: CoreId);
|
||||||
|
fn remove(&mut self, thread: ThreadId);
|
||||||
|
fn next(&mut self, core: CoreId) -> Option<ThreadId>;
|
||||||
|
fn tick(&mut self, core: CoreId, current: ThreadId) -> SchedulerDecision;
|
||||||
|
fn balance(&mut self);
|
||||||
|
}
|
||||||
52
crates/domain/src/scheduler/per_core.rs
Normal file
52
crates/domain/src/scheduler/per_core.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use super::priority_queue::PriorityQueue;
|
||||||
|
use types::bounded::Priority;
|
||||||
|
use types::id::{CoreId, ThreadId};
|
||||||
|
|
||||||
|
pub struct PerCoreScheduler {
|
||||||
|
core: CoreId,
|
||||||
|
queue: PriorityQueue,
|
||||||
|
current: Option<ThreadId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PerCoreScheduler {
|
||||||
|
pub fn new(core: CoreId) -> Self {
|
||||||
|
Self {
|
||||||
|
core,
|
||||||
|
queue: PriorityQueue::new(),
|
||||||
|
current: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn core(&self) -> CoreId {
|
||||||
|
self.core
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current(&self) -> Option<ThreadId> {
|
||||||
|
self.current
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, thread: ThreadId, priority: Priority) {
|
||||||
|
self.queue.add(thread, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, thread: ThreadId, priority: Priority) {
|
||||||
|
self.queue.remove(thread, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pick_next(&mut self) -> Option<ThreadId> {
|
||||||
|
let (thread, _priority) = self.queue.next()?;
|
||||||
|
self.current = Some(thread);
|
||||||
|
Some(thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_current(&mut self) {
|
||||||
|
self.current = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
// for load balancing — count of ready threads
|
||||||
|
// could track this incrementally instead of computing
|
||||||
|
// but for now, bitmap popcount works
|
||||||
|
self.queue.bitmap().count_ones() as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
108
crates/domain/src/scheduler/priority_queue.rs
Normal file
108
crates/domain/src/scheduler/priority_queue.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use types::bounded::Priority;
|
||||||
|
use types::id::ThreadId;
|
||||||
|
|
||||||
|
const NUM_PRIORITIES: usize = 32;
|
||||||
|
const MAX_THREADS_PER_PRIORITY: usize = 16;
|
||||||
|
|
||||||
|
struct RunList {
|
||||||
|
threads: [Option<ThreadId>; MAX_THREADS_PER_PRIORITY],
|
||||||
|
head: usize,
|
||||||
|
tail: usize,
|
||||||
|
count: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunList {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
threads: [None; MAX_THREADS_PER_PRIORITY],
|
||||||
|
head: 0,
|
||||||
|
tail: 0,
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, thread: ThreadId) -> bool {
|
||||||
|
match self.threads.iter_mut().find(|s| s.is_none()) {
|
||||||
|
Some(slot) => {
|
||||||
|
*slot = Some(thread);
|
||||||
|
self.count += 1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop(&mut self) -> Option<ThreadId> {
|
||||||
|
if self.count == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slot = self.threads.iter_mut().find(|s| s.is_some())?;
|
||||||
|
let thread = slot.take();
|
||||||
|
self.count -= 1;
|
||||||
|
thread
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, thread: ThreadId) -> bool {
|
||||||
|
for slot in &mut self.threads {
|
||||||
|
if *slot == Some(thread) {
|
||||||
|
*slot = None;
|
||||||
|
self.count -= 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.count == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PriorityQueue {
|
||||||
|
queues: [RunList; NUM_PRIORITIES],
|
||||||
|
bitmap: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PriorityQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
queues: core::array::from_fn(|_| RunList::new()),
|
||||||
|
bitmap: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bitmap(&self) -> u32 {
|
||||||
|
self.bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, thread: ThreadId, priority: Priority) {
|
||||||
|
let idx = priority.value() as usize;
|
||||||
|
self.queues[idx].push(thread);
|
||||||
|
self.bitmap |= 1 << idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, thread: ThreadId, priority: Priority) {
|
||||||
|
let idx = priority.value() as usize;
|
||||||
|
self.queues[idx].remove(thread);
|
||||||
|
if self.queues[idx].is_empty() {
|
||||||
|
self.bitmap &= !(1 << idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) -> Option<(ThreadId, Priority)> {
|
||||||
|
if self.bitmap == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// highest set bit = highest priority
|
||||||
|
let idx = 31 - self.bitmap.leading_zeros() as usize;
|
||||||
|
let thread = self.queues[idx].pop()?;
|
||||||
|
if self.queues[idx].is_empty() {
|
||||||
|
self.bitmap &= !(1 << idx);
|
||||||
|
}
|
||||||
|
Some((thread, Priority::new(idx as u8).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.bitmap == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
124
crates/domain/src/scheduler/tests.rs
Normal file
124
crates/domain/src/scheduler/tests.rs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
use super::per_core::PerCoreScheduler;
|
||||||
|
use super::priority_queue::PriorityQueue;
|
||||||
|
use types::bounded::Priority;
|
||||||
|
use types::id::{CoreId, ThreadId};
|
||||||
|
|
||||||
|
fn thread(id: u16) -> ThreadId {
|
||||||
|
ThreadId::from_raw(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prio(value: u8) -> Priority {
|
||||||
|
Priority::new(value).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PriorityQueue tests ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_queue_returns_none() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
assert!(q.next().is_none());
|
||||||
|
assert!(q.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_thread() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
q.add(thread(1), prio(10));
|
||||||
|
|
||||||
|
let (t, p) = q.next().unwrap();
|
||||||
|
assert_eq!(t, thread(1));
|
||||||
|
assert_eq!(p, prio(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn highest_priority_runs_first() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
q.add(thread(1), prio(5));
|
||||||
|
q.add(thread(2), prio(20));
|
||||||
|
q.add(thread(3), prio(10));
|
||||||
|
|
||||||
|
let (t, _) = q.next().unwrap();
|
||||||
|
assert_eq!(t, thread(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn same_priority_is_fifo() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
q.add(thread(1), prio(10));
|
||||||
|
q.add(thread(2), prio(10));
|
||||||
|
q.add(thread(3), prio(10));
|
||||||
|
|
||||||
|
assert_eq!(q.next().unwrap().0, thread(1));
|
||||||
|
assert_eq!(q.next().unwrap().0, thread(2));
|
||||||
|
assert_eq!(q.next().unwrap().0, thread(3));
|
||||||
|
assert!(q.next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_thread() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
q.add(thread(1), prio(10));
|
||||||
|
q.add(thread(2), prio(10));
|
||||||
|
|
||||||
|
q.remove(thread(1), prio(10));
|
||||||
|
|
||||||
|
let (t, _) = q.next().unwrap();
|
||||||
|
assert_eq!(t, thread(2));
|
||||||
|
assert!(q.next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_clears_bitmap_when_empty() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
q.add(thread(1), prio(10));
|
||||||
|
q.remove(thread(1), prio(10));
|
||||||
|
|
||||||
|
assert!(q.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drain_all_priorities() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
q.add(thread(1), prio(0));
|
||||||
|
q.add(thread(2), prio(15));
|
||||||
|
q.add(thread(3), prio(31));
|
||||||
|
|
||||||
|
assert_eq!(q.next().unwrap().0, thread(3)); // highest first
|
||||||
|
assert_eq!(q.next().unwrap().0, thread(2));
|
||||||
|
assert_eq!(q.next().unwrap().0, thread(1)); // lowest last
|
||||||
|
assert!(q.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PerCoreScheduler tests ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_core_has_no_current() {
|
||||||
|
let core = PerCoreScheduler::new(CoreId::from_raw(0));
|
||||||
|
assert!(core.current().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pick_next_sets_current() {
|
||||||
|
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
||||||
|
core.add(thread(1), prio(10));
|
||||||
|
|
||||||
|
let picked = core.pick_next().unwrap();
|
||||||
|
assert_eq!(picked, thread(1));
|
||||||
|
assert_eq!(core.current(), Some(thread(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pick_next_on_empty_returns_none() {
|
||||||
|
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
||||||
|
assert!(core.pick_next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clear_current() {
|
||||||
|
let mut core = PerCoreScheduler::new(CoreId::from_raw(0));
|
||||||
|
core.add(thread(1), prio(10));
|
||||||
|
core.pick_next();
|
||||||
|
|
||||||
|
core.clear_current();
|
||||||
|
assert!(core.current().is_none());
|
||||||
|
}
|
||||||
64
crates/domain/src/syscall.rs
Normal file
64
crates/domain/src/syscall.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
use types::{
|
||||||
|
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
||||||
|
bounded::{CapSlot, PageCount},
|
||||||
|
permission::PermissionSet,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::message::Message;
|
||||||
|
|
||||||
|
pub struct WaitSet {
|
||||||
|
endpoints: [Option<CapSlot>; 4],
|
||||||
|
interrupts: [Option<CapSlot>; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum WakeReason {
|
||||||
|
MessageReady(CapSlot),
|
||||||
|
InterruptFired(CapSlot),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Syscall {
|
||||||
|
Send {
|
||||||
|
endpoint: CapSlot,
|
||||||
|
message: Message,
|
||||||
|
},
|
||||||
|
Receive {
|
||||||
|
endpoint: CapSlot,
|
||||||
|
},
|
||||||
|
Delegate {
|
||||||
|
slot: CapSlot,
|
||||||
|
to_endpoint: CapSlot,
|
||||||
|
permissions: PermissionSet,
|
||||||
|
},
|
||||||
|
WaitForInterrupt {
|
||||||
|
irq: CapSlot,
|
||||||
|
},
|
||||||
|
AcknowledgeInterrupt {
|
||||||
|
irq: CapSlot,
|
||||||
|
},
|
||||||
|
WaitAny {
|
||||||
|
sources: WaitSet,
|
||||||
|
},
|
||||||
|
CreateProcess {
|
||||||
|
create_cap: CapSlot,
|
||||||
|
image: DataRef,
|
||||||
|
},
|
||||||
|
Kill {
|
||||||
|
process: CapSlot,
|
||||||
|
},
|
||||||
|
SpawnThread {
|
||||||
|
entry: EntryPoint,
|
||||||
|
stack: StackPointer,
|
||||||
|
},
|
||||||
|
ExitThread,
|
||||||
|
RequestPages {
|
||||||
|
count: PageCount,
|
||||||
|
},
|
||||||
|
ReleasePages {
|
||||||
|
addr: VirtualAddress,
|
||||||
|
count: PageCount,
|
||||||
|
},
|
||||||
|
GetTime,
|
||||||
|
GetProcessId,
|
||||||
|
Yield,
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
33
crates/domain/src/thread.rs
Normal file
33
crates/domain/src/thread.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
use types::{
|
||||||
|
bounded::{CapSlot, Priority},
|
||||||
|
id::{CoreId, ProcessId, ThreadId},
|
||||||
|
time::TimeSlice,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Thread {
|
||||||
|
id: ThreadId,
|
||||||
|
process: ProcessId,
|
||||||
|
state: ThreadState,
|
||||||
|
saved_context: SavedContext,
|
||||||
|
priority: Priority,
|
||||||
|
time_slice: TimeSlice,
|
||||||
|
core_affinity: Option<CoreId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ThreadState {
|
||||||
|
Ready,
|
||||||
|
Running(CoreId),
|
||||||
|
Blocked(BlockReason),
|
||||||
|
Dead,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SavedContext {
|
||||||
|
registers: [usize; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum BlockReason {
|
||||||
|
Sending(CapSlot),
|
||||||
|
Receiving(CapSlot),
|
||||||
|
WaitingForInterrupt(CapSlot),
|
||||||
|
WaitingAny,
|
||||||
|
}
|
||||||
@@ -4,3 +4,5 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
types = { workspace = true }
|
||||||
|
domain = { workspace = true }
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
pub fn add(left: u64, right: u64) -> u64 {
|
#![no_std]
|
||||||
left + right
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
use domain::{resource::MemoryRegion, thread::SavedContext};
|
||||||
mod tests {
|
use types::{
|
||||||
use super::*;
|
address::{EntryPoint, StackPointer},
|
||||||
|
id::{CoreId, IrqId, ProcessId},
|
||||||
|
time::{Ticks, TimerDuration},
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
pub trait PlatformContext: Copy + Default {}
|
||||||
fn it_works() {
|
|
||||||
let result = add(2, 2);
|
pub trait Platform {
|
||||||
assert_eq!(result, 4);
|
fn configure_protection(&mut self, process: ProcessId, regions: &[MemoryRegion]);
|
||||||
}
|
fn context_switch(&mut self, from: &mut SavedContext, to: &SavedContext);
|
||||||
|
fn enable_user_mode(&mut self, entry: EntryPoint, stack: StackPointer) -> !;
|
||||||
|
fn set_timer(&mut self, duration: TimerDuration);
|
||||||
|
fn current_ticks(&self) -> Ticks;
|
||||||
|
fn mask_irq(&mut self, irq: IrqId);
|
||||||
|
fn unmask_irq(&mut self, irq: IrqId);
|
||||||
|
fn send_ipi(&mut self, target: CoreId);
|
||||||
|
fn core_id(&self) -> CoreId;
|
||||||
|
fn num_cores(&self) -> u8;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,3 +3,12 @@ pub struct EntryPoint(usize);
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct StackPointer(usize);
|
pub struct StackPointer(usize);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct VirtualAddress(usize);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct DataRef {
|
||||||
|
addr: VirtualAddress,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ use crate::macros::bounded;
|
|||||||
|
|
||||||
bounded!(CapSlot, u8, 0, 63);
|
bounded!(CapSlot, u8, 0, 63);
|
||||||
bounded!(Priority, u8, 0, 31);
|
bounded!(Priority, u8, 0, 31);
|
||||||
|
bounded!(PageCount, u16, 1, 1024);
|
||||||
|
|||||||
@@ -9,3 +9,27 @@ impl Ticks {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct TimerDuration(u32);
|
pub struct TimerDuration(u32);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct TimeSlice {
|
||||||
|
remaining: u32,
|
||||||
|
quantum: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeSlice {
|
||||||
|
pub fn new(quantum: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
remaining: quantum,
|
||||||
|
quantum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self) -> bool {
|
||||||
|
self.remaining = self.remaining.saturating_sub(1);
|
||||||
|
self.remaining == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.remaining = self.quantum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user