Too late but it kinda works

This commit is contained in:
2026-02-11 15:19:10 +01:00
parent 53043fd3cd
commit 6fc08b5dbb
9 changed files with 570 additions and 59 deletions

View File

@@ -8,6 +8,7 @@ panic = "abort"
[profile.release]
panic = "abort"
debug = true
[dependencies]
embedded-alloc = "0.7"

115
kernel.svg Normal file
View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1000"
height="1000"
viewBox="0 0 264.58333 264.58333"
version="1.1"
id="svg1"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="kernel.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showguides="true"
showgrid="false"
inkscape:zoom="1.1688587"
inkscape:cx="148.00762"
inkscape:cy="533.85408"
inkscape:window-width="1916"
inkscape:window-height="1026"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1">
<inkscape:grid
id="grid3"
units="px"
originx="0"
originy="0"
spacingx="0.26458333"
spacingy="0.26458333"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
enabled="true"
visible="false" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="User Mode" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Supervisor Mode" />
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Machine Mode">
<path
style="fill:none;fill-opacity:1;stroke:#05668d;stroke-width:1.05833332;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none"
d="M 0,88.106249 H 264.58333"
id="path3"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-opacity:1;stroke:#05668d;stroke-width:1.05833332;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none"
d="M 0,176.2125 H 264.58332"
id="path3-3"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:7.05556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;writing-mode:lr-tb;direction:ltr;fill:#05668d;fill-opacity:1;stroke:none;stroke-width:1.05833;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="0.68901867"
y="6.6834888"
id="text3"><tspan
sodipodi:role="line"
id="tspan3"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:7.05556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#05668d;fill-opacity:1;stroke:none;stroke-width:1.05833"
x="0.68901867"
y="6.6834888">Machine</tspan><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:7.05556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#05668d;fill-opacity:1;stroke:none;stroke-width:1.05833"
x="0.68901867"
y="15.502938"
id="tspan5" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:7.05556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;writing-mode:lr-tb;direction:ltr;fill:#05668d;fill-opacity:1;stroke:none;stroke-width:1.05833;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="0.99907744"
y="94.789734"
id="text3-5"><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:7.05556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#05668d;fill-opacity:1;stroke:none;stroke-width:1.05833"
x="0.99907744"
y="94.789734"
id="tspan4">Supervisor</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:7.05556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;writing-mode:lr-tb;direction:ltr;fill:#05668d;fill-opacity:1;stroke:none;stroke-width:1.05833;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="0.99907744"
y="182.89598"
id="text3-5-8"><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:7.05556px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#05668d;fill-opacity:1;stroke:none;stroke-width:1.05833"
x="0.99907744"
y="182.89598"
id="tspan6">User</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -1,11 +1,11 @@
#[non_exhaustive]
#[repr(u64)]
pub enum EID {
pub enum EextensionID {
Time = 0x54494D45,
}
#[non_exhaustive]
#[repr(u64)]
pub enum TimeFID {
pub enum TimerFunctionID {
SetTimer = 0x0,
}

View File

@@ -1,17 +1,19 @@
use log::info;
use crate::{
boot::sbi::{TimeFID, EID},
clear_csr, generate_trap_handler, read_csr,
riscv::disable_interrupt,
set_csr,
time::{setup_next_timer_interrupt, IRQ_M_TIMER},
write_csr,
boot::sbi::{EextensionID, TimerFunctionID}, clear_csr, generate_trap_handler, process::ExecutionContext, read_csr, riscv::disable_interrupt, set_csr, time::{IRQ_M_TIMER, setup_next_timer_interrupt}, write_csr
};
use core::arch::naked_asm;
use crate::time::{setup_timer_interrupt, timer_interrupt};
#[unsafe(no_mangle)]
unsafe extern "C" fn machine_trap_handler(mcause: u64, mie: u64, mip: u64) {
unsafe extern "C" fn machine_trap_handler(
_interrupt_state: *const ExecutionContext,
mcause: u64,
mie: u64,
mip: u64,
) {
let mepc = read_csr!(mepc);
let mtval = read_csr!(mtval);
if mcause & (1 << 63) == 0 {
@@ -40,8 +42,8 @@ unsafe extern "C" fn machine_trap_handler(mcause: u64, mie: u64, mip: u64) {
#[allow(clippy::single_match)]
match eid {
c if c == EID::Time as u64 => match fid {
c if c == TimeFID::SetTimer as u64 => {
c if c == EextensionID::Time as u64 => match fid {
c if c == TimerFunctionID::SetTimer as u64 => {
clear_csr!(mip, 1 << 5);
setup_next_timer_interrupt();
}
@@ -82,7 +84,12 @@ unsafe extern "C" fn machine_trap_handler(mcause: u64, mie: u64, mip: u64) {
}
#[unsafe(no_mangle)]
unsafe extern "C" fn supervisor_trap_handler(scause: u64, _sie: u64, _sip: u64) {
unsafe extern "C" fn supervisor_trap_handler(
interrupt_state: *const ExecutionContext,
scause: u64,
_sie: u64,
_sip: u64,
) {
#[allow(clippy::single_match)]
match scause & !(1 << 63) {
5 => {
@@ -90,11 +97,11 @@ unsafe extern "C" fn supervisor_trap_handler(scause: u64, _sie: u64, _sip: u64)
core::arch::asm!(
"ecall",
in("a0") 0,
in("a6") TimeFID::SetTimer as u64,
in("a7") EID::Time as u64,
in("a6") TimerFunctionID::SetTimer as u64,
in("a7") EextensionID::Time as u64,
);
}
timer_interrupt();
timer_interrupt(unsafe { *interrupt_state });
}
_ => {}
}
@@ -124,52 +131,128 @@ macro_rules! generate_trap_handler {
unsafe extern "C" fn $name() {
naked_asm!(
concat!("
addi sp, sp, -128
addi sp, sp, -264
sd ra, 120(sp)
# Store the current frame
sd ra, 0(sp)
sd sp, 8(sp)
sd gp, 16(sp)
sd tp, 24(sp)
sd a0, 32(sp)
sd a1, 40(sp)
sd a2, 48(sp)
sd a3, 56(sp)
sd a4, 64(sp)
sd a5, 72(sp)
sd a6, 80(sp)
sd a7, 88(sp)
sd t0, 96(sp)
sd t1, 104(sp)
sd t2, 112(sp)
sd t3, 120(sp)
sd t4, 128(sp)
sd t5, 136(sp)
sd t6, 144(sp)
sd s0, 152(sp)
sd s1, 160(sp)
sd s2, 168(sp)
sd s3, 176(sp)
sd s4, 184(sp)
sd s5, 192(sp)
sd s6, 200(sp)
sd s7, 208(sp)
sd s8, 216(sp)
sd s9, 224(sp)
sd s10, 232(sp)
sd s11, 240(sp)
csrr t0, sepc
sd t0, 248(sp)
csrr t0, sstatus
sd t0, 256(sp)
// sd ra, 120(sp)
sd a0, 0(sp)
sd a1, 8(sp)
sd a2, 16(sp)
sd a3, 24(sp)
sd a4, 32(sp)
sd a5, 40(sp)
sd a6, 48(sp)
sd a7, 56(sp)
// sd a0, 0(sp)
// sd a1, 8(sp)
// sd a2, 16(sp)
// sd a3, 24(sp)
// sd a4, 32(sp)
// sd a5, 40(sp)
// sd a6, 48(sp)
// sd a7, 56(sp)
sd t0, 64(sp)
sd t1, 72(sp)
sd t2, 80(sp)
sd t3, 88(sp)
sd t4, 96(sp)
sd t5, 104(sp)
sd t6, 112(sp)
// sd t0, 64(sp)
// sd t1, 72(sp)
// sd t2, 80(sp)
// sd t3, 88(sp)
// sd t4, 96(sp)
// sd t5, 104(sp)
// sd t6, 112(sp)
csrr a0, ", stringify!($mode),"cause
csrr a1, ", stringify!($mode),"ie
csrr a2, ", stringify!($mode),"ip
mv a0, sp
csrr a1, ", stringify!($mode),"cause
csrr a2, ", stringify!($mode),"ie
csrr a3, ", stringify!($mode),"ip
jal ", stringify!($jump_to), "
ld a0, 0(sp)
ld a1, 8(sp)
ld a2, 16(sp)
ld a3, 24(sp)
ld a4, 32(sp)
ld a5, 40(sp)
ld a6, 48(sp)
ld a7, 56(sp)
# Restore registers
ld t0, 248(sp)
csrw sepc, t0
ld t0, 256(sp)
csrw sstatus, t0
ld ra, 0(sp)
ld gp, 16(sp)
ld tp, 24(sp)
ld a0, 32(sp)
ld a1, 40(sp)
ld a2, 48(sp)
ld a3, 56(sp)
ld a4, 64(sp)
ld a5, 72(sp)
ld a6, 80(sp)
ld a7, 88(sp)
ld t0, 96(sp)
ld t1, 104(sp)
ld t2, 112(sp)
ld t3, 120(sp)
ld t4, 128(sp)
ld t5, 136(sp)
ld t6, 144(sp)
ld s0, 152(sp)
ld s1, 160(sp)
ld s2, 168(sp)
ld s3, 176(sp)
ld s4, 184(sp)
ld s5, 192(sp)
ld s6, 200(sp)
ld s7, 208(sp)
ld s8, 216(sp)
ld s9, 224(sp)
ld s10, 232(sp)
ld s11, 240(sp)
ld t0, 64(sp)
ld t1, 72(sp)
ld t2, 80(sp)
ld t3, 88(sp)
ld t4, 96(sp)
ld t5, 104(sp)
ld t6, 112(sp)
ld sp, 8(sp)
ld ra, 120(sp)
// ld a0, 0(sp)
// ld a1, 8(sp)
// ld a2, 16(sp)
// ld a3, 24(sp)
// ld a4, 32(sp)
// ld a5, 40(sp)
// ld a6, 48(sp)
// ld a7, 56(sp)
// ld t0, 64(sp)
// ld t1, 72(sp)
// ld t2, 80(sp)
// ld t3, 88(sp)
// ld t4, 96(sp)
// ld t5, 104(sp)
// ld t6, 112(sp)
// ld ra, 120(sp)
addi sp, sp, 128
addi sp, sp, 264
", stringify!($mode),"ret")
)

View File

@@ -1,16 +1,18 @@
#![no_std]
#![no_main]
#![allow(static_mut_refs)]
#![feature(riscv_ext_intrinsics)]
#![feature(riscv_ext_intrinsics, const_trait_impl, iter_map_windows)]
use core::arch::riscv64::wfi;
use core::{arch::riscv64::wfi, time::Duration};
use embedded_alloc::LlffHeap as Heap;
use log::info;
use crate::{
io::init_log,
process::{create_processus, sleep},
riscv::enable_supervisor_interrupt,
scheduler::scheduler_init,
vga::{Color, Vga},
};
@@ -21,27 +23,50 @@ mod critical_section;
mod interrupt;
mod io;
mod panic_handler;
mod process;
mod riscv;
mod scheduler;
mod time;
mod uart;
mod vga;
pub const HEAP_SIZE: usize = 40960;
pub const HEAP_SIZE: usize = 4096;
#[global_allocator]
static HEAP: Heap = Heap::empty();
extern "C" fn test() {
loop {
info!("test");
enable_supervisor_interrupt();
sleep(Duration::new(2, 0));
unsafe { wfi() };
}
}
extern "C" fn proc2() {
loop {
info!("proc2");
enable_supervisor_interrupt();
sleep(Duration::new(3, 0));
unsafe { wfi() };
}
}
#[unsafe(no_mangle)]
pub extern "C" fn supervisor_mode_entry() {
unsafe {
embedded_alloc::init!(HEAP, HEAP_SIZE);
init_log().unwrap();
Vga::init();
scheduler_init();
enable_supervisor_interrupt();
}
info!("Hello World !");
unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE, Color::BLACK) };
create_processus(test, "proc1");
create_processus(proc2, "proc2");
loop {
unsafe { wfi() }
}

107
src/process.rs Normal file
View File

@@ -0,0 +1,107 @@
use core::{arch::riscv64::wfi, time::Duration};
use alloc::{format, string::String};
use crate::{
scheduler::{scheduler, ACTIVE_PID, PROCESSUS_COUNT, PROCESS_TABLE},
time::elapsed_time_since_startup,
};
const STACK_SIZE: usize = 4096;
#[derive(Debug, PartialEq, Eq)]
pub enum ProcessState {
Active,
Activable,
Dead,
Asleep,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ExecutionContext {
pub ra: *const usize,
pub sp: *const usize,
pub gp: usize,
pub tp: usize,
pub a: [usize; 8],
pub t: [usize; 7],
pub s: [usize; 11],
pub mepc: usize,
pub mstatus: usize,
}
pub struct Process {
pub pid: i64,
pub name: String,
pub state: ProcessState,
pub wake_time: Duration,
pub ctx: ExecutionContext,
pub entry_point: Option<extern "C" fn()>,
pub stack: [usize; STACK_SIZE],
}
impl core::fmt::Debug for Process {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Process")
.field("pid", &self.pid)
.field("name", &self.name)
.field("state", &self.state)
.field("wake_time", &self.wake_time)
.field("ctx", &self.ctx)
.field("entry_point", &self.entry_point)
.field("stack", &format!("[_; {}]", STACK_SIZE))
.finish()
}
}
pub fn create_processus<T: Into<String>>(code: extern "C" fn(), name: T) -> i64 {
let mut next_pid = 0;
while next_pid < PROCESSUS_COUNT
&& unsafe { PROCESS_TABLE[next_pid].state != ProcessState::Dead }
{
next_pid += 1;
}
if next_pid >= PROCESSUS_COUNT {
return -1;
}
unsafe {
PROCESS_TABLE[next_pid].pid = next_pid as i64;
PROCESS_TABLE[next_pid].name = name.into();
PROCESS_TABLE[next_pid].state = ProcessState::Activable;
PROCESS_TABLE[next_pid].entry_point = Some(code);
PROCESS_TABLE[next_pid].ctx.a[0] = code as usize;
PROCESS_TABLE[next_pid].ctx.ra = processus_launcher as *const _;
PROCESS_TABLE[next_pid].ctx.sp = &raw const PROCESS_TABLE[next_pid].stack[STACK_SIZE - 1];
}
next_pid as i64
}
extern "C" fn processus_launcher(code: extern "C" fn()) {
code();
terminate_processus();
}
fn terminate_processus() {
unsafe {
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Dead;
}
unsafe {
wfi();
}
// scheduler();
}
pub fn sleep(duration: Duration) {
unsafe {
PROCESS_TABLE[ACTIVE_PID].wake_time = elapsed_time_since_startup() + duration;
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Asleep;
}
unsafe {
wfi();
}
// scheduler();
}

173
src/scheduler.rs Normal file
View File

@@ -0,0 +1,173 @@
use core::{
arch::{naked_asm, riscv64::wfi},
array,
cell::LazyCell,
time::Duration,
};
use alloc::string::String;
use log::info;
use crate::{
process::{create_processus, ExecutionContext, Process, ProcessState},
riscv::enable_supervisor_interrupt,
time,
};
pub const PROCESSUS_COUNT: usize = 16;
pub static mut ACTIVE_PID: usize = 0;
pub static mut PROCESS_TABLE: LazyCell<[Process; PROCESSUS_COUNT]> = LazyCell::new(|| {
array::from_fn(|_| Process {
pid: -1,
name: String::new(),
state: ProcessState::Dead,
wake_time: Duration::new(0, 0),
ctx: ExecutionContext {
ra: core::ptr::null(),
sp: core::ptr::null(),
gp: 0,
tp: 0,
a: [0; _],
t: [0; _],
s: [0; _],
mepc: 0,
mstatus: 0,
},
entry_point: None,
stack: [0; _],
})
});
pub extern "C" fn idle() {
loop {
enable_supervisor_interrupt();
unsafe {
wfi();
}
}
}
pub fn scheduler_init() {
info!("scheduler init");
for pid in 0..PROCESSUS_COUNT {
unsafe {
PROCESS_TABLE[pid].state = ProcessState::Dead;
}
}
create_processus(idle, "idle");
unsafe {
PROCESS_TABLE[0].state = ProcessState::Active;
}
}
pub fn scheduler(interrupt_state: ExecutionContext) -> usize {
// info!("scheduler");
unsafe {
let prev_pid = ACTIVE_PID;
if PROCESS_TABLE[ACTIVE_PID].state == ProcessState::Active {
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Activable;
}
loop {
if PROCESS_TABLE[ACTIVE_PID].state == ProcessState::Asleep
&& time::elapsed_time_since_startup() > PROCESS_TABLE[ACTIVE_PID].wake_time
{
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Activable;
}
ACTIVE_PID = (ACTIVE_PID + 1) % PROCESSUS_COUNT;
if PROCESS_TABLE[ACTIVE_PID].state == ProcessState::Activable {
break;
}
}
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Active;
PROCESS_TABLE[prev_pid].ctx = interrupt_state;
// PROCESS_TABLE[prev_pid].ctx.t = interrupt_state.t;
// PROCESS_TABLE[prev_pid].ctx.ra = interrupt_state.ra;
context_switch(
PROCESS_TABLE[ACTIVE_PID].entry_point.unwrap(),
&raw mut PROCESS_TABLE[prev_pid].ctx,
&raw mut PROCESS_TABLE[ACTIVE_PID].ctx,
);
prev_pid
}
}
#[unsafe(naked)]
pub extern "C" fn context_switch(
code: extern "C" fn(),
current: *mut ExecutionContext,
next: *mut ExecutionContext,
) {
naked_asm!(
"
// sd ra, 0(a1)
// sd sp, 8(a1)
// sd gp, 16(a1)
// sd tp, 24(a1)
// sd s0, 152(a1)
// sd s1, 160(a1)
// sd s2, 168(a1)
// sd s3, 176(a1)
// sd s4, 184(a1)
// sd s5, 192(a1)
// sd s6, 200(a1)
// sd s7, 208(a1)
// sd s8, 216(a1)
// sd s9, 224(a1)
// sd s10, 232(a1)
// sd s11, 240(a1)
// csrr t0, sepc
// sd t0, 248(a1)
// csrr t0, sstatus
// sd t0, 256(a1)
# Load next execution context
ld t0, 248(a2)
csrw sepc, t0
ld t0, 256(a2)
csrw sstatus, t0
ld ra, 0(a2)
ld sp, 8(a2)
ld gp, 16(a2)
ld tp, 24(a2)
ld a0, 32(a2)
ld a1, 40(a2)
// Skip a2 since it used as a pointer
ld a3, 56(a2)
ld a4, 64(a2)
ld a5, 72(a2)
ld a6, 80(a2)
ld a7, 88(a2)
ld t0, 96(a2)
ld t1, 104(a2)
ld t2, 112(a2)
ld t3, 120(a2)
ld t4, 128(a2)
ld t5, 136(a2)
ld t6, 144(a2)
ld s0, 152(a2)
ld s1, 160(a2)
ld s2, 168(a2)
ld s3, 176(a2)
ld s4, 184(a2)
ld s5, 192(a2)
ld s6, 200(a2)
ld s7, 208(a2)
ld s8, 216(a2)
ld s9, 224(a2)
ld s10, 232(a2)
ld s11, 240(a2)
// Restore a2 at the end
ld a2, 48(a2)
addi sp, sp, 264
sret"
);
}

View File

@@ -1,10 +1,10 @@
use core::time::Duration;
use alloc::format;
use log::info;
use crate::{
set_csr,
vga::{Color, Vga, FONT_WIDTH, WIDTH},
process::ExecutionContext, scheduler::scheduler, set_csr, vga::{Color, FONT_WIDTH, Vga, WIDTH}
};
pub const IRQ_M_TIMER: u8 = 1 << 7;
@@ -30,7 +30,7 @@ pub fn setup_next_timer_interrupt() {
);
}
}
pub fn timer_interrupt() {
pub fn timer_interrupt(interrupt_state: ExecutionContext) -> usize {
let current_time = elapsed_time_since_startup();
let seconds = current_time.as_secs();
let minutes = seconds / 60 % 60;
@@ -46,6 +46,7 @@ pub fn timer_interrupt() {
Color::BLACK,
)
};
scheduler(interrupt_state)
}
pub fn elapsed_time_since_startup() -> Duration {

View File

@@ -5,5 +5,11 @@ pub fn write_char_uart(c: char) {
}
pub fn write_uart<T: AsRef<str>>(print: T) {
print.as_ref().chars().for_each(write_char_uart);
print.as_ref().chars().for_each(|a| {
// Add \r if needed
write_char_uart(a);
if a == '\n' {
write_char_uart('\r');
}
});
}