Remove micromath dependency, improve primitive drawing speed by 300x,

add mouse support and interrupts on x86_64 using a ps2 mouse which
needed pic unmasking, add span filling to framebuffer, add back
without_interrupts to serial console and framebuffer, add a mouse
rectangle which tracks the mouse position and test the performance of
the drawing.
This commit is contained in:
csd4ni3l
2026-03-29 14:38:43 +02:00
parent aa5cd85b48
commit 3ef95940a7
13 changed files with 494 additions and 56 deletions

7
kernel/Cargo.lock generated
View File

@@ -9,7 +9,6 @@ dependencies = [
"font8x8",
"lazy_static",
"limine",
"micromath",
"pc-keyboard",
"pic8259",
"spin 0.10.0",
@@ -67,12 +66,6 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "micromath"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "pc-keyboard"
version = "0.8.0"

View File

@@ -12,7 +12,6 @@ bench = false
font8x8 = { version = "0.3.1", default-features = false }
lazy_static = { version = "1.5.0", features = ["spin_no_std"] }
limine = "0.5"
micromath = "2.1.0"
pc-keyboard = "0.8.0"
pic8259 = "0.11.0"
spin = "0.10.0"

View File

@@ -1,5 +1,11 @@
use crate::arch::x86_64::gdt::load_gdt_x86_64;
use crate::arch::x86_64::interrupts::{InterruptIndex, PICS, init_idt_x86_64};
use crate::{
arch::x86_64::{
gdt::load_gdt_x86_64,
interrupts::{PICS, init_idt_x86_64},
mouse::setup_mouse,
},
driver::mouse::MOUSE,
};
use limine::response::{HhdmResponse, MemoryMapResponse};
use x86_64::instructions::interrupts::without_interrupts;
use x86_64::instructions::{interrupts, port::Port};
@@ -49,9 +55,12 @@ pub fn init_x86_64<'a>(
unsafe {
let mut pics = PICS.lock();
pics.initialize();
pics.write_masks(0xFC, 0xFF);
let master_mask = 0xF8; // unmask cascade to slave
let slave_mask = 0xEF; // unmask mouse interrupt (clear bit 4)
pics.write_masks(master_mask, slave_mask);
}
let mouse_status = setup_mouse();
set_pit_interval();
interrupts::enable();
@@ -63,5 +72,7 @@ pub fn init_x86_64<'a>(
.ok()
.expect("Failed to initalize heap");
MOUSE.set_status(mouse_status);
return (mapper, frame_allocator);
}

View File

@@ -1,3 +1,5 @@
use crate::arch::x86_64::mouse::mouse_interrupt;
use crate::driver::mouse::MOUSE;
use crate::driver::timer::TIMER;
use crate::{arch::x86_64::gdt, driver::keyboard::keyboard_interrupt_handler, println};
use lazy_static::lazy_static;
@@ -20,16 +22,16 @@ pub static PICS: Mutex<ChainedPics> =
pub enum InterruptIndex {
Timer = PIC_1_OFFSET,
Keyboard = PIC_1_OFFSET + 1,
Mouse = PIC_2_OFFSET + 4,
// RTC = PIC_2_OFFSET,
// ATA_primary = PIC_2_OFFSET + 7
// ATA_secondary = PIC_2_OFFSET + 8
}
impl InterruptIndex {
pub fn as_u8(self) -> u8 {
self as u8
}
fn as_usize(self) -> usize {
usize::from(self.as_u8())
}
}
lazy_static! {
@@ -45,6 +47,7 @@ lazy_static! {
idt.page_fault.set_handler_fn(page_fault_handler);
idt.general_protection_fault.set_handler_fn(gpf_handler);
idt[InterruptIndex::Keyboard.as_u8()].set_handler_fn(keyboard_interrupt_handler);
idt[InterruptIndex::Mouse.as_u8()].set_handler_fn(mouse_interrupt_handler);
idt
};
}
@@ -91,3 +94,22 @@ extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFr
.notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
}
}
extern "x86-interrupt" fn mouse_interrupt_handler(_stack_frame: InterruptStackFrame) {
let interrupt_result = mouse_interrupt();
if let Some(interrupt_result) = interrupt_result {
MOUSE.interrupt(
interrupt_result.0,
interrupt_result.1,
interrupt_result.2,
interrupt_result.3,
interrupt_result.4,
);
}
unsafe {
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Mouse.as_u8());
}
}

