First commit

This commit is contained in:
2026-01-22 18:09:14 +01:00
committed by Julien THILLARD
commit 7a25e89d4c
21 changed files with 609 additions and 0 deletions

13
.asm-lsp.toml Normal file
View File

@@ -0,0 +1,13 @@
[default_config]
version = "0.10.1"
assembler = "gas"
instruction_set = "riscv"
[default_config.opts]
compiler = "riscv64-unknown-elf-gcc"
compiler_args = [
"-march=rv64ima_zicsr",
"-mabi=lp64"
]
diagnostics = true
default_diagnostics = true

9
.cargo/config.toml Normal file
View File

@@ -0,0 +1,9 @@
[build]
target = "riscv64.json"
[unstable]
build-std = ["core", "compiler_builtins", "alloc"]
build-std-features = ["compiler-builtins-mem"]
[target.riscv64]
runner = "qemu-system-riscv64 -machine virt -device bochs-display -bios none -m 128M -kernel"

1
.gdbinit Normal file
View File

@@ -0,0 +1 @@
target remote localhost:1234

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.helix
**/target
Cargo.lock

16
Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "kernel-rust"
version = "0.1.0"
edition = "2024"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
[dependencies]
embedded-alloc = "0.7"
kernel-macros = { path = "kernel-macros" }
log = "0.4"
critical-section = { version = "1", features = ["restore-state-bool"] }

BIN
assets/fontplate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

3
build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("cargo::rustc-link-arg=-Tilm.ld");
}

45
ilm.ld Normal file
View File

@@ -0,0 +1,45 @@
/*
* ld directives the for barmetal RISCV
*/
OUTPUT_ARCH( "riscv" )
MEMORY
{
ram (wxa) : ORIGIN = 0x80000000, LENGTH = 128M
}
SECTIONS {
/* The kernel starts at 0x80000000 */
. = 0x80000000;
.text : {
ENTRY(entry)
KEEP(*(.text.entry))
*(.text.init) *(.text) *(.text.*)
_etext = .;
} > ram
.data : {
*(.sdata) *(.sdata.*)
*(.fini)
*(.anno)
*(.rodata) *(.rodata.*)
*(__ex_table)
*(.data) *(.data.*)
_edata = .;
} > ram
.bss : {
/* On veut un alignement sur 8 octets */
. = ALIGN(8);
__bss_start = .;
*(.sbss) *(.sbss.*)
*(.bss) *(.bss.*)
*(scommon) *(COMMON)
. = ALIGN(8);
__bss_end = .;
PROVIDE(_heap_start = __bss_end + 8);
PROVIDE(_heap_end = ORIGIN(ram) + LENGTH(ram));
} > ram
_end = .;
}

14
kernel-macros/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "kernel-macros"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
image = "0.25"
regex = "1"
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }

106
kernel-macros/src/image.rs Normal file
View File

