From 7a25e89d4c27411ed86d857a9e374bc1eaa9743d Mon Sep 17 00:00:00 2001 From: supersurviveur Date: Thu, 22 Jan 2026 18:09:14 +0100 Subject: [PATCH] First commit --- .asm-lsp.toml | 13 +++ .cargo/config.toml | 9 ++ .gdbinit | 1 + .gitignore | 4 + Cargo.toml | 16 ++++ assets/fontplate.png | Bin 0 -> 1664 bytes build.rs | 3 + ilm.ld | 45 ++++++++++ kernel-macros/Cargo.toml | 14 +++ kernel-macros/src/image.rs | 106 +++++++++++++++++++++++ kernel-macros/src/lib.rs | 8 ++ riscv64.json | 22 +++++ rust-toolchain.toml | 2 + src/boot.rs | 11 +++ src/critical_section.rs | 18 ++++ src/io.rs | 61 +++++++++++++ src/main.rs | 42 +++++++++ src/panic_handler.rs | 31 +++++++ src/riscv.rs | 22 +++++ src/uart.rs | 9 ++ src/vga.rs | 172 +++++++++++++++++++++++++++++++++++++ 21 files changed, 609 insertions(+) create mode 100644 .asm-lsp.toml create mode 100644 .cargo/config.toml create mode 100644 .gdbinit create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 assets/fontplate.png create mode 100644 build.rs create mode 100644 ilm.ld create mode 100644 kernel-macros/Cargo.toml create mode 100644 kernel-macros/src/image.rs create mode 100644 kernel-macros/src/lib.rs create mode 100644 riscv64.json create mode 100644 rust-toolchain.toml create mode 100644 src/boot.rs create mode 100644 src/critical_section.rs create mode 100644 src/io.rs create mode 100644 src/main.rs create mode 100644 src/panic_handler.rs create mode 100644 src/riscv.rs create mode 100644 src/uart.rs create mode 100644 src/vga.rs diff --git a/.asm-lsp.toml b/.asm-lsp.toml new file mode 100644 index 0000000..90f7a61 --- /dev/null +++ b/.asm-lsp.toml @@ -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 diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..171e577 --- /dev/null +++ b/.cargo/config.toml @@ -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" diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..24efe1e --- /dev/null +++ b/.gdbinit @@ -0,0 +1 @@ +target remote localhost:1234 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5e60db --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.helix + +**/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9f61050 --- /dev/null +++ b/Cargo.toml @@ -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"] } diff --git a/assets/fontplate.png b/assets/fontplate.png new file mode 100644 index 0000000000000000000000000000000000000000..7120f476040e9611b95f3297aeba5cb4565e3c83 GIT binary patch literal 1664 zcmV-`27md9P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007 zbV*G`2k8q92@ex=(r>r`00qQJL_t(|+U;HKj@%#&+^q8ckDT;RsVwS`8Dj{0w@6iW z2?1klW1E3JUa!~vBFb?b=DYi*nsVD>+DF=9Y5SJnQ4UW5Ji)rI>-B!W|NY;D4EZK{ z7QI8i>FY)GdCGz+tEZ&>yZgS)ajYsmO{`J+^l~DVJb~+(rzu8j!xB(WG0?s>I@#|l z1&Mv%_|xb{42M>~f37LLsNbTF~3nesONB9*DFm;Ysj+l*5viySi-MO zsK)GQ(3Yk1Xtd~l!dh8liugLRk|Xs-h55QJllLC~k$#cYjdFn)rO>RABnXWI-{LSV--)lm9<0>D-__jFYDhjSFxorV`L5S089l~Dbyq2v!6csyYod`Su(;Z z<0KjIddUMTvm=GS2W$4_j4XPTov@;QtA3QnwOkjgEeneh^{u@_F*Y)*!(ejy)MLa? zw!X(7B%9*V&pdB@@#=eu&8FRDd;PpbS))^nkfQ-;Oh@aZ+x;k*DY7yi0~o+rw9NKx zAQ`C&ej1FiyWjC4jmI09xG8Qci8R}Q!WbK<#b zvn?$vxM)1^bkNN1&@ZPiit06-#iuXtt1w55yO-tjN(`0f>a4(X%|O(?wP2yjYK#Pz zJ1E#L2V+paRnm(j53JxkqGKl+I4#X$n2q?L7AUG`7ILN_6B~Gwl!-A=BcTEN(0*A^ zU?qrcg+gce$!3JY_FfxogndhGcIs)IR;E9>5f%X^<#}q!mwwk6q4hY9;~pLKS6P03 zSq1jfc7mC@Y@Fa?fihbC#RxqsK%?`vBh+FhO=T`nX4%~ZQ94S;PEcs%EJfBm`AM#F z8`C>8iKE#D_84NT-*6s1zGal2=1)rBQoB!Qr>szwFsnK%{gjb);n1kN zlD!MkvYsB?*Sg>-LyvchLUwhqa`Yo`ars=kxj|crBK}zXoCSwQp06@QunU9;fG1=u zBfJSf@gosEk?Z2UV_hTdW($-*T7B!zP=@~+jWY@hdYf@ys5gI}Yd`gJ+?6QKUE_}x zWmhyP%jfJBD%OrULeII&tJ+DCt 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 = .; +} diff --git a/kernel-macros/Cargo.toml b/kernel-macros/Cargo.toml new file mode 100644 index 0000000..0856fbc --- /dev/null +++ b/kernel-macros/Cargo.toml @@ -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"] } diff --git a/kernel-macros/src/image.rs b/kernel-macros/src/image.rs new file mode 100644 index 0000000..63106d2 --- /dev/null +++ b/kernel-macros/src/image.rs @@ -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, Vec>, width: usize, height: usize) -> Vec { + 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, 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 { + 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, + ), + syn::Error, +> { + // parse the input into a comma separated list of arguments + let parsed_args = syn::parse::(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::>(); + 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() +} diff --git a/kernel-macros/src/lib.rs b/kernel-macros/src/lib.rs new file mode 100644 index 0000000..ad301a0 --- /dev/null +++ b/kernel-macros/src/lib.rs @@ -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) +} diff --git a/riscv64.json b/riscv64.json new file mode 100644 index 0000000..7d21f3a --- /dev/null +++ b/riscv64.json @@ -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" +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/boot.rs b/src/boot.rs new file mode 100644 index 0000000..d45500f --- /dev/null +++ b/src/boot.rs @@ -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" +); diff --git a/src/critical_section.rs b/src/critical_section.rs new file mode 100644 index 0000000..604626d --- /dev/null +++ b/src/critical_section.rs @@ -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); + } +} diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..7050114 --- /dev/null +++ b/src/io.rs @@ -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!(); + }; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..01b943e --- /dev/null +++ b/src/main.rs @@ -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") } + } +} diff --git a/src/panic_handler.rs b/src/panic_handler.rs new file mode 100644 index 0000000..f16a772 --- /dev/null +++ b/src/panic_handler.rs @@ -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") } + } +} diff --git a/src/riscv.rs b/src/riscv.rs new file mode 100644 index 0000000..1ec6fbf --- /dev/null +++ b/src/riscv.rs @@ -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(); + } +} diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 0000000..40030f1 --- /dev/null +++ b/src/uart.rs @@ -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>(print: T) { + print.as_ref().chars().for_each(write_char_uart); +} diff --git a/src/vga.rs b/src/vga.rs new file mode 100644 index 0000000..becfc5b --- /dev/null +++ b/src/vga.rs @@ -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>(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"};