View File

@@ -2,4 +2,5 @@ pub mod gdt;
pub mod heap;
pub mod init;
pub mod interrupts;
pub mod mouse;
pub mod paging;

View File

@@ -0,0 +1,230 @@
// Comment to self: WHY IS THIS SOO HARD
use crate::util::get_bit;
use core::sync::atomic::{AtomicU8, Ordering};
use x86_64::instructions::{interrupts::without_interrupts, port::Port};
static CURRENTLY_RECEIVING_STATE: AtomicU8 = AtomicU8::new(0);
static FLAGS_BYTE: AtomicU8 = AtomicU8::new(0);
static X_DELTA_BYTE: AtomicU8 = AtomicU8::new(0);
static Y_DELTA_BYTE: AtomicU8 = AtomicU8::new(0);
fn wait_input_buffer_clear(command_port: &mut Port<u8>) {
unsafe {
loop {
let status = command_port.read();
// IBF is bit 1: clear means we can write
if (status & 0b10) == 0 {
break;
}
}
}
}
fn wait_output_buffer_full(command_port: &mut Port<u8>) {
unsafe {
loop {
let status = command_port.read();
// OBF is bit 0: 1 = data available to read
if (status & 0b1) != 0 {
break;
}
}
}
}
fn read_ccb(command_port: &mut Port<u8>, data_port: &mut Port<u8>) -> u8 {
unsafe {
wait_input_buffer_clear(command_port);
command_port.write(0x20);
wait_output_buffer_full(command_port);
return data_port.read();
}
}
fn write_ccb(command_port: &mut Port<u8>, data_port: &mut Port<u8>, value: u8) {
unsafe {
wait_input_buffer_clear(command_port);
command_port.write(0x60);
wait_input_buffer_clear(command_port);
data_port.write(value);
}
}
fn check_clear_and_write(command_port: &mut Port<u8>, inst: u8) {
unsafe {
wait_input_buffer_clear(command_port);
command_port.write(inst);
}
}
fn write_and_expect_output(
command_port: &mut Port<u8>,
data_port: &mut Port<u8>,
inst: u8,
expected_output: u8,
) -> bool {
unsafe {
check_clear_and_write(command_port, inst);
wait_output_buffer_full(command_port);
return data_port.read() == expected_output;
}
}
fn clear_and_expect_output(
command_port: &mut Port<u8>,
data_port: &mut Port<u8>,
expected_output: u8,
) -> bool {
unsafe {
wait_input_buffer_clear(command_port);
wait_output_buffer_full(command_port);
return data_port.read() == expected_output;
}
}
pub fn setup_mouse() -> u8 {
without_interrupts(|| {
let mut command_port: Port<u8> = Port::new(0x64);
let mut data_port: Port<u8> = Port::new(0x60);
unsafe {
check_clear_and_write(&mut command_port, 0xAD); // disable port 1
check_clear_and_write(&mut command_port, 0xA7); // disable port 2
let mut ccb = read_ccb(&mut command_port, &mut data_port);
ccb = ccb | 0b00000001; // enable keyboard IRQ
ccb = ccb | 0b00000010; // enable mouse IRQ
ccb = ccb & 0b11011111; // disable mouse gating
ccb = ccb & 0b10111111; // disable scancode translation
write_ccb(&mut command_port, &mut data_port, ccb);
check_clear_and_write(&mut command_port, 0xAE); // enable port 1
check_clear_and_write(&mut command_port, 0xA8); // enable port 2
if !write_and_expect_output(&mut command_port, &mut data_port, 0xA9, 0x00) {
// mouse test reply doesnt work!
return 1;
}
// Reset Mouse
wait_input_buffer_clear(&mut command_port);
command_port.write(0xD4);
wait_input_buffer_clear(&mut command_port);
data_port.write(0xFF);
if !clear_and_expect_output(&mut command_port, &mut data_port, 0xFA) {
// ACK
return 2;
}
if !clear_and_expect_output(&mut command_port, &mut data_port, 0xAA) {
// Self-test passed
return 3;
}
if !clear_and_expect_output(&mut command_port, &mut data_port, 0x00) {
// Mouse ID
return 4;
}
// Enable data reporting
wait_input_buffer_clear(&mut command_port);
command_port.write(0xD4);
wait_input_buffer_clear(&mut command_port);
data_port.write(0xF4);
if !clear_and_expect_output(&mut command_port, &mut data_port, 0xFA) {
return 5; // ACK
}
return 6;
}
})
}
fn reset_state() {
CURRENTLY_RECEIVING_STATE.store(0, Ordering::Relaxed);
FLAGS_BYTE.store(0, Ordering::Relaxed);
X_DELTA_BYTE.store(0, Ordering::Relaxed);
Y_DELTA_BYTE.store(0, Ordering::Relaxed);
}
pub fn mouse_interrupt() -> Option<(u8, u8, u8, i16, i16)> {
let mut command_port: Port<u8> = Port::new(0x64);
let mut data_port: Port<u8> = Port::new(0x60);
unsafe {
if (command_port.read() & 0x20) == 0 {
// if this interrupt is not for the mouse, return
return None;
}
let byte = data_port.read();
let state_idx = CURRENTLY_RECEIVING_STATE.fetch_add(1, Ordering::Relaxed);
if state_idx == 0 {
if (byte & 0x08) == 0 {
// if sync bit unset, return
reset_state();
return None;
}
if (byte & 0b0100_0000) != 0 {
// if x overflow set, return
reset_state();
return None;
}
if (byte & 0b1000_0000) != 0 {
// if y overflow set, return
reset_state();
return None;
}
FLAGS_BYTE.store(byte, Ordering::Relaxed);
None
} else if state_idx == 1 {
X_DELTA_BYTE.store(byte, Ordering::Relaxed);
None
} else if state_idx == 2 {
Y_DELTA_BYTE.store(byte, Ordering::Relaxed);
let flags = FLAGS_BYTE.load(Ordering::Relaxed);
let left_button_pressed = get_bit(flags, 0);
let right_button_pressed = get_bit(flags, 1);
let middle_button_pressed = get_bit(flags, 2);
let x_delta_sign = get_bit(flags, 4);
let y_delta_sign = get_bit(flags, 5);
let x_delta: i16 = {
let x_delta = X_DELTA_BYTE.load(Ordering::Relaxed);
if x_delta_sign == 1 {
(x_delta as i16) - 256
} else {
x_delta as i16
}
};
let y_delta: i16 = -{
let y_delta = Y_DELTA_BYTE.load(Ordering::Relaxed);
if y_delta_sign == 1 {
(y_delta as i16) - 256
} else {
y_delta as i16
}
};
reset_state();
Some((
left_button_pressed,
right_button_pressed,
middle_button_pressed,
x_delta,
y_delta,
))
} else {
None
}
}
}

