-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathlib.rs
More file actions
298 lines (263 loc) · 10.5 KB
/
Copy pathlib.rs
File metadata and controls
298 lines (263 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
//! # The mnemos kernel
//!
//! The mnemos kernel is implemented as a library, with platform-specific code depending on the
//! kernel library and producing the final binary.
//!
//! ## The "kernelspace" entry point
//!
//! At the moment, the kernel requires some "entry point" code, more or less the `main()` function
//! that runs when the system boots, to perform any system-specific initialization.
//!
//! This entry point code is responsible for setting up any hardware or subsystems that exist
//! outside the kernel (or are required by any kernel services), as well as starting and running
//! the kernel itself.
//!
//! ## Creating the kernel
//!
//! To create the kernel, you give it a region of memory (as a `*mut u8` + `usize`), by calling
//! [`Kernel::new()`].
//!
//! At this point, the system is in "blocking" mode.
//!
//! Using the given region of memory, the kernel bootstraps itself, and creates the following:
//!
//! * An async executor, intended for "kernel services"
//! * A kernel service discovery registry
//! * An async heap allocator, intended for use by kernel services
//!
//! After creation, the executor is *not* running yet.
//!
//! ## Initialization phase
//!
//! After creating the kernel, we need to register kernel services, which are expected to act as
//! drivers for various system components.
//!
//! Since we are not running the executor yet, the kernel provides an interface,
//! [`Kernel::initialize()`], which takes a future and spawns it on the executor. Futures added with
//! `initialize` still do not run until the later "running" phase.
//!
//! Right now, it is generally suggested you use one or more `initialize`
//! calls to register all (initial) kernel services.
//!
//! ## Running mode
//!
//! Once everything is prepared and initialized, the startup code is expected to call
//! [`Kernel::tick()`] repeatedly. On each call to tick:
//!
//! * The allocator frees any synchronously dropped allocations, making them available for
//! asynchronous allocation
//! * The async executor is polled
//!
//! AT THE MOMENT, there is no indication of whether all tasks are blocked, which could be use to
//! inform whether we should put the CPU into some kind of sleep mode until a hardware event (like
//! a timer or DMA transaction) is triggered, and an async task has potentially been awoken.
//!
//! ## Not covered: "userspace"
//!
//! At the moment, there is SOME concept of a userspace, which interacts with the kernel via a
//! bidirectional IPC ringbuffer. Space for this ringbuffer is allocated when calling
//! [`Kernel::new()`]. Additionally this ringbuffer is polled on each call to `tick`, after freeing
//! allocations and before calling `tick` on the scheduler.
//!
//! This is an artifact of how mnemos 0.1 worked, where there was a single userspace executor that
//! existed and interacted with the kernel executor.
//!
//! As of 2023-05-30, I don't think this is the right abstraction for multiple userspace processes.
//! The pieces that exist currently are likely to be removed or reworked heavily before they are
//! usable, and should be considered nonfunctional at the moment.
#![no_std]
#![allow(clippy::missing_safety_doc)]
#![feature(impl_trait_in_assoc_type)]
pub mod comms;
pub mod drivers;
pub(crate) mod fmt;
pub mod forth;
pub mod registry;
use abi::{
bbqueue_ipc::{
framed::{FrameConsumer, FrameProducer},
BBBuffer,
},
syscall::{KernelResponse, UserRequest},
};
use comms::kchannel::KChannel;
use maitake::{
self,
scheduler::{StaticScheduler, TaskStub},
task::Storage,
};
use maitake::{sync::Mutex, task::Task as MaitakeTask};
use mnemos_alloc::{containers::HeapBox, heap::AHeap};
use registry::Registry;
use tracing::info;
pub struct Rings {
pub u2k: NonNull<BBBuffer>,
pub k2u: NonNull<BBBuffer>,
}
pub struct KernelSettings {
pub heap_start: *mut u8,
pub heap_size: usize,
pub max_drivers: usize,
pub k2u_size: usize,
pub u2k_size: usize,
}
pub struct Message {
pub request: UserRequest,
pub response: KChannel<KernelResponse>,
}
pub struct Kernel {
/// Items that do not require a lock to access, and must only
/// be accessed with shared refs
inner: KernelInner,
/// The run-time driver registry, accessed via an async Mutex
registry: Mutex<Registry>,
heap: NonNull<AHeap>,
}
unsafe impl Sync for Kernel {}
pub struct KernelInner {
u2k_ring: BBBuffer,
k2u_ring: BBBuffer,
scheduler: StaticScheduler,
}
impl Kernel {
pub unsafe fn new(settings: KernelSettings) -> Result<HeapBox<Self>, &'static str> {
info!(
start = ?settings.heap_start,
size = settings.heap_size,
"Initializing heap"
);
let (nn_heap, mut guard) = AHeap::bootstrap(settings.heap_start, settings.heap_size)
.map_err(|_| "failed to initialize heap")?;
let registry = registry::Registry::new(&mut guard, settings.max_drivers);
let (nn_u2k_buf, u2k_len) = guard
.alloc_box_array_with(|| 0, settings.u2k_size)
.map_err(|_| "failed to allocate u2k ring buf")?
.leak();
let (nn_k2u_buf, k2u_len) = guard
.alloc_box_array_with(|| 0, settings.k2u_size)
.map_err(|_| "failed to allocate k2u ring buf")?
.leak();
let u2k_ring = BBBuffer::new();
let k2u_ring = BBBuffer::new();
// SAFETY: The data buffers live in a heap allocation, which have a stable
// location. Therefore it is acceptable to initialize the rings using these
// buffers, then moving the HANDLES into the KernelInner structure.
//
// The BBBuffers themselves ONLY have a stable address AFTER they have been
// written into the static `inner` field. DO NOT create producers/consumers
// until that has happened.
u2k_ring.initialize(nn_u2k_buf.as_ptr(), u2k_len);
k2u_ring.initialize(nn_k2u_buf.as_ptr(), k2u_len);
// Safety: We only use the static stub once
let stub: &'static TaskStub = guard
.alloc_box(TaskStub::new())
.map_err(|_| "failed to allocate task stub")?
.leak()
.as_ref();
let scheduler = StaticScheduler::new_with_static_stub(stub);
let inner = KernelInner {
u2k_ring,
k2u_ring,
scheduler,
};
let new_kernel = guard
.alloc_box(Kernel {
inner,
registry: Mutex::new(registry),
heap: nn_heap,
})
.map_err(|_| "failed to allocate new kernel box")?;
Ok(new_kernel)
}
fn inner(&'static self) -> &'static KernelInner {
&self.inner
}
pub fn rings(&'static self) -> Rings {
unsafe {
Rings {
u2k: NonNull::new_unchecked(&self.inner.u2k_ring as *const _ as *mut _),
k2u: NonNull::new_unchecked(&self.inner.k2u_ring as *const _ as *mut _),
}
}
}
pub fn heap(&'static self) -> &'static AHeap {
unsafe { self.heap.as_ref() }
}
pub fn tick(&'static self) {
// Process heap allocations
self.heap().poll();
// process mailbox messages
let inner = self.inner();
let u2k_buf: *mut BBBuffer = &self.inner.u2k_ring as *const _ as *mut _;
let k2u_buf: *mut BBBuffer = &self.inner.k2u_ring as *const _ as *mut _;
let u2k: FrameConsumer<'static> = unsafe { BBBuffer::take_framed_consumer(u2k_buf) };
let _k2u: FrameProducer<'static> = unsafe { BBBuffer::take_framed_producer(k2u_buf) };
#[allow(unreachable_code)]
if let Some(mut _reg) = self.registry.try_lock() {
// Incoming messages
while let Some(msg) = u2k.read() {
match postcard::from_bytes::<UserRequest>(&msg) {
Ok(_req) => {
// let kind = req.driver_kind();
// if let Some(drv) = inner_mut.drivers.iter().find(|drv| drv.kind == kind) {
// drv.queue
// .enqueue_sync(Message {
// request: req,
// response: inner.user_reply.clone(),
// })
// .map_err(drop)
// .unwrap();
// }
todo!("Driver registry");
}
Err(_) => panic!(),
}
msg.release();
}
}
inner.scheduler.tick();
// TODO: Send time to userspace?
}
// TODO: This prooooobably should instead use a joinhandle, and poll on the initialize future
// to completion, to make sure that certain actions actually complete.
pub fn initialize<F: Future + 'static>(&'static self, fut: F) -> Result<(), ()> {
let task = self.new_task(fut);
let mut guard = self.heap().lock().map_err(drop)?;
let task_box = guard.alloc_box(task).map_err(drop)?;
self.spawn_allocated(task_box);
Ok(())
}
pub fn new_task<F: Future + 'static>(&'static self, fut: F) -> Task<F> {
Task(MaitakeTask::new(&self.inner.scheduler, fut))
}
pub async fn spawn<F: Future + 'static>(&'static self, fut: F) {
let task = Task(MaitakeTask::new(&self.inner.scheduler, fut));
let atask = self.heap().allocate(task).await;
self.spawn_allocated(atask);
}
pub async fn with_registry<F, R>(&'static self, f: F) -> R
where
F: FnOnce(&mut Registry) -> R,
{
let mut guard = self.registry.lock().await;
f(&mut guard)
}
pub fn spawn_allocated<F: Future + 'static>(&'static self, task: HeapBox<Task<F>>) {
self.inner.scheduler.spawn_allocated::<F, HBStorage>(task)
}
}
// TODO: De-dupe with userspace?
use core::{future::Future, ptr::NonNull};
#[repr(transparent)]
pub struct Task<F: Future + 'static>(MaitakeTask<&'static StaticScheduler, F, HBStorage>);
struct HBStorage;
impl<F: Future + 'static> Storage<&'static StaticScheduler, F> for HBStorage {
type StoredTask = HeapBox<Task<F>>;
fn into_raw(task: HeapBox<Task<F>>) -> NonNull<MaitakeTask<&'static StaticScheduler, F, Self>> {
task.leak()
.cast::<MaitakeTask<&'static StaticScheduler, F, HBStorage>>()
}
fn from_raw(ptr: NonNull<MaitakeTask<&'static StaticScheduler, F, Self>>) -> HeapBox<Task<F>> {
unsafe { HeapBox::from_leaked(ptr.cast::<Task<F>>()) }
}
}