/
main.rs
199 lines (172 loc) · 6.79 KB
/
main.rs
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
/* diosix hypervisor kernel main entry code
*
* (c) Chris Williams, 2018.
*
* See LICENSE for usage and copying.
*/
/* let the compiler know we're on our own here in bare-metal world */
#![no_std]
#![no_main]
#![allow(dead_code)]
#![allow(unused_unsafe)]
#![allow(improper_ctypes)]
/* we need this to plug our custom heap allocator into the Rust language */
#![feature(alloc_error_handler)]
#![feature(alloc)]
#![feature(box_syntax)]
extern crate alloc;
/* needed for lookup tables of stuff */
extern crate hashmap_core;
/* allow hypervisor and supervisor to use lazy statics and mutexes */
#[macro_use]
extern crate lazy_static;
extern crate spin;
use spin::Mutex;
/* this will bring in all the hardware-specific code */
extern crate platform;
/* and now for all our non-hw specific code */
#[macro_use]
mod lock; /* multi-threading locking primitives */
#[macro_use]
mod debug; /* get us some kind of debug output, typically to a serial port */
mod heap; /* per-CPU private heap management */
mod abort; /* implement abort() and panic() handlers */
mod irq; /* handle hw interrupts and sw exceptions, collectively known as IRQs */
mod physmem; /* manage physical memory */
mod cpu; /* manage CPU cores */
mod scheduler;
use scheduler::Priority;
/* manage containers */
mod container;
/* list of kernel error codes */
mod error;
use error::Cause;
/* and our builtin supervisor kernel, which runs in its own container(s) */
mod supervisor;
/* tell Rust to use our kAllocator to allocate and free heap memory.
while we'll keep track of physical memory, we'll let Rust perform essential
tasks, such as freeing memory when it's no longer needed, pointer checking, etc */
#[global_allocator]
static KERNEL_HEAP: heap::Kallocator = heap::Kallocator;
/* set to true to allow physical CPU cores to start running supervisor code */
lazy_static!
{
static ref INIT_DONE: Mutex<bool> = Mutex::new(false);
}
/* pointer sizes: do not assume this is a 32-bit or 64-bit system. it could be either.
stick to usize as much as possible */
/* kentry
This is the official entry point of the Rust-level machine/hypervsor kernel.
Call kmain, which is where all the real work happens, and catch any errors.
=> parameters described in kmain, passed directly from the bootloader...
<= return to infinite loop, awaiting inerrupts */
#[no_mangle]
pub extern "C" fn kentry(cpu_nr: usize, device_tree_buf: &u8)
{
/* set up each processor core with its own private heap pool and any other resources.
this uses physical memory assigned by the pre-kmain boot code. this should be called
first to set up every core, including the boot CPU, which then sets up the global
resouces. all non-boot CPUs should wait until global resources are ready. */
cpu::Core::init(cpu_nr);
klog!("CPU core available and initialized");
/* heap and debugging set up. let's rock and roll */
match kmain(cpu_nr, device_tree_buf)
{
Err(e) => kalert!("kmain bailed out with error: {:?}", e),
_ => () /* continue waiting for an IRQ to come in, otherwise */
};
}
/* kmain
This code runs at the machine/hypervisor level, with full physical memory access.
Its job is to initialize CPU cores and other resources so that containers can be
created that contain supervisor kernels that manage their own userspaces, in which
applications run. The hypervisor ensures containers of apps are kept apart using
hardware protections.
Assumes all CPUs enter this function during startup.
The boot CPU is chosen to initialize the system in pre-SMP mode.
If we're on a single CPU core then everything should run OK.
=> cpu_nr = arbitrary ID number assigned by boot code, separate from hardware ID number.
0 = boot CPU core.
device_tree_buf = pointer to device tree describing the hardware
<= return to infinite loop, waiting for interrupts
*/
fn kmain(cpu_nr: usize, device_tree_buf: &u8) -> Result<(), Cause>
{
/* delegate to boot CPU the welcome banner and set up global resources */
if cpu_nr == 0 /* boot CPU is zeroth core */
{
/* initialize global resources and root container */
init_global(device_tree_buf)?;
init_root_container()?;
/* allow other cores to continue */
*(INIT_DONE.lock()) = true;
}
else
{
/* non-boot cores must wait for early initialization to complete */
loop
{
if *(INIT_DONE.lock()) == true
{
break;
}
}
}
/* enable timer on this CPU core to sstart cheduling threads */
scheduler::start();
/* initialization complete. if we make it this far then fall through to infinite loop
waiting for a timer interrupt to come in. when it does fire, this stack will be flattened,
a virtual CPU loaded up to run, and this boot thread will disappear like tears in the rain. */
Ok(())
}
/* welcome the user and have the boot CPU initialize global structures and resources.
<= return success, or failure code */
fn init_global(device_tree: &u8) -> Result<(), Cause>
{
/* set up CPU management. discover how many CPUs we have */
let cpus = match cpu::init(device_tree)
{
Some(c) => c,
None =>
{
kalert!("CPU management failure: can't extract core count from config");
return Err(Cause::CPUBadConfig);
}
};
/* set up the physical memory management. find out available physical RAM */
let ram_size = match physmem::init(device_tree)
{
Some(s) => s,
None =>
{
kalert!("Physical memory failure: too little RAM, or config error");
return Err(Cause::PhysMemBadConfig);
}
};
scheduler::init(device_tree)?;
/* say hello */
klog!("Welcome to diosix {} ... using device tree at {:p}", env!("CARGO_PKG_VERSION"), device_tree);
klog!("Available RAM: {} MiB ({} bytes), CPU cores: {}", ram_size / 1024 / 1024, ram_size, cpus);
kdebug!("... Debugging enabled");
return Ok(());
}
/* create the root container */
fn init_root_container() -> Result<(), Cause>
{
/* create root container with 4MB of RAM and max CPU cores */
let root_mem = 4 * 1024 * 1024;
let root_name = "root";
let root_max_vcpu = 2;
container::create_from_builtin(root_name, root_mem, root_max_vcpu)?;
/* create a virtual CPU thread for the root container, starting it in sentry() with
top of allocated memory as the stack pointer */
scheduler::create_thread(root_name, supervisor::main::entry, root_mem, Priority::High);
Ok(())
}
/* mandatory error handler for memory allocations */
#[alloc_error_handler]
fn kalloc_error(attempt: core::alloc::Layout) -> !
{
kalert!("alloc_error_handler: Failed to allocate/free {} bytes. Halting...", attempt.size());
loop {} /* it would be nice to be able to not die here :( */
}