os_kernel_foundry 0.1.5

A modular, heavily test-driven foundation crate for building Rust-based operating systems in Rust.
Documentation
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
# os_kernel_foundry Manual


This manual is a detailed guide to using **os_kernel_foundry** as the foundation of your own Rust-based operating system.

The crate is intentionally a **foundation** library:

- It provides **traits** and small **orchestration helpers**.
- It does **not** provide a runnable kernel binary, bootloader integration, linker scripts, or architecture-specific low-level code.

If you want a full OS that boots on real hardware, you typically create a *separate* kernel workspace that depends on this crate.

---

## Table of contents


- [1. Mental model]#1-mental-model
- [2. Crate integration]#2-crate-integration
- [3. Architecture back-ends (`arch`)]#3-architecture-back-ends-arch
- [4. Boot pipelines (`boot`)]#4-boot-pipelines-boot
- [5. Kernel orchestration (`kernel`)]#5-kernel-orchestration-kernel
- [6. Devices (`device`)]#6-devices-device
- [7. Memory (`memory`)]#7-memory-memory
- [8. Synchronization (`sync`)]#8-synchronization-sync
- [9. Scheduling (`scheduler`)]#9-scheduling-scheduler
- [10. IPC (`ipc`)]#10-ipc-ipc
- [11. Testing strategy]#11-testing-strategy
- [12. `no_std` usage patterns]#12-no_std-usage-patterns
- [13. Common pitfalls]#13-common-pitfalls

---

## 1. Mental model


The crate is designed around an explicit separation of concerns.

- **Portable kernel logic** should depend only on *traits*.
- **Architecture/platform-specific logic** is implemented by you, outside this crate.
- **Boot** is modeled as a deterministic, testable pipeline of stages.

At a high level, you typically build your kernel like this:

1. Choose an architecture type `A` that implements `arch::Architecture`.
2. Build a list of boot stages `&[&dyn boot::BootStage<A>]`.
3. Construct `kernel::Kernel<A>` and call `Kernel::boot`.
4. Initialize devices via `device::DeviceRegistry`.
5. Continue into your scheduler / IPC / other subsystems.

Important: `os_kernel_foundry` gives you **boundaries** and **contracts**, not a full OS.

---

## 2. Crate integration


### 2.1 Add the dependency


**Via path** (for developing both crates together):

```toml
[dependencies]
os_kernel_foundry = { path = "../os_kernel_foundry" }
```

**Via crates.io** (when published):

```toml
[dependencies]
os_kernel_foundry = "0.1.4"
```

### 2.2 Features


- `std`: enables `std` support for host-side experimentation and unit tests.

The crate is `no_std` outside tests via `#![cfg_attr(not(test), no_std)]`.

As a consumer, you generally:

- keep your kernel `#![no_std]`
- enable `std` only in host-side tests or simulator binaries

---

## 3. Architecture back-ends (`arch`)


The `arch` module defines the minimal services your portable kernel logic expects.

### 3.1 `Timer`


```rust
pub trait Timer {
    type Tick: Copy + Ord;
    fn now(&self) -> Self::Tick;
}
```

**Design intent**

- Must be monotonic.
- Only exposes a tick counter; conversion to durations is left to higher layers.

**Typical real back-ends**

- x86_64: TSC / HPET / APIC timer
- aarch64: generic timer

**Typical test back-end**

- deterministic in-memory timer you can advance manually

### 3.2 `InterruptController`


```rust
pub trait InterruptController {
    fn enable(&mut self, id: u32);
    fn disable(&mut self, id: u32);
    fn acknowledge(&mut self, id: u32);
}
```

**Design intent**

- Explicitly models a small subset used during early kernel work.
- Keeps portable code independent from GIC/APIC/PLIC details.

### 3.3 `AddressTranslator`


```rust
pub trait AddressTranslator {
    fn translate(&self, virtual_address: usize) -> Option<usize>;
}
```

**Design intent**

- Gives portable code a minimal “best effort” translation operation.
- You can implement this using a page table walker, a mapping database, or a hypervisor API.

### 3.4 `Architecture`


```rust
pub trait Architecture {
    type Timer: Timer;
    type InterruptController: InterruptController;
    type AddressTranslator: AddressTranslator;

    fn timer(&self) -> &Self::Timer;
    fn interrupt_controller(&mut self) -> &mut Self::InterruptController;
    fn address_translator(&self) -> &Self::AddressTranslator;
}
```

**Key point**

Your kernel orchestration is parameterized over `A: Architecture`, which makes it easy to:

- swap architecture back-ends without changing portable logic
- test everything on the host with in-memory mocks

### 3.5 Recommended consumer layout


In *your* kernel repository, keep architecture back-ends separate:

- `kernel-core/` portable logic; depends on `os_kernel_foundry`
- `kernel-arch-x86_64/` implements `Architecture` for x86_64
- `kernel-arch-aarch64/` implements `Architecture` for aarch64
- `kernel-bin/` actual bootable binary crate (entrypoint, linker script, bootloader)

