+6
-1
.claude/settings.local.json
+6
-1
.claude/settings.local.json
···
7
7
"Bash(cargo tree:*)",
8
8
"Bash(grep:*)",
9
9
"Bash(1)",
10
-
"Bash(cargo clippy:*)"
10
+
"Bash(cargo clippy:*)",
11
+
"Bash(powershell -Command \"(Get-Content heartwood/src/attunement/idt.rs) -replace ''unsafe extern \"\"C\"\" fn'', ''extern \"\"C\"\" fn'' | Set-Content heartwood/src/attunement/idt.rs\")",
12
+
"Bash(powershell -Command \"\n$content = Get-Content idt.rs -Raw\n# Replace asm! with naked_asm! inside naked functions, but keep regular asm! elsewhere\n$content = $content -replace ''(?s)(#\\[unsafe\\(naked\\)\\].*?extern \"\"C\"\" fn.*?\\{.*?unsafe \\{)\\s*asm!'', ''$1 core::arch::naked_asm!''\nSet-Content idt.rs $content\n\")",
13
+
"Bash(powershell -Command \"(Get-Content idt_handlers.rs) -replace ''println!'', ''crate::println!'' | Set-Content idt_handlers.rs\")",
14
+
"Bash(powershell -Command \"(Get-Content idt_handlers.rs) -replace ''\\!'', ''!'' | Set-Content idt_handlers.rs\")",
15
+
"Bash(powershell -Command \"$content = (Get-Content idt_handlers.rs -Raw); $content = $content -replace ''\\\\!'', ''!''; Set-Content idt_handlers.rs $content\")"
11
16
],
12
17
"deny": [],
13
18
"ask": []
+272
aethelos-source/heartwood/src/attunement/gdt.rs
+272
aethelos-source/heartwood/src/attunement/gdt.rs
···
1
+
//! # Global Descriptor Table
2
+
//!
3
+
//! The GDT defines the privilege boundaries of AethelOS.
4
+
//! Unlike traditional systems that enforce rigid hierarchies,
5
+
//! our rings exist in symbiosis - Ring 0 (kernel) and Ring 3 (user)
6
+
//! dance together in mutual respect.
7
+
//!
8
+
//! ## Philosophy
9
+
//! Privilege is not dominion, but responsibility.
10
+
//! The kernel serves userspace as much as userspace relies on the kernel.
11
+
12
+
use core::arch::asm;
13
+
use core::mem::size_of;
14
+
15
+
/// GDT Entry - A segment descriptor
16
+
#[derive(Debug, Clone, Copy)]
17
+
#[repr(C, packed)]
18
+
pub struct GdtEntry {
19
+
limit_low: u16,
20
+
base_low: u16,
21
+
base_middle: u8,
22
+
access: u8,
23
+
granularity: u8,
24
+
base_high: u8,
25
+
}
26
+
27
+
impl GdtEntry {
28
+
/// Create a null descriptor
29
+
pub const fn null() -> Self {
30
+
GdtEntry {
31
+
limit_low: 0,
32
+
base_low: 0,
33
+
base_middle: 0,
34
+
access: 0,
35
+
granularity: 0,
36
+
base_high: 0,
37
+
}
38
+
}
39
+
40
+
/// Create a kernel code segment (Ring 0, executable, readable)
41
+
pub const fn kernel_code() -> Self {
42
+
GdtEntry {
43
+
limit_low: 0xFFFF,
44
+
base_low: 0,
45
+
base_middle: 0,
46
+
access: 0b10011010, // Present, Ring 0, Code, Execute/Read
47
+
granularity: 0b10101111, // 4KB granularity, 64-bit, limit high = 0xF
48
+
base_high: 0,
49
+
}
50
+
}
51
+
52
+
/// Create a kernel data segment (Ring 0, writable)
53
+
pub const fn kernel_data() -> Self {
54
+
GdtEntry {
55
+
limit_low: 0xFFFF,
56
+
base_low: 0,
57
+
base_middle: 0,
58
+
access: 0b10010010, // Present, Ring 0, Data, Read/Write
59
+
granularity: 0b11001111, // 4KB granularity, 32-bit, limit high = 0xF
60
+
base_high: 0,
61
+
}
62
+
}
63
+
64
+
/// Create a user code segment (Ring 3, executable, readable)
65
+
pub const fn user_code() -> Self {
66
+
GdtEntry {
67
+
limit_low: 0xFFFF,
68
+
base_low: 0,
69
+
base_middle: 0,
70
+
access: 0b11111010, // Present, Ring 3, Code, Execute/Read
71
+
granularity: 0b10101111, // 4KB granularity, 64-bit, limit high = 0xF
72
+
base_high: 0,
73
+
}
74
+
}
75
+
76
+
/// Create a user data segment (Ring 3, writable)
77
+
pub const fn user_data() -> Self {
78
+
GdtEntry {
79
+
limit_low: 0xFFFF,
80
+
base_low: 0,
81
+
base_middle: 0,
82
+
access: 0b11110010, // Present, Ring 3, Data, Read/Write
83
+
granularity: 0b11001111, // 4KB granularity, 32-bit, limit high = 0xF
84
+
base_high: 0,
85
+
}
86
+
}
87
+
88
+
/// Create a TSS descriptor from a TSS reference
89
+
pub fn tss(tss: &'static TaskStateSegment) -> [GdtEntry; 2] {
90
+
let ptr = tss as *const _ as u64;
91
+
let limit = (size_of::<TaskStateSegment>() - 1) as u64;
92
+
93
+
let low = GdtEntry {
94
+
limit_low: (limit & 0xFFFF) as u16,
95
+
base_low: (ptr & 0xFFFF) as u16,
96
+
base_middle: ((ptr >> 16) & 0xFF) as u8,
97
+
access: 0b10001001, // Present, Ring 0, TSS (available)
98
+
granularity: ((limit >> 16) & 0x0F) as u8,
99
+
base_high: ((ptr >> 24) & 0xFF) as u8,
100
+
};
101
+
102
+
let high = GdtEntry {
103
+
limit_low: ((ptr >> 32) & 0xFFFF) as u16,
104
+
base_low: ((ptr >> 48) & 0xFFFF) as u16,
105
+
base_middle: 0,
106
+
access: 0,
107
+
granularity: 0,
108
+
base_high: 0,
109
+
};
110
+
111
+
[low, high]
112
+
}
113
+
}
114
+
115
+
/// Task State Segment - Holds stack pointers for privilege transitions
116
+
#[derive(Debug, Clone, Copy)]
117
+
#[repr(C, packed)]
118
+
pub struct TaskStateSegment {
119
+
reserved_1: u32,
120
+
/// Privilege stack pointers (RSP for rings 0-2)
121
+
pub rsp: [u64; 3],
122
+
reserved_2: u64,
123
+
/// Interrupt stack table (IST 1-7)
124
+
pub ist: [u64; 7],
125
+
reserved_3: u64,
126
+
reserved_4: u16,
127
+
/// I/O Map Base Address
128
+
pub iomap_base: u16,
129
+
}
130
+
131
+
impl TaskStateSegment {
132
+
/// Create a new TSS with default values
133
+
pub const fn new() -> Self {
134
+
TaskStateSegment {
135
+
reserved_1: 0,
136
+
rsp: [0; 3],
137
+
reserved_2: 0,
138
+
ist: [0; 7],
139
+
reserved_3: 0,
140
+
reserved_4: 0,
141
+
iomap_base: size_of::<TaskStateSegment>() as u16,
142
+
}
143
+
}
144
+
145
+
/// Set the kernel stack pointer (used when switching from Ring 3 to Ring 0)
146
+
pub fn set_kernel_stack(&mut self, stack_ptr: u64) {
147
+
self.rsp[0] = stack_ptr;
148
+
}
149
+
150
+
/// Set an interrupt stack (IST entry)
151
+
pub fn set_interrupt_stack(&mut self, index: usize, stack_ptr: u64) {
152
+
if index < 7 {
153
+
self.ist[index] = stack_ptr;
154
+
}
155
+
}
156
+
}
157
+
158
+
/// GDT Pointer structure for LGDT instruction
159
+
#[repr(C, packed)]
160
+
struct GdtPointer {
161
+
limit: u16,
162
+
base: u64,
163
+
}
164
+
165
+
/// The Global Descriptor Table
166
+
#[repr(align(16))]
167
+
pub struct GlobalDescriptorTable {
168
+
entries: [GdtEntry; 8], // Null, K_Code, K_Data, U_Code, U_Data, TSS_Low, TSS_High, Reserved
169
+
len: usize,
170
+
}
171
+
172
+
impl GlobalDescriptorTable {
173
+
/// Create a new GDT with default segments
174
+
pub const fn new() -> Self {
175
+
GlobalDescriptorTable {
176
+
entries: [GdtEntry::null(); 8],
177
+
len: 1, // Start with null descriptor
178
+
}
179
+
}
180
+
181
+
/// Initialize the GDT with kernel and user segments
182
+
pub fn initialize(&mut self, tss: &'static TaskStateSegment) {
183
+
self.len = 1; // Reset to just null descriptor
184
+
185
+
// Add kernel code segment (index 1, selector 0x08)
186
+
self.entries[self.len] = GdtEntry::kernel_code();
187
+
self.len += 1;
188
+
189
+
// Add kernel data segment (index 2, selector 0x10)
190
+
self.entries[self.len] = GdtEntry::kernel_data();
191
+
self.len += 1;
192
+
193
+
// Add user code segment (index 3, selector 0x18)
194
+
self.entries[self.len] = GdtEntry::user_code();
195
+
self.len += 1;
196
+
197
+
// Add user data segment (index 4, selector 0x20)
198
+
self.entries[self.len] = GdtEntry::user_data();
199
+
self.len += 1;
200
+
201
+
// Add TSS (takes 2 entries in 64-bit mode)
202
+
let tss_entries = GdtEntry::tss(tss);
203
+
self.entries[self.len] = tss_entries[0];
204
+
self.len += 1;
205
+
self.entries[self.len] = tss_entries[1];
206
+
self.len += 1;
207
+
}
208
+
209
+
/// Load the GDT and reload segment registers
210
+
pub fn load(&'static self) {
211
+
let ptr = GdtPointer {
212
+
limit: (self.len * size_of::<GdtEntry>() - 1) as u16,
213
+
base: self.entries.as_ptr() as u64,
214
+
};
215
+
216
+
unsafe {
217
+
// Load GDT
218
+
asm!("lgdt [{}]", in(reg) &ptr, options(readonly, nostack, preserves_flags));
219
+
220
+
// Reload segment registers
221
+
// Code segment via far return
222
+
asm!(
223
+
"push 0x08", // Push kernel code selector
224
+
"lea {tmp}, [rip + 2f]",
225
+
"push {tmp}",
226
+
"retfq", // Far return to reload CS
227
+
"2:",
228
+
tmp = lateout(reg) _,
229
+
options(preserves_flags),
230
+
);
231
+
232
+
// Data segments
233
+
asm!(
234
+
"mov ax, 0x10", // Kernel data selector
235
+
"mov ds, ax",
236
+
"mov es, ax",
237
+
"mov fs, ax",
238
+
"mov gs, ax",
239
+
"mov ss, ax",
240
+
out("ax") _,
241
+
options(preserves_flags, nostack),
242
+
);
243
+
}
244
+
}
245
+
246
+
/// Load the Task State Segment
247
+
pub fn load_tss(&self) {
248
+
unsafe {
249
+
// TSS selector is at index 5, so selector = 5 * 8 = 0x28
250
+
asm!("ltr ax", in("ax") 0x28u16, options(nostack, preserves_flags));
251
+
}
252
+
}
253
+
}
254
+
255
+
/// Segment selectors
256
+
#[allow(dead_code)]
257
+
pub mod selectors {
258
+
/// Kernel code segment selector (Ring 0)
259
+
pub const KERNEL_CODE: u16 = 0x08;
260
+
261
+
/// Kernel data segment selector (Ring 0)
262
+
pub const KERNEL_DATA: u16 = 0x10;
263
+
264
+
/// User code segment selector (Ring 3)
265
+
pub const USER_CODE: u16 = 0x18 | 3; // RPL = 3
266
+
267
+
/// User data segment selector (Ring 3)
268
+
pub const USER_DATA: u16 = 0x20 | 3; // RPL = 3
269
+
270
+
/// TSS selector
271
+
pub const TSS: u16 = 0x28;
272
+
}
+174
aethelos-source/heartwood/src/attunement/idt.rs
+174
aethelos-source/heartwood/src/attunement/idt.rs
···
1
+
//! # The Interrupt Conduit
2
+
//!
3
+
//! Where hardware whispers to the kernel.
4
+
//! Interrupts are invitations, not intrusions.
5
+
//! Each interrupt is a message from hardware seeking harmony.
6
+
7
+
use core::arch::asm;
8
+
use core::mem::size_of;
9
+
10
+
#[path = "idt_handlers.rs"]
11
+
pub(super) mod idt_handlers;
12
+
13
+
/// The Interrupt Descriptor Table - 256 entries
14
+
pub struct InterruptDescriptorTable {
15
+
entries: [IdtEntry; 256],
16
+
}
17
+
18
+
/// A single entry in the IDT
19
+
#[derive(Clone, Copy)]
20
+
#[repr(C, packed)]
21
+
struct IdtEntry {
22
+
offset_low: u16, // Lower 16 bits of handler address
23
+
selector: u16, // Code segment selector
24
+
ist: u8, // Interrupt Stack Table offset (0 = don't use)
25
+
flags: u8, // Type and attributes
26
+
offset_mid: u16, // Middle 16 bits of handler address
27
+
offset_high: u32, // Upper 32 bits of handler address
28
+
reserved: u32, // Must be zero
29
+
}
30
+
31
+
/// IDT Pointer structure for loading
32
+
#[repr(C, packed)]
33
+
struct IdtPointer {
34
+
limit: u16,
35
+
base: u64,
36
+
}
37
+
38
+
impl IdtEntry {
39
+
/// Create a null entry
40
+
const fn null() -> Self {
41
+
Self {
42
+
offset_low: 0,
43
+
selector: 0,
44
+
ist: 0,
45
+
flags: 0,
46
+
offset_mid: 0,
47
+
offset_high: 0,
48
+
reserved: 0,
49
+
}
50
+
}
51
+
52
+
/// Create an interrupt gate entry
53
+
fn new(handler: usize, selector: u16) -> Self {
54
+
Self {
55
+
offset_low: (handler & 0xFFFF) as u16,
56
+
selector,
57
+
ist: 0,
58
+
flags: 0x8E, // Present, DPL=0, Type=Interrupt Gate (0x8E)
59
+
offset_mid: ((handler >> 16) & 0xFFFF) as u16,
60
+
offset_high: ((handler >> 32) & 0xFFFFFFFF) as u32,
61
+
reserved: 0,
62
+
}
63
+
}
64
+
65
+
/// Create a trap gate entry (for exceptions)
66
+
fn new_trap(handler: usize, selector: u16) -> Self {
67
+
Self {
68
+
offset_low: (handler & 0xFFFF) as u16,
69
+
selector,
70
+
ist: 0,
71
+
flags: 0x8F, // Present, DPL=0, Type=Trap Gate (0x8F)
72
+
offset_mid: ((handler >> 16) & 0xFFFF) as u16,
73
+
offset_high: ((handler >> 32) & 0xFFFFFFFF) as u32,
74
+
reserved: 0,
75
+
}
76
+
}
77
+
}
78
+
79
+
impl InterruptDescriptorTable {
80
+
/// Create a new IDT with all null entries
81
+
pub const fn new() -> Self {
82
+
Self {
83
+
entries: [IdtEntry::null(); 256],
84
+
}
85
+
}
86
+
87
+
/// Attune to hardware interrupts and exceptions
88
+
pub fn initialize(&mut self) {
89
+
use idt_handlers::*;
90
+
91
+
// CPU Exceptions (0-31) - These are disharmony alerts
92
+
self.set_trap_handler(0, divide_by_zero_handler);
93
+
self.set_trap_handler(1, debug_handler);
94
+
self.set_trap_handler(2, nmi_handler);
95
+
self.set_trap_handler(3, breakpoint_handler);
96
+
self.set_trap_handler(4, overflow_handler);
97
+
self.set_trap_handler(5, bound_range_handler);
98
+
self.set_trap_handler(6, invalid_opcode_handler);
99
+
self.set_trap_handler(7, device_not_available_handler);
100
+
self.set_trap_handler(8, double_fault_handler);
101
+
// 9 is reserved (legacy)
102
+
self.set_trap_handler(10, invalid_tss_handler);
103
+
self.set_trap_handler(11, segment_not_present_handler);
104
+
self.set_trap_handler(12, stack_segment_fault_handler);
105
+
self.set_trap_handler(13, general_protection_fault_handler);
106
+
self.set_trap_handler(14, page_fault_handler);
107
+
// 15 is reserved
108
+
self.set_trap_handler(16, x87_floating_point_handler);
109
+
self.set_trap_handler(17, alignment_check_handler);
110
+
self.set_trap_handler(18, machine_check_handler);
111
+
self.set_trap_handler(19, simd_floating_point_handler);
112
+
self.set_trap_handler(20, virtualization_handler);
113
+
// 21-29 are reserved
114
+
self.set_trap_handler(30, security_exception_handler);
115
+
// 31 is reserved
116
+
117
+
// Hardware Interrupts (32-47) - IRQs remapped from PIC
118
+
// These are invitations from hardware
119
+
self.set_interrupt_handler(32, timer_handler); // IRQ 0 - The Pulse of Time
120
+
self.set_interrupt_handler(33, keyboard_handler); // IRQ 1 - The Voice of Intent
121
+
self.set_interrupt_handler(34, cascade_handler); // IRQ 2 - PIC cascade
122
+
self.set_interrupt_handler(35, com2_handler); // IRQ 3 - Serial port 2
123
+
self.set_interrupt_handler(36, com1_handler); // IRQ 4 - Serial port 1
124
+
self.set_interrupt_handler(37, lpt2_handler); // IRQ 5 - Parallel port 2
125
+
self.set_interrupt_handler(38, floppy_handler); // IRQ 6 - Floppy disk
126
+
self.set_interrupt_handler(39, lpt1_handler); // IRQ 7 - Parallel port 1
127
+
self.set_interrupt_handler(40, rtc_handler); // IRQ 8 - Real-time clock
128
+
self.set_interrupt_handler(41, acpi_handler); // IRQ 9 - ACPI
129
+
self.set_interrupt_handler(42, available1_handler); // IRQ 10 - Available
130
+
self.set_interrupt_handler(43, available2_handler); // IRQ 11 - Available
131
+
self.set_interrupt_handler(44, mouse_handler); // IRQ 12 - PS/2 Mouse
132
+
self.set_interrupt_handler(45, fpu_handler); // IRQ 13 - FPU
133
+
self.set_interrupt_handler(46, ata1_handler); // IRQ 14 - Primary ATA
134
+
self.set_interrupt_handler(47, ata2_handler); // IRQ 15 - Secondary ATA
135
+
136
+
// Software interrupts (48-255)
137
+
// Used for system calls and inter-process communication
138
+
self.set_interrupt_handler(128, syscall_handler); // Int 0x80 - System call
139
+
}
140
+
141
+
/// Set a trap gate handler (for exceptions)
142
+
fn set_trap_handler(&mut self, index: u8, handler: extern "C" fn()) {
143
+
let handler_addr = handler as usize;
144
+
self.entries[index as usize] = IdtEntry::new_trap(handler_addr, 0x08); // 0x08 = kernel code segment
145
+
}
146
+
147
+
/// Set an interrupt gate handler (for hardware interrupts)
148
+
fn set_interrupt_handler(&mut self, index: u8, handler: extern "C" fn()) {
149
+
let handler_addr = handler as usize;
150
+
self.entries[index as usize] = IdtEntry::new(handler_addr, 0x08); // 0x08 = kernel code segment
151
+
}
152
+
153
+
/// Load this IDT into the CPU
154
+
pub fn load(&self) {
155
+
let ptr = IdtPointer {
156
+
limit: (size_of::<Self>() - 1) as u16,
157
+
base: self as *const _ as u64,
158
+
};
159
+
160
+
unsafe {
161
+
asm!(
162
+
"lidt [{}]",
163
+
in(reg) &ptr,
164
+
options(readonly, nostack, preserves_flags)
165
+
);
166
+
}
167
+
}
168
+
}
169
+
170
+
impl Default for InterruptDescriptorTable {
171
+
fn default() -> Self {
172
+
Self::new()
173
+
}
174
+
}
+327
aethelos-source/heartwood/src/attunement/idt_handlers.rs
+327
aethelos-source/heartwood/src/attunement/idt_handlers.rs
···
1
+
//! Interrupt handler implementations
2
+
//!
3
+
//! These handlers are called when hardware or CPU exceptions occur.
4
+
5
+
use core::arch::{asm, naked_asm};
6
+
7
+
//
8
+
// Exception Handlers (0-31)
9
+
// These are disharmony alerts from the CPU
10
+
//
11
+
12
+
/// Division by zero - mathematical disharmony
13
+
#[unsafe(naked)]
14
+
pub extern "C" fn divide_by_zero_handler() {
15
+
unsafe {
16
+
naked_asm!(
17
+
"push rax",
18
+
"push rcx",
19
+
"push rdx",
20
+
"push rsi",
21
+
"push rdi",
22
+
"push r8",
23
+
"push r9",
24
+
"push r10",
25
+
"push r11",
26
+
"call {inner}",
27
+
"pop r11",
28
+
"pop r10",
29
+
"pop r9",
30
+
"pop r8",
31
+
"pop rdi",
32
+
"pop rsi",
33
+
"pop rdx",
34
+
"pop rcx",
35
+
"pop rax",
36
+
"iretq",
37
+
inner = sym divide_by_zero_inner,
38
+
);
39
+
}
40
+
}
41
+
42
+
extern "C" fn divide_by_zero_inner() {
43
+
crate::println!("❖ Disharmony: Division by zero attempted");
44
+
crate::println!("❖ The void cannot be divided.");
45
+
}
46
+
47
+
/// General Protection Fault - privilege violation
48
+
#[unsafe(naked)]
49
+
pub extern "C" fn general_protection_fault_handler() {
50
+
unsafe {
51
+
naked_asm!(
52
+
"push rax",
53
+
"push rcx",
54
+
"push rdx",
55
+
"push rsi",
56
+
"push rdi",
57
+
"push r8",
58
+
"push r9",
59
+
"push r10",
60
+
"push r11",
61
+
"call {inner}",
62
+
"pop r11",
63
+
"pop r10",
64
+
"pop r9",
65
+
"pop r8",
66
+
"pop rdi",
67
+
"pop rsi",
68
+
"pop rdx",
69
+
"pop rcx",
70
+
"pop rax",
71
+
"add rsp, 8",
72
+
"iretq",
73
+
inner = sym general_protection_fault_inner,
74
+
);
75
+
}
76
+
}
77
+
78
+
extern "C" fn general_protection_fault_inner() {
79
+
crate::println!("❖ Disharmony: General protection fault");
80
+
crate::println!("❖ A boundary has been crossed without permission.");
81
+
}
82
+
83
+
/// Page Fault - memory seeking
84
+
#[unsafe(naked)]
85
+
pub extern "C" fn page_fault_handler() {
86
+
unsafe {
87
+
naked_asm!(
88
+
"push rax",
89
+
"push rcx",
90
+
"push rdx",
91
+
"push rsi",
92
+
"push rdi",
93
+
"push r8",
94
+
"push r9",
95
+
"push r10",
96
+
"push r11",
97
+
"call {inner}",
98
+
"pop r11",
99
+
"pop r10",
100
+
"pop r9",
101
+
"pop r8",
102
+
"pop rdi",
103
+
"pop rsi",
104
+
"pop rdx",
105
+
"pop rcx",
106
+
"pop rax",
107
+
"add rsp, 8",
108
+
"iretq",
109
+
inner = sym page_fault_inner,
110
+
);
111
+
}
112
+
}
113
+
114
+
extern "C" fn page_fault_inner() {
115
+
let faulting_address: u64;
116
+
unsafe {
117
+
asm!("mov {}, cr2", out(reg) faulting_address, options(nostack, preserves_flags));
118
+
}
119
+
crate::println!("❖ Disharmony: Page fault at address {:#x}", faulting_address);
120
+
crate::println!("❖ Memory seeks what has not been granted.");
121
+
}
122
+
123
+
/// Double Fault - cascade of disharmony
124
+
#[unsafe(naked)]
125
+
pub extern "C" fn double_fault_handler() {
126
+
unsafe {
127
+
naked_asm!(
128
+
"cli",
129
+
"call {inner}",
130
+
inner = sym double_fault_inner,
131
+
);
132
+
}
133
+
}
134
+
135
+
extern "C" fn double_fault_inner() -> ! {
136
+
crate::println!("❖❖❖ CRITICAL DISHARMONY: Double Fault ❖❖❖");
137
+
crate::println!("❖ The system has lost its balance.");
138
+
crate::println!("❖ The Heartwood must rest and be reborn.");
139
+
loop {
140
+
unsafe { asm!("hlt", options(nostack, nomem)) };
141
+
}
142
+
}
143
+
144
+
// Stub handlers for other exceptions
145
+
macro_rules! stub_exception_handler {
146
+
($name:ident, $message:expr) => {
147
+
#[unsafe(naked)]
148
+
pub extern "C" fn $name() {
149
+
unsafe {
150
+
naked_asm!(
151
+
"iretq"
152
+
);
153
+
}
154
+
}
155
+
};
156
+
}
157
+
158
+
stub_exception_handler!(debug_handler, "Debug exception");
159
+
stub_exception_handler!(nmi_handler, "Non-maskable interrupt");
160
+
stub_exception_handler!(breakpoint_handler, "Breakpoint");
161
+
stub_exception_handler!(overflow_handler, "Overflow");
162
+
stub_exception_handler!(bound_range_handler, "Bound range exceeded");
163
+
stub_exception_handler!(invalid_opcode_handler, "Invalid opcode");
164
+
stub_exception_handler!(device_not_available_handler, "Device not available");
165
+
stub_exception_handler!(invalid_tss_handler, "Invalid TSS");
166
+
stub_exception_handler!(segment_not_present_handler, "Segment not present");
167
+
stub_exception_handler!(stack_segment_fault_handler, "Stack segment fault");
168
+
stub_exception_handler!(x87_floating_point_handler, "x87 FPU error");
169
+
stub_exception_handler!(alignment_check_handler, "Alignment check");
170
+
stub_exception_handler!(machine_check_handler, "Machine check");
171
+
stub_exception_handler!(simd_floating_point_handler, "SIMD floating point");
172
+
stub_exception_handler!(virtualization_handler, "Virtualization exception");
173
+
stub_exception_handler!(security_exception_handler, "Security exception");
174
+
175
+
//
176
+
// Hardware Interrupt Handlers (32-47)
177
+
// These are invitations from hardware
178
+
//
179
+
180
+
/// Timer interrupt - The Pulse of Time
181
+
#[unsafe(naked)]
182
+
pub extern "C" fn timer_handler() {
183
+
unsafe {
184
+
naked_asm!(
185
+
"push rax",
186
+
"push rcx",
187
+
"push rdx",
188
+
"push rsi",
189
+
"push rdi",
190
+
"push r8",
191
+
"push r9",
192
+
"push r10",
193
+
"push r11",
194
+
"call {inner}",
195
+
"mov al, 0x20",
196
+
"out 0x20, al",
197
+
"pop r11",
198
+
"pop r10",
199
+
"pop r9",
200
+
"pop r8",
201
+
"pop rdi",
202
+
"pop rsi",
203
+
"pop rdx",
204
+
"pop rcx",
205
+
"pop rax",
206
+
"iretq",
207
+
inner = sym timer_inner,
208
+
);
209
+
}
210
+
}
211
+
212
+
extern "C" fn timer_inner() {
213
+
// Will be connected to PIT driver
214
+
crate::attunement::timer::on_tick();
215
+
}
216
+
217
+
/// Keyboard interrupt - The Voice of Intent
218
+
#[unsafe(naked)]
219
+
pub extern "C" fn keyboard_handler() {
220
+
unsafe {
221
+
naked_asm!(
222
+
"push rax",
223
+
"push rcx",
224
+
"push rdx",
225
+
"push rsi",
226
+
"push rdi",
227
+
"push r8",
228
+
"push r9",
229
+
"push r10",
230
+
"push r11",
231
+
"call {inner}",
232
+
"mov al, 0x20",
233
+
"out 0x20, al",
234
+
"pop r11",
235
+
"pop r10",
236
+
"pop r9",
237
+
"pop r8",
238
+
"pop rdi",
239
+
"pop rsi",
240
+
"pop rdx",
241
+
"pop rcx",
242
+
"pop rax",
243
+
"iretq",
244
+
inner = sym keyboard_inner,
245
+
);
246
+
}
247
+
}
248
+
249
+
extern "C" fn keyboard_inner() {
250
+
// Will be connected to keyboard driver
251
+
// For now, just read and discard the scancode
252
+
unsafe {
253
+
let scancode: u8;
254
+
asm!("in al, 0x60", out("al") scancode, options(nostack, preserves_flags));
255
+
// TODO: Send to keyboard driver via Nexus
256
+
}
257
+
}
258
+
259
+
// Stub handlers for other hardware interrupts
260
+
macro_rules! stub_irq_handler {
261
+
($name:ident, $irq:expr) => {
262
+
#[unsafe(naked)]
263
+
pub extern "C" fn $name() {
264
+
unsafe {
265
+
naked_asm!(
266
+
"push rax",
267
+
"mov al, 0x20",
268
+
"out 0x20, al",
269
+
concat!(".if ", stringify!($irq), " >= 8"),
270
+
"out 0xA0, al",
271
+
".endif",
272
+
"pop rax",
273
+
"iretq",
274
+
);
275
+
}
276
+
}
277
+
};
278
+
}
279
+
280
+
stub_irq_handler!(cascade_handler, 2);
281
+
stub_irq_handler!(com2_handler, 3);
282
+
stub_irq_handler!(com1_handler, 4);
283
+
stub_irq_handler!(lpt2_handler, 5);
284
+
stub_irq_handler!(floppy_handler, 6);
285
+
stub_irq_handler!(lpt1_handler, 7);
286
+
stub_irq_handler!(rtc_handler, 8);
287
+
stub_irq_handler!(acpi_handler, 9);
288
+
stub_irq_handler!(available1_handler, 10);
289
+
stub_irq_handler!(available2_handler, 11);
290
+
stub_irq_handler!(mouse_handler, 12);
291
+
stub_irq_handler!(fpu_handler, 13);
292
+
stub_irq_handler!(ata1_handler, 14);
293
+
stub_irq_handler!(ata2_handler, 15);
294
+
295
+
/// System call handler (Int 0x80)
296
+
#[unsafe(naked)]
297
+
pub extern "C" fn syscall_handler() {
298
+
unsafe {
299
+
naked_asm!(
300
+
"push rax",
301
+
"push rcx",
302
+
"push rdx",
303
+
"push rsi",
304
+
"push rdi",
305
+
"push r8",
306
+
"push r9",
307
+
"push r10",
308
+
"push r11",
309
+
"call {inner}",
310
+
"pop r11",
311
+
"pop r10",
312
+
"pop r9",
313
+
"pop r8",
314
+
"pop rdi",
315
+
"pop rsi",
316
+
"pop rdx",
317
+
"pop rcx",
318
+
"pop rax",
319
+
"iretq",
320
+
inner = sym syscall_inner,
321
+
);
322
+
}
323
+
}
324
+
325
+
extern "C" fn syscall_inner() {
326
+
crate::println!("❖ System call received (not yet implemented)");
327
+
}
+120
-12
aethelos-source/heartwood/src/attunement/mod.rs
+120
-12
aethelos-source/heartwood/src/attunement/mod.rs
···
9
9
//! The Attunement Layer speaks the language of the hardware, translating
10
10
//! the Heartwood's intentions into signals the silicon can comprehend.
11
11
12
+
pub mod gdt;
13
+
pub mod idt;
14
+
pub mod pic;
15
+
pub mod pit;
16
+
17
+
use lazy_static::lazy_static;
18
+
use spin::Mutex;
19
+
20
+
lazy_static! {
21
+
/// The global Task State Segment
22
+
static ref TSS: gdt::TaskStateSegment = gdt::TaskStateSegment::new();
23
+
24
+
/// The global Global Descriptor Table
25
+
static ref GDT: gdt::GlobalDescriptorTable = {
26
+
let mut gdt = gdt::GlobalDescriptorTable::new();
27
+
gdt.initialize(&TSS);
28
+
gdt
29
+
};
30
+
31
+
/// The global Interrupt Descriptor Table
32
+
static ref IDT: Mutex<idt::InterruptDescriptorTable> = {
33
+
let mut idt = idt::InterruptDescriptorTable::new();
34
+
idt.initialize();
35
+
Mutex::new(idt)
36
+
};
37
+
38
+
/// The global PIC configuration
39
+
static ref PIC: Mutex<pic::Pic> = Mutex::new(pic::Pic::new(32, 40));
40
+
41
+
/// The global PIT configuration
42
+
static ref PIT: pit::Pit = pit::Pit::new();
43
+
}
44
+
12
45
/// Initialize the Attunement Layer
13
46
pub fn init() {
47
+
crate::println!(" ◈ Attuning to hardware...");
48
+
14
49
// Initialize CPU features
15
50
cpu::init();
16
51
52
+
// Load the Global Descriptor Table
53
+
crate::println!(" ∴ Loading privilege rings (GDT)...");
54
+
GDT.load();
55
+
GDT.load_tss();
56
+
57
+
// Initialize Programmable Interrupt Controller (PIC)
58
+
// Remap IRQs 0-15 to interrupts 32-47
59
+
crate::println!(" ∴ Remapping interrupt controller (PIC)...");
60
+
unsafe {
61
+
PIC.lock().initialize();
62
+
PIC.lock().disable_all(); // Start with all IRQs masked
63
+
}
64
+
65
+
// Load the Interrupt Descriptor Table
66
+
crate::println!(" ∴ Preparing interrupt handlers (IDT)...");
67
+
IDT.lock().load();
68
+
17
69
// Initialize interrupt handling
18
70
interrupts::init();
19
71
20
-
// Initialize system timer
72
+
// Initialize system timer (PIT)
73
+
crate::println!(" ∴ Starting system heartbeat (PIT @ {} Hz)...", PIT.frequency());
21
74
timer::init();
75
+
76
+
crate::println!(" ◈ Hardware attunement complete");
22
77
}
23
78
24
79
/// CPU information and features
···
49
104
50
105
/// Interrupt handling
51
106
pub mod interrupts {
107
+
use core::arch::asm;
108
+
52
109
/// Initialize interrupt handling
53
110
pub fn init() {
54
-
// In a real implementation:
55
-
// - Set up IDT (Interrupt Descriptor Table)
56
-
// - Install interrupt handlers
57
-
// - Enable interrupts
111
+
// IDT is already loaded by main init()
112
+
// Enable interrupts
113
+
unsafe {
114
+
enable();
115
+
}
58
116
}
59
117
60
118
/// Disable interrupts
61
-
pub fn disable() {
62
-
// x86_64::instructions::interrupts::disable();
119
+
pub unsafe fn disable() {
120
+
asm!("cli", options(nostack, nomem));
63
121
}
64
122
65
123
/// Enable interrupts
66
-
pub fn enable() {
67
-
// x86_64::instructions::interrupts::enable();
124
+
pub unsafe fn enable() {
125
+
asm!("sti", options(nostack, nomem));
126
+
}
127
+
128
+
/// Check if interrupts are enabled
129
+
pub fn are_enabled() -> bool {
130
+
let flags: u64;
131
+
unsafe {
132
+
asm!("pushf; pop {}", out(reg) flags, options(nomem, preserves_flags));
133
+
}
134
+
(flags & 0x200) != 0 // IF flag is bit 9
68
135
}
69
136
}
70
137
···
75
142
76
143
lazy_static! {
77
144
static ref TICKS: Mutex<u64> = Mutex::new(0);
145
+
static ref UPTIME_MS: Mutex<u64> = Mutex::new(0);
78
146
}
79
147
80
148
/// Initialize the system timer
81
149
pub fn init() {
82
-
// In a real implementation:
83
-
// - Configure PIT or APIC timer
84
-
// - Set up timer interrupt handler
150
+
// Enable timer IRQ (IRQ 0)
151
+
unsafe {
152
+
super::PIC.lock().enable_irq(0);
153
+
}
154
+
155
+
// Initialize the PIT to generate interrupts at 100 Hz
156
+
unsafe {
157
+
super::PIT.initialize();
158
+
}
85
159
}
86
160
87
161
/// Get the number of timer ticks since boot
···
89
163
*TICKS.lock()
90
164
}
91
165
166
+
/// Get uptime in milliseconds
167
+
pub fn uptime_ms() -> u64 {
168
+
*UPTIME_MS.lock()
169
+
}
170
+
171
+
/// Get uptime in seconds
172
+
pub fn uptime_seconds() -> u64 {
173
+
uptime_ms() / 1000
174
+
}
175
+
92
176
/// Timer interrupt handler (called by interrupt)
93
177
pub fn on_tick() {
94
178
let mut ticks = TICKS.lock();
95
179
*ticks += 1;
180
+
181
+
// Update uptime (100 Hz = 10ms per tick)
182
+
let mut uptime = UPTIME_MS.lock();
183
+
*uptime += super::pit::ms_per_tick(&super::PIT) as u64;
184
+
}
185
+
186
+
/// Sleep for approximately the given number of milliseconds
187
+
///
188
+
/// # Note
189
+
/// This is a busy-wait implementation. In a real system, this would
190
+
/// yield to the scheduler.
191
+
pub fn sleep_ms(ms: u64) {
192
+
let start = uptime_ms();
193
+
while uptime_ms() - start < ms {
194
+
core::hint::spin_loop();
195
+
}
196
+
}
197
+
198
+
/// Sleep for approximately the given number of ticks
199
+
pub fn sleep_ticks(ticks_to_wait: u64) {
200
+
let start = ticks();
201
+
while ticks() - start < ticks_to_wait {
202
+
core::hint::spin_loop();
203
+
}
96
204
}
97
205
}
+144
aethelos-source/heartwood/src/attunement/pic.rs
+144
aethelos-source/heartwood/src/attunement/pic.rs
···
1
+
//! # Programmable Interrupt Controller (PIC)
2
+
//!
3
+
//! The 8259 PIC manages hardware interrupts.
4
+
//! We remap IRQs 0-15 to interrupts 32-47 to avoid conflicts with CPU exceptions.
5
+
6
+
use core::arch::asm;
7
+
8
+
/// I/O ports for the PICs
9
+
const PIC1_COMMAND: u16 = 0x20;
10
+
const PIC1_DATA: u16 = 0x21;
11
+
const PIC2_COMMAND: u16 = 0xA0;
12
+
const PIC2_DATA: u16 = 0xA1;
13
+
14
+
/// PIC initialization command words
15
+
const ICW1_INIT: u8 = 0x10;
16
+
const ICW1_ICW4: u8 = 0x01;
17
+
const ICW4_8086: u8 = 0x01;
18
+
19
+
/// End of Interrupt command
20
+
const EOI: u8 = 0x20;
21
+
22
+
/// The dual 8259 PIC configuration
23
+
pub struct Pic {
24
+
offset1: u8, // Offset for PIC1 (usually 32)
25
+
offset2: u8, // Offset for PIC2 (usually 40)
26
+
}
27
+
28
+
impl Pic {
29
+
/// Create a new PIC configuration
30
+
pub const fn new(offset1: u8, offset2: u8) -> Self {
31
+
Self { offset1, offset2 }
32
+
}
33
+
34
+
/// Initialize and remap the PICs
35
+
///
36
+
/// # Safety
37
+
///
38
+
/// This must be called exactly once during system initialization.
39
+
/// Incorrect PIC configuration can cause system instability.
40
+
pub unsafe fn initialize(&self) {
41
+
// Save masks
42
+
let mask1 = inb(PIC1_DATA);
43
+
let mask2 = inb(PIC2_DATA);
44
+
45
+
// Start initialization sequence
46
+
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4);
47
+
io_wait();
48
+
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
49
+
io_wait();
50
+
51
+
// Set vector offsets
52
+
outb(PIC1_DATA, self.offset1);
53
+
io_wait();
54
+
outb(PIC2_DATA, self.offset2);
55
+
io_wait();
56
+
57
+
// Tell PIC1 about PIC2 (cascade)
58
+
outb(PIC1_DATA, 4); // PIC2 is at IRQ2
59
+
io_wait();
60
+
outb(PIC2_DATA, 2); // Cascade identity
61
+
io_wait();
62
+
63
+
// Set 8086 mode
64
+
outb(PIC1_DATA, ICW4_8086);
65
+
io_wait();
66
+
outb(PIC2_DATA, ICW4_8086);
67
+
io_wait();
68
+
69
+
// Restore masks
70
+
outb(PIC1_DATA, mask1);
71
+
outb(PIC2_DATA, mask2);
72
+
}
73
+
74
+
/// Disable all interrupts (mask all IRQs)
75
+
pub unsafe fn disable_all(&self) {
76
+
outb(PIC1_DATA, 0xFF);
77
+
outb(PIC2_DATA, 0xFF);
78
+
}
79
+
80
+
/// Enable specific IRQ
81
+
pub unsafe fn enable_irq(&self, irq: u8) {
82
+
let port = if irq < 8 {
83
+
PIC1_DATA
84
+
} else {
85
+
PIC2_DATA
86
+
};
87
+
88
+
let value = inb(port);
89
+
let mask = !(1 << (irq % 8));
90
+
outb(port, value & mask);
91
+
}
92
+
93
+
/// Disable specific IRQ
94
+
pub unsafe fn disable_irq(&self, irq: u8) {
95
+
let port = if irq < 8 {
96
+
PIC1_DATA
97
+
} else {
98
+
PIC2_DATA
99
+
};
100
+
101
+
let value = inb(port);
102
+
let mask = 1 << (irq % 8);
103
+
outb(port, value | mask);
104
+
}
105
+
106
+
/// Send End of Interrupt signal
107
+
pub unsafe fn send_eoi(&self, irq: u8) {
108
+
if irq >= 8 {
109
+
outb(PIC2_COMMAND, EOI);
110
+
}
111
+
outb(PIC1_COMMAND, EOI);
112
+
}
113
+
}
114
+
115
+
/// Read a byte from an I/O port
116
+
#[inline]
117
+
unsafe fn inb(port: u16) -> u8 {
118
+
let value: u8;
119
+
asm!(
120
+
"in al, dx",
121
+
out("al") value,
122
+
in("dx") port,
123
+
options(nostack, preserves_flags)
124
+
);
125
+
value
126
+
}
127
+
128
+
/// Write a byte to an I/O port
129
+
#[inline]
130
+
unsafe fn outb(port: u16, value: u8) {
131
+
asm!(
132
+
"out dx, al",
133
+
in("dx") port,
134
+
in("al") value,
135
+
options(nostack, preserves_flags)
136
+
);
137
+
}
138
+
139
+
/// Wait for an I/O operation to complete
140
+
/// Uses port 0x80 (POST diagnostic port) for a brief delay
141
+
#[inline]
142
+
unsafe fn io_wait() {
143
+
outb(0x80, 0);
144
+
}
+183
aethelos-source/heartwood/src/attunement/pit.rs
+183
aethelos-source/heartwood/src/attunement/pit.rs
···
1
+
//! # Programmable Interval Timer (PIT)
2
+
//!
3
+
//! The heartbeat of AethelOS - the system's sense of time's passage.
4
+
//! The PIT provides rhythmic interrupts that drive scheduling, timeouts,
5
+
//! and our awareness of temporal flow.
6
+
//!
7
+
//! ## Philosophy
8
+
//! Time in AethelOS is not a tyrant demanding rigid adherence,
9
+
//! but a gentle rhythm that guides cooperation.
10
+
//! The PIT ticks not to command, but to remind us that moments pass.
11
+
//!
12
+
//! ## Technical Details
13
+
//! The Intel 8253/8254 PIT has three channels:
14
+
//! - Channel 0: System timer (IRQ 0)
15
+
//! - Channel 1: DRAM refresh (legacy, unused)
16
+
//! - Channel 2: PC speaker
17
+
//!
18
+
//! We use Channel 0 in mode 3 (square wave generator) for periodic interrupts.
19
+
20
+
use core::arch::asm;
21
+
22
+
/// PIT I/O ports
23
+
const PIT_CHANNEL0: u16 = 0x40; // Channel 0 data port (system timer)
24
+
const PIT_CHANNEL1: u16 = 0x41; // Channel 1 data port (unused)
25
+
const PIT_CHANNEL2: u16 = 0x42; // Channel 2 data port (PC speaker)
26
+
const PIT_COMMAND: u16 = 0x43; // Mode/Command register
27
+
28
+
/// PIT base frequency (Hz)
29
+
const PIT_BASE_FREQ: u32 = 1193182;
30
+
31
+
/// Default timer frequency (100 Hz = 10ms per tick)
32
+
/// This provides a good balance between precision and overhead
33
+
const DEFAULT_FREQ: u32 = 100;
34
+
35
+
/// PIT command byte components
36
+
/// Format: [Channel (2 bits)][Access mode (2 bits)][Operating mode (3 bits)][BCD (1 bit)]
37
+
const CMD_CHANNEL0: u8 = 0b00_000000; // Select channel 0
38
+
const CMD_ACCESS_LOHI: u8 = 0b00_11_0000; // Access mode: lobyte/hibyte
39
+
const CMD_MODE3: u8 = 0b00_00_011_0; // Operating mode 3: square wave
40
+
const CMD_BINARY: u8 = 0b00_00_000_0; // Binary mode (not BCD)
41
+
42
+
/// Helper function to output a byte to an I/O port
43
+
#[inline]
44
+
unsafe fn outb(port: u16, value: u8) {
45
+
asm!("out dx, al", in("dx") port, in("al") value, options(nomem, nostack, preserves_flags));
46
+
}
47
+
48
+
/// Helper function to input a byte from an I/O port
49
+
#[inline]
50
+
unsafe fn inb(port: u16) -> u8 {
51
+
let value: u8;
52
+
asm!("in al, dx", out("al") value, in("dx") port, options(nomem, nostack, preserves_flags));
53
+
value
54
+
}
55
+
56
+
/// The Programmable Interval Timer
57
+
pub struct Pit {
58
+
frequency: u32,
59
+
divisor: u16,
60
+
ticks_per_second: u32,
61
+
}
62
+
63
+
impl Pit {
64
+
/// Create a new PIT configuration with default frequency
65
+
pub const fn new() -> Self {
66
+
let divisor = (PIT_BASE_FREQ / DEFAULT_FREQ) as u16;
67
+
Pit {
68
+
frequency: DEFAULT_FREQ,
69
+
divisor,
70
+
ticks_per_second: DEFAULT_FREQ,
71
+
}
72
+
}
73
+
74
+
/// Create a PIT with custom frequency
75
+
///
76
+
/// # Arguments
77
+
/// * `freq` - Desired frequency in Hz (18.2 Hz to 1.19 MHz)
78
+
///
79
+
/// # Note
80
+
/// The actual frequency may differ slightly due to integer division.
81
+
pub fn with_frequency(freq: u32) -> Self {
82
+
let freq = freq.clamp(18, PIT_BASE_FREQ);
83
+
let divisor = (PIT_BASE_FREQ / freq) as u16;
84
+
let actual_freq = PIT_BASE_FREQ / divisor as u32;
85
+
86
+
Pit {
87
+
frequency: actual_freq,
88
+
divisor,
89
+
ticks_per_second: actual_freq,
90
+
}
91
+
}
92
+
93
+
/// Initialize the PIT and start generating interrupts
94
+
///
95
+
/// # Safety
96
+
/// This function writes to I/O ports and should only be called once during boot.
97
+
pub unsafe fn initialize(&self) {
98
+
// Disable interrupts during PIT programming
99
+
asm!("cli", options(nomem, nostack));
100
+
101
+
// Send command byte: Channel 0, lobyte/hibyte, mode 3, binary
102
+
let command = CMD_CHANNEL0 | CMD_ACCESS_LOHI | CMD_MODE3 | CMD_BINARY;
103
+
outb(PIT_COMMAND, command);
104
+
105
+
// Send divisor (low byte, then high byte)
106
+
outb(PIT_CHANNEL0, (self.divisor & 0xFF) as u8);
107
+
outb(PIT_CHANNEL0, ((self.divisor >> 8) & 0xFF) as u8);
108
+
109
+
// Re-enable interrupts
110
+
asm!("sti", options(nomem, nostack));
111
+
}
112
+
113
+
/// Get the configured frequency in Hz
114
+
pub fn frequency(&self) -> u32 {
115
+
self.frequency
116
+
}
117
+
118
+
/// Get the divisor value
119
+
pub fn divisor(&self) -> u16 {
120
+
self.divisor
121
+
}
122
+
123
+
/// Get ticks per second (same as frequency)
124
+
pub fn ticks_per_second(&self) -> u32 {
125
+
self.ticks_per_second
126
+
}
127
+
128
+
/// Read the current counter value
129
+
///
130
+
/// # Safety
131
+
/// Reads from I/O ports. The value may be unreliable if called during a count update.
132
+
pub unsafe fn read_count(&self) -> u16 {
133
+
// Send latch command
134
+
outb(PIT_COMMAND, 0b00_00_0000);
135
+
136
+
// Read count (low byte, then high byte)
137
+
let low = inb(PIT_CHANNEL0) as u16;
138
+
let high = inb(PIT_CHANNEL0) as u16;
139
+
140
+
(high << 8) | low
141
+
}
142
+
}
143
+
144
+
/// Calculate milliseconds per tick for the current PIT frequency
145
+
pub fn ms_per_tick(pit: &Pit) -> u32 {
146
+
1000 / pit.frequency()
147
+
}
148
+
149
+
/// Calculate microseconds per tick for the current PIT frequency
150
+
pub fn us_per_tick(pit: &Pit) -> u32 {
151
+
1_000_000 / pit.frequency()
152
+
}
153
+
154
+
#[cfg(test)]
155
+
mod tests {
156
+
use super::*;
157
+
158
+
#[test]
159
+
fn test_divisor_calculation() {
160
+
let pit = Pit::new();
161
+
// For 100 Hz: 1193182 / 100 = 11931.82, should round to 11931
162
+
assert_eq!(pit.divisor(), 11931);
163
+
}
164
+
165
+
#[test]
166
+
fn test_frequency() {
167
+
let pit = Pit::new();
168
+
assert!(pit.frequency() >= 99 && pit.frequency() <= 101);
169
+
}
170
+
171
+
#[test]
172
+
fn test_custom_frequency() {
173
+
let pit = Pit::with_frequency(1000);
174
+
assert!(pit.frequency() >= 999 && pit.frequency() <= 1001);
175
+
}
176
+
177
+
#[test]
178
+
fn test_ms_per_tick() {
179
+
let pit = Pit::new();
180
+
let ms = ms_per_tick(&pit);
181
+
assert!(ms >= 9 && ms <= 11); // Should be ~10ms
182
+
}
183
+
}