Skip to content

Commit 689d2ed

Browse files
committed
feat: implement keyboard interrupt handling and echo to serial
- Fully disable legacy PIC controller before enabling APIC to prevent interrupt conflicts - Init and config IOAPIC to route keyboard IRQ1 to IDT vector 33 - Verify real-time keyboard interrupt echoed via serial console
1 parent f94ec8c commit 689d2ed

10 files changed

Lines changed: 163 additions & 84 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ members = ["kernel",
88
"graphics",
99
"util",
1010
"task",
11+
"keyboard"
1112
]
1213

1314
[profile.release]

apic/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ edition = "2024"
77
x86_64 = "0.15.2"
88
hal = { path = "../hal" }
99
idt = { path = "../idt" }
10-
lazy_static = "1.5.0"
11-
10+
keyboard = { path = "../keyboard" }
1211
[profile.release]
1312
panic = "abort"
1413

apic/src/ioapic.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
const IOAPIC_BASE: u64 = 0xFEC00000;
22

33
fn ioapic_reg(offset: u32) -> *mut u32 {
4-
(IOAPIC_BASE + offset as u64) as *mut u32
4+
(IOAPIC_BASE + offset as u64) as *mut u32
55
}
66

7-
unsafe fn ioapic_read(reg: u32) {
8-
ioapic_reg(0x00).write_volatile(reg);
9-
ioapic_reg(0x10).read_volatile();
7+
unsafe fn ioapic_read(reg: u32) -> u32 {
8+
ioapic_reg(0x00).write_volatile(reg);
9+
ioapic_reg(0x10).read_volatile()
1010
}
11+
1112
unsafe fn ioapic_write(reg: u32, value: u32) {
12-
ioapic_reg(0x00).write_volatile(reg);
13-
ioapic_reg(0x10).write_volatile(value);
13+
ioapic_reg(0x00).write_volatile(reg);
14+
ioapic_reg(0x10).write_volatile(value);
1415
}
1516

1617
pub unsafe fn map_irq(irq: u8, vector: u8) {
17-
let reg = 0x10 + (irq as u32 * 2);
18-
ioapic_write(reg, vector as u32);
19-
ioapic_write(reg + 1, 0);
18+
let reg = 0x10 + (irq as u32 * 2);
19+
ioapic_write(reg, vector as u32);
20+
ioapic_write(reg + 1, 0);
2021
}
2122

2223
pub unsafe fn init_ioapic() {
23-
//example route irq 1 to vector 33(keyboard)
24-
map_irq(1, 33);
25-
//route irq 0 to vector 32 (timer)
26-
map_irq(0, 32);
24+
//example route irq 1 to vector 33(keyboard)
25+
map_irq(1, 33);
26+
//route irq 0 to vector 32 (timer)
27+
map_irq(0, 32);
2728
}

apic/src/lib.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use hal::serial_println;
55

6-
mod ioapic;
6+
pub mod ioapic;
77
pub mod timer;
88

99
const APIC_BASE: u64 = 0xFEE00000;
@@ -12,7 +12,42 @@ fn lapic_reg(offset: u32) -> *mut u32 {
1212
(APIC_BASE + offset as u64) as *mut u32
1313
}
1414

15+
pub unsafe fn disable_pic() {
16+
use x86_64::instructions::port::Port;
17+
18+
// Remap PIC to avoid conflicts, then mask all interrupts
19+
let mut pic1_cmd: Port<u8> = Port::new(0x20);
20+
let mut pic1_data: Port<u8> = Port::new(0x21);
21+
let mut pic2_cmd: Port<u8> = Port::new(0xA0);
22+
let mut pic2_data: Port<u8> = Port::new(0xA1);
23+
24+
// Start initialization sequence
25+
pic1_cmd.write(0x11);
26+
pic2_cmd.write(0x11);
27+
28+
// Remap IRQs to vectors 32-47
29+
pic1_data.write(0x20);
30+
pic2_data.write(0x28);
31+
32+
// Set up cascading
33+
pic1_data.write(0x04);
34+
pic2_data.write(0x02);
35+
36+
// Set 8086 mode
37+
pic1_data.write(0x01);
38+
pic2_data.write(0x01);
39+
40+
// Mask all interrupts
41+
pic1_data.write(0xFF);
42+
pic2_data.write(0xFF);
43+
44+
serial_println!("Legacy PIC disabled");
45+
}
46+
1547
pub unsafe fn enable() {
48+
// Disable legacy PIC first
49+
disable_pic();
50+
1651
// Check and enable LAPIC in IA32_APIC_BASE MSR (MSR 0x1B)
1752
let mut apic_base: u64;
1853
core::arch::asm!("rdmsr", in("ecx") 0x1Bu32, lateout("eax") apic_base, lateout("edx") _);
@@ -29,6 +64,8 @@ pub unsafe fn enable() {
2964
let svr = lapic_reg(0xF0);
3065
let val = svr.read_volatile() | 0x100; //Set bit 8
3166
svr.write_volatile(val);
67+
68+
serial_println!("APIC enabled");
3269
}
3370

3471
pub unsafe fn set_timer(vector: u8, divide: u32, initial_count: u32) {

idt/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition = "2024"
77
x86_64 = "0.15.2"
88
hal = { path = "../hal" }
99
util = { path = "../util" }
10+
keyboard = { path = "../keyboard" }
1011
lazy_static = { version = "1.5.0", features = ["spin_no_std"] }
1112

1213
[profile.release]

idt/src/lib.rs

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use util::panic::oops;
88
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode};
99

1010
struct IdtWrapper {
11-
idt: UnsafeCell<InterruptDescriptorTable>,
12-
loaded: UnsafeCell<bool>,
11+
idt: UnsafeCell<InterruptDescriptorTable>,
12+
loaded: UnsafeCell<bool>,
1313
}
1414

1515
unsafe impl Sync for IdtWrapper {}
@@ -20,58 +20,76 @@ lazy_static! {
2020
idt.divide_error.set_handler_fn(divide_by_zero_handler);
2121
idt.page_fault.set_handler_fn(page_fault_handler);
2222
idt.double_fault.set_handler_fn(double_fault_handler);
23+
idt[33].set_handler_fn(keyboard_interrupt_handler);
2324
IdtWrapper {
2425
idt: UnsafeCell::new(idt),
2526
loaded: UnsafeCell::new(false),
2627
}
2728
};
2829
}
30+
31+
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
32+
use x86_64::instructions::port::Port;
33+
34+
//Read scancode from keyboard port 0x60
35+
let mut port = Port::new(0x60);
36+
let scancode: u8 = unsafe { port.read() };
37+
38+
//Process scancode
39+
keyboard::handle_scancode(scancode);
40+
41+
//Send EOI to APIC directly
42+
unsafe {
43+
const APIC_EOI: *mut u32 = 0xFEE000B0 as *mut u32;
44+
APIC_EOI.write_volatile(0);
45+
}
46+
}
2947
extern "x86-interrupt" fn divide_by_zero_handler(_stack: InterruptStackFrame) {
30-
oops("Divide by Zero exception");
48+
oops("Divide by Zero exception");
3149
}
3250