---

## 4. Boot pipelines (`boot`)


Boot is modeled as a sequence of small, focused stages.

### 4.1 The key types


- `BootStage<A>`: one stage
- `BootContext<'_, A>`: passed to stages; provides controlled access to the architecture
- `BootState`: progress state machine
- `BootError`: stage error

Simplified signatures:

```rust
pub trait BootStage<A: Architecture> {
    fn name(&self) -> &'static str;
    fn run(&self, ctx: &mut BootContext<'_, A>) -> Result<(), BootError>;
}

pub fn run_boot_sequence<A: Architecture>(
    arch: &mut A,
    stages: &[&dyn BootStage<A>],
) -> Result<BootState, BootError>;
```

### 4.2 Stage design guidelines


- Keep each stage **single-responsibility**.
- Make stages **idempotent** when practical.
- Treat stage boundaries as “checkpoints” for invariants.

Examples of stage responsibilities:

- configure timer (calibration, interrupt enable)
- install early exception/interrupt handlers
- initialize early physical allocator
- initialize paging (if you do that early)
- build a device registry
- bring up a scheduler

### 4.3 Passing state between stages


`BootContext` currently exposes:

- `ctx.arch()` to access and mutate the architecture
- `ctx.state()` to read the current `BootState`

If you need to pass additional data between stages (e.g. allocator instances, vmm handles, device registry), you typically do that in your own kernel crate by:

- storing state in your architecture type `A` (if it’s architecture-scoped), or
- storing state in a separate “kernel state” struct owned by your boot stages and referenced by them, or
- extending the abstraction in your fork (if you want the context to carry more state)

### 4.4 Boot errors


`BootError` is intentionally small:

- `BootError::Fatal(&'static str)`

In a real kernel, you might want richer errors (stage name, subsystem error types, error codes). A common approach is:

- keep a simple error type for early boot
- log details via a serial console
- then transition to richer error reporting later

---

## 5. Kernel orchestration (`kernel`)


The `kernel` module provides `Kernel<A>`, which binds everything to a single architecture instance.

### 5.1 Constructing and booting


Conceptually:

```rust
use os_kernel_foundry::kernel::Kernel;
use os_kernel_foundry::boot::BootStage;

fn boot_kernel<A: os_kernel_foundry::arch::Architecture>(
    arch: A,
    stages: &[&dyn BootStage<A>],
) {
    let mut kernel = Kernel::new(arch);
    let _boot_state = kernel.boot(stages);
}
```

Notes:

- `Kernel::boot` simply delegates to `boot::run_boot_sequence`.
- `Kernel` intentionally does **not** keep global state besides `arch`.

### 5.2 Device initialization


`Kernel::init_devices` takes a `DeviceRegistry` and calls `registry.init_all()`.

This keeps the orchestration simple and lets your kernel decide:

- which drivers exist
- which order they are registered
- when they should be initialized

---

## 6. Devices (`device`)


The device model is designed to work without heap allocation.

### 6.1 `DeviceDriver`


```rust
pub trait DeviceDriver {
    fn name(&self) -> &'static str;
    fn init(&mut self) -> Result<(), DeviceError>;
}
```

### 6.2 `DeviceRegistry`


The registry is fixed-capacity and backed by caller-provided storage:

```rust
pub struct DeviceRegistry<'a> {
    devices: &'a mut [Option<&'a mut dyn DeviceDriver>],
    count: usize,
}
```

**Usage pattern**

- Create storage as a fixed array.
- Create a registry from the storage.
- Register drivers.
- Initialize them.

Example skeleton:

```rust
use os_kernel_foundry::device::{DeviceDriver, DeviceRegistry};

struct UartDriver;
impl DeviceDriver for UartDriver {
    fn name(&self) -> &'static str { "uart" }
    fn init(&mut self) -> Result<(), os_kernel_foundry::device::DeviceError> { Ok(()) }
}

fn init_drivers() {
    let mut uart = UartDriver;

    let mut storage: [Option<&mut dyn DeviceDriver>; 8] = [
        None, None, None, None, None, None, None, None,
    ];

    let mut reg = DeviceRegistry::new(&mut storage);
    let ok = reg.register(&mut uart);
    assert!(ok);

    reg.init_all().unwrap();
}
```

**Why this design?**

- Early boot often has no allocator.
- Some kernels want strict control over memory layout.

---

## 7. Memory (`memory`)


The memory traits are intentionally minimal and `unsafe`.

### 7.1 `PhysicalMemoryAllocator`


```rust
pub trait PhysicalMemoryAllocator {
    unsafe fn allocate_frame(&mut self) -> Option<usize>;
    unsafe fn deallocate_frame(&mut self, frame: usize);
}
```

**Expected invariants (consumer responsibility)**