View File

@@ -32,6 +32,7 @@ impl Framebuffer {
}
}
#[inline(always)]
pub fn put_pixel(&mut self, x: usize, y: usize, color: u32) {
if x >= self.width || y >= self.height {
return;
@@ -45,6 +46,20 @@ impl Framebuffer {
self.back_buffer[idx] = color;
}
#[inline(always)]
pub fn fill_span(&mut self, x: usize, y: usize, len: usize, color: u32) {
if y >= self.height || x >= self.width || len == 0 {
return;
}
let max_len = self.width - x;
let len = core::cmp::min(len, max_len);
let start = y * self.pitch + x;
let end = start + len;
unsafe {
self.back_buffer.get_unchecked_mut(start..end).fill(color);
}
}
pub fn swap(&mut self) {
unsafe {
core::ptr::copy_nonoverlapping(
@@ -69,8 +84,10 @@ pub fn init_framebuffer(raw: &LimineFramebuffer) {
}
pub fn with_framebuffer<F: FnOnce(&mut Framebuffer)>(f: F) {
without_interrupts(|| {
let mut guard = FRAMEBUFFER.lock();
if let Some(fb) = guard.as_mut() {
f(fb);
}
})
}

View File

@@ -1,9 +1,19 @@
use crate::driver::graphics::{base::rgb, font_render::render_text, framebuffer::Framebuffer};
use alloc::{format, string::ToString};
use core::f32::consts::PI;
use micromath::F32Ext;
use crate::driver::graphics::framebuffer::Framebuffer;
pub fn line(framebuffer: &mut Framebuffer, x0: usize, y0: usize, x1: usize, y1: usize, color: u32) {
if y0 == y1 {
let (xa, xb) = if x0 <= x1 { (x0, x1) } else { (x1, x0) };
framebuffer.fill_span(xa, y0, xb - xa + 1, color);
return;
}
if x0 == x1 {
let (ya, yb) = if y0 <= y1 { (y0, y1) } else { (y1, y0) };
for yy in ya..=yb {
framebuffer.put_pixel(x0, yy, color);
}
return;
}
let mut x0 = x0 as isize;
let mut y0 = y0 as isize;
let x1 = x1 as isize;
@@ -58,23 +68,58 @@ pub fn triangle_outline(
line(framebuffer, x2, y2, x3, y3, color);
}
pub fn circle_outline(framebuffer: &mut Framebuffer, x: usize, y: usize, radius: f32, color: u32) {
let mut i: f32 = 0.0;
pub fn circle_outline(
framebuffer: &mut Framebuffer,
cx: usize,
cy: usize,
radius: usize,
color: u32,
) {
let mut x = radius as isize;
let mut y: isize = 0;
let mut d = 1 - x;
let cx = cx as isize;
let cy = cy as isize;
loop {
i += 0.1;
#[inline(always)]
fn plot_points(
framebuffer: &mut Framebuffer,
cx: isize,
cy: isize,
x: isize,
y: isize,
color: u32,
) {
framebuffer.put_pixel((cx + x) as usize, (cy + y) as usize, color);
framebuffer.put_pixel((cx + y) as usize, (cy + x) as usize, color);
framebuffer.put_pixel((cx - y) as usize, (cy + x) as usize, color);
framebuffer.put_pixel((cx - x) as usize, (cy + y) as usize, color);
framebuffer.put_pixel((cx - x) as usize, (cy - y) as usize, color);
framebuffer.put_pixel((cx - y) as usize, (cy - x) as usize, color);
framebuffer.put_pixel((cx + y) as usize, (cy - x) as usize, color);
framebuffer.put_pixel((cx + x) as usize, (cy - y) as usize, color);
}
let x1: f32 = radius * (i * core::f32::consts::PI / 180.0).cos();
let y1: f32 = radius * (i * PI / 180.0).sin();
framebuffer.put_pixel((x as f32 + x1) as usize, (y as f32 + y1) as usize, color);
while y <= x {
plot_points(framebuffer, cx, cy, x, y, color);
y += 1;
if i >= 360.0 {
break;
if d <= 0 {
d += 2 * y + 1;
} else {
x -= 1;
d += 2 * (y - x) + 1;
}
}
}
pub fn circle_filled(framebuffer: &mut Framebuffer, x0: usize, y0: usize, radius: f32, color: u32) {
pub fn circle_filled(
framebuffer: &mut Framebuffer,
x0: usize,
y0: usize,
radius: usize,
color: u32,
) {
let mut x = radius as isize;
let mut y: isize = 0;
let mut x_change: isize = 1 - (radius as isize * 2);
@@ -113,10 +158,8 @@ pub fn rectangle_filled(
height: usize,
color: u32,
) {
for fb_x in x..x + width {
for fb_y in y..y + height {
framebuffer.put_pixel(fb_x, fb_y, color);
}
for yy in y..y + height {
framebuffer.fill_span(x, yy, width, color);
}
}

View File

@@ -1,4 +1,5 @@
pub mod graphics;
pub mod keyboard;
pub mod mouse;
pub mod serial;
pub mod timer;

View File

@@ -0,0 +1,67 @@
use core::sync::atomic::{AtomicI16, AtomicU8, Ordering};
use spin::Mutex;
pub struct Mouse {
left_button_pressed: AtomicU8,
right_button_pressed: AtomicU8,
middle_button_pressed: AtomicU8,
x_delta: AtomicI16,
y_delta: AtomicI16,
status: AtomicU8,
}
impl Mouse {
const fn new() -> Mouse {
Mouse {
left_button_pressed: AtomicU8::new(0),
right_button_pressed: AtomicU8::new(0),
middle_button_pressed: AtomicU8::new(0),
x_delta: AtomicI16::new(0),
y_delta: AtomicI16::new(0),
status: AtomicU8::new(0),
}
}
pub fn interrupt(
&self,
left_button_pressed: u8,
right_button_pressed: u8,
middle_button_pressed: u8,
x_delta: i16,
y_delta: i16,
) {
self.left_button_pressed
.store(left_button_pressed, Ordering::Relaxed);
self.right_button_pressed
.store(right_button_pressed, Ordering::Relaxed);
self.middle_button_pressed
.store(middle_button_pressed, Ordering::Relaxed);
self.x_delta.fetch_add(x_delta, Ordering::Relaxed);
self.y_delta.fetch_add(y_delta, Ordering::Relaxed);
}
pub fn is_left_button_pressed(&self) -> u8 {
self.left_button_pressed.load(Ordering::Relaxed)
}
pub fn is_right_button_pressed(&self) -> u8 {
self.right_button_pressed.load(Ordering::Relaxed)
}
pub fn is_middle_button_pressed(&self) -> u8 {
self.middle_button_pressed.load(Ordering::Relaxed)
}
pub fn get_x_delta(&self) -> i16 {
self.x_delta.swap(0, Ordering::Relaxed)
}
pub fn get_y_delta(&self) -> i16 {
self.y_delta.swap(0, Ordering::Relaxed)
}
pub fn set_status(&self, status: u8) {
self.status.store(status, Ordering::Relaxed);
}
pub fn get_status(&self) -> u8 {
self.status.load(Ordering::Relaxed)
}
}
pub static MOUSE: Mouse = Mouse::new();

View File

@@ -73,8 +73,10 @@ pub fn init_serial_console(start_x: usize, start_y: usize) {
}
pub fn with_serial_console<F: FnOnce(&mut SerialConsole)>(f: F) {
without_interrupts(|| {
let mut guard = SERIAL_CONSOLE.lock();
if let Some(fb) = guard.as_mut() {
f(fb);
}
});
}

View File

@@ -10,6 +10,7 @@ use limine::request::{
DateAtBootRequest, FramebufferRequest, HhdmRequest, MemoryMapRequest, MpRequest,
RequestsEndMarker, RequestsStartMarker,
};
use x86_64::instructions::interrupts::without_interrupts;
pub mod arch;
pub mod driver;
pub mod util;
@@ -20,6 +21,7 @@ use crate::driver::graphics::framebuffer::{init_framebuffer, with_framebuffer};
use crate::driver::graphics::primitives::{
circle_filled, circle_outline, rectangle_filled, rectangle_outline, triangle_outline,
};
use crate::driver::mouse::MOUSE;
use crate::driver::serial::{ConsoleWriter, init_serial_console, with_serial_console};
use crate::driver::timer::TIMER;
use crate::util::test_performance;
@@ -89,7 +91,6 @@ pub fn _print(args: core::fmt::Arguments) {
};
let _ = writer.write_fmt(args);
});
fb.swap();
});
}
@@ -99,9 +100,12 @@ unsafe extern "C" fn kmain() -> ! {
// removed by the linker.
assert!(BASE_REVISION.is_supported());
let mapper;
let frame_allocator;
if let Some(hhdm_response) = HHDM_REQUEST.get_response() {
if let Some(memory_map_response) = MEMORY_MAP_REQUEST.get_response() {
let (mapper, frame_allocator) = init(hhdm_response, memory_map_response);
(mapper, frame_allocator) = init(hhdm_response, memory_map_response);
} else {
kernel_crash(); // Could not get required info from Limine's memory map.
}
@@ -134,25 +138,67 @@ unsafe extern "C" fn kmain() -> ! {
sleep(500);
let mut current_mouse_x: usize = 100;
let mut current_mouse_y: usize = 100;
let mut mouse_status = 0;
let mut width = 0;
let mut height = 0;
let mut x_delta = 0;
let mut y_delta = 0;
loop {
with_serial_console(|serial_console| serial_console.clear(0, 0));
with_framebuffer(|fb| fb.clear(rgb(253, 129, 0)));
test_performance(|| {
with_framebuffer(|mut fb| {
fb.clear(rgb(253, 129, 0));
width = fb.width;
height = fb.height;
// rectangle_filled(&mut fb, 700, 400, 200, 200, rgb(0, 0, 0));
// rectangle_outline(&mut fb, 400, 400, 100, 100, rgb(0, 0, 0));
// circle_filled(&mut fb, 200, 200, 100.0, rgb(0, 0, 0));
circle_outline(&mut fb, 400, 200, 100.0, rgb(0, 0, 0));
rectangle_filled(&mut fb, 700, 400, 200, 200, rgb(0, 0, 0));
rectangle_outline(&mut fb, 400, 400, 100, 100, rgb(0, 0, 0));
circle_filled(&mut fb, 200, 200, 100, rgb(0, 0, 0));
circle_outline(&mut fb, 400, 200, 100, rgb(0, 0, 0));
triangle_outline(&mut fb, 100, 400, 200, 400, 150, 600, rgb(0, 0, 0));
});
let (hours, minutes, seconds) =
unix_to_hms(TIMER.get_date_at_boot() + (TIMER.now().elapsed()) / 1000);
print!("{:?}:{:?}:{:?}", hours, minutes, seconds);
mouse_status = MOUSE.get_status();
x_delta = MOUSE.get_x_delta() / 5;
y_delta = MOUSE.get_y_delta() / 5;
if x_delta != 0 {
current_mouse_x = (current_mouse_x as isize + x_delta as isize).max(0) as usize;
}
if y_delta != 0 {
current_mouse_y = (current_mouse_y as isize + y_delta as isize).max(0) as usize;
}
if current_mouse_x > width {
current_mouse_x = width - 15;
}
if current_mouse_y > height {
current_mouse_y = height - 15;
}
print!(
"{:?}:{:?}:{:?}\n{:?} {:?} {:?} {:?} {:?}",
hours,
minutes,
seconds,
mouse_status,
current_mouse_x,
current_mouse_y,
x_delta,
y_delta
);
});
with_framebuffer(|fb| {
rectangle_filled(fb, current_mouse_x, current_mouse_y, 15, 15, rgb(0, 255, 0));
fb.swap();
});
sleep(16);
}
}
@@ -183,6 +229,8 @@ fn boot_animation() {
i += 1;
with_framebuffer(|fb| fb.swap());
sleep(200);
}

View File

@@ -42,3 +42,7 @@ pub fn test_performance<F: FnOnce()>(function: F) {
println!("took {} ms", (TIMER.now() - start).elapsed());
ret
}
pub fn get_bit(value: u8, position: u8) -> u8 {
(value >> position) & 1
}