Skip to content

Commit 63f0d4c

Browse files
committed
kernel: add memory token mapping per-instance
1 parent 32a4a85 commit 63f0d4c

File tree

5 files changed

+205
-27
lines changed

5 files changed

+205
-27
lines changed

oro-kernel/src/iface/kernel/mem_token_v0.rs

+27-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
//! Allows for querying information about memory tokens.
2+
//!
3+
//! # ⚠️ Stability Note ⚠️
4+
//! **This interface is incomplete.** Mappings that work now may not work in the future.
5+
//!
6+
//! The interface currently **does not** check for executable mapping conflicts -
7+
//! i.e. code, data, and rodata program segments mapped in as part of the module.
8+
//!
9+
//! Mapping operations to set the base of a token that overlaps with those regions
10+
//! **will not fail**, but also **will not be mapped**, as there will be no page
11+
//! fault for accesses to those regions.
12+
//!
13+
//! In the future, setting a `base` that *does* conflict **will** fail. Please be
14+
//! extra careful about your base addresses and spans when using this interface
15+
//! in order to be future-proof.
216
3-
use oro_mem::mapper::MapError;
417
use oro_sysabi::{key, syscall::Error as SysError};
518

619
use super::KernelInterface;
7-
use crate::{arch::Arch, syscall::InterfaceResponse, tab::Tab, thread::Thread, token::Token};
20+
use crate::{
21+
arch::Arch, instance::TokenMapError, syscall::InterfaceResponse, tab::Tab, thread::Thread,
22+
token::Token,
23+
};
824

925
/// Interface specific errors.
1026
#[derive(Debug, Clone, Copy)]
1127
#[repr(u64)]
1228
pub enum Error {
1329
/// An address conflict (existing mapping) was encountered when mapping a token.
14-
Conflict = key!("conflict"),
30+
Conflict = key!("conflict"),
1531
/// The requested address is not aligned to the page size.
16-
NotAligned = key!("align"),
32+
NotAligned = key!("align"),
1733
/// The requested address is out of the address space range.
18-
OutOfRange = key!("range"),
19-
/// The system ran out of memory trying to service the request.
20-
OutOfMemory = key!("oom"),
34+
OutOfRange = key!("range"),
2135
}
2236

2337
/// Version 0 of the memory token query interface.
@@ -83,16 +97,16 @@ impl KernelInterface for MemTokenV0 {
8397
);
8498
};
8599