- A “frame” represents a unit of physical memory (e.g. 4 KiB page frame).
- Allocated frames must be tracked so they are:
  - not handed out twice simultaneously
  - not aliased in ways that break safety assumptions

The trait uses `usize` for maximum portability, but you can wrap it in your own strongly typed newtypes.

### 7.2 `VirtualMemoryManager`


```rust
pub trait VirtualMemoryManager {
    unsafe fn map(&mut self, virtual_address: usize, physical_address: usize) -> Result<(), ()>;
    unsafe fn unmap(&mut self, virtual_address: usize) -> Result<(), ()>;
}
```

**Design intent**

- Models the minimal “map/unmap” behavior.
- Does not impose paging structure.

**In a real kernel you will likely build a richer API** around this trait:

- typed virtual/physical address wrappers
- page size/permissions
- address space objects
- error types describing failures

---

## 8. Synchronization (`sync`)


The crate provides a simple `SpinLock<T>` suitable for early kernel code.

### 8.1 When to use it


- short critical sections
- early boot
- data structures shared across cores where sleeping is impossible or undesired

### 8.2 When not to use it


- long operations
- any code path that may block/sleep
- high-contention hot paths (you will need more advanced primitives)

### 8.3 Basic usage


```rust
use os_kernel_foundry::sync::SpinLock;

static COUNTER: SpinLock<u64> = SpinLock::new(0);

fn bump() {
    let mut guard = COUNTER.lock();
    *guard += 1;
}
```

---

## 9. Scheduling (`scheduler`)


Scheduling is modeled as *cooperative* and trait-based.

### 9.1 `SchedulableTask`


```rust
pub trait SchedulableTask {
    fn id(&self) -> u64;
    fn on_scheduled(&mut self);
}
```

### 9.2 `Scheduler<T>`


```rust
pub trait Scheduler<T: SchedulableTask> {
    fn add_task(&mut self, task: T);
    fn next_task(&mut self) -> Option<T>;
    fn len(&self) -> usize;
}
```

**Important**

- This crate does not perform context switches.
- It models only selection and bookkeeping.

In your kernel, `next_task()` might return a task control block handle, and your arch/platform layer will do the actual switch.

---

## 10. IPC (`ipc`)


IPC is modeled as message-based endpoints.

### 10.1 `Message`


```rust
pub trait Message {
    fn message_type(&self) -> &'static str;
}
```

### 10.2 `MessageEndpoint<M>`


```rust
pub trait MessageEndpoint<M: Message> {
    fn send(&mut self, msg: M) -> bool;
    fn recv(&mut self) -> Option<M>;
    fn len(&self) -> usize;
}
```

**Design intent**

- Keep the contract small.
- Allow deterministic in-memory channels in tests.

In a real kernel you might layer:

- blocking semantics
- back-pressure
- capabilities/permissions
- per-process mailboxing

---

## 11. Testing strategy


The crate itself demonstrates the intended usage style: most modules include `#[cfg(test)]` unit tests.

### 11.1 Host-side mocks


A recommended approach in your kernel project:

- build a `TestArch` that implements `Architecture` using in-memory mocks
- write boot stages as normal `BootStage<TestArch>`
- run the full boot sequence under `cargo test`

### 11.2 Determinism goals


For reliable CI, tests should be:

- deterministic
- free of timing dependencies
- independent from the host OS state

Use:

- mock timers
- in-memory registries
- pure functions where possible

---

## 12. `no_std` usage patterns


### 12.1 Avoiding allocation


- `DeviceRegistry` is allocation-free by design.
- Boot stages should avoid allocating unless you know an allocator exists.

### 12.2 Collections


In `no_std` code, you usually cannot use `Vec`, `BTreeMap`, etc. unless you bring in `alloc` and a global allocator.

Strategies:

- fixed-capacity arrays + manual indexing
- custom slab allocators
- `heapless`-style data structures (in your consumer crate)

### 12.3 Logging


This crate does not define logging.

In your kernel you typically implement:

- early serial logging (UART)
- later structured logging

Boot stages can use that logging to make failures diagnosable.

---

## 13. Common pitfalls


### 13.1 Trying to make this crate a bootable kernel


This crate is not a kernel binary. You need a separate binary crate for:

- entrypoint (`_start`)
- linker script
- boot protocol (UEFI, Multiboot2, custom)

### 13.2 Overloading `Architecture` with global kernel state


It is tempting to store everything inside `A`. Prefer clear ownership:

- architecture services in `A`
- kernel subsystems in a separate kernel state
- explicit passing of references where appropriate

### 13.3 Long-held spinlocks


Spinlocks must protect short critical sections. If you need blocking behavior, build proper primitives.

### 13.4 Unclear `unsafe` invariants


When you implement `PhysicalMemoryAllocator` / `VirtualMemoryManager`, document:

- frame size assumptions
- alignment requirements
- mapping constraints
- ownership rules

Even if the trait uses `usize`, your implementation should enforce stronger invariants.