working on kernel
This commit is contained in:
+14
-7
@@ -1,16 +1,23 @@
|
||||
[package]
|
||||
name = "kernel"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
limine = "0.3.1"
|
||||
spin = "0.9.8"
|
||||
bitflags = "2.4.0"
|
||||
lazy_static = { version = "1.5.0", features = ["spin", "spin_no_std"] }
|
||||
spin = { version = "0.9.8", features = ["lazy"] }
|
||||
bitflags = { version = "2.4.0", default-features = false }
|
||||
lazy_static = { version = "1.5.0", features = ["spin_no_std"] }
|
||||
x86_64 = { version = "0.15.1" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[[bin]]
|
||||
name = "kernel"
|
||||
path = "src/main.rs"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
# setup lib.rs
|
||||
[lib]
|
||||
name = "GoofyAhhOS"
|
||||
path = "src/lib.rs"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#![no_std]
|
||||
#![cfg_attr(test, no_main)]
|
||||
#![feature(custom_test_frameworks)]
|
||||
#![test_runner(crate::tests::test_runner)]
|
||||
#![reexport_test_harness_main = "test_main"]
|
||||
#![feature(abi_x86_interrupt)]
|
||||
|
||||
// #[cfg(test)]
|
||||
// use limine::BaseRevision;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
pub mod tests;
|
||||
pub mod sys;
|
||||
pub mod usr;
|
||||
|
||||
|
||||
pub use sys::kernel::drivers::framebuffer::textwriter::{
|
||||
_print,
|
||||
_printerr,
|
||||
_log,
|
||||
};
|
||||
|
||||
pub use sys::kernel::drivers::serial::{
|
||||
_serial_write,
|
||||
serial_read
|
||||
};
|
||||
|
||||
pub fn init() {
|
||||
sys::kernel::cpu::interrupts::init();
|
||||
}
|
||||
|
||||
pub fn hcf() -> ! {
|
||||
loop {
|
||||
unsafe { core::arch::asm!("cli; hlt") }
|
||||
}
|
||||
}
|
||||
|
||||
// Called on panic
|
||||
#[cfg(not(test))]
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
println!("{}", _info);
|
||||
hcf()
|
||||
}
|
||||
|
||||
// code for testing etc.
|
||||
|
||||
#[cfg(test)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kmain() -> ! {
|
||||
// Set up the base revision for Limine
|
||||
// static BASE_REVISION: BaseRevision = BaseRevision::new();
|
||||
|
||||
init();
|
||||
test_main();
|
||||
|
||||
serial_println!("All tests passed! exiting.");
|
||||
|
||||
sys::qemu::exit_success();
|
||||
}
|
||||
|
||||
|
||||
+31
-26
@@ -1,40 +1,45 @@
|
||||
#![no_std] // Don't link the Rust standard library
|
||||
#![no_main] // Disable all Rust-level entry points
|
||||
#![feature(custom_test_frameworks)]
|
||||
#![test_runner(GoofyAhhOS::tests::test_runner)]
|
||||
#![reexport_test_harness_main = "test_main"]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
use limine::*;
|
||||
|
||||
use limine::request::{
|
||||
FramebufferRequest, RequestsEndMarker, RequestsStartMarker,
|
||||
};
|
||||
|
||||
mod font;
|
||||
mod render;
|
||||
|
||||
use crate::font::FONT;
|
||||
use GoofyAhhOS::sys::kernel::drivers::serial::serial_read;
|
||||
use GoofyAhhOS::{print, println, serial_println};
|
||||
use GoofyAhhOS::sys::kernel::drivers::framebuffer::textwriter::clear_screen;
|
||||
|
||||
// Set the base revision
|
||||
static BASE_REVISION: BaseRevision = BaseRevision::new();
|
||||
|
||||
|
||||
// Halt and catch fire function
|
||||
fn hcf() -> ! {
|
||||
loop {
|
||||
unsafe { core::arch::asm!("cli; hlt") }
|
||||
}
|
||||
}
|
||||
|
||||
// Called on panic
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
hcf()
|
||||
}
|
||||
|
||||
// Kernel entry point
|
||||
#[no_mangle]
|
||||
pub extern "C" fn kmain() -> ! {
|
||||
render::init();
|
||||
render::write_string("Welcome to GoofyAhhOS!\nthis is the superior os\nif you disagree you are a heretic in the name of steven.", 0xff0000, 0);
|
||||
|
||||
hcf()
|
||||
GoofyAhhOS::init();
|
||||
|
||||
println!("Hello from GoofyAhhOS!");
|
||||
serial_println!("SERIAL OUT ACHIEVED :check:");
|
||||
|
||||
loop {
|
||||
let input: &str = serial_read();
|
||||
|
||||
clear_screen();
|
||||
|
||||
if input.starts_with("print ") {
|
||||
let input = &input[6..];
|
||||
println!("{}", input);
|
||||
} else if input == "" {
|
||||
let x: i32 = 239423889;
|
||||
let y: i32 = 123456678;
|
||||
|
||||
print!("num: {} {}", x, y);
|
||||
} else {
|
||||
println!("Unknown command: {}", input);
|
||||
}
|
||||
}
|
||||
|
||||
GoofyAhhOS::hcf()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
use lazy_static::lazy_static;
|
||||
use limine::framebuffer::Framebuffer;
|
||||
use spin::Mutex;
|
||||
use limine::request::FramebufferRequest;
|
||||
|
||||
use crate::font::FONT;
|
||||
|
||||
pub struct FramebufferWriter<'a> {
|
||||
framebuffer: Framebuffer<'a>,
|
||||
x_pos: AtomicUsize,
|
||||
y_pos: AtomicUsize,
|
||||
}
|
||||
|
||||
unsafe impl<'a> Send for FramebufferWriter<'a> {}
|
||||
unsafe impl<'a> Sync for FramebufferWriter<'a> {}
|
||||
|
||||
impl<'a> FramebufferWriter<'a> {
|
||||
pub fn new(framebuffer: Framebuffer<'a>) -> Self {
|
||||
Self {
|
||||
framebuffer,
|
||||
x_pos: AtomicUsize::new(0),
|
||||
y_pos: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_pixel(&self, x: usize, y: usize, color: u32) {
|
||||
let pitch = self.framebuffer.pitch() as usize;
|
||||
let bpp = (self.framebuffer.bpp() / 8) as usize;
|
||||
let pixel_offset = y * pitch + x * bpp;
|
||||
|
||||
unsafe {
|
||||
*(self.framebuffer.addr().add(pixel_offset) as *mut u32) = color;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_char(&self, x: u32, y: u32, fg_color: u32, bg_color: u32, mut c: u8) {
|
||||
if c < 32 || c > 126 {
|
||||
c = '?' as u8;
|
||||
}
|
||||
|
||||
let data: &[u8] = &FONT[c as usize * 16..(c as usize + 1) * 16];
|
||||
|
||||
for row in 0..16 {
|
||||
let line: u8 = data[row];
|
||||
for col in 0..8 {
|
||||
let pixel_x: u32 = x + col;
|
||||
let pixel_y: u32 = y + row as u32;
|
||||
if line & (0x80 >> col) != 0 {
|
||||
self.write_pixel(pixel_x as usize, pixel_y as usize, fg_color);
|
||||
} else {
|
||||
self.write_pixel(pixel_x as usize, pixel_y as usize, bg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_string(&self, x: u32, y: u32, fg_color: u32, bg_color: u32, s: &str) {
|
||||
let mut curr_x: u32 = x;
|
||||
let mut curr_y: u32 = y;
|
||||
|
||||
for c in s.chars() {
|
||||
if c == '\n' {
|
||||
curr_x = x;
|
||||
curr_y += 16;
|
||||
continue;
|
||||
}
|
||||
|
||||
if curr_x + 8 > self.framebuffer.width() as u32 {
|
||||
curr_x = x;
|
||||
curr_y += 16;
|
||||
}
|
||||
|
||||
if curr_y + 16 > self.framebuffer.height() as u32 {
|
||||
break;
|
||||
}
|
||||
|
||||
self.write_char(curr_x, curr_y, fg_color, bg_color, c as u8);
|
||||
curr_x += 8;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&self, color: u32) {
|
||||
let width = self.framebuffer.width() as usize;
|
||||
let height = self.framebuffer.height() as usize;
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
self.write_pixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FRAMEBUFFER_WRITER: Mutex<Option<FramebufferWriter<'static>>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.get_response() {
|
||||
// The framebuffer from the response has a 'static lifetime
|
||||
let framebuffer = framebuffer_response.framebuffers().next().unwrap();
|
||||
*FRAMEBUFFER_WRITER.lock() = Some(FramebufferWriter::new(framebuffer));
|
||||
clear_screen(0x00000000);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_screen(color: u32) {
|
||||
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_ref() {
|
||||
writer.clear(color);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_string(s: &str, fg_color: u32, bg_color: u32) {
|
||||
FRAMEBUFFER_WRITER.lock().as_ref().unwrap().write_string(0, 0, fg_color, bg_color, s);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//! CPU I/O port operations
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
/// Read a byte from the specified I/O port
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it performs direct I/O port operations
|
||||
/// which could have unpredictable effects on hardware
|
||||
#[inline]
|
||||
pub unsafe fn inb(port: u16) -> u8 {
|
||||
let value: u8;
|
||||
asm!(
|
||||
"in al, dx",
|
||||
out("al") value,
|
||||
in("dx") port,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
);
|
||||
value
|
||||
}
|
||||
|
||||
/// Write a byte to the specified I/O port
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it performs direct I/O port operations
|
||||
/// which could have unpredictable effects on hardware
|
||||
#[inline]
|
||||
pub unsafe fn outb(port: u16, value: u8) {
|
||||
asm!(
|
||||
"out dx, al",
|
||||
in("dx") port,
|
||||
in("al") value,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
);
|
||||
}
|
||||
|
||||
/// Read a word (16 bits) from the specified I/O port
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it performs direct I/O port operations
|
||||
/// which could have unpredictable effects on hardware
|
||||
#[inline]
|
||||
pub unsafe fn inw(port: u16) -> u16 {
|
||||
let value: u16;
|
||||
asm!(
|
||||
"in ax, dx",
|
||||
out("ax") value,
|
||||
in("dx") port,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
);
|
||||
value
|
||||
}
|
||||
|
||||
/// Write a word (16 bits) to the specified I/O port
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it performs direct I/O port operations
|
||||
/// which could have unpredictable effects on hardware
|
||||
#[inline]
|
||||
pub unsafe fn outw(port: u16, value: u16) {
|
||||
asm!(
|
||||
"out dx, ax",
|
||||
in("dx") port,
|
||||
in("ax") value,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
||||
|
||||
pub mod io;
|
||||
|
||||
pub use io::*;
|
||||
|
||||
// Add common CPU traits/interfaces here that all architectures must implement
|
||||
@@ -0,0 +1,73 @@
|
||||
use core::arch::asm;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use spin::lazy;
|
||||
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
|
||||
|
||||
use crate::{println_log, serial_println};
|
||||
|
||||
use super::pics::ChainedPics;
|
||||
|
||||
pub const PIC_1_OFFSET: u8 = 32;
|
||||
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
|
||||
|
||||
pub static PICS: spin::Mutex<ChainedPics> =
|
||||
spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum InterruptIndex {
|
||||
Timer = PIC_1_OFFSET,
|
||||
}
|
||||
|
||||
impl InterruptIndex {
|
||||
fn as_u8(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
fn as_usize(self) -> usize {
|
||||
usize::from(self.as_u8())
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref IDT: InterruptDescriptorTable = {
|
||||
let mut idt = InterruptDescriptorTable::new();
|
||||
idt.breakpoint.set_handler_fn(breakpoint_handler);
|
||||
idt.double_fault.set_handler_fn(double_fault_handler);
|
||||
idt[InterruptIndex::Timer.as_u8()].set_handler_fn(clock_handler);
|
||||
idt
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
IDT.load();
|
||||
}
|
||||
|
||||
|
||||
pub fn disable(func: impl Fn()) {
|
||||
unsafe {
|
||||
asm!("cli", options(nomem, nostack, preserves_flags));
|
||||
func();
|
||||
asm!("sti", options(nomem, nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
|
||||
println_log!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
|
||||
serial_println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn double_fault_handler(stack_frame: InterruptStackFrame, _error_code: u64) -> ! {
|
||||
serial_println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
|
||||
panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame)
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn clock_handler(stack_frame: InterruptStackFrame) {
|
||||
println_log!("EXCEPTION: CLOCK\n{:#?}", stack_frame);
|
||||
serial_println!("EXCEPTION: CLOCK\n{:#?}", stack_frame);
|
||||
|
||||
unsafe {
|
||||
PICS.lock().notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod interrupts;
|
||||
mod pics;
|
||||
|
||||
pub use interrupts::*;
|
||||
@@ -0,0 +1,137 @@
|
||||
use x86_64::instructions::port::Port;
|
||||
|
||||
const CMD_INIT: u8 = 0x11;
|
||||
const CMD_END_OF_INT: u8 = 0x20;
|
||||
const MODE_8086: u8 = 0x01;
|
||||
|
||||
struct Pic {
|
||||
offset: u8,
|
||||
data: Port<u8>,
|
||||
command: Port<u8>,
|
||||
}
|
||||
|
||||
impl Pic {
|
||||
/// Are we in charge of handling the specified interrupt?
|
||||
/// (Each PIC handles 8 interrupts.)
|
||||
fn handles_interrupt(&self, interrupt_id: u8) -> bool {
|
||||
self.offset <= interrupt_id && interrupt_id < self.offset + 8
|
||||
}
|
||||
|
||||
/// Notify us that an interrupt has been handled and that we're ready
|
||||
/// for more.
|
||||
unsafe fn end_of_interrupt(&mut self) {
|
||||
self.command.write(CMD_END_OF_INT);
|
||||
}
|
||||
|
||||
/// Reads the interrupt mask of this PIC.
|
||||
unsafe fn read_mask(&mut self) -> u8 {
|
||||
self.data.read()
|
||||
}
|
||||
|
||||
/// Writes the interrupt mask of this PIC.
|
||||
unsafe fn write_mask(&mut self, mask: u8) {
|
||||
self.data.write(mask)
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair of chained PICs. This is the standard setup on x86.
|
||||
pub struct ChainedPics {
|
||||
pics: [Pic; 2],
|
||||
}
|
||||
|
||||
impl ChainedPics {
|
||||
/// Create a new interface for the standard PIC1 and PIC2,
|
||||
/// specifying the desired interrupt offsets.
|
||||
pub const unsafe fn new(offset1: u8, offset2: u8) -> ChainedPics {
|
||||
ChainedPics {
|
||||
pics: [
|
||||
Pic {
|
||||
offset: offset1,
|
||||
command: Port::new(0x20),
|
||||
data: Port::new(0x21),
|
||||
},
|
||||
Pic {
|
||||
offset: offset2,
|
||||
command: Port::new(0xA0),
|
||||
data: Port::new(0xA1),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub const unsafe fn new_contiguous(primary_offset: u8) -> ChainedPics {
|
||||
Self::new(primary_offset, primary_offset + 8)
|
||||
}
|
||||
|
||||
pub unsafe fn initialize(&mut self) {
|
||||
|
||||
let mut wait_port: Port<u8> = Port::new(0x80);
|
||||
let mut wait = || wait_port.write(0);
|
||||
|
||||
// Save our original interrupt masks, because I'm too lazy to
|
||||
// figure out reasonable values. We'll restore these when we're
|
||||
// done.
|
||||
let saved_masks = self.read_masks();
|
||||
|
||||
// Tell each PIC that we're going to send it a three-byte
|
||||
// initialization sequence on its data port.
|
||||
self.pics[0].command.write(CMD_INIT);
|
||||
wait();
|
||||
self.pics[1].command.write(CMD_INIT);
|
||||
wait();
|
||||
|
||||
// Byte 1: Set up our base offsets.
|
||||
self.pics[0].data.write(self.pics[0].offset);
|
||||
wait();
|
||||
self.pics[1].data.write(self.pics[1].offset);
|
||||
wait();
|
||||
|
||||
// Byte 2: Configure chaining between PIC1 and PIC2.
|
||||
self.pics[0].data.write(4);
|
||||
wait();
|
||||
self.pics[1].data.write(2);
|
||||
wait();
|
||||
|
||||
// Byte 3: Set our mode.
|
||||
self.pics[0].data.write(MODE_8086);
|
||||
wait();
|
||||
self.pics[1].data.write(MODE_8086);
|
||||
wait();
|
||||
|
||||
// Restore our saved masks.
|
||||
self.write_masks(saved_masks[0], saved_masks[1])
|
||||
}
|
||||
|
||||
/// Reads the interrupt masks of both PICs.
|
||||
pub unsafe fn read_masks(&mut self) -> [u8; 2] {
|
||||
[self.pics[0].read_mask(), self.pics[1].read_mask()]
|
||||
}
|
||||
|
||||
/// Writes the interrupt masks of both PICs.
|
||||
pub unsafe fn write_masks(&mut self, mask1: u8, mask2: u8) {
|
||||
self.pics[0].write_mask(mask1);
|
||||
self.pics[1].write_mask(mask2);
|
||||
}
|
||||
|
||||
/// Disables both PICs by masking all interrupts.
|
||||
pub unsafe fn disable(&mut self) {
|
||||
self.write_masks(u8::MAX, u8::MAX)
|
||||
}
|
||||
|
||||
/// Do we handle this interrupt?
|
||||
pub fn handles_interrupt(&self, interrupt_id: u8) -> bool {
|
||||
self.pics.iter().any(|p| p.handles_interrupt(interrupt_id))
|
||||
}
|
||||
|
||||
/// Figure out which (if any) PICs in our chain need to know about this
|
||||
/// interrupt. This is tricky, because all interrupts from `pics[1]`
|
||||
/// get chained through `pics[0]`.
|
||||
pub unsafe fn notify_end_of_interrupt(&mut self, interrupt_id: u8) {
|
||||
if self.handles_interrupt(interrupt_id) {
|
||||
if self.pics[1].handles_interrupt(interrupt_id) {
|
||||
self.pics[1].end_of_interrupt();
|
||||
}
|
||||
self.pics[0].end_of_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod textwriter;
|
||||
pub mod render;
|
||||
|
||||
mod font;
|
||||
|
||||
|
||||
//
|
||||
@@ -0,0 +1,63 @@
|
||||
use core::panic;
|
||||
use lazy_static::lazy_static;
|
||||
use limine::framebuffer::Framebuffer;
|
||||
use spin::Mutex;
|
||||
use limine::request::FramebufferRequest;
|
||||
|
||||
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FRAMEBUFFER_WRITER: Mutex<Option<FramebufferWriter<'static>>> = Mutex::new({
|
||||
if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.get_response() {
|
||||
let framebuffer = framebuffer_response.framebuffers().next().unwrap();
|
||||
Some(FramebufferWriter::new(framebuffer))
|
||||
} else {
|
||||
panic!("Framebuffer request failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub struct FramebufferWriter<'a> {
|
||||
framebuffer: Framebuffer<'a>,
|
||||
}
|
||||
|
||||
unsafe impl<'a> Send for FramebufferWriter<'a> {}
|
||||
unsafe impl<'a> Sync for FramebufferWriter<'a> {}
|
||||
|
||||
impl<'a> FramebufferWriter<'a> {
|
||||
pub fn new(framebuffer: Framebuffer<'a>) -> Self {
|
||||
Self {
|
||||
framebuffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_pixel(&self, x: usize, y: usize, color: u32) {
|
||||
let pitch = self.framebuffer.pitch() as usize;
|
||||
let bpp = (self.framebuffer.bpp() / 8) as usize;
|
||||
let pixel_offset = y * pitch + x * bpp;
|
||||
|
||||
unsafe {
|
||||
*(self.framebuffer.addr().add(pixel_offset) as *mut u32) = color;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u32 {
|
||||
self.framebuffer.width() as u32
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
self.framebuffer.height() as u32
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
let width = self.framebuffer.width() as usize;
|
||||
let height = self.framebuffer.height() as usize;
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
self.write_pixel(x, y, 0x000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use spin::Mutex;
|
||||
|
||||
use crate::sys::kernel::cpu::interrupts;
|
||||
|
||||
use super::{font::FONT, render::FRAMEBUFFER_WRITER};
|
||||
|
||||
static FONT_WIDTH: u32 = 8;
|
||||
static FONT_HEIGHT: u32 = 16;
|
||||
|
||||
lazy_static!{
|
||||
static ref TEXT_WRITER: Mutex<TextWriter> = Mutex::new(TextWriter::new());
|
||||
}
|
||||
|
||||
|
||||
pub struct TextWriter {
|
||||
// these are measured in chars NOT pixels
|
||||
screen_width: u32,
|
||||
screen_height: u32,
|
||||
|
||||
text_line: u32, // 16 pixels tall
|
||||
text_col: u32, // 8 pixels wide
|
||||
|
||||
fg_color: u32,
|
||||
bg_color: u32
|
||||
}
|
||||
|
||||
impl TextWriter {
|
||||
|
||||
pub fn new() -> Self {
|
||||
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
|
||||
Self {
|
||||
screen_width: writer.width() as u32 / 8,
|
||||
screen_height: writer.height() as u32 / 16,
|
||||
text_line: 0,
|
||||
text_col: 0,
|
||||
fg_color: 0xFFFFFF,
|
||||
bg_color: 0x000000
|
||||
}
|
||||
} else {
|
||||
panic!("Framebuffer writer not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_char(&mut self, mut c: u8) {
|
||||
if c == b'\n' {
|
||||
self.newline();
|
||||
return;
|
||||
}
|
||||
|
||||
if c < 32 || c > 126 {
|
||||
c = '?' as u8;
|
||||
}
|
||||
|
||||
// get the character data from the font array. -- each byte is a row of pixels
|
||||
let data: &[u8] = &FONT[c as usize * 16..(c as usize + 1) * 16];
|
||||
|
||||
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
|
||||
for row in 0..16 {
|
||||
let line: u8 = data[row];
|
||||
for col in 0..8 {
|
||||
let pixel_x: u32 = self.text_col * FONT_WIDTH + col;
|
||||
let pixel_y: u32 = self.text_line * FONT_HEIGHT + row as u32;
|
||||
|
||||
if line & (0x80 >> col) != 0 {
|
||||
// write the foreground color
|
||||
writer.write_pixel(pixel_x as usize, pixel_y as usize, self.fg_color);
|
||||
} else {
|
||||
// write the background color
|
||||
writer.write_pixel(pixel_x as usize, pixel_y as usize, self.bg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go to next position
|
||||
if self.text_col + 1 >= self.screen_width {
|
||||
self.newline();
|
||||
} else {
|
||||
self.text_col += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_char(&mut self) {
|
||||
self.text_col += 1;
|
||||
}
|
||||
|
||||
pub fn newline(&mut self) {
|
||||
self.text_col = 0;
|
||||
|
||||
if self.text_line + 1 >= self.screen_height {
|
||||
self.text_line = 0;
|
||||
} else {
|
||||
self.text_line += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_string(&mut self, s: &str) {
|
||||
for c in s.chars() {
|
||||
self.write_char(c as u8);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_colour(&mut self, col: (u32, u32)) {
|
||||
self.fg_color = col.0;
|
||||
self.bg_color = col.1;
|
||||
}
|
||||
|
||||
pub fn reset_colour(&mut self) {
|
||||
self.fg_color = 0xFFFFFF;
|
||||
self.bg_color = 0x000000;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Write for TextWriter {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
self.write_string(s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write(args: fmt::Arguments, fg_color: u32, bg_color: u32) {
|
||||
use core::fmt::Write;
|
||||
|
||||
interrupts::disable(|| {
|
||||
let mut writer = TEXT_WRITER.lock();
|
||||
writer.set_colour((fg_color, bg_color));
|
||||
writer.write_fmt(args).unwrap();
|
||||
writer.reset_colour();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn _print(args: fmt::Arguments) {
|
||||
write(args, 0xFFFFFF, 0x000000);
|
||||
}
|
||||
|
||||
pub fn _printerr(args: fmt::Arguments) {
|
||||
write(args, 0xFF8080, 0x000000);
|
||||
}
|
||||
|
||||
pub fn _log(args: fmt::Arguments) {
|
||||
write(args, 0xFFFF00, 0x000000);
|
||||
}
|
||||
|
||||
pub fn clear_screen() {
|
||||
let mut writer = TEXT_WRITER.lock();
|
||||
writer.text_line = 0;
|
||||
writer.text_col = 0;
|
||||
|
||||
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
|
||||
writer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! println_log {
|
||||
() => ($crate::print_log!("\n"));
|
||||
($($arg:tt)*) => ($crate::print_log!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print_log {
|
||||
($($arg:tt)*) => ($crate::_log(format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => ($crate::print!("\n"));
|
||||
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ($crate::_print(format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! printlnerr {
|
||||
() => ($crate::printerr!("\n"));
|
||||
($($arg:tt)*) => ($crate::printerr!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! printerr {
|
||||
($($arg:tt)*) => ($crate::_printerr(format_args!($($arg)*)));
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod framebuffer;
|
||||
pub mod serial;
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod serial;
|
||||
|
||||
pub use serial::{
|
||||
_serial_write,
|
||||
serial_read
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
use core::fmt;
|
||||
use spin::Mutex;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::sys::kernel::cpu::{inb, outb};
|
||||
|
||||
static PORT: u16 = 0x3f8;
|
||||
static mut BUFFER: [u8; 256] = [0; 256];
|
||||
|
||||
lazy_static!{
|
||||
static ref SERIAL_WRITER: Mutex<SerialWriter> = Mutex::new(SerialWriter::new());
|
||||
}
|
||||
|
||||
struct SerialWriter {
|
||||
buffer_len: usize
|
||||
}
|
||||
|
||||
impl fmt::Write for SerialWriter {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for c in s.chars() {
|
||||
self.write(c as u8);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SerialWriter {
|
||||
pub fn new() -> SerialWriter {
|
||||
// first we make sure that the serial port is setup and working.
|
||||
unsafe {
|
||||
outb(PORT + 1, 0x00); // Disable all interrupts
|
||||
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
|
||||
outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud
|
||||
outb(PORT + 1, 0x00); // (hi byte)
|
||||
outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
|
||||
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-bytethreshold
|
||||
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
|
||||
outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip
|
||||
outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte)
|
||||
|
||||
if inb(PORT + 0) != 0xAE {
|
||||
panic!("serial port is not working!");
|
||||
}
|
||||
|
||||
outb(PORT + 4, 0x0F);
|
||||
}
|
||||
|
||||
SerialWriter {
|
||||
buffer_len: 0
|
||||
}
|
||||
}
|
||||
|
||||
// returnstrue if there is new data on the serial port
|
||||
unsafe fn serial_recieved(&self) -> bool {
|
||||
inb(PORT + 5) & 1 != 0
|
||||
}
|
||||
|
||||
// returns true if the transmit buffer is empty
|
||||
unsafe fn serial_sent(&self) -> bool {
|
||||
inb(PORT + 5) & 0x20 != 0
|
||||
}
|
||||
|
||||
pub fn read(&self) -> u8 { unsafe {
|
||||
while !self.serial_recieved() {};
|
||||
return inb(PORT + 0);
|
||||
}}
|
||||
|
||||
pub fn read_str_to_buffer(&mut self) { unsafe {
|
||||
while !self.serial_recieved() {};
|
||||
|
||||
self.buffer_len = 0;
|
||||
|
||||
while self.serial_recieved() && self.buffer_len < 256 {
|
||||
let c = inb(PORT + 0);
|
||||
BUFFER[self.buffer_len] = c;
|
||||
if c == b'\n' {
|
||||
break;
|
||||
}
|
||||
self.buffer_len += 1;
|
||||
}
|
||||
}}
|
||||
|
||||
pub fn write(&self, data: u8) { unsafe {
|
||||
while !self.serial_sent() {};
|
||||
outb(PORT + 0, data);
|
||||
}}
|
||||
}
|
||||
|
||||
pub fn _serial_write(args: fmt::Arguments) {
|
||||
use core::fmt::Write;
|
||||
|
||||
SERIAL_WRITER.lock().write_fmt(args).unwrap();
|
||||
}
|
||||
|
||||
pub fn serial_read() -> &'static str {
|
||||
SERIAL_WRITER.lock().read_str_to_buffer();
|
||||
let i = SERIAL_WRITER.lock().buffer_len;
|
||||
|
||||
if i == 0 {
|
||||
return "";
|
||||
}
|
||||
|
||||
unsafe {
|
||||
return core::str::from_utf8(&BUFFER[..i - 1]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serial_print {
|
||||
($($arg:tt)*) => ($crate::_serial_write(format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serial_println {
|
||||
() => ($crate::serial_print!("\n"));
|
||||
($($arg:tt)*) => ($crate::serial_print!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod drivers;
|
||||
pub mod cpu;
|
||||
@@ -1,2 +1,7 @@
|
||||
pub mod std;
|
||||
mod kernel;
|
||||
|
||||
// TODO: make this private
|
||||
pub mod kernel;
|
||||
|
||||
#[cfg(any(test, feature = "qemu"))]
|
||||
pub mod qemu;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use x86_64::instructions::port::Port;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum ExitCode {
|
||||
// Writing 0x10 will make QEMU exit with code 33 ((0x10 << 1) | 1)
|
||||
Success = 0x10,
|
||||
// Writing 0x11 will make QEMU exit with code 35 ((0x11 << 1) | 1)
|
||||
Failed = 0x11,
|
||||
}
|
||||
|
||||
/// Exit QEMU with the given exit code.
|
||||
///
|
||||
/// This function uses the special QEMU debug exit device with
|
||||
/// port 0xf4 to exit QEMU with a specified exit code.
|
||||
/// QEMU will exit with code: (value << 1) | 1
|
||||
pub fn exit(exit_code: ExitCode) -> ! {
|
||||
unsafe {
|
||||
let mut port = Port::new(0xf4);
|
||||
port.write(exit_code as u32);
|
||||
}
|
||||
loop {
|
||||
unsafe {
|
||||
core::arch::asm!("cli; hlt", options(nomem, nostack))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Exit QEMU with a success code
|
||||
/// Writing 0x10 will make QEMU exit with code 33
|
||||
#[cfg(test)]
|
||||
pub fn exit_success() -> ! {
|
||||
exit(ExitCode::Success)
|
||||
}
|
||||
|
||||
/// Exit QEMU with a failed code
|
||||
/// Writing 0x11 will make QEMU exit with code 35
|
||||
#[cfg(test)]
|
||||
pub fn exit_failed() -> ! {
|
||||
use crate::serial_println;
|
||||
serial_println!("[failed]\n");
|
||||
exit(ExitCode::Failed)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// mod file for standard library in kernel;
|
||||
@@ -0,0 +1,22 @@
|
||||
#[cfg(test)]
|
||||
use x86_64::instructions::interrupts::int3;
|
||||
#[cfg(test)]
|
||||
use crate::println;
|
||||
|
||||
#[test_case]
|
||||
pub fn test_println_many() {
|
||||
for _ in 0..200 {
|
||||
println!("test_println_many output");
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
pub fn test_println_output() {
|
||||
println!("test_println output");
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
pub fn test_breakpoint_exception() {
|
||||
// invoke a breakpoint exception
|
||||
int3();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
mod runner;
|
||||
pub use runner::test_runner;
|
||||
|
||||
pub mod kernel;
|
||||
|
||||
/// Called on panic
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - The panic info
|
||||
#[cfg(test)]
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
use crate::serial_println;
|
||||
|
||||
// print a failed message saying the kernel panicked
|
||||
serial_println!("[failed]\n");
|
||||
serial_println!("Error: {}\n", info);
|
||||
crate::sys::qemu::exit_failed();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use crate::{serial_print, serial_println};
|
||||
|
||||
pub fn test_runner(tests: &[&dyn Testable]) {
|
||||
serial_println!("Running {} tests\n", tests.len());
|
||||
for test in tests {
|
||||
test.run();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Testable {
|
||||
fn run(&self) -> ();
|
||||
}
|
||||
|
||||
impl<T> Testable for T
|
||||
where
|
||||
T: Fn(),
|
||||
{
|
||||
fn run(&self) {
|
||||
serial_print!("{}...\t", core::any::type_name::<T>());
|
||||
self();
|
||||
serial_println!("[ok]");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user