86-
i.map_token(&token, virt).map_or_else(
100+
i.try_map_token_at(&token, virt).map_or_else(
87101
|err| {
88102
InterfaceResponse::immediate(
89103
SysError::InterfaceError,
90104
match err {
91-
MapError::Exists => Error::Conflict as u64,
92-
MapError::OutOfMemory => Error::OutOfMemory as u64,
93-
MapError::VirtNotAligned => Error::NotAligned as u64,
94-
MapError::VirtOutOfRange
95-
| MapError::VirtOutOfAddressSpaceRange => Error::OutOfRange as u64,
105+
TokenMapError::Conflict => Error::Conflict as u64,
106+
TokenMapError::VirtNotAligned => Error::NotAligned as u64,
107+
TokenMapError::VirtOutOfRange => Error::OutOfRange as u64,
108+
// NOTE(qix-): We already handled this at the beginning of the match.
109+
TokenMapError::BadToken => unreachable!(),
96110
},
97111
)
98112
},

oro-kernel/src/instance.rs

+149-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! Module instance types and functionality.
22
3-
use oro_mem::mapper::{AddressSegment, AddressSpace as _, MapError};
3+
use oro_mem::{
4+
mapper::{AddressSegment, AddressSpace as _, MapError},
5+
phys::PhysAddr,
6+
};
47

58
use crate::{
69
AddressSpace, Kernel, UserHandle,
@@ -63,6 +66,13 @@ pub struct Instance<A: Arch> {
6366
/// If a token is here, the instance is allowed to map it
6467
/// into its address space.
6568
tokens: Table<Tab<Token>>,
69+
/// The virtual memory mappings of virtual addresses to tokens.
70+
/// Values are `(token, page_id)` pairs.
71+
// TODO(qix-): This assumes a 4096 byte page size, and is also
72+
// TODO(qix-): not a very efficient data structure. It will be
73+
// TODO(qix-): replaced with a more efficient data structure
74+
// TODO(qix-): in the future.
75+
token_vmap: Table<(Tab<Token>, usize)>,
6676
}
6777

6878
impl<A: Arch> Instance<A> {
@@ -103,6 +113,7 @@ impl<A: Arch> Instance<A> {
103113
handle,
104114
data: TypeTable::new(),
105115
tokens: Table::new(),
116+
token_vmap: Table::new(),
106117
})
107118
.ok_or(MapError::OutOfMemory)?;
108119

@@ -151,11 +162,110 @@ impl<A: Arch> Instance<A> {
151162
self.tokens.insert_tab(token)
152163
}
153164

154-
/// Maps a [`Token`] into the instance's address space.
165+
/// Maps (sets the base of and reserves) a [`Token`] into the instance's address space.
155166
///
156-
/// Returns the address segment of the mapping.
157-
pub fn map_token(&self, token: &Tab<Token>, virt: usize) -> Result<(), MapError> {
158-
todo!("map token: {:016X} -> {virt:016X}", token.id());
167+
/// **This does not immediately map in any memory.** It only marks the range
168+
/// in the _internal kernel_ address space as reserved for the token, to be
169+
/// committed later (typically via a page fault calling [`Instance::try_commit_token_at`]).
170+
pub fn try_map_token_at(
171+
&mut self,
172+
token: &Tab<Token>,
173+
virt: usize,
174+
) -> Result<(), TokenMapError> {
175+
if !self.tokens.contains(token.id()) {
176+
return Err(TokenMapError::BadToken);
177+
}
178+
179+
token.with(|t| {
180+
match t {
181+
Token::Normal(t) => {
182+
debug_assert!(
183+
t.page_size() == 4096,
184+
"page size != 4096 is not implemented"
185+
);
186+
debug_assert!(
187+
t.page_size().is_power_of_two(),
188+
"page size is not a power of 2"
189+
);
190+
191+
if (virt & (t.page_size() - 1)) != 0 {
192+
return Err(TokenMapError::VirtNotAligned);
193+
}
194+
195+
let segment = AddressSpace::<A>::user_data();
196+
197+
// Make sure that none of the tokens exist in the vmap.
198+
for page_idx in 0..t.page_count() {
199+
let page_base = virt + (page_idx * t.page_size());
200+
201+
if !virt_resides_within::<A>(&segment, page_base)
202+
|| !virt_resides_within::<A>(&segment, page_base + t.page_size() - 1)
203+
{
204+
return Err(TokenMapError::VirtOutOfRange);
205+
}
206+
207+
if self.token_vmap.contains(
208+
u64::try_from(page_base).map_err(|_| TokenMapError::VirtOutOfRange)?,
209+
) {
210+
return Err(TokenMapError::Conflict);
211+
}
212+
}
213+
214+
// Everything's okay, map them into the vmap now.
215+
for page_idx in 0..t.page_count() {
216+
let page_base = virt + (page_idx * t.page_size());
217+
// NOTE(qix-): We can use `as` here since we already check the page base above.
218+
self.token_vmap
219+
.insert(page_base as u64, (token.clone(), page_idx));
220+
}
221+
222+
Ok(())
223+
}
224+
}
225+
})
226+
}
227+
228+
/// Commits a [`Token`] into the instance's address space at
229+
/// the specified virtual address. Returns a mapping error
230+
/// if the mapping could not be completed.
231+
///
232+
/// **The `maybe_unaligned_virt` parameter is not guaranteed to be aligned
233+
/// to any page boundary.** In most cases, it is coming directly from a
234+
/// userspace application (typically via a fault).
235+
///
236+
/// The `Token` must have been previously mapped via [`Instance::try_map_token_at`],
237+
/// or else this method will fail.
238+
pub fn try_commit_token_at(&self, maybe_unaligned_virt: usize) -> Result<(), TryCommitError> {
239+
// TODO(qix-): We always assume a 4096 page boundary. This will change in the future.
240+
let virt = maybe_unaligned_virt & !0xFFF;
241+
242+
if let Some((token, page_idx)) = u64::try_from(virt)
243+
.ok()
244+
.and_then(|virt| self.token_vmap.get(virt))
245+
{
246+
token.with_mut(|t| {
247+
match t {
248+
Token::Normal(t) => {
249+
debug_assert!(*page_idx < t.page_count());
250+
debug_assert!(
251+
t.page_size() == 4096,
252+
"page size != 4096 is not implemented"
253+
);
254+
let page_base = virt + (*page_idx * t.page_size());
255+
let segment = AddressSpace::<A>::user_data();
256+
let phys = t
257+
.get_or_allocate(*page_idx)
258+
.ok_or(TryCommitError::MapError(MapError::OutOfMemory))?;
259+
segment
260+
.map(self.handle.mapper(), page_base, phys.address_u64())
261+
.map_err(TryCommitError::MapError)?;
262+
Ok(())
263+
}
264+
}
265+
})
266+
} else {
267+
Err(TryCommitError::BadVirt)
268+
}
159269
}
160270

161271
/// Returns the instance's address space handle.
@@ -177,3 +287,37 @@ impl<A: Arch> Instance<A> {
177287
&mut self.data
178288
}
179289
}
290+
291+
/// An error returned by [`Instance::try_map_token_at`].
292+
#[derive(Debug, Clone, Copy)]
293+
pub enum TokenMapError {
294+
/// The virtual address is not aligned.
295+
VirtNotAligned,
296+
/// The virtual address is out of range for the
297+
/// thread's address space.
298+
VirtOutOfRange,
299+
/// The token was not found for the instance.
300+
BadToken,
301+
/// The mapping conflicts (overlaps) with another mapping.
302+
Conflict,
303+
}
304+
305+
/// Checks if the given virtual address resides within the given address segment.
306+
#[inline]
307+
fn virt_resides_within<A: Arch>(
308+
segment: &<AddressSpace<A> as ::oro_mem::mapper::AddressSpace>::UserSegment,
309+
virt: usize,
310+
) -> bool {
311+
// NOTE(qix-): Range is *inclusive*.
312+
let (first, last) = segment.range();
313+
virt >= first && virt <= last
314+
}
315+
316+
/// An error returned by [`Instance::try_commit_token_at`].
317+
#[derive(Debug, Clone, Copy)]
318+
pub enum TryCommitError {
319+
/// The virtual address was not found in the virtual map.
320+
BadVirt,
321+
/// Mapping the token failed.
322+
MapError(MapError),
323+
}

oro-kernel/src/scheduler.rs

+18-8
Original file line numberDiff line numberDiff line change
@@ -261,15 +261,25 @@ impl<A: Arch> Scheduler<A> {
261261
let Some(thread) = self.current.take() else {
262262
panic!("event_page_fault() called with no current thread");
263263
};
264+
264265
let id = thread.id();
265-
unsafe {
266-
thread.with_mut(|t| t.terminate());
267-
}
268-
dbg_warn!(
269-
"thread {:#016X} terminated due to page fault: {fault_type:?} at {vaddr:016X}",
270-
id
271-
);
272-
let switch = Switch::from_schedule_action(self.pick_user_thread(), Some(id));
266+
267+
let instance = thread.with(|t| t.instance().clone());
268+
269+
let switch = if instance.with_mut(|i| i.try_commit_token_at(vaddr)).is_err() {
270+
unsafe {
271+
thread.with_mut(|t| t.terminate());
272+
}
273+
dbg_warn!(
274+
"thread {:#016X} terminated due to page fault: {fault_type:?} at {vaddr:016X}",
275+
id
276+
);
277+
Switch::from_schedule_action(self.pick_user_thread(), Some(id))
278+
} else {
279+
self.current = Some(thread.clone());
280+
Switch::UserResume(thread, None)
281+
};
282+
273283
self.kernel.handle().schedule_timer(1000);
274284
switch
275285
}

oro-kernel/src/table.rs

+6
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ impl<T: Sized, Alloc: Allocator + Default> Table<T, Alloc> {
9797
pub fn remove(&mut self, id: u64) -> Option<T> {
9898
self.0.remove(&id)
9999
}
100+
101+
/// Returns whether or not the given key exists in the table.
102+
#[inline]
103+
pub fn contains(&self, key: u64) -> bool {
104+
self.0.contains_key(&key)
105+
}
100106
}
101107

102108
/// A [`Table`] wrapper that allows for artibtrary singleton values

oro-kernel/src/token.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ impl NormalToken {
7575
pub(crate) fn new_4kib(pages: usize) -> Self {
7676
Self {
7777
page_size: 4096,
78-
phys_addrs: Vec::with_capacity(pages),
78+
phys_addrs: {
79+
let mut v = Vec::with_capacity(pages);
80+
v.resize_with(pages, || None);
81+
v
82+
},
7983
committed: 0,
8084
}
8185
}

0 commit comments

Comments
 (0)