First commit
This commit is contained in:
13
.asm-lsp.toml
Normal file
13
.asm-lsp.toml
Normal 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
9
.cargo/config.toml
Normal 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"
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.helix
|
||||||
|
|
||||||
|
**/target
|
||||||
|
Cargo.lock
|
||||||
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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
BIN
assets/fontplate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
3
build.rs
Normal file
3
build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("cargo::rustc-link-arg=-Tilm.ld");
|
||||||
|
}
|
||||||
45
ilm.ld
Normal file
45
ilm.ld
Normal 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
14
kernel-macros/Cargo.toml
Normal 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
106
kernel-macros/src/image.rs
Normal 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
8
kernel-macros/src/lib.rs
Normal 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
22
riscv64.json
Normal 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
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
11
src/boot.rs
Normal file
11
src/boot.rs
Normal 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
18
src/critical_section.rs
Normal 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
61
src/io.rs
Normal 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
42
src/main.rs
Normal 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
31
src/panic_handler.rs
Normal 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
22
src/riscv.rs
Normal 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
9
src/uart.rs
Normal 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
172
src/vga.rs
Normal 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"};
|
||||||
Reference in New Issue
Block a user