This commit is contained in:
MeexReay 2025-02-02 20:43:39 +03:00
parent 70bd58e5ec
commit 75c2759e5a
12 changed files with 453 additions and 203 deletions

View File

@ -1,25 +1,25 @@
.PHONY: clean run run-iso build_dir
.PHONY: clean run run-iso build-dir clean-build
TARGET_DIR = target/x86-unknown-bare_metal/release
build/mxrox.iso: build/kernel.elf build_dir
build/mxrox.iso: build/kernel.elf build-dir
cp 'grub.cfg' build/iso/boot/grub
cp '$<' build/iso/boot
grub-mkrescue -o '$@' build/iso
build/boot.o: boot.s build_dir
build/boot.o: boot.s build-dir
nasm -f elf32 '$<' -o '$@'
build/kernel.elf: linker.ld build/boot.o build/libkernel.a
i686-elf-gcc -nostdlib -o $@ -T $^
build/libkernel.a: build_dir
build/libkernel.a: build-dir
rm -rf $(TARGET_DIR)
rustup override set nightly
cargo build --release
cp $(TARGET_DIR)/libmxrox.a build/libkernel.a
build_dir:
build-dir:
mkdir -p build
mkdir -p build/iso
mkdir -p build/iso/boot
@ -27,11 +27,13 @@ build_dir:
build: build/mxrox.iso
clean:
cargo clean
clean-build:
rm -rf build
mkdir build
clean: clean-build
cargo clean
run-kernel: build/kernel.elf
qemu-system-i386 -kernel '$<' -m 512M

View File

@ -38,6 +38,7 @@ Internet resources where I found most information about OS dev
- https://os.phil-opp.com/
- https://wiki.osdev.org/Interrupts_Tutorial
- https://wiki.osdev.org/Interrupt_Vector_Table
- https://wiki.osdev.org/Inline_Assembly/Examples#I/O_access
### Contributing

54
boot.s
View File

@ -16,6 +16,60 @@ align 4
dd MBFLAGS
dd CHECKSUM
; %macro isr_err_stub 1
; isr_stub_%+%1:
; call exception_handler
; iret
; %endmacro
; %macro isr_no_err_stub 1
; isr_stub_%+%1:
; call exception_handler
; iret
; %endmacro
; extern exception_handler
; isr_no_err_stub 0
; isr_no_err_stub 1
; isr_no_err_stub 2
; isr_no_err_stub 3
; isr_no_err_stub 4
; isr_no_err_stub 5
; isr_no_err_stub 6
; isr_no_err_stub 7
; isr_err_stub 8
; isr_no_err_stub 9
; isr_err_stub 10
; isr_err_stub 11
; isr_err_stub 12
; isr_err_stub 13
; isr_err_stub 14
; isr_no_err_stub 15
; isr_no_err_stub 16
; isr_err_stub 17
; isr_no_err_stub 18
; isr_no_err_stub 19
; isr_no_err_stub 20
; isr_no_err_stub 21
; isr_no_err_stub 22
; isr_no_err_stub 23
; isr_no_err_stub 24
; isr_no_err_stub 25
; isr_no_err_stub 26
; isr_no_err_stub 27
; isr_no_err_stub 28
; isr_no_err_stub 29
; isr_err_stub 30
; isr_no_err_stub 31
; global isr_stub_table
; isr_stub_table:
; %assign i 0
; %rep 32
; dd isr_stub_%+i ; use DQ instead if targeting 64-bit
; %assign i i+1
; %endrep
; The multiboot standard does not define the value of the stack pointer register
; (esp) and it is up to the kernel to provide a stack. This allocates room for a
; small stack by creating a symbol at the bottom of it, then allocating 16384

View File