3351
extern "x86-interrupt" fn page_fault_handler(stack: InterruptStackFrame, err: PageFaultErrorCode) {
34-
serial_println!(
52+
serial_println!(
3553
"Page fault at instruction pointer: {:#x}",
3654
stack.instruction_pointer.as_u64()
3755
);
3856

39-
let cr2: u64;
40-
unsafe {
41-
core::arch::asm!("mov {}, cr2", out(reg) cr2);
42-
}
43-
serial_println!("Page fault address: {:#x}", cr2);
44-
serial_println!("Error Code: {:?}", err);
57+
let cr2: u64;
58+
unsafe {
59+
core::arch::asm!("mov {}, cr2", out(reg) cr2);
60+
}
61+
serial_println!("Page fault address: {:#x}", cr2);
62+
serial_println!("Error Code: {:?}", err);
4563

46-
oops("Page fault exception");
64+
oops("Page fault exception");
4765
}
4866

4967
extern "x86-interrupt" fn double_fault_handler(_stack: InterruptStackFrame, _err: u64) -> ! {
50-
serial_println!(
68+
serial_println!(
5169
"Double fault at instruction pointer: {:#x}",
5270
_stack.instruction_pointer.as_u64()
5371
);
54-
panic!("Double fault exception");
72+
panic!("Double fault exception");
5573
}
5674

5775
pub fn init_idt() {
58-
unsafe {
59-
(*IDT.idt.get()).load();
60-
*IDT.loaded.get() = true;
61-
}
76+
unsafe {
77+
(*IDT.idt.get()).load();
78+
*IDT.loaded.get() = true;
79+
}
6280
}
6381

6482
pub fn register_interrupt_handler(
65-
vector: u8,
66-
handler: extern "x86-interrupt" fn(InterruptStackFrame),
83+
vector: u8,
84+
handler: extern "x86-interrupt" fn(InterruptStackFrame),
6785
) {
68-
unsafe {
69-
let idt = &mut *IDT.idt.get();
70-
idt[vector].set_handler_fn(handler);
71-
72-
// Only reload if IDT was already loaded
73-
if *IDT.loaded.get() {
74-
idt.load();
75-
}
76-
}
86+
unsafe {
87+
let idt = &mut *IDT.idt.get();
88+
idt[vector].set_handler_fn(handler);
89+
90+
// Only reload if IDT was already loaded
91+
if *IDT.loaded.get() {
92+
idt.load();
93+
}
94+
}
7795
}

kernel/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ hal = { path = "../hal" }
1212
idt = { path = "../idt" }
1313
memory = { path = "../memory" }
1414
util = { path = "../util" }
15-
task={ path = "../task" }
15+
task = { path = "../task" }
16+
keyboard = { path = "../keyboard" }
17+
1618
[profile.release]
1719
panic = "abort"
1820

kernel/src/main.rs

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ use hal::serial_println;
99
use limine::request::{FramebufferRequest, MemoryMapRequest};
1010
use limine::BaseRevision;
1111
use memory::heap::{init_heap, StaticBootFrameAllocator};
12-
use task::{SchedClass, Scheduler, TaskCB, TaskManager};
12+
use task::{Scheduler, TaskManager};
1313
use util::panic::halt_loop;
14+
use x86_64::instructions::hlt;
1415
use x86_64::structures::paging::PhysFrame;
1516
use x86_64::{PhysAddr, VirtAddr};
1617

@@ -65,12 +66,19 @@ pub extern "C" fn _start() -> ! {
6566
serial_println!("Serial console initialized");
6667

6768
unsafe {
68-
apic::enable();
69+
apic::enable(); // This now also disables PIC
70+
apic::ioapic::init_ioapic(); // Route IRQs through IOAPIC
6971
apic::timer::register_handler(); // Register timer handler before IDT is loaded
7072
}
7173

7274
idt::init_idt(); // Setup CPU exception handlers and load IDT
7375

76+
//Init keyboard
77+
hal::serial_println!("Keyboard ready for input!");
78+
79+
//Enable interrupts globally
80+
x86_64::instructions::interrupts::enable();
81+
7482
unsafe {
7583
apic::timer::init_hardware(); // Initialize timer hardware
7684
}
@@ -116,43 +124,10 @@ pub extern "C" fn _start() -> ! {
116124
fill_screen_blue(&fb);
117125
draw_memory_map(&fb, mmap_response.entries());
118126
}
119-
127+
120128
// Initialize global scheduler
121129
Scheduler::init_global();
122-
{
123-
let mut scheduler = Scheduler::global().lock();
124-
125-
// Allocate proper stacks for tasks (8KB each)
126-
const STACK_SIZE: usize = 8192;
127-
128-
// Create kernel boot task - this represents the current execution context
129-
let kernel_stack_mem = alloc::vec![0u8; STACK_SIZE];
130-
let kernel_stack = VirtAddr::from_ptr(kernel_stack_mem.as_ptr()) + STACK_SIZE as u64;
131-
core::mem::forget(kernel_stack_mem);
132-
133-
let idle_stack_mem = alloc::vec![0u8; STACK_SIZE];
134-
let task1_stack_mem = alloc::vec![0u8; STACK_SIZE];
135-
136-
// Get stack top addresses (stacks grow downward)
137-
let idle_stack = VirtAddr::from_ptr(idle_stack_mem.as_ptr()) + STACK_SIZE as u64;
138-
let task1_stack = VirtAddr::from_ptr(task1_stack_mem.as_ptr()) + STACK_SIZE as u64;
139-
140-
// Prevent stack memory from being deallocated
141-
core::mem::forget(idle_stack_mem);
142-
core::mem::forget(task1_stack_mem);
143-
144-
// Add kernel boot task as first task (will never actually run, just holds boot context)
145-
let kernel_task = TaskCB::new("kernel_boot", idle_task, kernel_stack, SchedClass::Fair(0));
146-
scheduler.add_task(kernel_task);
147-
148-
let idle = TaskCB::new("idle", idle_task, idle_stack, SchedClass::Fair(120));
149-
let task1 = TaskCB::new("task1", task1, task1_stack, SchedClass::Fair(110));
150-
scheduler.add_task(idle);
151-
scheduler.add_task(task1);
152-
153-
serial_println!("Starting scheduler with {} tasks", scheduler.task_count());
154-
} // Lock is dropped here
155-
156-
// Jump into scheduler - this never returns
157-
unsafe { Scheduler::start(); }
130+
loop {
131+
hlt()
132+
}
158133
}

keyboard/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "keyboard"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
hal = { path = "../hal" }
8+
x86_64 = "0.15.2"

keyboard/src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#![no_std]
2+
3+
//Basic US QWERTY scancode to ASCII mapping
4+
const SCANDCODE_TO_ASCII: [u8; 128] = [
5+
0, 27, b'1', b'2', b'3', b'4', b'5', b'6', // 0x00-0x07
6+
b'7', b'8', b'9', b'0', b'-', b'=', 8, b'\t', // 0x08-0x0F (backspace=8)
7+
b'q', b'w', b'e', b'r', b't', b'y', b'u', b'i', // 0x10-0x17
8+
b'o', b'p', b'[', b']', b'\n', 0, b'a', b's', // 0x18-0x1F (enter=\n, ctrl=0)
9+
b'd', b'f', b'g', b'h', b'j', b'k', b'l', b';', // 0x20-0x27
10+
b'\'', b'`', 0, b'\\', b'z', b'x', b'c', b'v', // 0x28-0x2F (lshift=0)
11+
b'b', b'n', b'm', b',', b'.', b'/', 0, b'*', // 0x30-0x37 (rshift=0)
12+
0, b' ', 0, 0, 0, 0, 0, 0, // 0x38-0x3F (alt=0, space)
13+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40-0x4F
14+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x50-0x5F
15+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60-0x6F
16+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70-0x7F
17+
];
18+
19+
pub fn handle_scancode(scancode: u8) {
20+
if scancode & 0x80 != 0 {
21+
return; //Break code, ignore for now
22+
}
23+
24+
if let Some(&ascii) = SCANDCODE_TO_ASCII.get(scancode as usize) {
25+
if ascii != 0 {
26+
hal::serial_print!("{}", ascii as char);
27+
}
28+
}
29+
}
30+
31+
pub fn enable_keyboard_interrupt() {
32+
unsafe {
33+
let mut port = x86_64::instructions::port::Port::new(0x21); //PIC1 data port
34+
let mask: u8 = port.read();
35+
port.write(mask & !0x02); //Clear bit 1 (IRQ1)
36+
}
37+
}

0 commit comments

Comments
 (0)