Try loading code at runtime
This commit is contained in:
49
src/boot.rs
49
src/boot.rs
@@ -1,49 +0,0 @@
|
||||
use core::arch::naked_asm;
|
||||
|
||||
use crate::{
|
||||
clear_csr,
|
||||
interrupt::{setup_machine_trap_handler, setup_supervisor_trap_handler},
|
||||
mret, set_csr, supervisor_mode_entry, write_csr,
|
||||
};
|
||||
|
||||
pub mod sbi;
|
||||
|
||||
#[unsafe(naked)]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn entry() {
|
||||
naked_asm!(
|
||||
"
|
||||
la sp, _heap_end
|
||||
jal machine_mode_entry
|
||||
1:
|
||||
j 1b"
|
||||
)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn machine_mode_entry() {
|
||||
unsafe { setup_machine_trap_handler() };
|
||||
|
||||
// Set supervisor mode
|
||||
set_csr!(mstatus, 1 << 11);
|
||||
clear_csr!(mstatus, 1 << 12);
|
||||
|
||||
write_csr!(mepc, supervisor_mode_entry as *const () as u64);
|
||||
// Allow all R/W/X
|
||||
write_csr!(pmpaddr0, 0xffff_ffff_ffff_ffffu64);
|
||||
// NAPOT
|
||||
write_csr!(pmpcfg0, 0b0000_1111);
|
||||
|
||||
// Desactivate paging
|
||||
write_csr!(satp, 0);
|
||||
|
||||
// Delegate timer interrupt
|
||||
set_csr!(mideleg, 1 << 5);
|
||||
set_csr!(medeleg, 1 << 8);
|
||||
|
||||
unsafe {
|
||||
setup_supervisor_trap_handler();
|
||||
};
|
||||
|
||||
mret!();
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#[non_exhaustive]
|
||||
#[repr(usize)]
|
||||
pub enum EextensionID {
|
||||
Time = 0x54494D45,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[repr(usize)]
|
||||
pub enum TimerFunctionID {
|
||||
SetTimer = 0x0,
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use critical_section::RawRestoreState;
|
||||
|
||||
use crate::riscv::{disable_supervisor_interrupt, get_supervisor_interrupt_state, restore_supervisor_interrupt};
|
||||
|
||||
struct MyCriticalSection;
|
||||
critical_section::set_impl!(MyCriticalSection);
|
||||
|
||||
unsafe impl critical_section::Impl for MyCriticalSection {
|
||||
unsafe fn acquire() -> RawRestoreState {
|
||||
let restore = get_supervisor_interrupt_state();
|
||||
disable_supervisor_interrupt();
|
||||
restore
|
||||
}
|
||||
|
||||
unsafe fn release(token: RawRestoreState) {
|
||||
restore_supervisor_interrupt(token);
|
||||
}
|
||||
}
|
||||
324
src/interrupt.rs
324
src/interrupt.rs
@@ -1,324 +0,0 @@
|
||||
use alloc::str;
|
||||
use log::info;
|
||||
|
||||
use crate::{
|
||||
boot::sbi::{EextensionID, TimerFunctionID},
|
||||
clear_csr, generate_trap_handler,
|
||||
process::{sleep, ExecutionContext},
|
||||
read_csr,
|
||||
riscv::disable_interrupt,
|
||||
scheduler::scheduler,
|
||||
set_csr,
|
||||
syscall::SysCall,
|
||||
time::{setup_next_timer_interrupt, IRQ_M_TIMER},
|
||||
write_csr,
|
||||
};
|
||||
use core::{arch::naked_asm, time::Duration};
|
||||
|
||||
use crate::time::{setup_timer_interrupt, timer_interrupt};
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn machine_trap_handler(
|
||||
mcause: u64,
|
||||
mie: u64,
|
||||
mip: u64,
|
||||
interrupt_state: *const MachineInterruptState,
|
||||
) {
|
||||
let mepc = read_csr!(mepc);
|
||||
let mtval = read_csr!(mtval);
|
||||
if mcause & (1 << 63) == 0 {
|
||||
let message = match mcause & !(1 << 63) {
|
||||
0 => "Instruction address misaligned",
|
||||
1 => "Instruction access fault",
|
||||
2 => "Illegal instruction",
|
||||
3 => "Breakpoint",
|
||||
4 => "Load address misaligned",
|
||||
5 => "Load access fault",
|
||||
6 => "Store/AMO address misaligned",
|
||||
7 => "Store/AMO access fault",
|
||||
8 => "Environment call from U-mode",
|
||||
9 => {
|
||||
// Environment call from S-mode
|
||||
let eid = unsafe { (*interrupt_state).a[7] };
|
||||
let fid = unsafe { (*interrupt_state).a[6] };
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
match eid {
|
||||
c if c == EextensionID::Time as usize => match fid {
|
||||
c if c == TimerFunctionID::SetTimer as usize => {
|
||||
clear_csr!(mip, 1 << 5);
|
||||
// setup_next_timer_interrupt();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => panic!("Unhandled SBI call eid={eid}, fid={fid}"),
|
||||
}
|
||||
|
||||
// Advance mepc to exit the ecall
|
||||
let mepc = read_csr!(mepc);
|
||||
write_csr!(mepc, mepc + 4);
|
||||
return;
|
||||
}
|
||||
11 => "Environment call from M-mode",
|
||||
12 => "Instruction page fault",
|
||||
13 => "Load page fault",
|
||||
15 => "Store/AMO page fault",
|
||||
16 => "Double trap",
|
||||
18 => "Software check",
|
||||
19 => "Hardware error",
|
||||
_ => panic!(
|
||||
"unknown exception, mcause={}, mie={}, mip={}",
|
||||
mcause, mie, mip
|
||||
),
|
||||
};
|
||||
disable_interrupt();
|
||||
panic!("{} at PC=0x{:x}, mtval={}", message, mepc, mtval);
|
||||
} else {
|
||||
#[allow(clippy::single_match)]
|
||||
match mcause & !(1 << 63) {
|
||||
7 => {
|
||||
setup_next_timer_interrupt();
|
||||
set_csr!(mip, 1 << 5);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn supervisor_trap_handler(
|
||||
interrupt_state: *mut ExecutionContext,
|
||||
scause: u64,
|
||||
_sie: u64,
|
||||
_sip: u64,
|
||||
) -> *const ExecutionContext {
|
||||
if scause & (1 << 63) == 0 {
|
||||
#[allow(clippy::single_match)]
|
||||
match scause & !(1 << 63) {
|
||||
8 => {
|
||||
// Environment call from S-mode
|
||||
let syscall_u64: u64 = unsafe { (*interrupt_state).a[0] };
|
||||
let a1: u64 = unsafe { (*interrupt_state).a[1] };
|
||||
let a2: u64 = unsafe { (*interrupt_state).a[2] };
|
||||
let syscall: SysCall = syscall_u64.into();
|
||||
match syscall {
|
||||
SysCall::NanoSleep => sleep(Duration::new(a1, a2 as u32)),
|
||||
SysCall::WriteTemp => {
|
||||
info!("Print from user space : {}", unsafe {
|
||||
str::from_raw_parts(a1 as *const u8, a2 as usize)
|
||||
})
|
||||
}
|
||||
SysCall::Unimplemented => {
|
||||
unimplemented!("Syscall {syscall_u64} is not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
// Advance sepc to exit the ecall
|
||||
unsafe {
|
||||
(*interrupt_state).mepc = (*interrupt_state).mepc.byte_add(4);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
#[allow(clippy::single_match)]
|
||||
match scause & !(1 << 63) {
|
||||
5 => {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
in("a0") 0,
|
||||
in("a6") TimerFunctionID::SetTimer as u64,
|
||||
in("a7") EextensionID::Time as u64,
|
||||
clobber_abi("system")
|
||||
);
|
||||
}
|
||||
timer_interrupt();
|
||||
return scheduler(unsafe { *interrupt_state });
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
interrupt_state
|
||||
}
|
||||
|
||||
pub unsafe fn setup_machine_trap_handler() {
|
||||
write_csr!(mtvec, _machine_mode_trap);
|
||||
set_csr!(mie, IRQ_M_TIMER);
|
||||
}
|
||||
pub unsafe fn setup_supervisor_trap_handler() {
|
||||
write_csr!(stvec, _supervisor_mode_trap);
|
||||
setup_timer_interrupt();
|
||||
}
|
||||
|
||||
#[unsafe(naked)]
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn _machine_mode_trap() {
|
||||
naked_asm!(
|
||||
"
|
||||
addi sp, sp, -128
|
||||
|
||||
# Store the current frame
|
||||
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 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, mcause
|
||||
csrr a1, mie
|
||||
csrr a2, mip
|
||||
mv a3, sp
|
||||
jal machine_trap_handler
|
||||
|
||||
# Restore registers
|
||||
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)
|
||||
|
||||
addi sp, sp, 128
|
||||
mret"
|
||||
)
|
||||
}
|
||||
generate_trap_handler! {
|
||||
_supervisor_mode_trap, supervisor_trap_handler, s
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! generate_trap_handler {
|
||||
($name:ident, $jump_to:ident, $mode:ident) => {
|
||||
#[unsafe(naked)]
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn $name() {
|
||||
naked_asm!(concat!(
|
||||
"
|
||||
mv t0, sp
|
||||
addi sp, sp, -264
|
||||
|
||||
# Store the current frame
|
||||
sd ra, 0(sp)
|
||||
sd t0, 8(sp) // 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)
|
||||
|
||||
mv a0, sp
|
||||
csrr a1, ",
|
||||
stringify!($mode),
|
||||
"cause
|
||||
csrr a2, ",
|
||||
stringify!($mode),
|
||||
"ie
|
||||
csrr a3, ",
|
||||
stringify!($mode),
|
||||
"ip
|
||||
jal ",
|
||||
stringify!($jump_to),
|
||||
"
|
||||
|
||||
# Restore registers
|
||||
ld t0, 248(a0)
|
||||
csrw sepc, t0
|
||||
ld t0, 256(a0)
|
||||
csrw sstatus, t0
|
||||
ld ra, 0(a0)
|
||||
ld sp, 8(a0)
|
||||
ld gp, 16(a0)
|
||||
ld tp, 24(a0)
|
||||
ld a1, 40(a0)
|
||||
ld a2, 48(a0)
|
||||
ld a3, 56(a0)
|
||||
ld a4, 64(a0)
|
||||
ld a5, 72(a0)
|
||||
ld a6, 80(a0)
|
||||
ld a7, 88(a0)
|
||||
ld t0, 96(a0)
|
||||
ld t1, 104(a0)
|
||||
ld t2, 112(a0)
|
||||
ld t3, 120(a0)
|
||||
ld t4, 128(a0)
|
||||
ld t5, 136(a0)
|
||||
ld t6, 144(a0)
|
||||
ld s0, 152(a0)
|
||||
ld s1, 160(a0)
|
||||
ld s2, 168(a0)
|
||||
ld s3, 176(a0)
|
||||
ld s4, 184(a0)
|
||||
ld s5, 192(a0)
|
||||
ld s6, 200(a0)
|
||||
ld s7, 208(a0)
|
||||
ld s8, 216(a0)
|
||||
ld s9, 224(a0)
|
||||
ld s10, 232(a0)
|
||||
ld s11, 240(a0)
|
||||
|
||||
ld a0, 32(a0)
|
||||
|
||||
",
|
||||
stringify!($mode),
|
||||
"ret"
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct MachineInterruptState {
|
||||
pub a: [usize; 8],
|
||||
pub t: [usize; 7],
|
||||
pub ra: usize,
|
||||
}
|
||||
61
src/io.rs
61
src/io.rs
@@ -1,61 +0,0 @@
|
||||
use crate::println;
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use log::{Level, Metadata, Record};
|
||||
use log::{LevelFilter, SetLoggerError};
|
||||
|
||||
use crate::uart::write_uart;
|
||||
|
||||
fn print(content: String) {
|
||||
write_uart(content);
|
||||
}
|
||||
|
||||
struct Logger;
|
||||
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= Level::Info
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
if let Some((file, line)) = record.file().zip(record.line()) {
|
||||
println!(
|
||||
"[{}] at {}:{} - {}",
|
||||
record.level(),
|
||||
file,
|
||||
line,
|
||||
record.args()
|
||||
);
|
||||
} else {
|
||||
println!("[{}] - {}", record.level(), record.args());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
static LOGGER: Logger = Logger;
|
||||
|
||||
pub fn init_log() -> Result<(), SetLoggerError> {
|
||||
log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info))
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($args:expr),*) => {
|
||||
$crate::io::print(format!($($args),*))
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => {
|
||||
$crate::print!("\n\r");
|
||||
};
|
||||
($($args:expr),*) => {
|
||||
$crate::print!($($args),*);
|
||||
$crate::println!();
|
||||
};
|
||||
}
|
||||
69
src/main.rs
69
src/main.rs
@@ -1,69 +0,0 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![allow(static_mut_refs)]
|
||||
#![feature(
|
||||
riscv_ext_intrinsics,
|
||||
const_trait_impl,
|
||||
iter_map_windows,
|
||||
str_from_raw_parts,
|
||||
macro_metavar_expr,
|
||||
macro_metavar_expr_concat
|
||||
)]
|
||||
|
||||
use embedded_alloc::LlffHeap as Heap;
|
||||
use log::info;
|
||||
|
||||
use crate::{
|
||||
io::init_log,
|
||||
process::create_process,
|
||||
riscv::enable_supervisor_interrupt,
|
||||
scheduler::{idle, scheduler_init},
|
||||
tests_fat::MemoryDisk,
|
||||
user::{proc2, test},
|
||||
vga::{Color, Vga},
|
||||
};
|
||||
|
||||
extern crate alloc;
|
||||
mod boot;
|
||||
mod critical_section;
|
||||
mod interrupt;
|
||||
mod io;
|
||||
mod panic_handler;
|
||||
mod process;
|
||||
mod riscv;
|
||||
mod scheduler;
|
||||
mod syscall;
|
||||
mod tests_fat;
|
||||
mod time;
|
||||
mod uart;
|
||||
mod user;
|
||||
mod vga;
|
||||
|
||||
pub const HEAP_SIZE: usize = 4096;
|
||||
#[global_allocator]
|
||||
static HEAP: Heap = Heap::empty();
|
||||
|
||||
// Usize is assumed to be an u64 in the whole kernel
|
||||
const _: () = assert!(size_of::<usize>() == size_of::<u64>());
|
||||
|
||||
#[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_process(test, "proc1");
|
||||
create_process(proc2, "proc2");
|
||||
|
||||
MemoryDisk::new();
|
||||
|
||||
enable_supervisor_interrupt();
|
||||
idle();
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
use core::arch::riscv64::wfi;
|
||||
|
||||
use alloc::{format, string::ToString};
|
||||
use log::error;
|
||||
|
||||
use crate::vga::{Color, Vga, FONT_HEIGHT};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(panic_info: &core::panic::PanicInfo) -> ! {
|
||||
error!("PANIC !");
|
||||
let mut panic_message = panic_info.message().to_string();
|
||||
if let Some(location) = panic_info.location() {
|
||||
panic_message = format!("{panic_message} at {}:{}", location.file(), location.line());
|
||||
}
|
||||
error!("{panic_message}");
|
||||
|
||||
Vga::clear_screen(Color::WHITE);
|
||||
unsafe { Vga::draw_string(0, 0, "PANIC !", Color::BLACK, Color::WHITE) };
|
||||
unsafe {
|
||||
Vga::draw_string(
|
||||
0,
|
||||
FONT_HEIGHT as u16,
|
||||
panic_message,
|
||||
Color::BLACK,
|
||||
Color::WHITE,
|
||||
)
|
||||
};
|
||||
|
||||
loop {
|
||||
unsafe { wfi() }
|
||||
}
|
||||
}
|
||||
107
src/process.rs
107
src/process.rs
@@ -1,107 +0,0 @@
|
||||
use core::{arch::riscv64::wfi, time::Duration};
|
||||
|
||||
use alloc::{format, string::String};
|
||||
|
||||
use crate::{
|
||||
scheduler::{ACTIVE_PID, PROCESS_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 u64,
|
||||
pub sp: *const u64,
|
||||
pub gp: u64,
|
||||
pub tp: u64,
|
||||
pub a: [u64; 8],
|
||||
pub t: [u64; 7],
|
||||
pub s: [u64; 12],
|
||||
pub mepc: *const u64,
|
||||
pub mstatus: u64,
|
||||
}
|
||||
|
||||
pub struct Process {
|
||||
pub pid: i64,
|
||||
pub name: String,
|
||||
pub state: ProcessState,
|
||||
pub entry: Option<fn()>,
|
||||
pub wake_time: Duration,
|
||||
pub ctx: ExecutionContext,
|
||||
pub stack: [u64; 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("stack", &format!("[_; {}]", STACK_SIZE))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_process<T: Into<String>>(code: fn(), name: T) -> i64 {
|
||||
let mut next_pid = 0;
|
||||
while next_pid < PROCESS_COUNT && unsafe { PROCESS_TABLE[next_pid].state != ProcessState::Dead }
|
||||
{
|
||||
next_pid += 1;
|
||||
}
|
||||
|
||||
if next_pid >= PROCESS_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 = Some(code);
|
||||
PROCESS_TABLE[next_pid].ctx.a[0] =
|
||||
PROCESS_TABLE[next_pid].entry.as_ref().unwrap_unchecked() as *const fn() as u64;
|
||||
PROCESS_TABLE[next_pid].ctx.mepc = process_launcher as *const _;
|
||||
PROCESS_TABLE[next_pid].ctx.mstatus = 1 << 1 | 1 << 5;
|
||||
PROCESS_TABLE[next_pid].ctx.sp = &raw const PROCESS_TABLE[next_pid].stack[STACK_SIZE - 1];
|
||||
}
|
||||
|
||||
next_pid as i64
|
||||
}
|
||||
|
||||
extern "C" fn process_launcher(code: *const fn()) {
|
||||
unsafe { (*code)() };
|
||||
terminate_process();
|
||||
}
|
||||
|
||||
fn terminate_process() {
|
||||
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();
|
||||
}
|
||||
101
src/riscv.rs
101
src/riscv.rs
@@ -1,101 +0,0 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use core::arch::naked_asm;
|
||||
|
||||
use crate::clear_csr;
|
||||
use crate::read_csr;
|
||||
use crate::set_csr;
|
||||
|
||||
pub struct MStatus;
|
||||
pub struct SStatus;
|
||||
|
||||
impl MStatus {
|
||||
pub const MIE: usize = 1 << 3;
|
||||
pub const MPIE: usize = 1 << 7;
|
||||
}
|
||||
impl SStatus {
|
||||
pub const SIE: usize = 1 << 1;
|
||||
pub const SPIE: usize = 1 << 5;
|
||||
}
|
||||
|
||||
pub fn get_interrupt_state() -> bool {
|
||||
(read_csr!(mstatus) & MStatus::MIE as u64) != 0
|
||||
}
|
||||
pub fn get_supervisor_interrupt_state() -> bool {
|
||||
(read_csr!(sstatus) & SStatus::SIE as u64) != 0
|
||||
}
|
||||
pub fn enable_interrupt() {
|
||||
set_csr!(mstatus, MStatus::MIE);
|
||||
}
|
||||
pub fn enable_supervisor_interrupt() {
|
||||
set_csr!(sstatus, SStatus::SIE);
|
||||
}
|
||||
pub fn disable_interrupt() {
|
||||
clear_csr!(mstatus, MStatus::MIE);
|
||||
}
|
||||
pub fn disable_supervisor_interrupt() {
|
||||
clear_csr!(sstatus, SStatus::SIE);
|
||||
}
|
||||
pub fn restore_interrupt(previous_state: bool) {
|
||||
if previous_state {
|
||||
enable_interrupt();
|
||||
} else {
|
||||
disable_interrupt();
|
||||
}
|
||||
}
|
||||
pub fn restore_supervisor_interrupt(previous_state: bool) {
|
||||
if previous_state {
|
||||
enable_supervisor_interrupt();
|
||||
} else {
|
||||
disable_supervisor_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! read_csr {
|
||||
($name:ident) => {{
|
||||
let res: u64;
|
||||
unsafe { core::arch::asm!(concat!("csrr {}, ", stringify!($name)), out(reg) res, options(nomem, nostack)) };
|
||||
res
|
||||
}};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! write_csr {
|
||||
($name:ident, $value:expr) => {
|
||||
let val = $value;
|
||||
unsafe { core::arch::asm!(concat!("csrw ", stringify!($name), ", {}"), in(reg) val, options(nomem, nostack)) };
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! set_csr {
|
||||
($name:ident, $value:expr) => {
|
||||
let val = $value;
|
||||
unsafe { core::arch::asm!(concat!("csrs ", stringify!($name), ", {}"), in(reg) val, options(nomem, nostack)) };
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! clear_csr {
|
||||
($name:ident, $value:expr) => {
|
||||
let val = $value;
|
||||
unsafe { core::arch::asm!(concat!("csrc ", stringify!($name), ", {}"), in(reg) val, options(nomem, nostack)) };
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! mret {
|
||||
() => {
|
||||
unsafe { core::arch::asm!("mret", options(noreturn)) }
|
||||
};
|
||||
}
|
||||
|
||||
#[unsafe(naked)]
|
||||
pub extern "C" fn exit_qemu() {
|
||||
naked_asm!(
|
||||
"
|
||||
li a0, 0x100000
|
||||
li a1, 0x5555
|
||||
sw a1, 0(a0)
|
||||
wfi
|
||||
"
|
||||
)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
use core::{arch::riscv64::wfi, array, cell::LazyCell, time::Duration};
|
||||
|
||||
use alloc::string::String;
|
||||
use log::info;
|
||||
|
||||
use crate::{
|
||||
process::{create_process, ExecutionContext, Process, ProcessState},
|
||||
time,
|
||||
};
|
||||
|
||||
pub const PROCESS_COUNT: usize = 16;
|
||||
|
||||
pub static mut ACTIVE_PID: usize = 0;
|
||||
pub static mut PROCESS_TABLE: LazyCell<[Process; PROCESS_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: core::ptr::null(),
|
||||
mstatus: 0,
|
||||
},
|
||||
stack: [0; _],
|
||||
entry: None,
|
||||
})
|
||||
});
|
||||
|
||||
pub fn idle() {
|
||||
loop {
|
||||
// write_string_temp("idle");
|
||||
// info!("idle");
|
||||
unsafe {
|
||||
wfi();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scheduler_init() {
|
||||
info!("scheduler init");
|
||||
for pid in 0..PROCESS_COUNT {
|
||||
unsafe {
|
||||
PROCESS_TABLE[pid].state = ProcessState::Dead;
|
||||
}
|
||||
}
|
||||
|
||||
create_process(idle, "idle");
|
||||
unsafe {
|
||||
PROCESS_TABLE[0].state = ProcessState::Active;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scheduler(interrupt_state: ExecutionContext) -> *const ExecutionContext {
|
||||
// info!("scheduler");
|
||||
unsafe {
|
||||
let prev_pid = ACTIVE_PID;
|
||||
PROCESS_TABLE[prev_pid].ctx = interrupt_state;
|
||||
|
||||
if PROCESS_TABLE[prev_pid].state == ProcessState::Active {
|
||||
PROCESS_TABLE[prev_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) % PROCESS_COUNT;
|
||||
if PROCESS_TABLE[ACTIVE_PID].state == ProcessState::Activable {
|
||||
break;
|
||||
}
|
||||
}
|
||||
PROCESS_TABLE[ACTIVE_PID].state = ProcessState::Active;
|
||||
|
||||
&raw const PROCESS_TABLE[ACTIVE_PID].ctx
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
use core::time::Duration;
|
||||
|
||||
#[repr(u64)]
|
||||
pub enum SysCall {
|
||||
NanoSleep = 101,
|
||||
WriteTemp = 999,
|
||||
Unimplemented = 1 << 31,
|
||||
}
|
||||
|
||||
impl From<u64> for SysCall {
|
||||
fn from(value: u64) -> Self {
|
||||
match value {
|
||||
101 => SysCall::NanoSleep,
|
||||
999 => SysCall::WriteTemp,
|
||||
_ => SysCall::Unimplemented,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn _syscall(
|
||||
syscall: SysCall,
|
||||
mut a1: u64,
|
||||
mut a2: u64,
|
||||
mut a3: u64,
|
||||
mut a4: u64,
|
||||
mut a5: u64,
|
||||
mut a6: u64,
|
||||
mut a7: u64,
|
||||
) -> (u64, u64, u64, u64, u64, u64, u64, u64) {
|
||||
let mut a0 = syscall as u64;
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
inlateout("a0") a0,
|
||||
inlateout("a1") a1,
|
||||
inlateout("a2") a2,
|
||||
inlateout("a3") a3,
|
||||
inlateout("a4") a4,
|
||||
inlateout("a5") a5,
|
||||
inlateout("a6") a6,
|
||||
inlateout("a7") a7,
|
||||
clobber_abi("system")
|
||||
);
|
||||
}
|
||||
(a0, a1, a2, a3, a4, a5, a6, a7)
|
||||
}
|
||||
|
||||
macro_rules! syscall {
|
||||
($syscall:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr, $a7:expr) => {
|
||||
_syscall($syscall, $a1, $a2, $a3, $a4, $a5, $a6, $a7)
|
||||
};
|
||||
($syscall:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr) => {
|
||||
syscall!($syscall, $a1, $a2, $a3, $a4, $a5, $a6, 0)
|
||||
};
|
||||
($syscall:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr) => {
|
||||
syscall!($syscall, $a1, $a2, $a3, $a4, $a5, 0)
|
||||
};
|
||||
($syscall:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {
|
||||
syscall!($syscall, $a1, $a2, $a3, $a4, 0)
|
||||
};
|
||||
($syscall:expr, $a1:expr, $a2:expr, $a3:expr) => {
|
||||
syscall!($syscall, $a1, $a2, $a3, 0)
|
||||
};
|
||||
($syscall:expr, $a1:expr, $a2:expr) => {
|
||||
syscall!($syscall, $a1, $a2, 0)
|
||||
};
|
||||
($syscall:expr, $a1:expr) => {
|
||||
syscall!($syscall, $a1, 0)
|
||||
};
|
||||
($syscall:expr) => {
|
||||
syscall!($syscall, 0)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sleep(duration: Duration) {
|
||||
unsafe {
|
||||
let (duration_secs, duration_nanos) = (duration.as_secs(), duration.subsec_nanos() as u64);
|
||||
syscall!(SysCall::NanoSleep, duration_secs, duration_nanos);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_string_temp(content: &'static str) {
|
||||
unsafe {
|
||||
syscall!(
|
||||
SysCall::WriteTemp,
|
||||
content.as_ptr() as u64,
|
||||
content.len() as u64
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
use core::str;
|
||||
use log::info;
|
||||
|
||||
const DISK_ADDR: usize = 0x9000_0000;
|
||||
const DISK_SIZE: usize = 16 * 1024 * 1024; // 16MB
|
||||
|
||||
pub struct MemoryDisk {}
|
||||
|
||||
impl MemoryDisk {
|
||||
pub fn new() {
|
||||
let test = unsafe { str::from_raw_parts(DISK_ADDR as *const u8, 13) };
|
||||
info!("{}", test);
|
||||
}
|
||||
}
|
||||
66
src/time.rs
66
src/time.rs
@@ -1,66 +0,0 @@
|
||||
use core::time::Duration;
|
||||
|
||||
use alloc::format;
|
||||
|
||||
use crate::{
|
||||
set_csr,
|
||||
vga::{Color, Vga, FONT_WIDTH, WIDTH},
|
||||
};
|
||||
|
||||
pub const IRQ_M_TIMER: u8 = 1 << 7;
|
||||
pub const IRQ_S_TIMER: u8 = 1 << 5;
|
||||
const CLINT_TIMER_CMP: *mut u64 = 0x02004000 as *mut u64;
|
||||
const CLINT_TIMER: *const u64 = 0x0200bff8 as *const u64;
|
||||
const TIMER_FREQUENCY: u64 = 10000000; // 10MHz
|
||||
const INTERRUPT_FREQUENCY: u64 = 20; // 20Hz
|
||||
|
||||
static mut START_TIME: Instant = Instant(0);
|
||||
|
||||
pub fn setup_timer_interrupt() {
|
||||
unsafe { START_TIME = Instant::now() };
|
||||
set_csr!(sie, IRQ_S_TIMER);
|
||||
setup_next_timer_interrupt();
|
||||
}
|
||||
|
||||
pub fn setup_next_timer_interrupt() {
|
||||
unsafe {
|
||||
core::ptr::write_volatile(
|
||||
CLINT_TIMER_CMP,
|
||||
Instant::now().0 + TIMER_FREQUENCY / INTERRUPT_FREQUENCY,
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn timer_interrupt() {
|
||||
let current_time = elapsed_time_since_startup();
|
||||
let seconds = current_time.as_secs();
|
||||
let minutes = seconds / 60 % 60;
|
||||
let hours = seconds / 3600 % 60;
|
||||
let seconds = seconds % 60;
|
||||
let formated_time = format!("{:02}:{:02}:{:02}", hours, minutes, seconds);
|
||||
unsafe {
|
||||
Vga::draw_string(
|
||||
(WIDTH - formated_time.len() * FONT_WIDTH) as u16,
|
||||
0,
|
||||
formated_time,
|
||||
Color::WHITE,
|
||||
Color::BLACK,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn elapsed_time_since_startup() -> Duration {
|
||||
unsafe { START_TIME.elapsed() }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Instant(u64);
|
||||
|
||||
impl Instant {
|
||||
pub fn now() -> Self {
|
||||
Instant(unsafe { core::ptr::read_volatile(CLINT_TIMER) })
|
||||
}
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
let now = Self::now();
|
||||
Duration::from_nanos((now.0 - self.0) * (1_000_000_000 / TIMER_FREQUENCY))
|
||||
}
|
||||
}
|
||||
15
src/uart.rs
15
src/uart.rs
@@ -1,15 +0,0 @@
|
||||
const UART_BASE: *mut u8 = 0x10000000 as *mut _;
|
||||
pub fn write_char_uart(c: char) {
|
||||
while unsafe { core::ptr::read_volatile(UART_BASE.byte_add(0x5)) } >> 5 & 1 == 0 {}
|
||||
unsafe { core::ptr::write_volatile(UART_BASE, c as u8) };
|
||||
}
|
||||
|
||||
pub fn write_uart<T: AsRef<str>>(print: T) {
|
||||
print.as_ref().chars().for_each(|a| {
|
||||
// Add \r if needed
|
||||
write_char_uart(a);
|
||||
if a == '\n' {
|
||||
write_char_uart('\r');
|
||||
}
|
||||
});
|
||||
}
|
||||
17
src/user.rs
17
src/user.rs
@@ -1,17 +0,0 @@
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::syscall::{sleep, write_string_temp};
|
||||
|
||||
pub fn test() {
|
||||
loop {
|
||||
write_string_temp("test");
|
||||
sleep(Duration::new(2, 0));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proc2() {
|
||||
loop {
|
||||
write_string_temp("proc2");
|
||||
sleep(Duration::new(3, 0));
|
||||
}
|
||||
}
|
||||
149
src/vga.rs
149
src/vga.rs
@@ -1,149 +0,0 @@
|
||||
use kernel_macros::include_font_plate;
|
||||
use log::info;
|
||||
|
||||
const PCI_ECAM_BASE_ADDRESS: *mut u32 = 0x30000000 as *mut _;
|
||||
const BOCHS_DISPLAY_BASE_ADDRESS: *mut u32 = 0x50000000 as *mut _;
|
||||
const BOCHS_CONFIG_BASE_ADDRESS: *mut u16 = 0x40000000 as *mut _;
|
||||
pub const VGA_ADDRESS: *mut Color = BOCHS_DISPLAY_BASE_ADDRESS as *mut Color;
|
||||
pub const WIDTH: usize = 1600;
|
||||
pub const HEIGHT: usize = 900;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Color(u32);
|
||||
|
||||
#[allow(unused)]
|
||||
impl Color {
|
||||
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Self((r as u32) << 16 | (g as u32) << 8 | b as u32)
|
||||
}
|
||||
|
||||
pub const WHITE: Color = Color::from_rgb(255, 255, 255);
|
||||
pub const GRAY: Color = Color::from_rgb(128, 128, 128);
|
||||
pub const LIGHT_GRAY: Color = Color::from_rgb(128, 64, 64);
|
||||
pub const BLACK: Color = Color::from_rgb(0, 0, 0);
|
||||
pub const RED: Color = Color::from_rgb(255, 0, 0);
|
||||
pub const GREEN: Color = Color::from_rgb(0, 255, 0);
|
||||
pub const BLUE: Color = Color::from_rgb(0, 0, 255);
|
||||
}
|
||||
|
||||
pub struct Vga {}
|
||||
|
||||
impl Vga {
|
||||
pub unsafe fn init() {
|
||||
for i in 0..32 {
|
||||
let addr = PCI_ECAM_BASE_ADDRESS.wrapping_byte_add(i << 11);
|
||||
let header = unsafe { core::ptr::read_volatile(addr) };
|
||||
if header >> 16 == 0x1111 && header & 0xFFFF == 0x1234 {
|
||||
info!("VGA Bochs PCI found");
|
||||
unsafe {
|
||||
let mut command = core::ptr::read_volatile(addr.byte_add(0x04));
|
||||
command |= 0b111;
|
||||
core::ptr::write_volatile(addr.byte_add(0x04), command);
|
||||
|
||||
core::ptr::write_volatile(
|
||||
addr.byte_add(0x10),
|
||||
BOCHS_DISPLAY_BASE_ADDRESS as u32,
|
||||
);
|
||||
core::ptr::write_volatile(addr.byte_add(0x18), BOCHS_CONFIG_BASE_ADDRESS as u32)
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x508), 0x0);
|
||||
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x502), WIDTH as u16);
|
||||
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x504), HEIGHT as u16);
|
||||
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x506), 32);
|
||||
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x50a), 0x0);
|
||||
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x510), 0x0);
|
||||
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x512), 0x0);
|
||||
core::ptr::write_volatile(BOCHS_CONFIG_BASE_ADDRESS.byte_add(0x508), 0x41)
|
||||
};
|
||||
|
||||
Vga::clear_screen(Color::BLACK);
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT`
|
||||
pub unsafe fn write_pixel_unsafe(x: u16, y: u16, color: Color) {
|
||||
let pixel_index = x as usize + y as usize * WIDTH;
|
||||
|
||||
unsafe { *VGA_ADDRESS.add(pixel_index) = color }
|
||||
}
|
||||
|
||||
pub unsafe fn draw_char_bg(x: u16, y: u16, c: char, color: Color, bg_color: Color) {
|
||||
let c = if (c as u8 > b'~') || ((c as u8) < b' ') {
|
||||
b'/' - b' '
|
||||
} else {
|
||||
c as u8 - b' '
|
||||
};
|
||||
|
||||
// Get char position within font plate
|
||||
let char_x = (c as usize % 32) * FONT_WIDTH;
|
||||
let char_y = (c as usize / 32) * FONT_HEIGHT;
|
||||
|
||||
for i in 0..(FONT_WIDTH as u16) {
|
||||
for j in 0..(FONT_HEIGHT as u16) {
|
||||
let xx = x + i;
|
||||
let yy = y + j;
|
||||
|
||||
if xx < (WIDTH as u16) && yy < (HEIGHT as u16) {
|
||||
if unsafe { Self::font_plate_index(char_x as u16 + i, char_y as u16 + j) } {
|
||||
unsafe { Self::write_pixel_unsafe(xx, yy, color) }
|
||||
} else {
|
||||
unsafe { Self::write_pixel_unsafe(xx, yy, bg_color) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The text must have a length that can fit within a `u16`
|
||||
pub unsafe fn draw_string<T: AsRef<str>>(
|
||||
x: u16,
|
||||
mut y: u16,
|
||||
str: T,
|
||||
color: Color,
|
||||
bg_color: Color,
|
||||
) {
|
||||
let mut current_x = x;
|
||||
str.as_ref().chars().for_each(|c| unsafe {
|
||||
match c {
|
||||
'\n' => {
|
||||
current_x = x;
|
||||
y += FONT_HEIGHT as u16;
|
||||
}
|
||||
'\r' => {
|
||||
current_x = x;
|
||||
}
|
||||
c => {
|
||||
Self::draw_char_bg(current_x, y, c, color, bg_color);
|
||||
current_x += FONT_WIDTH as u16;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clear_screen(color: Color) {
|
||||
for i in 0..WIDTH * HEIGHT {
|
||||
unsafe { *VGA_ADDRESS.add(i) = color }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn font_plate_index(x: u16, y: u16) -> bool {
|
||||
let pixel_index = (y as usize) * FONTPLATE_WIDTH + (x as usize);
|
||||
let byte_index = pixel_index / 8;
|
||||
let bit_index = pixel_index % 8;
|
||||
|
||||
(FONTPLATE[byte_index] >> bit_index) & 0b1 == 0b1
|
||||
}
|
||||
}
|
||||
|
||||
pub const FONT_WIDTH: usize = 6;
|
||||
pub const FONT_HEIGHT: usize = 13;
|
||||
pub const FONTPLATE_WIDTH: usize = 32 * FONT_WIDTH;
|
||||
pub const FONTPLATE_HEIGHT: usize = 3 * FONT_HEIGHT;
|
||||
pub const FONTPLATE_SIZE: usize = FONTPLATE_WIDTH * FONTPLATE_HEIGHT / 8;
|
||||
pub static FONTPLATE: [u8; FONTPLATE_SIZE] = include_font_plate! {"assets/fontplate.png"};
|
||||
Reference in New Issue
Block a user