@ -4,6 +4,8 @@ use core::mem;
use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicUsize, Ordering};
use super::terminal::log_info;
struct FreeListNode {
size: usize,
next: *mut FreeListNode,
@ -122,4 +124,6 @@ pub fn init_heap(heap_start: usize, heap_size: usize) {
unsafe {
ALLOCATOR.init(heap_start, heap_size);
}
log_info(&format!("Heap initialized on {heap_start} with size {heap_size}"));
}

205
src/kernel/interrupt.rs Normal file
View File

@ -0,0 +1,205 @@
use core::ptr::write_volatile;
use core::arch::asm;
use crate::kernel::util::{cli, sti};
use super::show_error;
use super::terminal::{log_error, log_info};
use super::util::{inb, io_wait, outb};
const APIC_BASE: u32 = 0xFEE00000;
const APIC_EOI: u32 = APIC_BASE + 0x0B0; // End Of Interrupt
const APIC_SVR: u32 = APIC_BASE + 0x0F0; // Spurious Interrupt Vector Register
const IA32_APIC_BASE_MSR: u32 = 0x1B;
const APIC_BASE_ENABLE: u64 = 1 << 11;
const PIC1_COMMAND: u16 = 0x0020;
const PIC1_DATA: u16 = 0x0021;
const PIC2_COMMAND: u16 = 0x00A0;
const PIC2_DATA: u16 = 0x00A1;
const ICW1_ICW4: u8 = 0x01; /* Indicates that ICW4 will be present */
const ICW1_SINGLE: u8 = 0x02; /* Single (cascade) mode */
const ICW1_INTERVAL4: u8 = 0x04; /* Call address interval 4 (8) */
const ICW1_LEVEL: u8 = 0x08; /* Level triggered (edge) mode */
const ICW1_INIT: u8 = 0x10; /* Initialization - required! */
const ICW4_8086: u8 = 0x01; /* 8086/88 (MCS-80/85) mode */
const ICW4_AUTO: u8 = 0x02; /* Auto (normal) EOI */
const ICW4_BUF_SLAVE: u8 = 0x08; /* Buffered mode/slave */
const ICW4_BUF_MASTER: u8 = 0x0C; /* Buffered mode/master */
const ICW4_SFNM: u8 = 0x10; /* Special fully nested (not) */
const PIC_EOI: u8 = 0x20; /* End-of-interrupt command code */
const APIC_ENABLED: bool = false;
#[repr(C, packed)]
struct IDTDescriptor {
limit: u16,
base: u32,
}
#[repr(C, packed)]
#[derive(Copy, Clone)]
struct IDTEntry {
offset_low: u16,
selector: u16,
ist: u8,
type_attr: u8,
offset_mid: u16,
}
const IDT_MAX_DESCRIPTORS: usize = 256;
static mut IDT: [IDTEntry; IDT_MAX_DESCRIPTORS] = [IDTEntry {
offset_low: 0,
selector: 0,
ist: 0,
type_attr: 0,
offset_mid: 0,
}; IDT_MAX_DESCRIPTORS];
// static mut VECTORS: [bool; 32] = [false; 32];
static mut IDTR: IDTDescriptor = IDTDescriptor { limit: 0, base: 0 };
// extern "C" {
// static ist_stub_table: [u32; 32];
// }
pub unsafe fn idt_set_descriptor(vector: u8, handler: u32, type_attr: u8) {
IDT[vector as usize] = IDTEntry {
offset_low: handler as u16,
selector: 0x08, // Kernel code segment
ist: type_attr,
type_attr,
offset_mid: (handler >> 16) as u16
};
}
pub unsafe fn load_idt() {
IDTR.base = &IDT as *const _ as u32;
IDTR.limit = (size_of::<IDTEntry>() * IDT_MAX_DESCRIPTORS - 1) as u16;
// for vector in 0..32 {
// idt_set_descriptor(vector, ist_stub_table[vector as usize], 0x8E);
// VECTORS[vector as usize] = true;
// }
asm!("lidt [{}]", in(reg) &IDTR);
asm!("sti");
}
unsafe fn is_apic_available() -> bool {
if !APIC_ENABLED {
return false;
}
let edx: u32;
asm!(
"cpuid",
inout("eax") 1 => _,
lateout("edx") edx
);
(edx & (1 << 9)) != 0
}
pub unsafe extern "C" fn exception_handler() {
log_error("Unknown error");
asm!("cli; hlt");
loop {}
}
unsafe fn send_apic_eoi() {
write_volatile(APIC_EOI as *mut u32, 0);
}
unsafe fn send_pic_eoi(irq: usize) {
if irq >= 8 {
write_volatile(PIC2_COMMAND as *mut u8, PIC_EOI);
}
write_volatile(PIC1_COMMAND as *mut u8, PIC_EOI);
}
pub unsafe fn init_pic() {
let offset1 = 0x20;
let offset2 = 0x28;
let a1 = inb(PIC1_DATA); // save masks
let a2 = inb(PIC2_DATA);
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // starts the initialization sequence (in cascade mode)
io_wait();
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
io_wait();
outb(PIC1_DATA, offset1); // ICW2: Master PIC vector offset
io_wait();
outb(PIC2_DATA, offset2); // ICW2: Slave PIC vector offset
io_wait();
outb(PIC1_DATA, 4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
io_wait();
outb(PIC2_DATA, 2); // ICW3: tell Slave PIC its cascade identity (0000 0010)
io_wait();
outb(PIC1_DATA, ICW4_8086); // ICW4: have the PICs use 8086 mode (and not 8080 mode)
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
outb(PIC1_DATA, a1); // restore saved masks.
outb(PIC2_DATA, a2);
}
pub unsafe fn disable_pic() {
outb(PIC1_DATA, 0xff);
outb(PIC2_DATA, 0xff);
}
pub fn send_eoi(irq: usize) {
unsafe {
if is_apic_available() {
send_apic_eoi()
} else {
send_pic_eoi(irq)
}
}
}
unsafe fn read_msr(msr: u32) -> u64 {
let low: u32;
let high: u32;
asm!("rdmsr", in("ecx") msr, out("eax") low, out("edx") high);
((high as u64) << 32) | (low as u64)
}
unsafe fn write_msr(msr: u32, value: u64) {
let low = value as u32;
let high = (value >> 32) as u32;
asm!("wrmsr", in("ecx") msr, in("eax") low, in("edx") high);
}
pub unsafe fn init_apic() {
let apic_base = read_msr(IA32_APIC_BASE_MSR) | APIC_BASE_ENABLE;
write_msr(IA32_APIC_BASE_MSR, apic_base);
let svr_value: u32 = 0x1FF;
write_volatile(APIC_SVR as *mut u32, svr_value);
write_volatile(APIC_EOI as *mut u32, 0);
}
pub fn init_interrupts() {
unsafe {
init_pic();
log_info("PIC initialized");
if is_apic_available() {
disable_pic();
log_info("Disable PIC");
init_apic();
log_info("APIC initialized");
}
load_idt();
}
}

View File

@ -1,132 +0,0 @@
use core::ptr::write_volatile;
use core::arch::asm;
const APIC_BASE: u32 = 0xFEE00000;
const APIC_EOI: u32 = APIC_BASE + 0x0B0;
const APIC_SVR: u32 = APIC_BASE + 0x0F0;
const PIC1_CMD: u16 = 0x20;
const PIC1_DATA: u16 = 0x21;
const PIC2_CMD: u16 = 0xA0;
const PIC2_DATA: u16 = 0xA1;
const ICW1_INIT: u8 = 0x10;
const ICW1_ICW4: u8 = 0x01;
const ICW4_8086: u8 = 0x01;
const PIC_EOI: u8 = 0x20; /* End-of-interrupt command code */
const APIC_ENABLED: bool = false;
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct IdtEntry {
offset_low: u16,
selector: u16,
ist: u8,
type_attr: u8,
offset_middle: u16
}
pub const IDT_SIZE: usize = 256;
#[repr(C)]
pub struct Idt {
entries: [IdtEntry; IDT_SIZE],
}
static mut IDT: Idt = Idt {
entries: [IdtEntry {
offset_low: 0,
selector: 0,
ist: 0,
type_attr: 0,
offset_middle: 0,
}; IDT_SIZE],
};
unsafe fn is_apic_available() -> bool {
if !APIC_ENABLED {
return false;
}
let edx: u32;
asm!(
"cpuid",
inout("eax") 1 => _,
lateout("edx") edx
);
(edx & (1 << 9)) != 0
}
unsafe fn init_idt() {
asm!("lidt [{}]", "sti", in(reg) &IDT as *const _);
}
unsafe fn init_apic() {
write_volatile(APIC_SVR as *mut u32, 0x100 | 0x1);
}
unsafe fn send_apic_eoi() {
write_volatile(APIC_EOI as *mut u32, 0);
}
unsafe fn send_pic_eoi(irq: usize) {
if irq >= 8 { write_volatile(PIC2_CMD as *mut u8, PIC_EOI); }
write_volatile(PIC1_CMD as *mut u8, PIC_EOI);
}
unsafe fn init_pic() {
write_volatile(PIC1_CMD as *mut u8, ICW1_INIT | ICW1_ICW4);
write_volatile(PIC2_CMD as *mut u8, ICW1_INIT | ICW1_ICW4);
write_volatile(PIC1_DATA as *mut u8, 0x20);
write_volatile(PIC2_DATA as *mut u8, 0x28);
write_volatile(PIC1_DATA as *mut u8, 4);
write_volatile(PIC2_DATA as *mut u8, 2);
write_volatile(PIC1_DATA as *mut u8, ICW4_8086);
write_volatile(PIC2_DATA as *mut u8, ICW4_8086);
write_volatile(PIC1_DATA as *mut u8, 0xFB);
write_volatile(PIC2_DATA as *mut u8, 0xFF);
}
pub fn send_eoi(irq: usize) {
unsafe {
if is_apic_available() {
send_apic_eoi()
} else {
send_pic_eoi(irq)
}
}
}
/// all interrupt vectors you can find here: https://wiki.osdev.org/Interrupt_Vector_Table
pub fn register_idt(handler: u32, int_vec: u8) {
unsafe {
let entry = &mut IDT.entries[int_vec as usize];
entry.offset_low = handler as u16;
entry.selector = 0x08;
entry.ist = 0;
entry.type_attr = 0x8E;
entry.offset_middle = (handler >> 16) as u16;
}
}
pub fn init_interrupts() {
unsafe {
init_idt();
if is_apic_available() {
init_apic();
} else {
init_pic();
}
}
}

View File

@ -1,35 +1,35 @@
use irq::init_interrupts;
use interrupt::init_interrupts;
use heap::init_heap;
use pit::init_pit;
use time::init_pit;
use ps2::init_ps2;
use terminal::{log_info, println};
use vga::{
fill_with_color,
put_string,
put_string_by_index,
VGA_COLOR_BLACK,
VGA_COLOR_LIGHT_MAGENTA,
VGA_COLOR_RED
};
mod vga;
mod ps2;
mod irq;
mod pit;
mod interrupt;
mod time;
mod heap;
mod util;
mod terminal;
pub fn show_error(message: &str) {
fill_with_color(VGA_COLOR_BLACK);
put_string_by_index(0, message, VGA_COLOR_BLACK, VGA_COLOR_RED);
}
pub fn init_kernel() {
init_heap(16400, 16384);
init_pit();
init_ps2();
init_interrupts();
fill_with_color(VGA_COLOR_BLACK);
loop {}
}
pub fn init_kernel() {
init_heap(0x200000, 1048576);
init_interrupts();
init_pit();
// init_ps2();
loop {}
}

View File

@ -1,38 +0,0 @@
use core::{ptr::write_volatile, sync::atomic::{AtomicUsize, Ordering}};
use alloc::{sync::Arc, vec::Vec};
use spin::RwLock;
use core::sync::atomic::AtomicBool;
use super::irq::{register_idt, send_eoi};
const DIVISOR: u16 = ((1193182u32 + 500u32) / 1000) as u16;
static TIMERS: RwLock<Vec<Arc<AtomicUsize>>> = RwLock::new(Vec::new());
extern "C" fn pit_handler() {
for t in TIMERS.read().iter() {
t.fetch_add(1, Ordering::SeqCst);
}
send_eoi(0);
}
pub fn init_pit() {
unsafe {
write_volatile(0x43 as *mut u8, 0x36);
write_volatile(0x40 as *mut u8, (DIVISOR & 0xFF) as u8);
write_volatile(0x40 as *mut u8, ((DIVISOR >> 8) & 0xFF) as u8);
}
register_idt(pit_handler as u32, 0x08);
}
pub fn sleep(millis: usize) {
let atomic = Arc::new(AtomicUsize::new(0));
TIMERS.write().push(atomic.clone());
while atomic.load(Ordering::SeqCst) < millis {
core::hint::spin_loop(); // Уменьшаем нагрузку на CPU
}
TIMERS.write().retain(|t| !Arc::ptr_eq(t, &atomic));
}

View File

@ -1,10 +1,8 @@
use core::ptr::{read_volatile, write_volatile};
use alloc::vec::Vec;
use crate::kernel::irq::register_idt;
use crate::kernel::interrupt::idt_set_descriptor;
use super::{irq::send_eoi, pit::sleep};
use super::{interrupt::send_eoi, terminal::log_info, time::sleep, util::*};
const DATA_PORT: u16 = 0x60;
const STATUS_PORT: u16 = 0x64;
@ -16,23 +14,24 @@ static mut DEVICE1_TYPE: Vec<u8> = Vec::new();
static mut DEVICE2_TYPE: Vec<u8> = Vec::new();
unsafe fn send_data(data: u8) {
write_volatile(DATA_PORT as *mut u8, data)
outb(DATA_PORT, data)
}
unsafe fn read_data() -> u8 {
read_volatile(DATA_PORT as *mut u8)
inb(DATA_PORT)
}
unsafe fn send_command(command: u8) {
write_volatile(STATUS_PORT as *mut u8, command)
outb(STATUS_PORT, command)
}
unsafe fn read_status() -> u8 {
read_volatile(STATUS_PORT as *mut u8)
inb(STATUS_PORT)
}
unsafe fn on_device_1_irq() {
let data = read_data();
log_info(&format!("[PS/2] device 1 data: 0x{:X}", data));
send_eoi(DEVICE1_IRQ);
}
@ -86,28 +85,43 @@ unsafe fn send_identify() -> Vec<u8> {
}
unsafe fn is_ps2_2_device_enabled() -> bool {
true
false
}
pub fn init_ps2() {
unsafe {
send_command(0xAD); // disable device 1
log_info("[PS/2] Disabled device 1");
if is_ps2_2_device_enabled() {
DEVICE2_TYPE = send_identify();
// DEVICE2_TYPE = send_identify();
log_info("[PS/2] Inspected device 2 identify");
send_command(0xA7); // disable device 2
log_info("[PS/2] Disabled device 2");
}
read_data(); // flush device data
send_command(0x60); // set CCB command
send_command(0x000000u8); // clear CCB
log_info("[PS/2] Flush device data and clear CCB");
send_command(0xAE); // enable device 1
log_info("[PS/2] Enable device 1");
// DEVICE1_TYPE = send_identify();
log_info("[PS/2] Inspected device 1 identify");
if is_ps2_2_device_enabled() {
DEVICE1_TYPE = send_identify();
send_command(0xA8); // enable device 2
log_info("[PS/2] Disable device 2");
}
idt_set_descriptor(0x21, on_device_1_irq as u32, 0x09); // bind handler for device 1
log_info("[PS/2] Bind handler for device 1");
if is_ps2_2_device_enabled() {
idt_set_descriptor(0x8B, on_device_2_irq as u32, 0x74); // bind handler for device 2
log_info("[PS/2] Bind handler for device 2");
}
}
register_idt(on_device_1_irq as u32, 0x09); // bind handler for device 1
register_idt(on_device_2_irq as u32, 0x74); // bind handler for device 2
}

48
src/kernel/terminal.rs Normal file
View File

@ -0,0 +1,48 @@
use alloc::{string::{String, ToString}, vec::Vec};
use spin::RwLock;
use super::vga::{put_string, VGA_COLOR_BLACK, VGA_COLOR_WHITE, VGA_HEIGHT, VGA_WIDTH};
use super::time::get_time_millis;
static TEXT: RwLock<String> = RwLock::new(String::new());
pub fn update() {
let text = TEXT.read().to_string();
let mut lines: Vec<String> = Vec::new();
for line in text.as_str().lines() {
let chars: Vec<char> = line.chars().collect();
lines.append(&mut chars.chunks(VGA_WIDTH)
.map(|chunk| chunk.iter().collect())
.collect::<Vec<String>>())
}
while lines.len() > VGA_HEIGHT {
lines.remove(0);
}
for y in 0..VGA_HEIGHT {
if let Some(line) = lines.get(y) {
put_string(0, y, &(line.to_string()+&" ".repeat(VGA_WIDTH-line.len())), VGA_COLOR_BLACK, VGA_COLOR_WHITE);
} else {
put_string(0, y, &" ".repeat(VGA_WIDTH), VGA_COLOR_BLACK, VGA_COLOR_BLACK);
}
}
}
pub fn print(text: &str) {
TEXT.write().push_str(text);
update();
}
pub fn println(text: &str) {
print(&format!("{text}\n"))
}
pub fn log_info(text: &str) {
print(&format!("[{}] [INFO] {text}\n", get_time_millis()))
}
pub fn log_error(text: &str) {
print(&format!("[{}] [FATAL] {text}\n", get_time_millis()))
}

65
src/kernel/time.rs Normal file
View File

@ -0,0 +1,65 @@
use core::{ptr::write_volatile, sync::atomic::{AtomicUsize, Ordering}};
use alloc::{sync::Arc, vec::Vec};
use spin::RwLock;
use super::{interrupt::{idt_set_descriptor, load_idt, send_eoi}, terminal::log_info, util::{cli, io_wait, outb, sti}};
const DIVISOR: u16 = ((1193182u32 + 500u32) / 100) as u16;
static TIMERS: RwLock<Vec<Arc<AtomicUsize>>> = RwLock::new(Vec::new());
static TIME_MILLIS: AtomicUsize = AtomicUsize::new(0);
static TIME: AtomicUsize = AtomicUsize::new(0);
extern "C" fn pit_handler() {
for t in TIMERS.read().iter() {
t.fetch_add(1, Ordering::SeqCst);
}
if TIME_MILLIS.fetch_add(1, Ordering::SeqCst) >= 999 {
TIME_MILLIS.store(0, Ordering::SeqCst);
TIME.fetch_add(1, Ordering::SeqCst);
log_info("proshlo second");
}
send_eoi(0);
}
pub fn get_time_seconds() -> usize {
TIME.load(Ordering::SeqCst)
}
pub fn get_time_millis() -> usize {
TIME.load(Ordering::SeqCst) * 1000 + TIME_MILLIS.load(Ordering::SeqCst)
}
pub fn init_pit() {
log_info("PIT initialization");
unsafe {
outb(0x43, 0x36);
cli();
outb(0x40, (DIVISOR & 0xFF) as u8);
outb(0x40, ((DIVISOR & 0xFF) >> 8) as u8);
log_info("PIT initialized");
log_info(&format!("{}", pit_handler as u32));
idt_set_descriptor(0x20, pit_handler as u32, 0x8E);
log_info("PIT registered");
load_idt();
log_info("PIT loaded idt");
}
}
pub fn sleep(millis: usize) {
let atomic = Arc::new(AtomicUsize::new(0));
TIMERS.write().push(atomic.clone());
while atomic.load(Ordering::SeqCst) < millis {
core::hint::spin_loop();
}
TIMERS.write().retain(|t| !Arc::ptr_eq(t, &atomic));
}

View File

@ -0,0 +1,27 @@
use core::arch::asm;
pub unsafe fn outb(port: u16, value: u8) {
asm!("out dx, al", in("dx") port, in("al") value, options(nostack, preserves_flags));
}
pub unsafe fn inb(port: u16) -> u8 {
let result: u8;
asm!("inb %dx, %al", in("dx") port, out("al") result, options(nostack, preserves_flags, att_syntax));
result
}
pub unsafe fn nop() {
asm!("nop", options(nostack, preserves_flags));
}
pub unsafe fn io_wait() {
outb(0x80, 0);
}
pub unsafe fn sti() {
asm!("sti");
}
pub unsafe fn cli() {
asm!("cli");
}