@@ -0,0 +1,106 @@
use image::{ImageBuffer, Luma};
use proc_macro::{Span, TokenStream};
use quote::quote;
use regex::Regex;
use syn::parse::Parse;
fn remove_non_alphanumeric(input: &str) -> String {
let re = Regex::new(r"[^a-zA-Z0-9_]+").unwrap();
re.replace_all(input, "").to_string()
}
fn to_format(img: ImageBuffer<Luma<u8>, Vec<u8>>, width: usize, height: usize) -> Vec<u8> {
let mut output = Vec::new();
let mut bit: u8 = 0;
let mut byte: u8 = 0;
for y in 0..height {
for x in 0..width {
let pixel = img.get_pixel(x as u32, y as u32)[0];
if pixel >= 127 {
byte |= 1 << bit;
}
bit += 1;
if bit == 8 {
output.push(byte);
byte = 0;
bit = 0;
}
}
}
if bit != 0 {
output.push(byte);
}
output
}
fn path_to_image(path: &str) -> (Vec<u8>, String, usize, usize) {
let img = match image::open(path) {
Ok(img) => img.to_luma8(),
Err(e) => panic!("failed to open image {}: {}", path, e),
};
let width = img.width() as usize;
let height = img.height() as usize;
let bytes = to_format(img, width, height);
let path = path
.split('/')
.next_back()
.expect("failed to get last part of path");
let split: Vec<_> = path.split('.').collect();
let name = remove_non_alphanumeric(&split[0..split.len() - 1].join(".")).to_uppercase();
(bytes, name, width, height)
}
struct ParsedArgs {
path: String,
}
impl Parse for ParsedArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let path: syn::LitStr = input.parse()?;
let path = path.value();
Ok(ParsedArgs { path })
}
}
pub fn parse_image(
input: TokenStream,
) -> Result<
(
u8,
u8,
proc_macro2::Ident,
usize,
Vec<proc_macro2::TokenStream>,
),
syn::Error,
> {
// parse the input into a comma separated list of arguments
let parsed_args = syn::parse::<ParsedArgs>(input)?;
// let parsed_args = parse_macro_input!(input as ParsedArgs);
let (bytes, name, width, height) = path_to_image(&parsed_args.path);
let width = width as u8;
let height = height as u8;
let byte_array = bytes.as_slice();
let byte_count = byte_array.len();
let name_ident = syn::Ident::new(&name, Span::call_site().into());
let byte_tokens = bytes.iter().map(|b| quote! { #b }).collect::<Vec<_>>();
Ok((width, height, name_ident, byte_count, byte_tokens))
}
pub fn include_font_plate_impl(input: TokenStream) -> TokenStream {
let (_, _, _, _, byte_tokens) = parse_image(input).unwrap();
let output = quote! {
[#(#byte_tokens),*]
};
output.into()
}

8
kernel-macros/src/lib.rs Normal file
View File

@@ -0,0 +1,8 @@
mod image;
use proc_macro::TokenStream;
#[proc_macro]
pub fn include_font_plate(input: TokenStream) -> TokenStream {
image::include_font_plate_impl(input)
}

22
riscv64.json Normal file
View File

@@ -0,0 +1,22 @@
{
"llvm-target": "riscv64",
"llvm-abiname": "lp64",
"abi": "lp64",
"data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128",
"target-endian": "little",
"target-pointer-width": 64,
"arch": "riscv64",
"os": "none",
"vendor": "unknown",
"env": "",
"features": "+i,+m,+a,+zicsr",
"linker": "ld.lld",
"linker-flavor": "ld",
"executables": true,
"panic-strategy": "abort",
"relocation-model": "static",
"disable-redzone": true,
"emit-debug-gdb-scripts": false,
"eh-frame-header": false,
"code-model": "medium"
}

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

11
src/boot.rs Normal file
View File

@@ -0,0 +1,11 @@
use core::arch::global_asm;
global_asm!(
".section .text.entry
.globl entry
entry:
la sp, _heap_end
jal main
loop:
j loop"
);

18
src/critical_section.rs Normal file
View File

@@ -0,0 +1,18 @@
use critical_section::RawRestoreState;
use crate::riscv::{disable_interrupt, get_interrupt_state, restore_interrupt};
struct MyCriticalSection;
critical_section::set_impl!(MyCriticalSection);
unsafe impl critical_section::Impl for MyCriticalSection {
unsafe fn acquire() -> RawRestoreState {
let restore = get_interrupt_state();
disable_interrupt();
restore
}
unsafe fn release(token: RawRestoreState) {
restore_interrupt(token);
}
}

61
src/io.rs Normal file
View File

@@ -0,0 +1,61 @@
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!();
};
}

42
src/main.rs Normal file
View File

@@ -0,0 +1,42 @@
#![no_std]
#![no_main]
use core::arch::asm;
use embedded_alloc::LlffHeap as Heap;
use log::info;
use crate::{
io::init_log,
vga::{Color, Vga},
};
extern crate alloc;
mod boot;
mod critical_section;
mod io;
mod panic_handler;
mod riscv;
mod uart;
mod vga;
pub const HEAP_SIZE: usize = 40960;
#[global_allocator]
static HEAP: Heap = Heap::empty();
#[unsafe(no_mangle)]
pub extern "C" fn main() {
unsafe {
embedded_alloc::init!(HEAP, HEAP_SIZE);
}
init_log().unwrap();
Vga::init();
info!("Hello World !");
unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE) };
loop {
unsafe { asm!("wfi") }
}
}

31
src/panic_handler.rs Normal file
View File

@@ -0,0 +1,31 @@
use core::arch::asm;
use alloc::{
format,
string::{String, ToString},
};
use log::error;
use crate::vga::{Color, FONT_HEIGHT, Vga};
#[panic_handler]
fn panic(panic_info: &core::panic::PanicInfo) -> ! {
error!("PANIC !");
let mut panic_message = if let Some(message) = panic_info.message().as_str() {
message.to_string()
} else {
String::new()
};
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) };
unsafe { Vga::draw_string(0, FONT_HEIGHT as u16, panic_message, Color::BLACK) };
loop {
unsafe { asm!("wfi") }
}
}

22
src/riscv.rs Normal file
View File

@@ -0,0 +1,22 @@
use core::arch::asm;
const MSTATUS_MIE: usize = 0x8;
pub fn get_interrupt_state() -> bool {
let res: u64;
unsafe { asm!("csrr {}, mstatus", out(reg) res) };
(res & MSTATUS_MIE as u64) != 0
}
pub fn enable_interrupt() {
unsafe { asm!("csrs mstatus, {}", in(reg) MSTATUS_MIE) };
}
pub fn disable_interrupt() {
unsafe { asm!("csrc mstatus, {}", in(reg) MSTATUS_MIE) };
}
pub fn restore_interrupt(previous_state: bool) {
if previous_state {
enable_interrupt();
} else {
disable_interrupt();
}
}

9
src/uart.rs Normal file
View File

@@ -0,0 +1,9 @@
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(write_char_uart);
}

172
src/vga.rs Normal file
View File

@@ -0,0 +1,172 @@
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 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 }
}
/// # Safety
/// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT`
pub unsafe fn draw_char(x: u16, y: u16, c: char, 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)
&& unsafe { Self::font_plate_index(char_x as u16 + i, char_y as u16 + j) }
{
unsafe { Self::write_pixel_unsafe(xx, yy, color) }
}
}
}
}
#[allow(unused)]
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) {
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(current_x, y, c, 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 const FONTPLATE: [u8; FONTPLATE_SIZE] = include_font_plate! {"assets/fontplate.png"};