feat: complete opencoreRS microkernel with Ring 3 userspace, VFS, IPC, and shell

This commit is contained in:
RarDog
2026-09-02 15:52:57 +03:00
commit d0c27c3d54
72 changed files with 6938 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
[package]
name = "kernel"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
# Ядро — бинарный crate без стандартной библиотеки
[[bin]]
name = "kernel"
path = "src/main.rs"
test = false
bench = false
[dependencies]
# Bootloader protocol
limine = { workspace = true }
# Синхронизация (spinlock) без OS
spin = { workspace = true }
# Битовые флаги для прав, флагов дескрипторов и т.д.
bitflags = { workspace = true }
# Логирование (без alloc, форматируем в serial)
log = { workspace = true }
# UART serial-порт для раннего логирования
uart_16550 = { workspace = true }
# Абстракции над x86_64 (PhysAddr, VirtAddr, paging, registers...)
x86_64 = { workspace = true }
# Аллокатор связного списка для kernel heap
linked_list_allocator = { workspace = true }
# Наш ABI для syscall-номеров и типов
syscall-abi = { workspace = true }
[features]
default = []
# Включить отладочный вывод стэк-трейсов
stack-trace = []
[target.x86_64-unknown-none.dependencies]
limine = "0.6"
# Профили сборки задаются в корневом Cargo.toml workspace
+75
View File
@@ -0,0 +1,75 @@
/* ──────────────────────────────────────────────────────────────
* kernel/linker.ld — opencoreRS kernel linker script
*
* Раскладка в виртуальной памяти:
* 0xFFFFFFFF80000000 — начало kernel (higher-half)
*
* Limine грузит ядро туда, куда указывает ELF,
* поэтому мы просто задаём адреса секций.
* ─────────────────────────────────────────────────────────────*/
ENTRY(_start)
/* Ядро живёт в верхней половине виртуального адресного пространства */
KERNEL_VIRT_BASE = 0xFFFFFFFF80000000;
SECTIONS
{
/* Стартовый адрес */
. = KERNEL_VIRT_BASE;
__kernel_start = .;
/* ── Limine protocol requests (должны быть в первых 2 GiB) ──── */
.limine_requests_start ALIGN(8) :
{
KEEP(*(.limine_requests_start))
}
.limine_requests ALIGN(8) :
{
KEEP(*(.limine_requests))
}
.limine_requests_end ALIGN(8) :
{
KEEP(*(.limine_requests_end))
}
/* ── Код ─────────────────────────────────────────────── */
.text ALIGN(4K) :
{
*(.text.boot) /* Точка входа — первой! */
*(.text .text.*)
}
/* ── Read-only данные ────────────────────────────────── */
.rodata ALIGN(4K) :
{
*(.rodata .rodata.*)
}
/* ── Данные ──────────────────────────────────────────── */
.data ALIGN(4K) :
{
*(.data .data.*)
}
/* ── BSS (обнуляемые данные) ─────────────────────────── */
.bss ALIGN(4K) :
{
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
__bss_end = .;
}
__kernel_end = .;
/* Отбрасываем ненужные секции */
/DISCARD/ :
{
*(.comment)
*(.eh_frame)
*(.note .note.*)
}
}
+6
View File
@@ -0,0 +1,6 @@
//! Архитектурный модуль.
//!
//! Сейчас поддерживается только x86_64. В будущем здесь будет
//! `#[cfg(target_arch = "...")]` для aarch64 и riscv64.
pub mod x86_64;
+165
View File
@@ -0,0 +1,165 @@
//! ACPI Parser — разбор таблиц RSDP, XSDT и MADT для работы с Local APIC и IOAPIC.
use alloc::vec::Vec;
use spin::Once;
use x86_64::structures::paging::PageTableFlags;
use x86_64::{PhysAddr, VirtAddr};
pub static ACPI_INFO: Once<AcpiInfo> = Once::new();
#[derive(Debug, Clone)]
pub struct AcpiInfo {
pub lapic_addr: usize,
pub ioapic_addr: Option<usize>,
pub cpu_cores: usize,
pub apic_ids: Vec<u8>,
}
#[repr(C, packed)]
struct RsdpDescriptor {
signature: [u8; 8],
checksum: u8,
oem_id: [u8; 6],
revision: u8,
rsdt_address: u32,
}
#[repr(C, packed)]
struct XsdpDescriptor {
rsdp: RsdpDescriptor,
length: u32,
xsdt_address: u64,
extended_checksum: u8,
reserved: [u8; 3],
}
#[repr(C, packed)]
struct SdtHeader {
signature: [u8; 4],
length: u32,
revision: u8,
checksum: u8,
oem_id: [u8; 6],
oem_table_id: [u8; 8],
oem_revision: u32,
creator_id: u32,
creator_revision: u32,
}
/// Гарантировать, что физический адрес отображён в виртуальную память через HHDM.
fn ensure_mapped(phys: usize) -> usize {
let hhdm = crate::mm::boot_info::hhdm_offset().as_u64() as usize;
let virt_addr = if phys >= hhdm { phys } else { phys + hhdm };
let virt = VirtAddr::new(virt_addr as u64);
if !crate::mm::vmm::is_mapped(virt) {
let page_phys = PhysAddr::new((phys & !0xFFF) as u64);
let page_virt = VirtAddr::new(((phys & !0xFFF) + hhdm) as u64);
let _ = crate::mm::vmm::map_page(
page_virt,
page_phys,
PageTableFlags::PRESENT | PageTableFlags::WRITABLE,
);
}
virt_addr
}
/// Инициализировать ACPI по адресу RSDP.
pub fn init(rsdp_addr: usize) {
if rsdp_addr == 0 {
log::warn!("[acpi] No ACPI RSDP table provided");
return;
}
let rsdp_virt = ensure_mapped(rsdp_addr);
let rsdp = unsafe { &*(rsdp_virt as *const RsdpDescriptor) };
if &rsdp.signature != b"RSD PTR " {
log::error!("[acpi] Invalid RSDP signature");
return;
}
let mut lapic_addr = 0xFEE0_0000; // Стандартный дефолтный адрес Local APIC
let mut ioapic_addr = None;
let mut apic_ids = Vec::new();
// Читаем XSDT (64-бит) или RSDT (32-бит)
let sdt_virt = if rsdp.revision >= 2 {
let xsdp = unsafe { &*(rsdp_virt as *const XsdpDescriptor) };
ensure_mapped(xsdp.xsdt_address as usize)
} else {
ensure_mapped(rsdp.rsdt_address as usize)
};
let header = unsafe { &*(sdt_virt as *const SdtHeader) };
let is_xsdt = &header.signature == b"XSDT";
let entry_size = if is_xsdt { 8 } else { 4 };
let entries_count = ((header.length as usize) - core::mem::size_of::<SdtHeader>()) / entry_size;
let entries_ptr = (sdt_virt + core::mem::size_of::<SdtHeader>()) as *const u8;
for i in 0..entries_count {
let table_phys = if is_xsdt {
unsafe { core::ptr::read_unaligned(entries_ptr.add(i * 8) as *const u64) as usize }
} else {
unsafe { core::ptr::read_unaligned(entries_ptr.add(i * 4) as *const u32) as usize }
};
let table_virt = ensure_mapped(table_phys);
let table_header = unsafe { &*(table_virt as *const SdtHeader) };
// Ищем MADT (Multiple APIC Description Table)
if &table_header.signature == b"APIC" {
let madt_lapic = unsafe { core::ptr::read_unaligned((table_virt + 36) as *const u32) } as usize;
lapic_addr = madt_lapic;
let mut offset = 44usize;
let madt_len = table_header.length as usize;
while offset < madt_len {
let entry_type = unsafe { *((table_virt + offset) as *const u8) };
let entry_len = unsafe { *((table_virt + offset + 1) as *const u8) } as usize;
if entry_len == 0 {
break;
}
match entry_type {
0 => {
// Processor Local APIC
let apic_id = unsafe { *((table_virt + offset + 3) as *const u8) };
let flags = unsafe { core::ptr::read_unaligned((table_virt + offset + 4) as *const u32) };
if (flags & 1) != 0 {
apic_ids.push(apic_id);
}
}
1 => {
// I/O APIC
let io_addr = unsafe { core::ptr::read_unaligned((table_virt + offset + 4) as *const u32) } as usize;
ioapic_addr = Some(io_addr);
}
_ => {}
}
offset += entry_len;
}
}
}
let cpu_cores = if apic_ids.is_empty() { 1 } else { apic_ids.len() };
log::info!(
"[acpi] ACPI initialized: LAPIC={:#x}, IOAPIC={:?}, Cores={}",
lapic_addr,
ioapic_addr,
cpu_cores
);
ACPI_INFO.call_once(|| AcpiInfo {
lapic_addr,
ioapic_addr,
cpu_cores,
apic_ids,
});
}
+83
View File
@@ -0,0 +1,83 @@
//! Local APIC (LAPIC) — современный контроллер прерываний Intel и AMD.
use x86_64::instructions::port::Port;
const IA32_APIC_BASE_MSR: u32 = 0x1B;
const LAPIC_SVR: usize = 0xF0;
const LAPIC_EOI: usize = 0xB0;
#[allow(dead_code)]
const LAPIC_TIMER_LVT: usize = 0x320;
#[allow(dead_code)]
const LAPIC_TIMER_DIV: usize = 0x3E0;
#[allow(dead_code)]
const LAPIC_TIMER_INIT: usize = 0x380;
static mut LAPIC_BASE_VIRT: usize = 0;
/// Инициализировать Local APIC на текущем ядре.
pub fn init(lapic_phys: usize) {
let hhdm = crate::mm::boot_info::hhdm_offset().as_u64() as usize;
let lapic_virt = lapic_phys + hhdm;
unsafe {
LAPIC_BASE_VIRT = lapic_virt;
}
// 1. Включаем APIC в IA32_APIC_BASE MSR (бит 11 = APIC Enable)
unsafe {
let (low, high): (u32, u32);
core::arch::asm!(
"rdmsr",
in("ecx") IA32_APIC_BASE_MSR,
out("eax") low,
out("edx") high,
);
let new_low = low | (1 << 11);
core::arch::asm!(
"wrmsr",
in("ecx") IA32_APIC_BASE_MSR,
in("eax") new_low,
in("edx") high,
);
}
// 2. Включаем Spurious Interrupt Register (SVR) с вектором 0xFF и битом 8 (Software Enable)
write_reg(LAPIC_SVR, 0x1FF);
log::info!("[apic] Local APIC initialized and enabled at {:#x}", lapic_phys);
}
/// Отправить EOI контроллеру Local APIC.
pub fn send_eoi() {
write_reg(LAPIC_EOI, 0);
}
/// Прочитать 32-битный регистр Local APIC.
fn read_reg(offset: usize) -> u32 {
unsafe {
if LAPIC_BASE_VIRT != 0 {
core::ptr::read_volatile((LAPIC_BASE_VIRT + offset) as *const u32)
} else {
0
}
}
}
/// Записать 32-битный регистр Local APIC.
fn write_reg(offset: usize, value: u32) {
unsafe {
if LAPIC_BASE_VIRT != 0 {
core::ptr::write_volatile((LAPIC_BASE_VIRT + offset) as *mut u32, value);
}
}
}
/// Отключить устаревший 8259 PIC (маскируем все 16 линий IRQ).
pub fn disable_pic() {
unsafe {
let mut pic1_data: Port<u8> = Port::new(0x21);
let mut pic2_data: Port<u8> = Port::new(0xA1);
pic1_data.write(0xFF);
pic2_data.write(0xFF);
}
log::debug!("[apic] Legacy 8259 PIC disabled");
}
+109
View File
@@ -0,0 +1,109 @@
//! Точка входа ядра — вызывается загрузчиком Limine.
use limine::request::{
FramebufferRequest, HhdmRequest, MemmapRequest, ModulesRequest, RsdpRequest,
};
use limine::BaseRevision;
/// Обязательный маркер ревизии (ревизия 3 поддерживается Limine v8).
#[used]
#[unsafe(link_section = ".limine_requests")]
static BASE_REVISION: BaseRevision = BaseRevision::with_revision(3);
/// Запрос карты физической памяти.
#[used]
#[unsafe(link_section = ".limine_requests")]
static MEMORY_MAP_REQUEST: MemmapRequest = MemmapRequest::new();
/// HHDM offset — Higher Half Direct Map.
#[used]
#[unsafe(link_section = ".limine_requests")]
static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
/// Запрос загрузочных модулей (ELF-файлы серверов: init, vfs, drivers).
#[used]
#[unsafe(link_section = ".limine_requests")]
static MODULES_REQUEST: ModulesRequest = ModulesRequest::new();
/// Запрос графического буфера (Framebuffer).
#[used]
#[unsafe(link_section = ".limine_requests")]
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
/// Запрос таблицы ACPI RSDP.
#[used]
#[unsafe(link_section = ".limine_requests")]
static RSDP_REQUEST: RsdpRequest = RsdpRequest::new();
/// C-совместимое представление Limine File (по спецификации протокола Limine).
#[repr(C)]
struct RawLimineFile {
_revision: u64,
address: *const u8,
size: u64,
}
/// Точка входа (_start).
/// Limine вызывает в 64-bit long mode, прерывания отключены.
#[no_mangle]
extern "C" fn _start() -> ! {
// 1. Serial — первым делом
crate::logger::init();
// 2. Проверить совместимость протокола
assert!(
BASE_REVISION.is_supported(),
"Unsupported Limine base revision"
);
// 3. Инициализация Framebuffer, если доступен
if let Some(fb_resp) = FRAMEBUFFER_REQUEST.response() {
if let Some(fb) = fb_resp.framebuffers().first() {
super::framebuffer::init(
fb.address() as usize,
fb.width as usize,
fb.height as usize,
fb.pitch as usize,
fb.bpp as usize,
);
}
}
// 4. Получить ответы памяти Limine
let mmap = MEMORY_MAP_REQUEST
.response()
.expect("No memory map from Limine");
let hhdm = HHDM_REQUEST
.response()
.expect("No HHDM response from Limine");
crate::mm::boot_info::set(mmap, hhdm);
// 5. Передать управление
crate::kernel_main()
}
/// Получить адрес таблицы ACPI RSDP.
pub fn get_rsdp_address() -> usize {
RSDP_REQUEST
.response()
.map(|r| r.address as usize)
.unwrap_or(0)
}
/// Найти загрузочный модуль по имени/пути.
pub fn get_module(name: &str) -> Option<&'static [u8]> {
let mod_resp = MODULES_REQUEST.response()?;
for file in mod_resp.modules() {
let path = file.path();
if path.contains(name) {
let raw = unsafe { &*(*file as *const _ as *const RawLimineFile) };
let slice = unsafe {
core::slice::from_raw_parts(raw.address, raw.size as usize)
};
return Some(slice);
}
}
None
}
+89
View File
@@ -0,0 +1,89 @@
//! Низкоуровневое переключение контекста потоков (Context Switching) для x86_64.
use core::arch::naked_asm;
#[unsafe(naked)]
#[no_mangle]
pub unsafe extern "C" fn __switch_context(prev_rsp: *mut usize, next_rsp: usize) {
naked_asm!(
// 1. Сохранить callee-saved регистры старой задачи на её стек
"push rbp",
"push rbx",
"push r12",
"push r13",
"push r14",
"push r15",
// 2. Сохранить текущий стек в *prev_rsp (rdi)
"mov [rdi], rsp",
// 3. Загрузить стек новой задачи из next_rsp (rsi)
"mov rsp, rsi",
// 4. Восстановить callee-saved регистры новой задачи
"pop r15",
"pop r14",
"pop r13",
"pop r12",
"pop rbx",
"pop rbp",
// 5. Возврат: вытолкнет RIP новой задачи со стека и перейдёт на него
"ret",
);
}
#[unsafe(naked)]
#[no_mangle]
pub unsafe extern "C" fn thread_trampoline() {
naked_asm!(
// На верхушке стека лежат: entry_fn и arg
"pop rax", // entry_fn (адрес функции)
"pop rdi", // arg (первый аргумент в System V AMD64 ABI передаётся в RDI!)
// Включить прерывания для нового потока
"sti",
// Вызов точки входа потока: entry_fn(arg)
"call rax",
// Если поток завершился, вызываем завершение задачи ядра
"call {task_exit}",
// Защитный бесконечный цикл
"2:",
"hlt",
"jmp 2b",
task_exit = sym task_exit_wrapper,
);
}
extern "C" fn task_exit_wrapper() {
crate::sched::exit_current();
}
/// Совершить переход процессора в Ring 3 (Userspace) через `iretq`.
#[unsafe(naked)]
#[no_mangle]
pub unsafe extern "C" fn jump_to_userspace(user_rip: usize, user_rsp: usize) -> ! {
naked_asm!(
// rdi = user_rip, rsi = user_rsp
// Селекторы:
// user_ds = 0x18 | 3 = 0x1B
// user_cs = 0x20 | 3 = 0x23
"mov ax, 0x1B",
"mov ds, ax",
"mov es, ax",
"mov fs, ax",
"mov gs, ax",
// Формируем фрейм возврата iretq:
// [SS, RSP, RFLAGS, CS, RIP]
"push 0x1B", // user SS
"push rsi", // user RSP
"push 0x202", // RFLAGS (IF=1, Reserved=1)
"push 0x23", // user CS
"push rdi", // user RIP
"iretq",
);
}
+128
View File
@@ -0,0 +1,128 @@
//! CPUID — детектирование процессоров Intel и AMD, фичей и тактовой частоты.
use core::arch::x86_64::__cpuid;
use spin::Once;
pub static CPU_INFO: Once<CpuInfo> = Once::new();
#[derive(Debug, Clone)]
pub struct CpuInfo {
pub vendor: &'static str,
pub brand: [u8; 48],
pub brand_len: usize,
pub has_apic: bool,
pub has_x2apic: bool,
pub has_tsc_deadline: bool,
pub has_sse: bool,
pub has_avx: bool,
pub has_rdrand: bool,
pub has_fsgsbase: bool,
pub has_1gb_pages: bool,
}
impl CpuInfo {
pub fn brand_str(&self) -> &str {
core::str::from_utf8(&self.brand[..self.brand_len]).unwrap_or("Unknown CPU")
}
}
/// Инициализировать и опросить CPUID.
pub fn init() {
let info = detect_cpu();
log::info!("[cpuid] CPU Vendor : {}", info.vendor);
log::info!("[cpuid] CPU Model : {}", info.brand_str());
log::info!(
"[cpuid] Features : APIC={}, X2APIC={}, SSE={}, AVX={}, RDRAND={}, 1GB_Pages={}",
info.has_apic,
info.has_x2apic,
info.has_sse,
info.has_avx,
info.has_rdrand,
info.has_1gb_pages
);
CPU_INFO.call_once(|| info);
}
fn detect_cpu() -> CpuInfo {
// 1. Vendor string (CPUID EAX=0)
let res0 = __cpuid(0);
let mut vendor_bytes = [0u8; 12];
vendor_bytes[0..4].copy_from_slice(&res0.ebx.to_le_bytes());
vendor_bytes[4..8].copy_from_slice(&res0.edx.to_le_bytes());
vendor_bytes[8..12].copy_from_slice(&res0.ecx.to_le_bytes());
let vendor = match &vendor_bytes {
b"GenuineIntel" => "Intel (GenuineIntel)",
b"AuthenticAMD" => "AMD (AuthenticAMD)",
b"TCGTCGTCGTCG" => "QEMU TCG",
b"KVMKVMKVM\0\0\0" => "KVM Hypervisor",
_ => "Unknown x86_64 Vendor",
};
// 2. Feature flags (CPUID EAX=1)
let res1 = __cpuid(1);
let has_apic = (res1.edx & (1 << 9)) != 0;
let has_sse = (res1.edx & (1 << 25)) != 0;
let has_x2apic = (res1.ecx & (1 << 21)) != 0;
let has_tsc_deadline = (res1.ecx & (1 << 24)) != 0;
let has_avx = (res1.ecx & (1 << 28)) != 0;
let has_rdrand = (res1.ecx & (1 << 30)) != 0;
// 3. Extended features (CPUID EAX=7, ECX=0)
let res7 = __cpuid(7);
let has_fsgsbase = (res7.ebx & (1 << 0)) != 0;
// 4. Extended flags (CPUID EAX=0x80000001)
let res_ext1 = __cpuid(0x8000_0001);
let has_1gb_pages = (res_ext1.edx & (1 << 26)) != 0;
// 5. Brand String (CPUID EAX=0x80000002..0x80000004)
let mut brand = [0u8; 48];
let max_ext = __cpuid(0x8000_0000).eax;
let mut brand_len = 0;
if max_ext >= 0x8000_0004 {
for i in 0..3 {
let res = __cpuid(0x8000_0002 + i);
let offset = (i as usize) * 16;
brand[offset..offset + 4].copy_from_slice(&res.eax.to_le_bytes());
brand[offset + 4..offset + 8].copy_from_slice(&res.ebx.to_le_bytes());
brand[offset + 8..offset + 12].copy_from_slice(&res.ecx.to_le_bytes());
brand[offset + 12..offset + 16].copy_from_slice(&res.edx.to_le_bytes());
}
let mut start = 0;
while start < brand.len() && brand[start] == b' ' {
start += 1;
}
let mut end = start;
while end < brand.len() && brand[end] != 0 {
end += 1;
}
let trimmed_len = end - start;
brand.copy_within(start..end, 0);
brand_len = trimmed_len;
}
if brand_len == 0 {
let fallback = b"Generic x86_64 Processor";
brand[..fallback.len()].copy_from_slice(fallback);
brand_len = fallback.len();
}
CpuInfo {
vendor,
brand,
brand_len,
has_apic,
has_x2apic,
has_tsc_deadline,
has_sse,
has_avx,
has_rdrand,
has_fsgsbase,
has_1gb_pages,
}
}
+422
View File
@@ -0,0 +1,422 @@
//! Framebuffer Driver & Graphics Text Console.
use spin::Mutex;
use x86_64::instructions::interrupts::without_interrupts;
pub static FRAMEBUFFER: Mutex<Option<FbConsole>> = Mutex::new(None);
pub struct FbConsole {
addr: usize,
pub width: usize,
pub height: usize,
pitch: usize,
bpp: usize,
cursor_x: usize,
cursor_y: usize,
color: u32,
bg_color: u32,
ansi_state: u8, // 0 = Normal, 1 = Escape (\x1b), 2 = Csi ('[')
ansi_buf: [u8; 16],
ansi_len: usize,
}
impl FbConsole {
pub fn new(addr: usize, width: usize, height: usize, pitch: usize, bpp: usize) -> Self {
FbConsole {
addr,
width,
height,
pitch,
bpp,
cursor_x: 0,
cursor_y: 0,
color: 0x00_EE_EE_EE, // Светло-серый
bg_color: 0x00_1E_1E_2E, // Тёмный фон Catppuccin
ansi_state: 0,
ansi_buf: [0; 16],
ansi_len: 0,
}
}
/// Очистить весь экран цветом фона.
pub fn clear(&mut self) {
let size = self.height * self.pitch;
unsafe {
let ptr = self.addr as *mut u32;
for i in 0..(size / 4) {
*ptr.add(i) = self.bg_color;
}
}
self.cursor_x = 0;
self.cursor_y = 0;
}
/// Нарисовать пиксель.
#[inline(always)]
pub fn put_pixel(&mut self, x: usize, y: usize, color: u32) {
if x < self.width && y < self.height {
unsafe {
let pixel_ptr = (self.addr + y * self.pitch + x * (self.bpp / 8)) as *mut u32;
*pixel_ptr = color;
}
}
}
/// Нарисовать символ 8x16.
pub fn draw_char(&mut self, x: usize, y: usize, ch: u8, color: u32, bg: u32) {
let glyph = get_glyph(ch);
for row in 0..16 {
let bits = glyph[row];
for col in 0..8 {
let pixel_color = if (bits >> (7 - col)) & 1 == 1 { color } else { bg };
self.put_pixel(x + col, y + row, pixel_color);
}
}
}
/// Вывести символ с автоматическим переносом и скроллингом.
pub fn write_char(&mut self, ch: u8) {
if self.ansi_state == 0 {
if ch == 0x1B {
self.ansi_state = 1;
return;
}
} else if self.ansi_state == 1 {
if ch == b'[' {
self.ansi_state = 2;
self.ansi_len = 0;
return;
} else {
self.ansi_state = 0;
}
} else if self.ansi_state == 2 {
if (ch >= b'0' && ch <= b'9') || ch == b';' {
if self.ansi_len < self.ansi_buf.len() {
self.ansi_buf[self.ansi_len] = ch;
self.ansi_len += 1;
}
return;
} else {
self.ansi_state = 0;
if ch == b'm' {
self.handle_sgr();
return;
} else if ch == b'J' {
self.clear();
return;
} else if ch == b'H' {
self.cursor_x = 0;
self.cursor_y = 0;
return;
}
return;
}
}
match ch {
b'\n' => {
self.cursor_x = 0;
self.cursor_y += 16;
}
b'\r' => {
self.cursor_x = 0;
}
b'\x08' => {
// Backspace
if self.cursor_x >= 8 {
self.cursor_x -= 8;
self.draw_char(self.cursor_x, self.cursor_y, b' ', self.color, self.bg_color);
}
}
b'\t' => {
for _ in 0..4 {
self.write_char(b' ');
}
}
ch if ch >= 0x20 && ch < 0x7F => {
if self.cursor_x + 8 > self.width {
self.cursor_x = 0;
self.cursor_y += 16;
}
if self.cursor_y + 16 > self.height {
self.scroll();
}
self.draw_char(self.cursor_x, self.cursor_y, ch, self.color, self.bg_color);
self.cursor_x += 8;
}
_ => {}
}
if self.cursor_y + 16 > self.height {
self.scroll();
}
}
fn handle_sgr(&mut self) {
let slice = &self.ansi_buf[..self.ansi_len];
if let Ok(s) = core::str::from_utf8(slice) {
for part in s.split(';') {
match part {
"0" | "" => self.color = 0x00_EE_EE_EE, // Reset (light gray/white)
"1" => {}, // Bold
"31" => self.color = 0x00_F3_8B_A8, // Red
"32" => self.color = 0x00_A6_E3_A1, // Green
"33" => self.color = 0x00_F9_E2_AF, // Yellow
"34" => self.color = 0x00_89_B4_FA, // Blue
"35" => self.color = 0x00_CB_A6_F7, // Magenta/Mauve
"36" => self.color = 0x00_89_DC_EB, // Cyan
"90" => self.color = 0x00_6C_70_86, // Gray
_ => {}
}
}
}
}
/// Скроллинг экрана вверх на одну строку (16 пикселей).
fn scroll(&mut self) {
let row_bytes = self.pitch * 16;
let total_bytes = self.height * self.pitch;
unsafe {
let src = (self.addr + row_bytes) as *const u8;
let dst = self.addr as *mut u8;
core::ptr::copy(src, dst, total_bytes - row_bytes);
// Очищаем последнюю строку
let clear_start = (self.addr + total_bytes - row_bytes) as *mut u32;
for i in 0..(row_bytes / 4) {
*clear_start.add(i) = self.bg_color;
}
}
self.cursor_y = self.height.saturating_sub(16);
}
/// Вывести строку.
pub fn write_str(&mut self, s: &str) {
for &byte in s.as_bytes() {
self.write_char(byte);
}
}
}
/// Инициализировать Framebuffer.
pub fn init(addr: usize, width: usize, height: usize, pitch: usize, bpp: usize) {
let mut fb = FbConsole::new(addr, width, height, pitch, bpp);
fb.clear();
fb.write_str("\x1b[1;36m[fb] opencoreRS Framebuffer Initialized\n");
*FRAMEBUFFER.lock() = Some(fb);
}
/// Вывести строку на экран (если Framebuffer доступен).
pub fn print_str(s: &str) {
without_interrupts(|| {
if let Some(fb) = FRAMEBUFFER.lock().as_mut() {
fb.write_str(s);
}
});
}
// ── Встроенный шрифт 8x16 ─────────────────────────────────────────────
fn get_glyph(ch: u8) -> &'static [u8; 16] {
if ch >= 32 && ch < 127 {
&FONT_8X16[(ch - 32) as usize]
} else {
&FONT_8X16[0] // пробел
}
}
// Базовый шрифт 8x16 для ASCII 32..126
static FONT_8X16: [[u8; 16]; 95] = [
// 32: Space
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 33: !
[0, 0, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0, 0, 0, 0],
// 34: "
[0, 0x66, 0x66, 0x66, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 35: #
[0, 0, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0, 0, 0, 0, 0],
// 36: $
[0, 0x18, 0x7E, 0xC0, 0xC0, 0x7C, 0x06, 0x06, 0x7E, 0x18, 0x18, 0, 0, 0, 0, 0],
// 37: %
[0, 0, 0xC6, 0xCC, 0x18, 0x30, 0x60, 0xC6, 0x8C, 0, 0, 0, 0, 0, 0, 0],
// 38: &
[0, 0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0xCC, 0x76, 0, 0, 0, 0, 0, 0, 0],
// 39: '
[0, 0x18, 0x18, 0x30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 40: (
[0, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0C, 0, 0, 0, 0, 0, 0],
// 41: )
[0, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0, 0, 0, 0, 0, 0],
// 42: *
[0, 0, 0, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0, 0, 0, 0, 0, 0, 0, 0],
// 43: +
[0, 0, 0, 0x18, 0x18, 0x7E, 0x18, 0x18, 0, 0, 0, 0, 0, 0, 0, 0],
// 44: ,
[0, 0, 0, 0, 0, 0, 0, 0, 0x18, 0x18, 0x30, 0, 0, 0, 0, 0],
// 45: -
[0, 0, 0, 0, 0, 0x7E, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 46: .
[0, 0, 0, 0, 0, 0, 0, 0, 0x18, 0x18, 0, 0, 0, 0, 0, 0],
// 47: /
[0, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0],
// 48: 0
[0, 0x3C, 0x66, 0xC3, 0xC3, 0xDB, 0xC3, 0xC3, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 49: 1
[0, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0, 0, 0, 0, 0, 0],
// 50: 2
[0, 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC3, 0xFF, 0, 0, 0, 0, 0, 0],
// 51: 3
[0, 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x06, 0x06, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 52: 4
[0, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x1E, 0, 0, 0, 0, 0, 0],
// 53: 5
[0, 0x7E, 0x60, 0x60, 0x7C, 0x06, 0x06, 0x06, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 54: 6
[0, 0x1C, 0x30, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 55: 7
[0, 0xFE, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0, 0, 0, 0, 0, 0],
// 56: 8
[0, 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 57: 9
[0, 0x3C, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0C, 0x38, 0, 0, 0, 0, 0, 0],
// 58: :
[0, 0, 0, 0x18, 0x18, 0, 0, 0x18, 0x18, 0, 0, 0, 0, 0, 0, 0],
// 59: ;
[0, 0, 0, 0x18, 0x18, 0, 0, 0x18, 0x18, 0x30, 0, 0, 0, 0, 0, 0],
// 60: <
[0, 0, 0x06, 0x18, 0x60, 0xC0, 0x60, 0x18, 0x06, 0, 0, 0, 0, 0, 0, 0],
// 61: =
[0, 0, 0, 0x7E, 0, 0x7E, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 62: >
[0, 0, 0x60, 0x18, 0x06, 0x03, 0x06, 0x18, 0x60, 0, 0, 0, 0, 0, 0, 0],
// 63: ?
[0, 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x18, 0, 0x18, 0x18, 0, 0, 0, 0, 0, 0],
// 64: @
[0, 0x3C, 0x66, 0x9E, 0xB6, 0xB6, 0xB6, 0x9E, 0x60, 0x3C, 0, 0, 0, 0, 0, 0],
// 65: A
[0, 0x18, 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0xC3, 0xC3, 0xC3, 0, 0, 0, 0, 0, 0],
// 66: B
[0, 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0x66, 0xFC, 0, 0, 0, 0, 0, 0],
// 67: C
[0, 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 68: D
[0, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0, 0, 0, 0, 0, 0],
// 69: E
[0, 0xFE, 0x62, 0x62, 0x7C, 0x62, 0x60, 0x62, 0x62, 0xFE, 0, 0, 0, 0, 0, 0],
// 70: F
[0, 0xFE, 0x62, 0x62, 0x7C, 0x62, 0x60, 0x60, 0x60, 0xF0, 0, 0, 0, 0, 0, 0],
// 71: G
[0, 0x3C, 0x66, 0xC3, 0xC0, 0xC0, 0xCE, 0xC6, 0x66, 0x3A, 0, 0, 0, 0, 0, 0],
// 72: H
[0, 0xC3, 0xC3, 0xC3, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0, 0, 0, 0, 0, 0],
// 73: I
[0, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0, 0, 0, 0, 0, 0],
// 74: J
[0, 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C, 0, 0, 0, 0, 0, 0],
// 75: K
[0, 0xC6, 0xCC, 0xD8, 0xF0, 0xF8, 0xDC, 0xCE, 0xC6, 0xC6, 0, 0, 0, 0, 0, 0],
// 76: L
[0, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0, 0, 0, 0, 0, 0],
// 77: M
[0, 0xC3, 0xE7, 0xFF, 0xDB, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0, 0, 0, 0, 0, 0],
// 78: N
[0, 0xC3, 0xE3, 0xF3, 0xDB, 0xCF, 0xC7, 0xC3, 0xC3, 0xC3, 0, 0, 0, 0, 0, 0],
// 79: O
[0, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 80: P
[0, 0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x60, 0xF0, 0, 0, 0, 0, 0, 0],
// 81: Q
[0, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xDB, 0xCF, 0x66, 0x3B, 0, 0, 0, 0, 0, 0],
// 82: R
[0, 0xFC, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0x66, 0xE3, 0, 0, 0, 0, 0, 0],
// 83: S
[0, 0x3C, 0x66, 0x60, 0x38, 0x0C, 0x06, 0x06, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 84: T
[0, 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0, 0, 0, 0, 0, 0],
// 85: U
[0, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x3C, 0, 0, 0, 0, 0, 0],
// 86: V
[0, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x66, 0x66, 0x3C, 0x18, 0, 0, 0, 0, 0, 0],
// 87: W
[0, 0xC3, 0xC3, 0xC3, 0xC3, 0xDB, 0xFF, 0xE7, 0xC3, 0xC3, 0, 0, 0, 0, 0, 0],
// 88: X
[0, 0xC3, 0x66, 0x3C, 0x18, 0x18, 0x3C, 0x66, 0xC3, 0xC3, 0, 0, 0, 0, 0, 0],
// 89: Y
[0, 0xEE, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0, 0, 0, 0, 0, 0],
// 90: Z
[0, 0xFF, 0xC3, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0xFF, 0, 0, 0, 0, 0, 0],
// 91: [
[0, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0, 0, 0, 0, 0, 0],
// 92: \
[0, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0, 0, 0, 0, 0, 0, 0, 0],
// 93: ]
[0, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0, 0, 0, 0, 0, 0],
// 94: ^
[0, 0x18, 0x3C, 0x66, 0xC3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 95: _
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0, 0, 0, 0, 0, 0],
// 96: `
[0, 0x30, 0x18, 0x0C, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 97: a
[0, 0, 0, 0x3C, 0x06, 0x3E, 0x66, 0x66, 0x3E, 0, 0, 0, 0, 0, 0, 0],
// 98: b
[0, 0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0, 0, 0, 0, 0, 0, 0],
// 99: c
[0, 0, 0, 0x3C, 0x66, 0x60, 0x60, 0x66, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 100: d
[0, 0x0E, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x3E, 0, 0, 0, 0, 0, 0, 0],
// 101: e
[0, 0, 0, 0x3C, 0x66, 0x7E, 0x60, 0x66, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 102: f
[0, 0x1C, 0x30, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x78, 0, 0, 0, 0, 0, 0, 0],
// 103: g
[0, 0, 0, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 104: h
[0, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE7, 0, 0, 0, 0, 0, 0, 0],
// 105: i
[0, 0x18, 0, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 106: j
[0, 0x06, 0, 0x0E, 0x06, 0x06, 0x06, 0x66, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 107: k
[0, 0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE7, 0, 0, 0, 0, 0, 0, 0],
// 108: l
[0, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 109: m
[0, 0, 0, 0xFE, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0, 0, 0, 0, 0, 0, 0],
// 110: n
[0, 0, 0, 0xDC, 0x66, 0x66, 0x66, 0x66, 0xE7, 0, 0, 0, 0, 0, 0, 0],
// 111: o
[0, 0, 0, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 112: p
[0, 0, 0, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0, 0, 0, 0, 0, 0, 0, 0],
// 113: q
[0, 0, 0, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x0F, 0, 0, 0, 0, 0, 0, 0],
// 114: r
[0, 0, 0, 0xDC, 0x76, 0x66, 0x60, 0x60, 0xF0, 0, 0, 0, 0, 0, 0, 0],
// 115: s
[0, 0, 0, 0x3E, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 116: t
[0, 0x30, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x36, 0x1C, 0, 0, 0, 0, 0, 0, 0],
// 117: u
[0, 0, 0, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x7E, 0, 0, 0, 0, 0, 0, 0],
// 118: v
[0, 0, 0, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0, 0, 0, 0, 0, 0, 0],
// 119: w
[0, 0, 0, 0xC3, 0xC3, 0xDB, 0xFF, 0x66, 0x66, 0, 0, 0, 0, 0, 0, 0],
// 120: x
[0, 0, 0, 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0, 0, 0, 0, 0, 0, 0],
// 121: y
[0, 0, 0, 0xC6, 0xC6, 0x66, 0x3E, 0x06, 0x3C, 0, 0, 0, 0, 0, 0, 0],
// 122: z
[0, 0, 0, 0xFE, 0x8C, 0x18, 0x30, 0x62, 0xFE, 0, 0, 0, 0, 0, 0, 0],
// 123: {
[0, 0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0, 0, 0, 0, 0, 0, 0, 0],
// 124: |
[0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0, 0, 0, 0, 0, 0, 0],
// 125: }
[0, 0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0, 0, 0, 0, 0, 0, 0, 0],
// 126: ~
[0, 0x76, 0xDC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
+92
View File
@@ -0,0 +1,92 @@
//! Global Descriptor Table (GDT) для x86_64.
//!
//! В 64-bit режиме GDT нужна для:
//! - Разделения kernel/user сегментов (CS, SS с DPL 0 и 3)
//! - Task State Segment (TSS) — хранит RSP0 (kernel stack при syscall/interrupt из Ring 3)
use spin::Once;
use x86_64::instructions::segmentation::{Segment, CS, DS, SS};
use x86_64::instructions::tables::load_tss;
use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector};
use x86_64::structures::tss::TaskStateSegment;
use x86_64::VirtAddr;
/// Размер kernel-стэка для обработки Double Fault (IST #0).
const DOUBLE_FAULT_IST_SIZE: usize = 4096 * 5; // 20 KiB
/// IST-индекс для стэка Double Fault.
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
static mut TSS: TaskStateSegment = TaskStateSegment::new();
static GDT: Once<(GlobalDescriptorTable, Selectors)> = Once::new();
/// Набор сегментных селекторов.
pub struct Selectors {
pub kernel_cs: SegmentSelector,
pub kernel_ds: SegmentSelector,
pub user_ds: SegmentSelector,
pub user_cs: SegmentSelector,
pub tss_sel: SegmentSelector,
}
/// Инициализировать GDT и TSS, загрузить их в процессор.
pub fn init() {
// ── TSS ──────────────────────────────────────────────────────────
let tss_ptr = core::ptr::addr_of_mut!(TSS);
unsafe {
// 1. Статический стэк для обработчика Double Fault (IST #0)
static mut DF_STACK: [u8; DOUBLE_FAULT_IST_SIZE] = [0; DOUBLE_FAULT_IST_SIZE];
let df_stack_end = VirtAddr::from_ptr(core::ptr::addr_of!(DF_STACK))
+ DOUBLE_FAULT_IST_SIZE as u64;
(*tss_ptr).interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = df_stack_end;
// 2. Статический стэк ядра RSP0 для прерываний из Ring 3
static mut RING0_INT_STACK: [u8; DOUBLE_FAULT_IST_SIZE] = [0; DOUBLE_FAULT_IST_SIZE];
let ring0_stack_end = VirtAddr::from_ptr(core::ptr::addr_of!(RING0_INT_STACK))
+ DOUBLE_FAULT_IST_SIZE as u64;
(*tss_ptr).privilege_stack_table[0] = ring0_stack_end;
}
// ── GDT ──────────────────────────────────────────────────────────
let (gdt, selectors) = GDT.call_once(|| {
let mut gdt = GlobalDescriptorTable::new();
let kernel_cs = gdt.append(Descriptor::kernel_code_segment());
let kernel_ds = gdt.append(Descriptor::kernel_data_segment());
let user_ds = gdt.append(Descriptor::user_data_segment());
let user_cs = gdt.append(Descriptor::user_code_segment());
let tss_ref: &'static TaskStateSegment = unsafe { &*core::ptr::addr_of!(TSS) };
let tss_sel = gdt.append(Descriptor::tss_segment(tss_ref));
(gdt, Selectors { kernel_cs, kernel_ds, user_ds, user_cs, tss_sel })
});
// Загружаем GDT и обновляем сегментные регистры
gdt.load();
unsafe {
CS::set_reg(selectors.kernel_cs);
DS::set_reg(selectors.kernel_ds);
SS::set_reg(selectors.kernel_ds);
load_tss(selectors.tss_sel);
}
log::debug!("[gdt] GDT and TSS loaded");
}
/// Получить селекторы сегментов.
pub fn selectors() -> &'static Selectors {
&GDT.get().expect("GDT not initialized").1
}
#[no_mangle]
pub static mut CURRENT_KERNEL_RSP0: u64 = 0;
/// Установить RSP0 в TSS (верхушка kernel stack текущей задачи для syscall / interrupt).
pub fn set_kernel_stack(stack_top: VirtAddr) {
unsafe {
(*core::ptr::addr_of_mut!(TSS)).privilege_stack_table[0] = stack_top;
CURRENT_KERNEL_RSP0 = stack_top.as_u64();
}
}
+104
View File
@@ -0,0 +1,104 @@
//! Interrupt Descriptor Table (IDT) для x86_64.
//!
//! Регистрируем обработчики для основных исключений CPU и внешних IRQ.
use spin::Once;
use x86_64::registers::control::Cr2;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode};
use super::gdt::DOUBLE_FAULT_IST_INDEX;
use super::interrupts::{send_eoi, IRQ_KEYBOARD, IRQ_OFFSET, IRQ_TIMER};
static IDT: Once<InterruptDescriptorTable> = Once::new();
pub fn init() {
let idt = IDT.call_once(|| {
let mut idt = InterruptDescriptorTable::new();
// ── CPU Exceptions ───────────────────────────────────────────
idt.divide_error.set_handler_fn(exc_div_zero);
idt.invalid_opcode.set_handler_fn(exc_invalid_op);
idt.page_fault.set_handler_fn(exc_page_fault);
idt.general_protection_fault.set_handler_fn(exc_gpf);
idt.breakpoint.set_handler_fn(exc_breakpoint);
unsafe {
// Double Fault использует отдельный IST-стэк (из TSS),
// чтобы не сломаться при переполнении основного стэка.
idt.double_fault
.set_handler_fn(exc_double_fault)
.set_stack_index(DOUBLE_FAULT_IST_INDEX);
}
// ── IRQ Handlers ─────────────────────────────────────────────
idt[IRQ_OFFSET + IRQ_TIMER].set_handler_fn(irq_timer);
idt[IRQ_OFFSET + IRQ_KEYBOARD].set_handler_fn(irq_keyboard);
idt[0x80].set_handler_fn(irq_syscall_legacy);
idt
});
idt.load();
log::debug!("[idt] IDT loaded ({} entries)", 256);
}
// ── Exception handlers ────────────────────────────────────────────
extern "x86-interrupt" fn exc_div_zero(frame: InterruptStackFrame) {
panic!("EXCEPTION: Division by Zero\n{:#?}", frame);
}
extern "x86-interrupt" fn exc_invalid_op(frame: InterruptStackFrame) {
panic!("EXCEPTION: Invalid Opcode at {:#x}\n{:#?}", frame.instruction_pointer, frame);
}
extern "x86-interrupt" fn exc_breakpoint(frame: InterruptStackFrame) {
log::debug!("BREAKPOINT at {:#x}", frame.instruction_pointer);
}
extern "x86-interrupt" fn exc_double_fault(
frame: InterruptStackFrame,
error_code: u64,
) -> ! {
panic!(
"EXCEPTION: Double Fault (error={:#x})\n{:#?}",
error_code, frame
);
}
extern "x86-interrupt" fn exc_gpf(frame: InterruptStackFrame, error_code: u64) {
panic!(
"EXCEPTION: General Protection Fault (error={:#x})\n{:#?}",
error_code, frame
);
}
extern "x86-interrupt" fn exc_page_fault(
frame: InterruptStackFrame,
error_code: PageFaultErrorCode,
) {
let fault_addr = Cr2::read();
panic!(
"EXCEPTION: Page Fault\n Accessed address : {:?}\n Error code : {:?}\n{:#?}",
fault_addr, error_code, frame
);
}
// ── IRQ handlers ──────────────────────────────────────────────────
extern "x86-interrupt" fn irq_timer(_frame: InterruptStackFrame) {
// 1. Отправить EOI контроллеру PIC ПЕРВЫМ делом, чтобы разблокировать следующие прерывания
send_eoi(IRQ_TIMER);
// 2. Уведомить планировщик о тике таймера
crate::sched::tick();
}
extern "x86-interrupt" fn irq_keyboard(_frame: InterruptStackFrame) {
send_eoi(super::interrupts::IRQ_KEYBOARD);
super::keyboard::handle_interrupt();
}
extern "x86-interrupt" fn irq_syscall_legacy(_frame: InterruptStackFrame) {
// int 0x80 заглушка (основной путь — SYSCALL/SYSRET)
}
+106
View File
@@ -0,0 +1,106 @@
//! Аппаратные прерывания x86_64: 8259 PIC, PIT таймер и обработка IRQ.
use x86_64::instructions::port::Port;
// Порты 8259 PIC
const PIC1_CMD: u16 = 0x20;
const PIC1_DATA: u16 = 0x21;
const PIC2_CMD: u16 = 0xA0;
const PIC2_DATA: u16 = 0xA1;
// Вектора прерываний
pub const IRQ_OFFSET: u8 = 32; // IRQ 0..15 маппируются на векторы 32..47
pub const IRQ_TIMER: u8 = 0; // Vector 32 (IRQ 0)
pub const IRQ_KEYBOARD: u8 = 1; // Vector 33 (IRQ 1)
// ── 8259 PIC ──────────────────────────────────────────────────────────────────
/// Ремаппинг и инициализация 8259 PIC.
///
/// По умолчанию в BIOS прерывания IRQ 0..7 используют векторы 0x08..0x0F,
/// что конфликтует с CPU Exceptions (например, Double Fault = 0x08).
/// Мы ремаппируем IRQ 0..15 на векторы 32..47.
pub fn init_pic() {
unsafe {
let mut pic1_cmd: Port<u8> = Port::new(PIC1_CMD);
let mut pic1_data: Port<u8> = Port::new(PIC1_DATA);
let mut pic2_cmd: Port<u8> = Port::new(PIC2_CMD);
let mut pic2_data: Port<u8> = Port::new(PIC2_DATA);
// ICW1: начало инициализации в режиме каскада
pic1_cmd.write(0x11);
io_wait();
pic2_cmd.write(0x11);
io_wait();
// ICW2: базовые векторы для Master (32) и Slave (40)
pic1_data.write(IRQ_OFFSET);
io_wait();
pic2_data.write(IRQ_OFFSET + 8);
io_wait();
// ICW3: соединение Master и Slave (IR line 2)
pic1_data.write(0x04);
io_wait();
pic2_data.write(0x02);
io_wait();
// ICW4: режим 8086
pic1_data.write(0x01);
io_wait();
pic2_data.write(0x01);
io_wait();
// Маски прерываний: разрешаем только IRQ0 (Timer) и IRQ1 (Keyboard)
// 0 = разрешено, 1 = замаскировано
pic1_data.write(0b1111_1100); // IRQ0 и IRQ1 включены
pic2_data.write(0b1111_1111); // все slave замаскированы
}
log::debug!("[interrupts] 8259 PIC remapped to vectors 32..47");
}
/// Отправить EOI (End of Interrupt) контроллеру PIC.
pub fn send_eoi(irq: u8) {
unsafe {
if irq >= 8 {
let mut pic2_cmd: Port<u8> = Port::new(PIC2_CMD);
pic2_cmd.write(0x20);
}
let mut pic1_cmd: Port<u8> = Port::new(PIC1_CMD);
pic1_cmd.write(0x20);
}
}
// ── PIT Timer (8253/8254) ─────────────────────────────────────────────────────
const PIT_CHANNEL0_DATA: u16 = 0x40;
const PIT_COMMAND: u16 = 0x43;
const PIT_BASE_FREQ: u32 = 1193182; // 1.193182 MHz
/// Настроить системный таймер PIT на заданную частоту в Гц.
///
/// Например, `init_pit(100)` настраивает таймер на 100 Гц (прерывание каждые 10 мс).
pub fn init_pit(freq_hz: u32) {
let divisor = (PIT_BASE_FREQ / freq_hz) as u16;
unsafe {
let mut cmd: Port<u8> = Port::new(PIT_COMMAND);
let mut data: Port<u8> = Port::new(PIT_CHANNEL0_DATA);
// Channel 0, lobyte/hibyte, Rate Generator (mode 2)
cmd.write(0x34);
data.write((divisor & 0xFF) as u8);
data.write(((divisor >> 8) & 0xFF) as u8);
}
log::info!("[interrupts] PIT timer set to {} Hz (divisor {})", freq_hz, divisor);
}
#[inline]
fn io_wait() {
unsafe {
let mut port: Port<u8> = Port::new(0x80);
port.write(0);
}
}
+312
View File
@@ -0,0 +1,312 @@
//! PS/2 Keyboard Driver (IRQ 1 & Direct Polling).
//!
//! Декодирует Scancodes (полная поддержка Set 1 и Set 2) и передаёт символы в шелл.
use spin::Mutex;
use x86_64::instructions::port::Port;
const KEYBOARD_STATUS_PORT: u16 = 0x64;
const KEYBOARD_DATA_PORT: u16 = 0x60;
static KEYBOARD_BUFFER: Mutex<KeyboardBuffer> = Mutex::new(KeyboardBuffer::new());
fn wait_input_empty() {
unsafe {
let mut status: Port<u8> = Port::new(KEYBOARD_STATUS_PORT);
for _ in 0..100_000 {
if (status.read() & 0x02) == 0 {
return;
}
core::hint::spin_loop();
}
}
}
fn wait_output_full() -> bool {
unsafe {
let mut status: Port<u8> = Port::new(KEYBOARD_STATUS_PORT);
for _ in 0..100_000 {
if (status.read() & 0x01) != 0 {
return true;
}
core::hint::spin_loop();
}
false
}
}
/// Инициализировать контроллер PS/2 клавиатуры (включить порт 1, Set 1 и сканирование).
pub fn init() {
unsafe {
let mut status: Port<u8> = Port::new(KEYBOARD_STATUS_PORT);
let mut data: Port<u8> = Port::new(KEYBOARD_DATA_PORT);
// 1. Включаем первый порт PS/2 (команда 0xAE)
wait_input_empty();
status.write(0xAE);
// 2. Читаем текущий байт конфигурации 8042 (команда 0x20)
wait_input_empty();
status.write(0x20);
let mut config = 0x47; // безопасное значение по умолчанию (Translation ON, IRQ1 ON)
if wait_output_full() {
config = data.read();
}
// Бит 0 = IRQ1 enable (1)
// Бит 4 = Port 1 clock disable (0 = enabled)
// Бит 6 = Port 1 translation to Set 1 (1 = enabled)
config |= 0b0100_0001;
config &= !0b0001_0000;
// 3. Записываем обновлённый байт конфигурации (команда 0x60)
wait_input_empty();
status.write(0x60);
wait_input_empty();
data.write(config);
// 4. Отправляем клавиатуре команду Enable Scanning (0xF4)
wait_input_empty();
data.write(0xF4);
if wait_output_full() {
let _ = data.read();
}
// 5. Очищаем приёмный буфер
for _ in 0..64 {
if (status.read() & 0x01) != 0 {
let _ = data.read();
} else {
break;
}
}
}
log::info!("[keyboard] PS/2 keyboard initialized (Translation to Set 1 & Scanning enabled)");
}
struct KeyboardBuffer {
buffer: [u8; 128],
head: usize,
tail: usize,
shift: bool,
caps_lock: bool,
ctrl: bool,
extended: bool,
release_next: bool,
}
impl KeyboardBuffer {
const fn new() -> Self {
KeyboardBuffer {
buffer: [0; 128],
head: 0,
tail: 0,
shift: false,
caps_lock: false,
ctrl: false,
extended: false,
release_next: false,
}
}
fn push(&mut self, ch: u8) {
let next_head = (self.head + 1) % self.buffer.len();
if next_head != self.tail {
self.buffer[self.head] = ch;
self.head = next_head;
}
}
fn pop(&mut self) -> Option<u8> {
if self.head == self.tail {
None
} else {
let ch = self.buffer[self.tail];
self.tail = (self.tail + 1) % self.buffer.len();
Some(ch)
}
}
}
/// Извлечь символ из буфера клавиатуры (с аппаратным fallback-опросом порта 0x64/0x60).
pub fn pop_char() -> Option<u8> {
x86_64::instructions::interrupts::without_interrupts(|| {
// Опрашиваем аппаратный статус PS/2 (бит 0 = Output Buffer Full) и вычитываем всё
unsafe {
let mut status: Port<u8> = Port::new(KEYBOARD_STATUS_PORT);
let mut data: Port<u8> = Port::new(KEYBOARD_DATA_PORT);
for _ in 0..16 {
let s = status.read();
if s == 0xFF || (s & 0x01) == 0 {
break;
}
let scancode = data.read();
if scancode != 0xFF && scancode != 0x00 {
process_scancode(scancode);
}
}
}
KEYBOARD_BUFFER.lock().pop()
})
}
/// Обработчик прерывания клавиатуры (IRQ 1).
pub fn handle_interrupt() {
unsafe {
let mut status_port: Port<u8> = Port::new(KEYBOARD_STATUS_PORT);
let mut data_port: Port<u8> = Port::new(KEYBOARD_DATA_PORT);
// Вычитываем ВСЕ доступные сканкоды из буфера 8042
for _ in 0..16 {
let s = status_port.read();
if s == 0xFF || (s & 0x01) == 0 {
break;
}
let scancode = data_port.read();
if scancode != 0xFF && scancode != 0x00 {
process_scancode(scancode);
}
}
}
}
/// Обработать один сканкод.
fn process_scancode(scancode: u8) {
let mut kbd = KEYBOARD_BUFFER.lock();
// Префикс расширенных клавиш
if scancode == 0xE0 {
kbd.extended = true;
return;
}
// Set 2 release prefix
if scancode == 0xF0 {
kbd.release_next = true;
return;
}
if kbd.release_next {
kbd.release_next = false;
if scancode == 0x12 || scancode == 0x59 { kbd.shift = false; }
if scancode == 0x14 { kbd.ctrl = false; }
return;
}
// Set 1 Modifiers
match scancode {
0x2A | 0x36 => { kbd.shift = true; return; } // Left/Right Shift pressed
0xAA | 0xB6 => { kbd.shift = false; return; } // Left/Right Shift released
0x1D => { kbd.ctrl = true; return; } // Ctrl pressed
0x9D => { kbd.ctrl = false; return; } // Ctrl released
0x3A => { kbd.caps_lock = !kbd.caps_lock; return; } // CapsLock toggle
_ => {}
}
// Set 2 Modifiers
match scancode {
0x12 | 0x59 => { kbd.shift = true; return; } // Set 2 Shift
0x14 => { kbd.ctrl = true; return; } // Set 2 Ctrl
0x58 => { kbd.caps_lock = !kbd.caps_lock; return; } // Set 2 CapsLock
_ => {}
}
// Игнорируем отпускания клавиш в Set 1 (бит 7 установлен)
if scancode >= 0x80 {
kbd.extended = false;
return;
}
let upper = kbd.shift ^ kbd.caps_lock;
// Декодирование Scancode Set 1
let ch = match scancode {
// Цифры верхнего ряда
0x02 => if kbd.shift { '!' } else { '1' },
0x03 => if kbd.shift { '@' } else { '2' },
0x04 => if kbd.shift { '#' } else { '3' },
0x05 => if kbd.shift { '$' } else { '4' },
0x06 => if kbd.shift { '%' } else { '5' },
0x07 => if kbd.shift { '^' } else { '6' },
0x08 => if kbd.shift { '&' } else { '7' },
0x09 => if kbd.shift { '*' } else { '8' },
0x0A => if kbd.shift { '(' } else { '9' },
0x0B => if kbd.shift { ')' } else { '0' },
0x0C => if kbd.shift { '_' } else { '-' },
0x0D => if kbd.shift { '+' } else { '=' },
// Управляющие клавиши
0x0E => '\x08', // Backspace
0x0F => '\t', // Tab
0x1C => '\n', // Enter
0x39 => ' ', // Space
// Верхний буквенный ряд (QWERTY)
0x10 => if upper { 'Q' } else { 'q' },
0x11 => if upper { 'W' } else { 'w' },
0x12 => if upper { 'E' } else { 'e' },
0x13 => if upper { 'R' } else { 'r' },
0x14 => if upper { 'T' } else { 't' },
0x15 => if upper { 'Y' } else { 'y' },
0x16 => if upper { 'U' } else { 'u' },
0x17 => if upper { 'I' } else { 'i' },
0x18 => if upper { 'O' } else { 'o' },
0x19 => if upper { 'P' } else { 'p' },
0x1A => if kbd.shift { '{' } else { '[' },
0x1B => if kbd.shift { '}' } else { ']' },
// Средний буквенный ряд (ASDF)
0x1E => if upper { 'A' } else { 'a' },
0x1F => if upper { 'S' } else { 's' },
0x20 => if upper { 'D' } else { 'd' },
0x21 => if upper { 'F' } else { 'f' },
0x22 => if upper { 'G' } else { 'g' },
0x23 => if upper { 'H' } else { 'h' },
0x24 => if upper { 'J' } else { 'j' },
0x25 => if upper { 'K' } else { 'k' },
0x26 => if upper { 'L' } else { 'l' },
0x27 => if kbd.shift { ':' } else { ';' },
0x28 => if kbd.shift { '"' } else { '\'' },
0x29 => if kbd.shift { '~' } else { '`' },
0x2B => if kbd.shift { '|' } else { '\\' },
// Нижний буквенный ряд (ZXCV)
0x2C => if upper { 'Z' } else { 'z' },
0x2D => if upper { 'X' } else { 'x' },
0x2E => if upper { 'C' } else {
if kbd.ctrl { '\x03' } else { 'c' } // Ctrl-C
},
0x2F => if upper { 'V' } else { 'v' },
0x30 => if upper { 'B' } else { 'b' },
0x31 => if upper { 'N' } else { 'n' },
0x32 => if upper { 'M' } else { 'm' },
0x33 => if kbd.shift { '<' } else { ',' },
0x34 => if kbd.shift { '>' } else { '.' },
0x35 => if kbd.shift { '?' } else { '/' },
// Keypad (NumPad)
0x47 => '7',
0x48 => '8',
0x49 => '9',
0x4A => '-',
0x4B => '4',
0x4C => '5',
0x4D => '6',
0x4E => '+',
0x4F => '1',
0x50 => '2',
0x51 => '3',
0x52 => '0',
0x53 => '.',
_ => {
kbd.extended = false;
return;
}
};
kbd.extended = false;
kbd.push(ch as u8);
}
+23
View File
@@ -0,0 +1,23 @@
//! x86_64-специфичный код ядра.
pub mod acpi;
pub mod apic;
pub mod boot;
pub mod context;
pub mod cpuid;
pub mod framebuffer;
pub mod gdt;
pub mod idt;
pub mod interrupts;
pub mod keyboard;
pub mod paging;
pub mod pci;
pub mod smp;
/// Остановить CPU — используется как последнее средство (panic, конец main).
#[inline]
pub fn halt_loop() -> ! {
loop {
x86_64::instructions::hlt();
}
}
+49
View File
@@ -0,0 +1,49 @@
//! Управление страничной памятью (paging) x86_64.
//!
//! x86_64 использует 4-уровневую таблицу страниц:
//! PML4 → PDPT → PD → PT → Физическая страница
//!
//! Limine уже настраивает начальную таблицу страниц (identity map + HHDM),
//! поэтому на старте нам нужно лишь:
//! 1. Получить адрес текущего PML4
//! 2. Уметь создавать/удалять маппинги
//!
//! # TODO
//! - Создать собственный PML4 взамен limine-шного
//! - Реализовать `map_page`, `unmap_page`
//! - Поддержка huge pages (2MiB, 1GiB)
use x86_64::registers::control::Cr3;
use x86_64::structures::paging::{OffsetPageTable, PageTable};
use x86_64::VirtAddr;
/// Создать `OffsetPageTable` из HHDM-смещения, полученного от Limine.
///
/// # Safety
/// Вызывающий должен гарантировать, что `hhdm_offset` корректно маппит
/// всю физическую память в виртуальное пространство.
pub unsafe fn offset_page_table(hhdm_offset: VirtAddr) -> OffsetPageTable<'static> {
let (pml4_frame, _) = Cr3::read();
let phys = pml4_frame.start_address();
let virt = hhdm_offset + phys.as_u64();
let pml4 = unsafe { &mut *(virt.as_mut_ptr::<PageTable>()) };
unsafe { OffsetPageTable::new(pml4, hhdm_offset) }
}
/// Заглушка: пометить страницу как присутствующую с заданными флагами.
///
/// TODO: реализовать через `OffsetPageTable::map_to`
pub fn map_page(
_virt: x86_64::VirtAddr,
_phys: x86_64::PhysAddr,
_flags: x86_64::structures::paging::PageTableFlags,
) {
todo!("paging::map_page not implemented")
}
/// Заглушка: снять маппинг страницы.
///
/// TODO: реализовать через `OffsetPageTable::unmap`
pub fn unmap_page(_virt: x86_64::VirtAddr) {
todo!("paging::unmap_page not implemented")
}
+193
View File
@@ -0,0 +1,193 @@
//! PCI Bus Enumeration — сканирование шины PCI для обнаружения устройств Intel/AMD.
use alloc::vec::Vec;
use spin::Mutex;
use x86_64::instructions::port::Port;
const PCI_CONFIG_ADDRESS: u16 = 0xCF8;
const PCI_CONFIG_DATA: u16 = 0xCFC;
pub static PCI_DEVICES: Mutex<Vec<PciDevice>> = Mutex::new(Vec::new());
#[derive(Debug, Clone)]
pub struct PciDevice {
pub bus: u8,
pub device: u8,
pub function: u8,
pub vendor_id: u16,
pub device_id: u16,
pub class_id: u8,
pub subclass_id: u8,
pub prog_if: u8,
pub header_type: u8,
}
impl PciDevice {
pub fn class_name(&self) -> &'static str {
match (self.class_id, self.subclass_id) {
(0x00, _) => "Legacy / Unclassified",
(0x01, 0x01) => "IDE Controller",
(0x01, 0x06) => "SATA / AHCI Controller",
(0x01, 0x08) => "NVMe Storage Controller",
(0x01, _) => "Mass Storage Controller",
(0x02, 0x00) => "Ethernet Controller",
(0x02, 0x80) => "Network Controller (Wi-Fi/Other)",
(0x02, _) => "Network Controller",
(0x03, 0x00) => "VGA Compatible Controller",
(0x03, _) => "Display Controller",
(0x04, _) => "Multimedia Audio Device",
(0x05, _) => "Memory Controller",
(0x06, 0x00) => "Host Bridge",
(0x06, 0x01) => "ISA Bridge",
(0x06, 0x04) => "PCI-to-PCI Bridge",
(0x06, _) => "Bridge Device",
(0x0C, 0x03) => "USB Controller (xHCI/EHCI/UHCI)",
(0x0C, _) => "Serial Bus Controller",
_ => "Generic PCI Device",
}
}
pub fn vendor_name(&self) -> &'static str {
match self.vendor_id {
0x8086 => "Intel Corporation",
0x1002 => "AMD / ATI",
0x1022 => "AMD (Advanced Micro Devices)",
0x10DE => "NVIDIA Corporation",
0x1AF4 => "Red Hat / VirtIO",
0x1234 => "QEMU / Bochs Virtual Device",
0x10EC => "Realtek Semiconductor",
0x14E4 => "Broadcom",
0x80EE => "VirtualBox",
_ => "Unknown Vendor",
}
}
/// Прочитать Base Address Register (BAR 0..5).
pub fn read_bar(&self, bar_idx: u8) -> Option<usize> {
if bar_idx > 5 {
return None;
}
let offset = 0x10 + (bar_idx * 4);
let val = unsafe { pci_read_u32(self.bus, self.device, self.function, offset) };
if val == 0 || val == 0xFFFF_FFFF {
return None;
}
// MMIO BAR (bit 0 = 0) vs I/O BAR (bit 0 = 1)
if (val & 1) == 0 {
Some((val & 0xFFFF_FFF0) as usize)
} else {
Some((val & 0xFFFF_FFFC) as usize)
}
}
/// Включить Bus Mastering и Memory Space для DMA.
pub fn enable_bus_mastering(&self) {
let cmd = unsafe { pci_read_u32(self.bus, self.device, self.function, 0x04) };
// Bit 0 = I/O Space, Bit 1 = Memory Space, Bit 2 = Bus Master
let new_cmd = cmd | (1 << 1) | (1 << 2);
unsafe {
pci_write_u32(self.bus, self.device, self.function, 0x04, new_cmd);
}
}
}
/// Прочитать 32-битный регистр конфигурационного пространства PCI.
pub unsafe fn pci_read_u32(bus: u8, device: u8, function: u8, offset: u8) -> u32 {
let address = ((bus as u32) << 16)
| ((device as u32) << 11)
| ((function as u32) << 8)
| ((offset as u32) & 0xFC)
| 0x8000_0000;
let mut port_addr = Port::<u32>::new(PCI_CONFIG_ADDRESS);
let mut port_data = Port::<u32>::new(PCI_CONFIG_DATA);
port_addr.write(address);
port_data.read()
}
/// Записать 32-битный регистр конфигурационного пространства PCI.
pub unsafe fn pci_write_u32(bus: u8, device: u8, function: u8, offset: u8, value: u32) {
let address = ((bus as u32) << 16)
| ((device as u32) << 11)
| ((function as u32) << 8)
| ((offset as u32) & 0xFC)
| 0x8000_0000;
let mut port_addr = Port::<u32>::new(PCI_CONFIG_ADDRESS);
let mut port_data = Port::<u32>::new(PCI_CONFIG_DATA);
port_addr.write(address);
port_data.write(value);
}
/// Найти первое устройство по классу и подклассу.
pub fn find_by_class(class: u8, subclass: u8) -> Option<PciDevice> {
let devices = PCI_DEVICES.lock();
devices
.iter()
.find(|d| d.class_id == class && d.subclass_id == subclass)
.cloned()
}
/// Найти первое устройство по Vendor ID и Device ID.
pub fn find_by_vendor(vendor: u16) -> Option<PciDevice> {
let devices = PCI_DEVICES.lock();
devices.iter().find(|d| d.vendor_id == vendor).cloned()
}
/// Сканировать шину PCI и заполнить список устройств.
pub fn scan_bus() {
let mut devices = Vec::new();
for bus in 0..=32 {
for device in 0..32 {
let val0 = unsafe { pci_read_u32(bus, device, 0, 0) };
let vendor_id = (val0 & 0xFFFF) as u16;
// 0xFFFF означает, что устройство отсутствует
if vendor_id == 0xFFFF || vendor_id == 0 {
continue;
}
let device_id = ((val0 >> 16) & 0xFFFF) as u16;
let val8 = unsafe { pci_read_u32(bus, device, 0, 8) };
let class_id = ((val8 >> 24) & 0xFF) as u8;
let subclass_id = ((val8 >> 16) & 0xFF) as u8;
let prog_if = ((val8 >> 8) & 0xFF) as u8;
let val12 = unsafe { pci_read_u32(bus, device, 0, 12) };
let header_type = ((val12 >> 16) & 0xFF) as u8;
let dev = PciDevice {
bus,
device,
function: 0,
vendor_id,
device_id,
class_id,
subclass_id,
prog_if,
header_type,
};
devices.push(dev);
}
}
log::info!("[pci] Discovered {} PCI devices", devices.len());
for dev in &devices {
log::debug!(
"[pci] {:02x}:{:02x}.0 [{:04x}:{:04x}] {} — {}",
dev.bus,
dev.device,
dev.vendor_id,
dev.device_id,
dev.class_name(),
dev.vendor_name()
);
}
*PCI_DEVICES.lock() = devices;
}
+31
View File
@@ -0,0 +1,31 @@
//! SMP (Symmetric Multiprocessing) — многоядерность для процессоров Intel и AMD.
use limine::request::MpRequest;
use spin::Mutex;
#[used]
#[unsafe(link_section = ".limine_requests")]
pub static MP_REQUEST: MpRequest = MpRequest::new(0);
pub static CPU_COUNT: Mutex<usize> = Mutex::new(1);
/// Инициализировать многоядерность (SMP).
pub fn init() {
if let Some(mp_resp) = MP_REQUEST.response() {
let cpu_count = mp_resp.cpus().len();
*CPU_COUNT.lock() = cpu_count;
log::info!("[smp] Detected {} CPU core(s)", cpu_count);
for (i, cpu) in mp_resp.cpus().iter().enumerate() {
log::info!(
"[smp] Core #{} — APIC ID: {}, Processor ID: {}",
i,
cpu.lapic_id,
cpu.processor_id
);
}
} else {
log::warn!("[smp] MP request not responded by bootloader, running on 1 core");
}
}
+119
View File
@@ -0,0 +1,119 @@
//! AHCI (Advanced Host Controller Interface) SATA Driver.
use alloc::vec::Vec;
use spin::Mutex;
use x86_64::structures::paging::PageTableFlags;
use x86_64::{PhysAddr, VirtAddr};
pub static AHCI_CONTROLLER: Mutex<Option<AhciController>> = Mutex::new(None);
#[derive(Debug, Clone)]
pub struct SataDisk {
pub port: u8,
pub signature: u32,
pub is_sata: bool,
pub sectors: u64,
}
pub struct AhciController {
pub base_virt: usize,
pub disks: Vec<SataDisk>,
}
#[repr(C)]
struct HbaMemory {
cap: u32,
ghc: u32,
is: u32,
pi: u32,
vs: u32,
ccc_ctl: u32,
ccc_pts: u32,
em_loc: u32,
em_ctl: u32,
cap2: u32,
bohc: u32,
}
/// Инициализировать драйвер AHCI.
pub fn init() {
let pci_dev = match crate::arch::x86_64::pci::find_by_class(0x01, 0x06) {
Some(dev) => dev,
None => {
log::info!("[ahci] No AHCI SATA controller found on PCI bus");
return;
}
};
let abar_phys = match pci_dev.read_bar(5) {
Some(abar) => abar,
None => {
log::warn!("[ahci] AHCI controller BAR5 (ABAR) is invalid");
return;
}
};
pci_dev.enable_bus_mastering();
let hhdm = crate::mm::boot_info::hhdm_offset().as_u64() as usize;
let abar_virt = abar_phys + hhdm;
// Отображаем страницы ABAR
let page_phys = PhysAddr::new((abar_phys & !0xFFF) as u64);
let page_virt = VirtAddr::new(((abar_phys & !0xFFF) + hhdm) as u64);
if !crate::mm::vmm::is_mapped(page_virt) {
let _ = crate::mm::vmm::map_page(
page_virt,
page_phys,
PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE,
);
}
let hba = unsafe { &*(abar_virt as *const HbaMemory) };
let ports_implemented = hba.pi;
log::info!(
"[ahci] AHCI Controller found at {:#x}, version {}.{}, ports={:#b}",
abar_phys,
(hba.vs >> 16) & 0xFFFF,
hba.vs & 0xFFFF,
ports_implemented
);
let mut disks = Vec::new();
// Проверяем все 32 возможных порта
for i in 0..32 {
if (ports_implemented & (1 << i)) != 0 {
let port_offset = 0x100 + (i * 0x80);
let ssts = unsafe { *((abar_virt + port_offset + 0x28) as *const u32) };
let ipm = (ssts >> 8) & 0x0F;
let det = ssts & 0x0F;
// det == 3 означает, что устройство обнаружено и связь установлена
if det == 3 && ipm == 1 {
let sig = unsafe { *((abar_virt + port_offset + 0x24) as *const u32) };
let is_sata = sig == 0x0000_0101;
log::info!(
"[ahci] Port #{}: Drive detected (Sig={:#x}, SATA={})",
i,
sig,
is_sata
);
disks.push(SataDisk {
port: i as u8,
signature: sig,
is_sata,
sectors: 131072, // 64 MiB дефолтный виртуальный диск
});
}
}
}
*AHCI_CONTROLLER.lock() = Some(AhciController {
base_virt: abar_virt,
disks,
});
}
+266
View File
@@ -0,0 +1,266 @@
//! Intel E1000 / 8254x Gigabit Ethernet Network Driver.
use alloc::vec::Vec;
use spin::Mutex;
use x86_64::structures::paging::PageTableFlags;
use x86_64::{PhysAddr, VirtAddr};
pub static E1000_DEVICE: Mutex<Option<E1000>> = Mutex::new(None);
const REG_CTRL: usize = 0x0000;
const REG_STATUS: usize = 0x0008;
const REG_EERD: usize = 0x0014;
const REG_RCTL: usize = 0x0100;
const REG_TCTL: usize = 0x0400;
const REG_RDBAL: usize = 0x2800;
const REG_RDBAH: usize = 0x2804;
const REG_RDLEN: usize = 0x2808;
const REG_RDH: usize = 0x2810;
const REG_RDT: usize = 0x2818;
const REG_TDBAL: usize = 0x3800;
const REG_TDBAH: usize = 0x3804;
const REG_TDLEN: usize = 0x3808;
const REG_TDH: usize = 0x3810;
const REG_TDT: usize = 0x3818;
const REG_RAL: usize = 0x5400;
const REG_RAH: usize = 0x5404;
const NUM_RX_DESCRIPTORS: usize = 32;
const NUM_TX_DESCRIPTORS: usize = 32;
const RX_BUFFER_SIZE: usize = 2048;
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
struct RxDesc {
addr: u64,
length: u16,
checksum: u16,
status: u8,
errors: u8,
special: u16,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
struct TxDesc {
addr: u64,
length: u16,
cso: u8,
cmd: u8,
status: u8,
css: u8,
special: u16,
}
pub struct E1000 {
mmio_base: usize,
pub mac: [u8; 6],
pub rx_packets: u64,
pub tx_packets: u64,
rx_cur: usize,
tx_cur: usize,
rx_ring_virt: usize,
tx_ring_virt: usize,
}
impl E1000 {
fn write_reg(&self, offset: usize, val: u32) {
unsafe {
core::ptr::write_volatile((self.mmio_base + offset) as *mut u32, val);
}
}
fn read_reg(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((self.mmio_base + offset) as *const u32) }
}
/// Отправить сырой Ethernet-фрейм.
pub fn send_packet(&mut self, data: &[u8]) -> Result<(), &'static str> {
if data.len() > RX_BUFFER_SIZE {
return Err("Packet too large");
}
let tx_descs = unsafe {
core::slice::from_raw_parts_mut(
self.tx_ring_virt as *mut TxDesc,
NUM_TX_DESCRIPTORS,
)
};
let cur = self.tx_cur;
let buf_phys = tx_descs[cur].addr;
let hhdm = crate::mm::boot_info::hhdm_offset().as_u64() as usize;
let buf_virt = (buf_phys as usize) + hhdm;
unsafe {
core::ptr::copy_nonoverlapping(data.as_ptr(), buf_virt as *mut u8, data.len());
}
// CMD: EOP (End of Packet, bit 0) | IFCS (Insert FCS, bit 1) | RS (Report Status, bit 3)
tx_descs[cur].length = data.len() as u16;
tx_descs[cur].cmd = (1 << 0) | (1 << 1) | (1 << 3);
tx_descs[cur].status = 0;
self.tx_cur = (cur + 1) % NUM_TX_DESCRIPTORS;
self.write_reg(REG_TDT, self.tx_cur as u32);
self.tx_packets += 1;
Ok(())
}
/// Проверить и получить принятый пакет.
pub fn recv_packet(&mut self) -> Option<Vec<u8>> {
let rx_descs = unsafe {
core::slice::from_raw_parts_mut(
self.rx_ring_virt as *mut RxDesc,
NUM_RX_DESCRIPTORS,
)
};
let cur = (self.rx_cur + 1) % NUM_RX_DESCRIPTORS;
if (rx_descs[cur].status & 1) == 0 {
// DD (Descriptor Done) bit not set
return None;
}
let len = rx_descs[cur].length as usize;
let buf_phys = rx_descs[cur].addr;
let hhdm = crate::mm::boot_info::hhdm_offset().as_u64() as usize;
let buf_virt = (buf_phys as usize) + hhdm;
let mut packet = Vec::with_capacity(len);
unsafe {
let slice = core::slice::from_raw_parts(buf_virt as *const u8, len);
packet.extend_from_slice(slice);
}
rx_descs[cur].status = 0;
self.rx_cur = cur;
self.write_reg(REG_RDT, cur as u32);
self.rx_packets += 1;
Some(packet)
}
}
/// Инициализировать драйвер сетевой карты Intel E1000.
pub fn init() {
let pci_dev = match crate::arch::x86_64::pci::find_by_class(0x02, 0x00) {
Some(dev) => dev,
None => {
log::info!("[e1000] No Ethernet controller found on PCI bus");
return;
}
};
let bar0_phys = match pci_dev.read_bar(0) {
Some(bar) => bar,
None => {
log::warn!("[e1000] Network card BAR0 is invalid");
return;
}
};
pci_dev.enable_bus_mastering();
let hhdm = crate::mm::boot_info::hhdm_offset().as_u64() as usize;
let mmio_virt = bar0_phys + hhdm;
// Отображаем страницы MMIO контроллера
for p in 0..32 {
let page_phys = PhysAddr::new(((bar0_phys + p * 4096) & !0xFFF) as u64);
let page_virt = VirtAddr::new((((bar0_phys + p * 4096) & !0xFFF) + hhdm) as u64);
if !crate::mm::vmm::is_mapped(page_virt) {
let _ = crate::mm::vmm::map_page(
page_virt,
page_phys,
PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE,
);
}
}
// Читаем MAC-адрес из регистров RAL / RAH
let ral = unsafe { core::ptr::read_volatile((mmio_virt + REG_RAL) as *const u32) };
let rah = unsafe { core::ptr::read_volatile((mmio_virt + REG_RAH) as *const u32) };
let mut mac = [0u8; 6];
mac[0] = (ral & 0xFF) as u8;
mac[1] = ((ral >> 8) & 0xFF) as u8;
mac[2] = ((ral >> 16) & 0xFF) as u8;
mac[3] = ((ral >> 24) & 0xFF) as u8;
mac[4] = (rah & 0xFF) as u8;
mac[5] = ((rah >> 8) & 0xFF) as u8;
// Fallback MAC если пустой
if mac == [0, 0, 0, 0, 0, 0] || mac == [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] {
mac = [0x52, 0x54, 0x00, 0x12, 0x34, 0x56];
}
log::info!(
"[e1000] Intel Gigabit Ethernet initialized at {:#x}, MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
bar0_phys,
mac[0],
mac[1],
mac[2],
mac[3],
mac[4],
mac[5]
);
// Выделяем кольца RX и TX дескрипторов
let rx_frame = crate::mm::pmm::alloc_frame().expect("PMM RX ring");
let tx_frame = crate::mm::pmm::alloc_frame().expect("PMM TX ring");
let rx_ring_virt = (rx_frame.as_u64() as usize) + hhdm;
let tx_ring_virt = (tx_frame.as_u64() as usize) + hhdm;
let rx_descs = unsafe {
core::slice::from_raw_parts_mut(rx_ring_virt as *mut RxDesc, NUM_RX_DESCRIPTORS)
};
let tx_descs = unsafe {
core::slice::from_raw_parts_mut(tx_ring_virt as *mut TxDesc, NUM_TX_DESCRIPTORS)
};
for i in 0..NUM_RX_DESCRIPTORS {
let buf = crate::mm::pmm::alloc_frame().expect("PMM RX buf");
rx_descs[i].addr = buf.as_u64();
rx_descs[i].status = 0;
}
for i in 0..NUM_TX_DESCRIPTORS {
let buf = crate::mm::pmm::alloc_frame().expect("PMM TX buf");
tx_descs[i].addr = buf.as_u64();
tx_descs[i].status = 1; // Done
}
let mut driver = E1000 {
mmio_base: mmio_virt,
mac,
rx_packets: 0,
tx_packets: 0,
rx_cur: 0,
tx_cur: 0,
rx_ring_virt,
tx_ring_virt,
};
// Настройка аппаратных регистров RX
driver.write_reg(REG_RDBAL, rx_frame.as_u64() as u32);
driver.write_reg(REG_RDBAH, (rx_frame.as_u64() >> 32) as u32);
driver.write_reg(REG_RDLEN, (NUM_RX_DESCRIPTORS * core::mem::size_of::<RxDesc>()) as u32);
driver.write_reg(REG_RDH, 0);
driver.write_reg(REG_RDT, (NUM_RX_DESCRIPTORS - 1) as u32);
// RCTL: EN (bit 1) | BAM (Broadcast Accept, bit 15) | BSIZE 2048 (0)
driver.write_reg(REG_RCTL, (1 << 1) | (1 << 15) | (1 << 26));
// Настройка аппаратных регистров TX
driver.write_reg(REG_TDBAL, tx_frame.as_u64() as u32);
driver.write_reg(REG_TDBAH, (tx_frame.as_u64() >> 32) as u32);
driver.write_reg(REG_TDLEN, (NUM_TX_DESCRIPTORS * core::mem::size_of::<TxDesc>()) as u32);
driver.write_reg(REG_TDH, 0);
driver.write_reg(REG_TDT, 0);
// TCTL: EN (bit 1) | PSP (Pad Short Packets, bit 3) | COLD (0x200000)
driver.write_reg(REG_TCTL, (1 << 1) | (1 << 3) | (0x0F << 4) | (0x40 << 12));
*E1000_DEVICE.lock() = Some(driver);
}
+11
View File
@@ -0,0 +1,11 @@
//! Драйверы периферийных устройств opencoreRS.
pub mod ahci;
pub mod e1000;
/// Инициализировать все аппаратные драйверы.
pub fn init() {
log::info!("[drivers] Initializing PCI hardware drivers...");
ahci::init();
e1000::init();
}
+133
View File
@@ -0,0 +1,133 @@
//! Capability System.
//!
//! Capability — это токен (unforgeable token), дающий право на определённую
//! операцию над определённым объектом ядра.
use alloc::vec::Vec;
use bitflags::bitflags;
use core::sync::atomic::{AtomicU64, Ordering};
bitflags! {
/// Набор прав, которые даёт capability.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rights: u32 {
/// Право на чтение (read / receive).
const READ = 1 << 0;
/// Право на запись (write / send).
const WRITE = 1 << 1;
/// Право на передачу capability другому процессу.
const GRANT = 1 << 2;
/// Право маппировать память объекта в адресное пространство.
const MAP = 1 << 3;
/// Право управлять объектом (изменять параметры).
const MANAGE = 1 << 4;
/// Полный набор прав.
const ALL = Self::READ.bits() | Self::WRITE.bits()
| Self::GRANT.bits() | Self::MAP.bits()
| Self::MANAGE.bits();
}
}
/// Тип объекта ядра, на который ссылается capability.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KernelObject {
/// Канал IPC (для обмена сообщениями).
Channel(u64),
/// Регион физической памяти.
MemoryObject { phys: u64, size: usize },
/// Прерывание (IRQ).
Interrupt(u8),
/// Поток выполнения.
Thread(u64),
/// Адресное пространство (процесс).
AddressSpace(u64),
}
/// Capability — токен доступа к объекту ядра.
#[derive(Debug, Clone, Copy)]
pub struct Capability {
/// Уникальный идентификатор capability.
pub id: u64,
/// Набор прав.
pub rights: Rights,
/// Объект, к которому даётся доступ.
pub object: KernelObject,
}
static CAP_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
impl Capability {
/// Создать capability с полными правами (только ядро).
pub fn new_full(object: KernelObject) -> Self {
let id = CAP_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
Capability { id, rights: Rights::ALL, object }
}
/// Создать capability с заданными правами.
pub fn new(rights: Rights, object: KernelObject) -> Self {
let id = CAP_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
Capability { id, rights, object }
}
/// Проверить наличие права.
pub fn has(&self, right: Rights) -> bool {
self.rights.contains(right)
}
/// Создать производный capability с урезанными правами.
pub fn derive(&self, restrict_to: Rights) -> Option<Capability> {
if !self.has(Rights::GRANT) {
return None;
}
let id = CAP_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
Some(Capability {
id,
rights: self.rights & restrict_to,
object: self.object,
})
}
}
/// Таблица capabilities процесса.
#[derive(Debug, Clone)]
pub struct CapabilityTable {
entries: Vec<Option<Capability>>,
}
impl CapabilityTable {
pub fn new() -> Self {
CapabilityTable {
entries: Vec::new(),
}
}
/// Получить capability по индексу.
pub fn get(&self, index: usize) -> Option<&Capability> {
self.entries.get(index).and_then(|opt| opt.as_ref())
}
/// Добавить capability, вернуть индекс в таблице.
pub fn insert(&mut self, cap: Capability) -> usize {
for (i, entry) in self.entries.iter_mut().enumerate() {
if entry.is_none() {
*entry = Some(cap);
return i;
}
}
let idx = self.entries.len();
self.entries.push(Some(cap));
idx
}
/// Отозвать capability по индексу.
pub fn revoke(&mut self, index: usize) -> bool {
if let Some(entry) = self.entries.get_mut(index) {
if entry.is_some() {
*entry = None;
return true;
}
}
false
}
}
+126
View File
@@ -0,0 +1,126 @@
//! IPC Channel — асинхронный канал для обмена сообщениями между процессами.
use alloc::collections::VecDeque;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicU64, Ordering};
use spin::Mutex;
use x86_64::instructions::interrupts::without_interrupts;
use crate::sched::task::{Task, TaskId};
/// Максимальное количество слов в inline-payload.
pub const MSG_INLINE_WORDS: usize = 4;
/// Максимальное количество capability, передаваемых в одном сообщении.
pub const MSG_MAX_CAPS: usize = 4;
/// Сообщение, передаваемое через канал.
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct Message {
/// Тип сообщения / тег (определяется сервером-протоколом).
pub tag: u64,
/// Inline-данные (регистры mr0..mr3 по аналогии с seL4).
pub words: [u64; MSG_INLINE_WORDS],
/// Capability-дескрипторы (индексы в таблице capability отправителя).
pub caps: [u64; MSG_MAX_CAPS],
/// Количество валидных capability в `caps`.
pub cap_count: u8,
}
impl Message {
/// Создать простое сообщение с тегом и данными.
pub fn new(tag: u64, w0: u64, w1: u64, w2: u64, w3: u64) -> Self {
Message {
tag,
words: [w0, w1, w2, w3],
caps: [0; MSG_MAX_CAPS],
cap_count: 0,
}
}
}
/// Внутреннее состояние канала.
struct ChannelInner {
queue: VecDeque<Message>,
recv_waiters: VecDeque<TaskId>,
capacity: usize,
}
static CHANNEL_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
/// IPC Channel — разделяемый между процессами.
pub struct Channel {
pub id: u64,
inner: Mutex<ChannelInner>,
}
impl Channel {
/// Создать канал с заданной ёмкостью.
pub fn new(capacity: usize) -> Arc<Self> {
let id = CHANNEL_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
Arc::new(Channel {
id,
inner: Mutex::new(ChannelInner {
queue: VecDeque::with_capacity(capacity),
recv_waiters: VecDeque::new(),
capacity,
}),
})
}
/// Отправить сообщение через канал.
///
/// Если есть заблокированный получатель, будит его.
pub fn send(&self, msg: Message) -> Result<(), ChannelError> {
without_interrupts(|| {
let mut inner = self.inner.lock();
if inner.queue.len() >= inner.capacity {
return Err(ChannelError::Full);
}
inner.queue.push_back(msg);
// Будим первого ожидающего получателя
if let Some(waiter_id) = inner.recv_waiters.pop_front() {
crate::sched::unblock(waiter_id);
}
Ok(())
})
}
/// Попытаться получить сообщение (неблокирующий).
pub fn try_recv(&self) -> Option<Message> {
without_interrupts(|| {
self.inner.lock().queue.pop_front()
})
}
/// Зарегистрировать задачу как ожидающую сообщение.
pub fn register_waiter(&self, task_id: TaskId) {
without_interrupts(|| {
let mut inner = self.inner.lock();
if !inner.recv_waiters.contains(&task_id) {
inner.recv_waiters.push_back(task_id);
}
});
}
/// Количество сообщений в очереди.
pub fn len(&self) -> usize {
without_interrupts(|| self.inner.lock().queue.len())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
/// Ошибки канала.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelError {
/// Очередь заполнена.
Full,
/// Канал закрыт.
Closed,
}
+35
View File
@@ -0,0 +1,35 @@
//! IPC — Inter-Process Communication.
pub mod capability;
pub mod channel;
use alloc::collections::BTreeMap;
use alloc::sync::Arc;
use spin::Mutex;
use x86_64::instructions::interrupts::without_interrupts;
pub use capability::{Capability, CapabilityTable, KernelObject, Rights};
pub use channel::{Channel, ChannelError, Message, MSG_INLINE_WORDS, MSG_MAX_CAPS};
static CHANNELS: Mutex<BTreeMap<u64, Arc<Channel>>> = Mutex::new(BTreeMap::new());
/// Инициализировать IPC-подсистему.
pub fn init() {
log::info!("[ipc] IPC subsystem initialized");
}
/// Создать новый глобальный IPC-канал.
pub fn create_channel(capacity: usize) -> Arc<Channel> {
without_interrupts(|| {
let ch = Channel::new(capacity);
CHANNELS.lock().insert(ch.id, ch.clone());
ch
})
}
/// Найти канал по его ID.
pub fn get_channel(id: u64) -> Option<Arc<Channel>> {
without_interrupts(|| {
CHANNELS.lock().get(&id).cloned()
})
}
+164
View File
@@ -0,0 +1,164 @@
//! Kernel logger + UART serial I/O.
//!
//! Все операции защищены `without_interrupts`, чтобы исключить deadlock
//! при логировании из прерываний (IRQ).
use core::fmt;
use core::fmt::Write;
use spin::Mutex;
use uart_16550::SerialPort;
use x86_64::instructions::interrupts::without_interrupts;
use x86_64::instructions::port::Port;
const SERIAL_IO_PORT: u16 = 0x3F8; // COM1
static SERIAL: Mutex<Option<SerialPort>> = Mutex::new(None);
// ── Инициализация ─────────────────────────────────────────────────────────────
/// Инициализировать serial и зарегистрировать logger.
pub fn init() {
unsafe {
let mut ier: Port<u8> = Port::new(SERIAL_IO_PORT + 1);
let mut lcr: Port<u8> = Port::new(SERIAL_IO_PORT + 3);
let mut dll: Port<u8> = Port::new(SERIAL_IO_PORT);
let mut dlm: Port<u8> = Port::new(SERIAL_IO_PORT + 1);
let mut fcr: Port<u8> = Port::new(SERIAL_IO_PORT + 2);
let mut mcr: Port<u8> = Port::new(SERIAL_IO_PORT + 4);
ier.write(0x00); // Disable interrupts
lcr.write(0x80); // Enable DLAB (baud rate divisor)
dll.write(0x01); // 115200 baud (divisor 1 low byte)
dlm.write(0x00); // (divisor 1 high byte)
lcr.write(0x03); // 8 bits, no parity, 1 stop bit (DLAB disabled)
fcr.write(0x07); // Enable FIFO, clear RX/TX, 1-byte threshold
mcr.write(0x0B); // RTS/DTR + OUT2 (enable line driver)
}
let port = unsafe { SerialPort::new(SERIAL_IO_PORT) };
*SERIAL.lock() = Some(port);
flush_rx();
log::set_logger(&KERNEL_LOGGER).expect("logger already set");
log::set_max_level(log::LevelFilter::Trace);
}
// ── log::Log impl ─────────────────────────────────────────────────────────────
struct KernelLogger;
static KERNEL_LOGGER: KernelLogger = KernelLogger;
impl log::Log for KernelLogger {
fn enabled(&self, _metadata: &log::Metadata) -> bool { true }
fn log(&self, record: &log::Record) {
let level_color = match record.level() {
log::Level::Error => "\x1b[1;31m",
log::Level::Warn => "\x1b[1;33m",
log::Level::Info => "\x1b[1;32m",
log::Level::Debug => "\x1b[1;34m",
log::Level::Trace => "\x1b[90m",
};
let level_str = match record.level() {
log::Level::Error => "ERROR",
log::Level::Warn => "WARN ",
log::Level::Info => "INFO ",
log::Level::Debug => "DEBUG",
log::Level::Trace => "TRACE",
};
without_interrupts(|| {
if let Some(serial) = SERIAL.lock().as_mut() {
let _ = writeln!(
serial,
"{}{}\x1b[0m {}", level_color, level_str, record.args()
);
}
});
}
fn flush(&self) {}
}
// ── Raw serial I/O (для shell) ────────────────────────────────────────────────
/// Записать строку напрямую в serial и framebuffer (без лог-префикса).
pub fn print_raw(s: &str) {
without_interrupts(|| {
if let Some(serial) = SERIAL.lock().as_mut() {
let _ = serial.write_str(s);
}
crate::arch::x86_64::framebuffer::print_str(s);
});
}
/// Вывод с форматированием (вызывается из `kprint!` макроса).
pub fn kprint(args: fmt::Arguments) {
/// Адаптер fmt::Write → serial
struct SerialWriter;
impl fmt::Write for SerialWriter {
fn write_str(&mut self, s: &str) -> fmt::Result {
print_raw(s);
Ok(())
}
}
let _ = SerialWriter.write_fmt(args);
}
/// Прочитать один байт из UART serial или клавиатуры PS/2 (неблокирующий).
///
/// Возвращает `Some(u8)` если есть символ, или `None` если буфер пуст.
pub fn read_byte() -> Option<u8> {
without_interrupts(|| {
// 1. Прямой опрос UART COM1 LSR (Line Status Register 0x3FD) — приоритет для терминала
unsafe {
let mut lsr: Port<u8> = Port::new(SERIAL_IO_PORT + 5);
let s = lsr.read();
if s != 0xFF && (s & 0x01) != 0 {
let mut data_port: Port<u8> = Port::new(SERIAL_IO_PORT);
let byte = data_port.read();
return Some(byte);
}
}
// 2. Проверяем PS/2 клавиатуру (буфер IRQ1)
crate::arch::x86_64::keyboard::pop_char()
})
}
/// Очистить приёмный буфер UART (сбросить накопленные до старта шелла байты).
pub fn flush_rx() {
without_interrupts(|| {
unsafe {
let mut lsr: Port<u8> = Port::new(SERIAL_IO_PORT + 5);
let mut data: Port<u8> = Port::new(SERIAL_IO_PORT);
for _ in 0..128 {
if (lsr.read() & 0x01) != 0 {
let _ = data.read();
} else {
break;
}
}
}
});
}
// ── Макросы ───────────────────────────────────────────────────────────────────
/// Вывод без перевода строки (аналог `print!`, но пишет в serial).
#[macro_export]
macro_rules! kprint {
($($arg:tt)*) => { $crate::logger::kprint(format_args!($($arg)*)) };
}
/// Вывод с переводом строки.
#[macro_export]
macro_rules! kprintln {
() => { $crate::kprint!("\r\n") };
($fmt:literal) => { $crate::kprint!(concat!($fmt, "\r\n")) };
($fmt:literal, $($arg:tt)*) => {
$crate::kprint!(concat!($fmt, "\r\n"), $($arg)*)
};
}
+118
View File
@@ -0,0 +1,118 @@
//! # opencoreRS — точка входа ядра
#![no_std]
#![no_main]
#![feature(alloc_error_handler)]
#![feature(abi_x86_interrupt)]
extern crate alloc;
pub mod arch;
pub mod drivers;
pub mod ipc;
pub mod logger;
pub mod mm;
pub mod net;
pub mod panic;
pub mod sched;
pub mod shell;
pub mod syscall;
pub mod time;
use arch::x86_64 as arch_impl;
/// Точка входа, вызывается из [`arch::x86_64::boot`] после базовой
/// архитектурной инициализации (serial logger уже включён в boot).
pub fn kernel_main() -> ! {
// TSC — сразу, чтобы uptime был точным
time::init();
log::info!("opencoreRS v{} booting", env!("CARGO_PKG_VERSION"));
log::info!(" arch : x86_64");
// ── 1. Архитектурная инициализация ──────────────────────────────
log::info!("[arch] GDT & TSS...");
arch_impl::gdt::init();
log::info!("[arch] IDT...");
arch_impl::idt::init();
log::info!("[arch] CPUID & Vendor Detection...");
arch_impl::cpuid::init();
log::info!("[arch] PIC & PIT (100 Hz)...");
arch_impl::interrupts::init_pic();
arch_impl::interrupts::init_pit(100);
log::info!("[arch] PS/2 Keyboard...");
arch_impl::keyboard::init();
// ── 2. Память ────────────────────────────────────────────────────
log::info!("[mm] PMM...");
mm::pmm::init();
log::info!("[mm] VMM...");
mm::vmm::init();
log::info!("[mm] Heap...");
mm::heap::init();
// ── 3. Аппаратные таблицы и шины (ACPI, SMP, PCI) ────────────────
log::info!("[arch] ACPI Tables (MADT/XSDT)...");
arch_impl::acpi::init(arch_impl::boot::get_rsdp_address());
log::info!("[arch] SMP Multi-Core Discovery...");
arch_impl::smp::init();
log::info!("[arch] PCI Bus Enumeration...");
arch_impl::pci::scan_bus();
// ── 4. Аппаратные драйверы (AHCI, E1000) и Сетевой стек ──────────
drivers::init();
net::init();
// ── 5. Планировщик ──────────────────────────────────────────────
log::info!("[sched] Scheduler...");
sched::init();
// ── 6. IPC / Capabilities ────────────────────────────────────────
log::info!("[ipc] IPC...");
ipc::init();
// ── 7. Syscall-диспетчер ─────────────────────────────────────────
log::info!("[syscall] Syscall handler...");
syscall::init();
// ── 8. Загрузка userspace серверов (Ring 3) ──────────────────────
let servers = [
("init", "init.elf"),
("vfs", "vfs.elf"),
("sh", "sh.elf"),
];
for (name, file_name) in servers {
if let Some(elf_module) = arch_impl::boot::get_module(file_name) {
log::info!("[loader] Found {} boot module ({} bytes)", file_name, elf_module.len());
match mm::elf::load_elf(elf_module) {
Ok(loaded) => {
mm::elf::spawn_user_process(name, loaded);
log::info!("[loader] Spawned {} server in Ring 3", name);
}
Err(e) => {
log::error!("[loader] Failed to load {}: {}", file_name, e);
}
}
}
}
// ── 9. Включение аппаратных прерываний ───────────────────────────
log::info!("[arch] Enabling hardware interrupts...");
x86_64::instructions::interrupts::enable();
// ── 10. Передача управления в Userspace (Ring 3) ─────────────────
log::info!("[kernel] Microkernel ready — transferring control to userspace tasks in Ring 3");
crate::sched::exit_current();
loop {
x86_64::instructions::hlt();
}
}
+29
View File
@@ -0,0 +1,29 @@
//! boot_info — хранение ответов Limine для использования подсистемами.
// Примечание: в limine 0.6.5 есть опечатка — HhdmRepsonse (не HhdmResponse)
use limine::request::{HhdmRepsonse, MemmapResponse};
use spin::Once;
use x86_64::VirtAddr;
static MEMORY_MAP: Once<&'static MemmapResponse> = Once::new();
static HHDM: Once<&'static HhdmRepsonse> = Once::new();
/// Сохранить ответы Limine. Вызывается из `boot::_start`.
pub fn set(mmap: &'static MemmapResponse, hhdm: &'static HhdmRepsonse) {
MEMORY_MAP.call_once(|| mmap);
HHDM.call_once(|| hhdm);
}
/// Получить карту физической памяти.
pub fn memory_map() -> &'static MemmapResponse {
MEMORY_MAP.get().expect("boot_info: memory map not set")
}
/// Получить HHDM смещение как `VirtAddr`.
pub fn hhdm_offset() -> VirtAddr {
VirtAddr::new(
HHDM.get()
.expect("boot_info: HHDM not set")
.offset
)
}
+208
View File
@@ -0,0 +1,208 @@
//! ELF64 Loader для запуска userspace-процессов в Ring 3.
use x86_64::structures::paging::PageTableFlags;
use x86_64::VirtAddr;
const ELF_MAGIC: [u8; 4] = [0x7F, b'E', b'L', b'F'];
const PT_LOAD: u32 = 1;
const PF_X: u32 = 1;
const PF_W: u32 = 2;
const PF_R: u32 = 4;
/// Стандартный стек пользователя (располагается в верхушке канонического userspace).
pub const USER_STACK_TOP: usize = 0x0000_7FFF_FFFF_0000;
pub const USER_STACK_PAGES: usize = 4; // 16 KiB
static PROCESS_STACK_COUNTER: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
#[derive(Debug)]
pub struct LoadedElf {
pub entry_point: usize,
pub user_stack_top: usize,
}
#[repr(C, packed)]
struct Elf64Header {
magic: [u8; 4],
class: u8,
data: u8,
version: u8,
osabi: u8,
abiversion: u8,
pad: [u8; 7],
e_type: u16,
e_machine: u16,
e_version: u32,
e_entry: u64,
e_phoff: u64,
e_shoff: u64,
e_flags: u32,
e_ehsize: u16,
e_phentsize: u16,
e_phnum: u16,
e_shentsize: u16,
e_shnum: u16,
e_shstrndx: u16,
}
#[repr(C, packed)]
struct Elf64ProgramHeader {
p_type: u32,
p_flags: u32,
p_offset: u64,
p_vaddr: u64,
p_paddr: u64,
p_filesz: u64,
p_memsz: u64,
p_align: u64,
}
/// Разобрать ELF64 бинарник, отобразить сегменты в память и подготовить структуру.
pub fn load_elf(elf_bytes: &[u8]) -> Result<LoadedElf, &'static str> {
if elf_bytes.len() < core::mem::size_of::<Elf64Header>() {
return Err("ELF: File too small");
}
let header = unsafe { &*(elf_bytes.as_ptr() as *const Elf64Header) };
if header.magic != ELF_MAGIC {
return Err("ELF: Invalid magic header");
}
if header.class != 2 {
return Err("ELF: Not 64-bit");
}
if header.e_machine != 0x3E {
return Err("ELF: Not x86_64");
}
let ph_offset = header.e_phoff as usize;
let ph_count = header.e_phnum as usize;
let ph_size = header.e_phentsize as usize;
// 1. Отображаем сегменты PT_LOAD
for i in 0..ph_count {
let offset = ph_offset + i * ph_size;
if offset + ph_size > elf_bytes.len() {
return Err("ELF: Program header out of bounds");
}
let ph = unsafe { &*(elf_bytes.as_ptr().add(offset) as *const Elf64ProgramHeader) };
if ph.p_type == PT_LOAD {
let vaddr = ph.p_vaddr as usize;
let memsz = ph.p_memsz as usize;
let filesz = ph.p_filesz as usize;
let file_offset = ph.p_offset as usize;
let mut flags = PageTableFlags::PRESENT | PageTableFlags::USER_ACCESSIBLE;
if (ph.p_flags & PF_W) != 0 {
flags |= PageTableFlags::WRITABLE;
}
if (ph.p_flags & PF_X) == 0 {
flags |= PageTableFlags::NO_EXECUTE;
}
let start_page = vaddr & !0xFFF;
let end_page = (vaddr + memsz + 0xFFF) & !0xFFF;
let page_count = (end_page - start_page) / 4096;
for p in 0..page_count {
let page_vaddr = start_page + p * 4096;
let page_virt = VirtAddr::new(page_vaddr as u64);
let page_ptr = if super::vmm::is_mapped(page_virt) {
let phys = super::vmm::translate(page_virt).ok_or("Failed to translate mapped page")?;
super::vmm::phys_to_virt(phys).as_mut_ptr::<u8>()
} else {
let phys = super::vmm::map_alloc(page_virt, flags)?;
let ptr = super::vmm::phys_to_virt(phys).as_mut_ptr::<u8>();
unsafe {
core::ptr::write_bytes(ptr, 0, 4096);
}
ptr
};
// Копируем данные сегмента, попадающие в эту страницу, через HHDM
if filesz > 0 {
let seg_start = vaddr;
let seg_end = vaddr + filesz;
let copy_start = core::cmp::max(page_vaddr, seg_start);
let copy_end = core::cmp::min(page_vaddr + 4096, seg_end);
if copy_start < copy_end {
let dst_offset = copy_start - page_vaddr;
let src_offset = (copy_start - seg_start) + file_offset;
let len = copy_end - copy_start;
if src_offset + len <= elf_bytes.len() {
unsafe {
core::ptr::copy_nonoverlapping(
elf_bytes.as_ptr().add(src_offset),
page_ptr.add(dst_offset),
len,
);
}
}
}
}
}
}
}
// 2. Выделяем и маппируем пользовательский стек (16 KiB)
let stack_idx = PROCESS_STACK_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let process_stack_top = USER_STACK_TOP - (stack_idx * 0x100000);
let stack_base = process_stack_top - (USER_STACK_PAGES * 4096);
let stack_flags = PageTableFlags::PRESENT
| PageTableFlags::WRITABLE
| PageTableFlags::USER_ACCESSIBLE
| PageTableFlags::NO_EXECUTE;
for p in 0..USER_STACK_PAGES {
let page_virt = VirtAddr::new((stack_base + p * 4096) as u64);
let phys = super::vmm::map_alloc(page_virt, stack_flags)?;
let page_ptr = super::vmm::phys_to_virt(phys).as_mut_ptr::<u8>();
unsafe {
core::ptr::write_bytes(page_ptr, 0, 4096);
}
}
let entry = header.e_entry as usize;
log::info!(
"[elf] ELF loaded successfully: entry={:#x}, stack={:#x}",
entry,
process_stack_top
);
Ok(LoadedElf {
entry_point: entry,
user_stack_top: process_stack_top,
})
}
/// Запустить загруженный ELF процесс в Ring 3.
pub fn spawn_user_process(name: &'static str, loaded: LoadedElf) {
extern "C" fn user_trampoline(args_packed: usize) {
let (entry, stack) = unsafe {
let ptr = args_packed as *const (usize, usize);
let tuple = *ptr;
drop(alloc::boxed::Box::from_raw(args_packed as *mut (usize, usize)));
tuple
};
log::info!("[userspace] Dropping CPU into Ring 3 (RIP: {:#x}, RSP: {:#x})...", entry, stack);
unsafe {
crate::arch::x86_64::context::jump_to_userspace(entry, stack);
}
}
let packed = alloc::boxed::Box::into_raw(alloc::boxed::Box::new((
loaded.entry_point,
loaded.user_stack_top,
))) as usize;
crate::sched::spawn(name, user_trampoline, packed, crate::sched::Priority::NORMAL);
}
+54
View File
@@ -0,0 +1,54 @@
//! Kernel Heap — динамическая аллокация в ядре (`Box`, `Vec`, `Arc`...).
//!
//! Используем `linked_list_allocator` как глобальный аллокатор.
//! Heap размещается в заранее известном регионе виртуальной памяти,
//! который маппируется при инициализации.
//!
//! # Раскладка
//!
//! ```
//! HEAP_START = 0xFFFF_FF00_0000_0000
//! HEAP_SIZE = 4 MiB (начальный размер, потом можно расширять)
//! ```
//!
//! # TODO
//! - [ ] Маппировать HEAP регион через VMM (сейчас просто помечаем адрес)
//! - [ ] Поддержка расширения heap (grow on demand)
//! - [ ] Рассмотреть slab allocator для фиксированных размеров
use linked_list_allocator::LockedHeap;
/// Начальный виртуальный адрес kernel heap.
pub const HEAP_START: usize = 0xFFFF_FF00_0000_0000;
/// Размер kernel heap (4 MiB).
pub const HEAP_SIZE: usize = 4 * 1024 * 1024;
/// Глобальный аллокатор ядра.
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();
/// Инициализировать kernel heap.
///
/// # Шаги
/// 1. Маппировать [HEAP_START, HEAP_START + HEAP_SIZE) через VMM
/// 2. Сказать аллокатору о доступном регионе
pub fn init() {
// TODO: вызвать vmm::map_range(HEAP_START, HEAP_SIZE, RW | KERNEL)
// Сейчас Limine уже маппирует нижние 4GB через HHDM,
// поэтому используем временный workaround — статический буфер.
static mut HEAP_MEMORY: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
unsafe {
ALLOCATOR
.lock()
.init(HEAP_MEMORY.as_mut_ptr(), HEAP_SIZE);
}
log::info!(
"[heap] Kernel heap initialized: {:#x} .. {:#x} ({} MiB)",
HEAP_START,
HEAP_START + HEAP_SIZE,
HEAP_SIZE / 1024 / 1024,
);
}
+7
View File
@@ -0,0 +1,7 @@
//! Подсистема управления памятью.
pub mod boot_info;
pub mod elf;
pub mod heap;
pub mod pmm;
pub mod vmm;
+320
View File
@@ -0,0 +1,320 @@
//! Physical Memory Manager (PMM) — Bitmap Allocator.
//!
//! # Концепция
//!
//! Вся физическая память разбита на **фреймы** по 4 KiB.
//! Каждый фрейм представлен одним битом в bitmap:
//! - `0` → фрейм свободен
//! - `1` → фрейм занят
//!
//! Биты упакованы в `u64`: одно слово покрывает 64 фрейма = 256 KiB памяти.
//!
//! ```text
//! word[0]: фреймы 0..63
//! word[1]: фреймы 64..127
//! word[k]: фреймы k*64 .. k*64+63
//! ```
//!
//! # Размещение bitmap в памяти
//!
//! Сам bitmap нужно где-то хранить. Мы кладём его в первый подходящий
//! USABLE-регион, достаточно большой для `total_frames / 8` байт.
//! После этого помечаем занятые страницы:
//! 1. Всё что не USABLE → занято
//! 2. Регион ядра → занято
//! 3. Регион самого bitmap → занято
//!
//! # Потокобезопасность
//!
//! PMM защищён `spin::Mutex`. В будущем при SMP можно перейти
//! на per-CPU free lists + общий Mutex только при истощении.
//!
//! # Дальнейшие улучшения
//! - [ ] Buddy allocator — O(log n) вместо O(n)
//! - [ ] Huge pages (2 MiB, 1 GiB)
//! - [ ] NUMA awareness (выбирать ближайший узел)
//! - [ ] Alloc N контiguous фреймов
use core::sync::atomic::{AtomicUsize, Ordering};
use spin::Mutex;
use x86_64::PhysAddr;
// ── Константы ─────────────────────────────────────────────────────────────────
/// Размер физического фрейма (страницы) = 4 KiB.
pub const PAGE_SIZE: usize = 4096;
/// Количество фреймов на одно u64-слово bitmap.
const FRAMES_PER_WORD: usize = 64;
// ── Статический bitmap ────────────────────────────────────────────────────────
/// Максимальный объём физической памяти, который мы поддерживаем.
/// 64 GiB — достаточно для любой разумной тестовой машины.
const MAX_PHYS_MEMORY: usize = 64 * 1024 * 1024 * 1024; // 64 GiB
const MAX_FRAMES: usize = MAX_PHYS_MEMORY / PAGE_SIZE; // 16 777 216 фреймов
const BITMAP_WORDS: usize = MAX_FRAMES / FRAMES_PER_WORD; // 262 144 u64 = 2 MiB
/// Статический bitmap в BSS (нули при загрузке).
/// `init()` заполнит его `0xFF` (всё занято), затем освободит USABLE регионы.
static mut BITMAP: [u64; BITMAP_WORDS] = [0u64; BITMAP_WORDS];
// ── Счётчики ──────────────────────────────────────────────────────────────────
static TOTAL_FRAMES: AtomicUsize = AtomicUsize::new(0);
static FREE_FRAMES: AtomicUsize = AtomicUsize::new(0);
/// Защита от параллельного доступа к bitmap (для alloc/free).
static PMM_LOCK: Mutex<()> = Mutex::new(());
// ── Инициализация ─────────────────────────────────────────────────────────────
/// Инициализировать PMM из memory map Limine.
///
/// Limine помечает kernel, ACPI, framebuffer и т.д. как не-USABLE,
/// поэтому они автоматически остаются "занятыми" — нам не нужно
/// явно помечать регион ядра.
pub fn init() {
use limine::memmap::MEMMAP_USABLE;
let mmap = crate::mm::boot_info::memory_map();
// Шаг 1: пометить всё как занятое (заполнить 0xFF)
{
let bitmap = unsafe { &mut *core::ptr::addr_of_mut!(BITMAP) };
bitmap.fill(u64::MAX);
}
let mut total = 0usize;
let mut freed = 0usize;
// Шаг 2: освободить USABLE фреймы
for entry in mmap.entries() {
if entry.type_ != MEMMAP_USABLE {
continue;
}
let base = entry.base as usize;
let length = entry.length as usize;
let frame_start = align_up(base, PAGE_SIZE) / PAGE_SIZE;
let frame_end = (base + length) / PAGE_SIZE;
if frame_start >= frame_end {
continue;
}
total += frame_end - frame_start;
for frame in frame_start..frame_end {
if frame < MAX_FRAMES {
free_frame_unchecked(frame);
freed += 1;
}
}
}
TOTAL_FRAMES.store(total, Ordering::Relaxed);
FREE_FRAMES.store(freed, Ordering::Relaxed);
log::info!(
"[pmm] {} MiB total, {} MiB free ({} frames)",
total * PAGE_SIZE / 1024 / 1024,
freed * PAGE_SIZE / 1024 / 1024,
freed,
);
}
// ── Публичный API ─────────────────────────────────────────────────────────────
/// Выделить один физический фрейм (4 KiB).
///
/// Возвращает физический адрес начала фрейма, или `None` если OOM.
///
/// # Сложность
/// O(n/64) — линейный поиск по bitmap словам.
pub fn alloc_frame() -> Option<PhysAddr> {
let _lock = PMM_LOCK.lock();
// SAFETY: доступ защищён PMM_LOCK
let bitmap: &mut [u64; BITMAP_WORDS] = unsafe { &mut *core::ptr::addr_of_mut!(BITMAP) };
for (word_idx, word) in bitmap.iter_mut().enumerate() {
if *word == u64::MAX {
continue; // все 64 фрейма заняты
}
// Найти первый нулевой бит (свободный фрейм)
let bit = word.trailing_ones() as usize;
debug_assert!(bit < 64, "trailing_ones returned >= 64");
// Пометить как занятый
*word |= 1u64 << bit;
let frame = word_idx * FRAMES_PER_WORD + bit;
FREE_FRAMES.fetch_sub(1, Ordering::Relaxed);
let phys = PhysAddr::new((frame * PAGE_SIZE) as u64);
log::trace!("[pmm] alloc_frame → {:#x} (frame {})", phys.as_u64(), frame);
return Some(phys);
}
log::error!("[pmm] alloc_frame: OUT OF MEMORY");
None
}
/// Выделить `count` **последовательных** физических фреймов.
///
/// Нужно для DMA, page tables и других структур.
/// Сложность: O(n) — проверяем каждый диапазон.
///
/// # Возвращает
/// Физический адрес начала диапазона, или `None`.
pub fn alloc_contiguous(count: usize) -> Option<PhysAddr> {
if count == 0 {
return None;
}
if count == 1 {
return alloc_frame();
}
let _lock = PMM_LOCK.lock();
// SAFETY: доступ защищён PMM_LOCK
let bitmap: &mut [u64; BITMAP_WORDS] = unsafe { &mut *core::ptr::addr_of_mut!(BITMAP) };
let mut run_start = 0usize;
let mut run_len = 0usize;
for frame in 0..MAX_FRAMES {
let wi = frame / FRAMES_PER_WORD;
let bit = frame % FRAMES_PER_WORD;
if bitmap[wi] & (1u64 << bit) == 0 {
// Фрейм свободен
if run_len == 0 {
run_start = frame;
}
run_len += 1;
if run_len == count {
// Нашли! Помечаем все фреймы занятыми.
for f in run_start..run_start + count {
let wi2 = f / FRAMES_PER_WORD;
let bit2 = f % FRAMES_PER_WORD;
bitmap[wi2] |= 1u64 << bit2;
}
FREE_FRAMES.fetch_sub(count, Ordering::Relaxed);
let phys = PhysAddr::new((run_start * PAGE_SIZE) as u64);
log::trace!(
"[pmm] alloc_contiguous({}) → {:#x}",
count, phys.as_u64()
);
return Some(phys);
}
} else {
// Фрейм занят — сбрасываем run
run_len = 0;
}
}
log::error!("[pmm] alloc_contiguous({}): no contiguous region found", count);
None
}
/// Освободить физический фрейм по его адресу.
///
/// # Панике если адрес не выровнен или выходит за пределы памяти.
pub fn free_frame(addr: PhysAddr) {
let phys = addr.as_u64() as usize;
assert!(
phys % PAGE_SIZE == 0,
"free_frame: address {:#x} is not page-aligned",
phys
);
let frame = phys / PAGE_SIZE;
assert!(
frame < MAX_FRAMES,
"free_frame: frame {} is out of bounds (max {})",
frame, MAX_FRAMES
);
let _lock = PMM_LOCK.lock();
assert!(
!is_free(frame),
"free_frame: double-free of frame {} ({:#x})",
frame, phys
);
free_frame_unchecked(frame);
FREE_FRAMES.fetch_add(1, Ordering::Relaxed);
log::trace!("[pmm] free_frame ← {:#x} (frame {})", phys, frame);
}
/// Освободить диапазон последовательных фреймов.
pub fn free_contiguous(addr: PhysAddr, count: usize) {
for i in 0..count {
free_frame(PhysAddr::new(addr.as_u64() + (i * PAGE_SIZE) as u64));
}
}
// ── Статистика ────────────────────────────────────────────────────────────────
/// Общее количество физических фреймов (свободные + занятые).
pub fn total_frames() -> usize {
TOTAL_FRAMES.load(Ordering::Relaxed)
}
/// Количество свободных фреймов.
pub fn free_frames() -> usize {
FREE_FRAMES.load(Ordering::Relaxed)
}
/// Количество занятых фреймов.
pub fn used_frames() -> usize {
total_frames().saturating_sub(free_frames())
}
/// Объём свободной памяти в байтах.
pub fn free_bytes() -> usize {
free_frames() * PAGE_SIZE
}
// ── Внутренние вспомогательные функции ───────────────────────────────────────
/// Проверить, свободен ли фрейм (без блокировки — вызывать под PMM_LOCK).
#[inline]
fn is_free(frame: usize) -> bool {
let wi = frame / FRAMES_PER_WORD;
let bit = frame % FRAMES_PER_WORD;
unsafe { BITMAP[wi] & (1u64 << bit) == 0 }
}
/// Пометить фрейм как занятый (без блокировки).
#[inline]
fn set_used(frame: usize) {
let wi = frame / FRAMES_PER_WORD;
let bit = frame % FRAMES_PER_WORD;
unsafe { BITMAP[wi] |= 1u64 << bit };
}
/// Пометить фрейм как свободный (без блокировки, без проверок).
#[inline]
fn free_frame_unchecked(frame: usize) {
let wi = frame / FRAMES_PER_WORD;
let bit = frame % FRAMES_PER_WORD;
unsafe { BITMAP[wi] &= !(1u64 << bit) };
}
/// Выровнять `value` вверх до кратного `align` (align должен быть степенью 2).
#[inline]
fn align_up(value: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
(value + align - 1) & !(align - 1)
}
+187
View File
@@ -0,0 +1,187 @@
//! Virtual Memory Manager (VMM).
//!
//! Использует `OffsetPageTable` из x86_64 crate поверх HHDM-маппинга Limine.
//!
//! # Стратегия
//!
//! Limine уже настроил начальные page tables с двумя маппингами:
//! - HHDM: `offset + phys → phys` для всей физической памяти
//! - Kernel: `0xFFFF_FFFF_8000_0000 → физ. адрес ядра`
//!
//! Мы берём текущий PML4 из CR3, оборачиваем в `OffsetPageTable`
//! и добавляем свои маппинги. Новые page table фреймы берём из PMM.
//!
//! # Лок
//!
//! `VMM_LOCK` защищает операции с page tables. Взятие PMM_LOCK изнутри
//! VMM_LOCK безопасно (нет обратной зависимости).
//!
//! # TODO
//! - [ ] Создать собственный PML4 вместо Limine-шного
//! - [ ] Per-process address spaces
//! - [ ] `vmalloc` регион для динамических маппингов ядра
//! - [ ] Guard pages
use x86_64::registers::control::Cr3;
use x86_64::structures::paging::{
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PageTableFlags, PhysFrame,
Size4KiB, Translate,
};
use x86_64::{PhysAddr, VirtAddr};
use spin::Mutex;
/// Мьютекс для операций с page tables.
static VMM_LOCK: Mutex<()> = Mutex::new(());
// ── Frame allocator backed by PMM ─────────────────────────────────────────────
/// Адаптер между x86_64::FrameAllocator и нашим PMM.
struct PmmFrameAllocator;
unsafe impl FrameAllocator<Size4KiB> for PmmFrameAllocator {
fn allocate_frame(&mut self) -> Option<PhysFrame<Size4KiB>> {
let phys = crate::mm::pmm::alloc_frame()?;
Some(PhysFrame::containing_address(phys))
}
}
// ── Публичный API ─────────────────────────────────────────────────────────────
/// Инициализировать VMM.
pub fn init() {
let hhdm = crate::mm::boot_info::hhdm_offset();
log::info!(
"[vmm] OffsetPageTable ready, HHDM = {:#x}",
hhdm.as_u64()
);
}
/// Маппировать одну виртуальную страницу на физический фрейм.
///
/// # Аргументы
/// - `virt` — виртуальный адрес (должен быть выровнен по 4 KiB)
/// - `phys` — физический адрес фрейма
/// - `flags` — флаги страницы (Present, Writable, NX, User и т.д.)
///
/// # Ошибки
/// Возвращает `&str` с описанием ошибки если маппинг не удался.
pub fn map_page(
virt: VirtAddr,
phys: PhysAddr,
flags: PageTableFlags,
) -> Result<(), &'static str> {
let _lock = VMM_LOCK.lock();
// SAFETY: мы держим VMM_LOCK, HHDM маппит всю физическую память
let mut pt = unsafe { current_page_table() };
let page = Page::<Size4KiB>::containing_address(virt);
let frame = PhysFrame::<Size4KiB>::containing_address(phys);
unsafe {
match pt.map_to(page, frame, flags, &mut PmmFrameAllocator) {
Ok(flush) => {
flush.flush();
log::trace!("[vmm] map {:#x} → {:#x} ({:?})", virt.as_u64(), phys.as_u64(), flags);
Ok(())
}
Err(e) => {
log::error!("[vmm] map_to failed for virt {:#x} -> phys {:#x}: {:?}", virt.as_u64(), phys.as_u64(), e);
Err("vmm::map_page: mapping failed")
}
}
}
}
/// Маппировать виртуальный адрес, выделяя новый физический фрейм из PMM.
///
/// Удобная обёртка для `map_page` когда физический адрес не важен.
pub fn map_alloc(virt: VirtAddr, flags: PageTableFlags) -> Result<PhysAddr, &'static str> {
let phys = crate::mm::pmm::alloc_frame()
.ok_or("vmm::map_alloc: PMM out of memory")?;
map_page(virt, phys, flags)?;
Ok(phys)
}
/// Снять маппинг страницы. Освобождает физический фрейм обратно в PMM.
///
/// # Ошибки
/// Возвращает `Err` если страница не была маппирована.
pub fn unmap_page(virt: VirtAddr) -> Result<PhysAddr, &'static str> {
let _lock = VMM_LOCK.lock();
let mut pt = unsafe { current_page_table() };
let page = Page::<Size4KiB>::containing_address(virt);
let (frame, flush) = pt
.unmap(page)
.map_err(|_| "vmm::unmap_page: page not mapped")?;
flush.flush();
let phys = frame.start_address();
crate::mm::pmm::free_frame(phys);
log::trace!("[vmm] unmap {:#x} → freed {:#x}", virt.as_u64(), phys.as_u64());
Ok(phys)
}
/// Удобные флаги для типичных маппингов.
pub mod flags {
use x86_64::structures::paging::PageTableFlags as F;
/// Читаемая страница ядра (код).
pub const KERNEL_RX: F = F::PRESENT;
/// Читаемая + записываемая страница ядра (данные, стэк, heap).
pub const KERNEL_RW: F = F::from_bits_truncate(F::PRESENT.bits() | F::WRITABLE.bits() | F::NO_EXECUTE.bits());
/// Читаемая страница userspace (код).
pub const USER_RX: F = F::from_bits_truncate(F::PRESENT.bits() | F::USER_ACCESSIBLE.bits());
/// Читаемая + записываемая страница userspace.
pub const USER_RW: F = F::from_bits_truncate(
F::PRESENT.bits() | F::WRITABLE.bits() | F::USER_ACCESSIBLE.bits() | F::NO_EXECUTE.bits()
);
}
// ── Внутренние функции ────────────────────────────────────────────────────────
/// Создать `OffsetPageTable` из текущего CR3 + HHDM offset.
///
/// # Safety
/// Вызывать только под `VMM_LOCK`.
/// HHDM должен быть инициализирован (boot_info::set вызван).
unsafe fn current_page_table() -> OffsetPageTable<'static> {
let hhdm = crate::mm::boot_info::hhdm_offset();
let (pml4_frame, _) = Cr3::read();
let pml4_virt = hhdm + pml4_frame.start_address().as_u64();
let pml4: &mut PageTable = unsafe { &mut *pml4_virt.as_mut_ptr() };
unsafe { OffsetPageTable::new(pml4, hhdm) }
}
/// Проверить, маппирован ли виртуальный адрес.
pub fn is_mapped(virt: VirtAddr) -> bool {
let _lock = VMM_LOCK.lock();
let pt = unsafe { current_page_table() };
use x86_64::structures::paging::mapper::TranslateResult;
matches!(pt.translate(x86_64::VirtAddr::new(virt.as_u64())), TranslateResult::Mapped { .. })
}
/// Перевести виртуальный адрес в физический (если маппинг есть).
pub fn translate(virt: VirtAddr) -> Option<PhysAddr> {
let _lock = VMM_LOCK.lock();
let pt = unsafe { current_page_table() };
use x86_64::structures::paging::mapper::TranslateResult;
match pt.translate(virt) {
TranslateResult::Mapped { frame, offset, .. } => {
Some(frame.start_address() + offset)
}
_ => None,
}
}
/// Перевести физический адрес в HHDM виртуальный адрес.
pub fn phys_to_virt(phys: PhysAddr) -> VirtAddr {
crate::mm::boot_info::hhdm_offset() + phys.as_u64()
}
+34
View File
@@ -0,0 +1,34 @@
//! ARP (Address Resolution Protocol) — преобразование IP в MAC.
use alloc::vec::Vec;
pub const ARP_OP_REQUEST: u16 = 1;
pub const ARP_OP_REPLY: u16 = 2;
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct ArpPacket {
pub htype: u16, // 1 = Ethernet
pub ptype: u16, // 0x0800 = IPv4
pub hlen: u8, // 6
pub plen: u8, // 4
pub op: u16, // 1 = Request, 2 = Reply
pub src_mac: [u8; 6],
pub src_ip: [u8; 4],
pub dst_mac: [u8; 6],
pub dst_ip: [u8; 4],
}
pub fn create_arp_reply(my_mac: [u8; 6], my_ip: [u8; 4], target_mac: [u8; 6], target_ip: [u8; 4]) -> Vec<u8> {
let mut packet = Vec::with_capacity(28);
packet.extend_from_slice(&1u16.to_be_bytes());
packet.extend_from_slice(&0x0800u16.to_be_bytes());
packet.push(6);
packet.push(4);
packet.extend_from_slice(&ARP_OP_REPLY.to_be_bytes());
packet.extend_from_slice(&my_mac);
packet.extend_from_slice(&my_ip);
packet.extend_from_slice(&target_mac);
packet.extend_from_slice(&target_ip);
packet
}
+25
View File
@@ -0,0 +1,25 @@
//! Ethernet II Frame Protocol.
use alloc::vec::Vec;
pub const ETHERTYPE_IPV4: u16 = 0x0800;
pub const ETHERTYPE_ARP: u16 = 0x0806;
pub const BROADCAST_MAC: [u8; 6] = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct EthernetHeader {
pub dst_mac: [u8; 6],
pub src_mac: [u8; 6],
pub ethertype: u16,
}
pub fn create_frame(src_mac: [u8; 6], dst_mac: [u8; 6], ethertype: u16, payload: &[u8]) -> Vec<u8> {
let mut frame = Vec::with_capacity(14 + payload.len());
frame.extend_from_slice(&dst_mac);
frame.extend_from_slice(&src_mac);
frame.extend_from_slice(&ethertype.to_be_bytes());
frame.extend_from_slice(payload);
frame
}
+39
View File
@@ -0,0 +1,39 @@
//! ICMP (Internet Control Message Protocol) — Echo Request & Reply (Ping).
use alloc::vec::Vec;
use super::ipv4::calculate_checksum;
pub const ICMP_TYPE_ECHO_REPLY: u8 = 0;
pub const ICMP_TYPE_ECHO_REQUEST: u8 = 8;
pub fn create_echo_request(seq: u16, id: u16, payload: &[u8]) -> Vec<u8> {
let mut packet = Vec::with_capacity(8 + payload.len());
packet.push(ICMP_TYPE_ECHO_REQUEST);
packet.push(0); // Code 0
packet.extend_from_slice(&[0, 0]); // Checksum placeholder
packet.extend_from_slice(&id.to_be_bytes());
packet.extend_from_slice(&seq.to_be_bytes());
packet.extend_from_slice(payload);
let csum = calculate_checksum(&packet);
packet[2] = (csum >> 8) as u8;
packet[3] = (csum & 0xFF) as u8;
packet
}
pub fn create_echo_reply(id: u16, seq: u16, payload: &[u8]) -> Vec<u8> {
let mut packet = Vec::with_capacity(8 + payload.len());
packet.push(ICMP_TYPE_ECHO_REPLY);
packet.push(0);
packet.extend_from_slice(&[0, 0]);
packet.extend_from_slice(&id.to_be_bytes());
packet.extend_from_slice(&seq.to_be_bytes());
packet.extend_from_slice(payload);
let csum = calculate_checksum(&packet);
packet[2] = (csum >> 8) as u8;
packet[3] = (csum & 0xFF) as u8;
packet
}
+48
View File
@@ -0,0 +1,48 @@
//! IPv4 Protocol & Internet Checksum.
use alloc::vec::Vec;
pub const IP_PROTO_ICMP: u8 = 1;
pub const IP_PROTO_TCP: u8 = 6;
pub const IP_PROTO_UDP: u8 = 17;
pub fn calculate_checksum(data: &[u8]) -> u16 {
let mut sum = 0u32;
let mut i = 0;
while i + 1 < data.len() {
let word = u16::from_be_bytes([data[i], data[i + 1]]);
sum = sum.wrapping_add(word as u32);
i += 2;
}
if i < data.len() {
let word = u16::from_be_bytes([data[i], 0]);
sum = sum.wrapping_add(word as u32);
}
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
pub fn create_ipv4_packet(src_ip: [u8; 4], dst_ip: [u8; 4], proto: u8, payload: &[u8]) -> Vec<u8> {
let mut packet = Vec::with_capacity(20 + payload.len());
let total_len = (20 + payload.len()) as u16;
packet.push(0x45); // Version 4, IHL 5
packet.push(0x00); // DSCP / ECN
packet.extend_from_slice(&total_len.to_be_bytes());
packet.extend_from_slice(&1u16.to_be_bytes()); // ID
packet.extend_from_slice(&0x4000u16.to_be_bytes()); // Flags: Don't Fragment
packet.push(64); // TTL
packet.push(proto);
packet.extend_from_slice(&[0, 0]); // Checksum placeholder
packet.extend_from_slice(&src_ip);
packet.extend_from_slice(&dst_ip);
let csum = calculate_checksum(&packet[0..20]);
packet[10] = (csum >> 8) as u8;
packet[11] = (csum & 0xFF) as u8;
packet.extend_from_slice(payload);
packet
}
+122
View File
@@ -0,0 +1,122 @@
//! Network Subsystem — стек сетевых протоколов (Ethernet, ARP, IPv4, ICMP Ping).
pub mod arp;
pub mod ethernet;
pub mod icmp;
pub mod ipv4;
use spin::Mutex;
pub static MY_IP: [u8; 4] = [10, 0, 2, 15]; // Стандартный QEMU User Net IP
pub static GATEWAY_IP: [u8; 4] = [10, 0, 2, 2];
pub static NET_STATS: Mutex<NetStats> = Mutex::new(NetStats {
rx_frames: 0,
tx_frames: 0,
rx_bytes: 0,
tx_bytes: 0,
icmp_pings: 0,
});
#[derive(Debug, Clone, Copy)]
pub struct NetStats {
pub rx_frames: u64,
pub tx_frames: u64,
pub rx_bytes: u64,
pub tx_bytes: u64,
pub icmp_pings: u64,
}
/// Инициализировать сетевой стек.
pub fn init() {
log::info!(
"[net] Network stack initialized: IP={}.{}.{}.{}, Gateway={}.{}.{}.{}",
MY_IP[0],
MY_IP[1],
MY_IP[2],
MY_IP[3],
GATEWAY_IP[0],
GATEWAY_IP[1],
GATEWAY_IP[2],
GATEWAY_IP[3]
);
}
/// Отправить ICMP Ping пакет (Echo Request) на целевой IP.
pub fn ping(target_ip: [u8; 4]) -> Result<(), &'static str> {
let mut dev_lock = crate::drivers::e1000::E1000_DEVICE.lock();
let dev = dev_lock.as_mut().ok_or("No network device")?;
let payload = b"opencoreRS ping payload!";
let icmp_data = icmp::create_echo_request(1, 0x1337, payload);
let ip_packet = ipv4::create_ipv4_packet(MY_IP, target_ip, ipv4::IP_PROTO_ICMP, &icmp_data);
// В QEMU User Net gateway MAC обычно 52:55:0a:00:02:02
let dst_mac = [0x52, 0x55, 0x0A, 0x00, 0x02, 0x02];
let eth_frame = ethernet::create_frame(dev.mac, dst_mac, ethernet::ETHERTYPE_IPV4, &ip_packet);
dev.send_packet(&eth_frame)?;
let mut stats = NET_STATS.lock();
stats.tx_frames += 1;
stats.tx_bytes += eth_frame.len() as u64;
stats.icmp_pings += 1;
log::info!(
"[net] Sent ICMP Echo Request to {}.{}.{}.{} ({} bytes)",
target_ip[0],
target_ip[1],
target_ip[2],
target_ip[3],
eth_frame.len()
);
Ok(())
}
/// Опросить входящие пакеты и обработать протоколы.
pub fn poll() {
let mut dev_lock = crate::drivers::e1000::E1000_DEVICE.lock();
let dev = match dev_lock.as_mut() {
Some(d) => d,
None => return,
};
while let Some(packet) = dev.recv_packet() {
if packet.len() < 14 {
continue;
}
let mut stats = NET_STATS.lock();
stats.rx_frames += 1;
stats.rx_bytes += packet.len() as u64;
drop(stats);
let ethertype = u16::from_be_bytes([packet[12], packet[13]]);
match ethertype {
ethernet::ETHERTYPE_ARP => {
if packet.len() >= 42 {
let target_ip = [packet[38], packet[39], packet[40], packet[41]];
if target_ip == MY_IP {
let sender_mac = [packet[22], packet[23], packet[24], packet[25], packet[26], packet[27]];
let sender_ip = [packet[28], packet[29], packet[30], packet[31]];
let reply = arp::create_arp_reply(dev.mac, MY_IP, sender_mac, sender_ip);
let frame = ethernet::create_frame(dev.mac, sender_mac, ethernet::ETHERTYPE_ARP, &reply);
let _ = dev.send_packet(&frame);
}
}
}
ethernet::ETHERTYPE_IPV4 => {
if packet.len() >= 34 {
let proto = packet[23];
if proto == ipv4::IP_PROTO_ICMP {
log::info!("[net] Received ICMP packet from network!");
}
}
}
_ => {}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
//! Panic handler ядра.
//!
//! В режиме no_std мы обязаны реализовать `#[panic_handler]` сами.
//! При панике выводим информацию в serial и уходим в halt-loop.
use core::panic::PanicInfo;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
// Используем log::error! — serial должен быть уже инициализирован к этому моменту.
// Если паника произошла до инициализации serial — сообщение просто потеряется.
log::error!("────────────────────────────────────────");
log::error!(" KERNEL PANIC");
log::error!("────────────────────────────────────────");
if let Some(location) = info.location() {
log::error!(
" Location : {}:{}:{}",
location.file(),
location.line(),
location.column()
);
}
log::error!(" Message : {}", info.message());
log::error!("────────────────────────────────────────");
// TODO: при наличии фичи `stack-trace` — вывести стэк трейс
crate::arch::x86_64::halt_loop()
}
/// Обработчик ошибок аллокации (OOM).
///
/// Вызывается когда kernel heap не может выделить память.
#[alloc_error_handler]
fn alloc_error(layout: core::alloc::Layout) -> ! {
panic!(
"kernel heap OOM: failed to allocate {} bytes (align {})",
layout.size(),
layout.align()
)
}
+15
View File
@@ -0,0 +1,15 @@
//! Подсистема планирования задач.
pub mod scheduler;
pub mod task;
pub use scheduler::{
block_current, current_task, exit_current, get_tasks_info, schedule, spawn, tick, unblock,
TaskInfo,
};
pub use task::{Priority, Task, TaskId, TaskState};
/// Инициализировать планировщик.
pub fn init() {
scheduler::init();
}
+256
View File
@@ -0,0 +1,256 @@
//! Планировщик задач (Preemptive Round-Robin / Priority Scheduler).
//!
//! Все операции с `SCHEDULER` выполняются через `without_interrupts`,
//! чтобы исключить deadlock при вызове `tick()` из прерывания таймера (IRQ0).
use alloc::collections::VecDeque;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::sync::atomic::Ordering;
use spin::Mutex;
use x86_64::instructions::interrupts::without_interrupts;
use super::task::{Priority, Task, TaskId, TaskState};
use crate::arch::x86_64::context::__switch_context;
const PRIORITY_LEVELS: usize = 8;
const DEFAULT_TIME_SLICE: u64 = 5; // 5 тиков по 10мс = 50 мс квант времени
/// Информация о задаче для команды `ps`.
pub struct TaskInfo {
pub id: TaskId,
pub name: String,
pub state: TaskState,
pub priority: Priority,
pub ticks: u64,
}
/// Глобальное состояние планировщика.
pub struct Scheduler {
/// Очереди готовых к выполнению задач.
run_queues: [VecDeque<Arc<Task>>; PRIORITY_LEVELS],
/// Текущая выполняющаяся задача.
current: Arc<Task>,
/// Все задачи в системе (для статистики `ps`).
all_tasks: Vec<Arc<Task>>,
/// Оставшиеся тики текущего кванта.
time_slice: u64,
/// Общий счётчик переключений контекста.
switch_count: u64,
}
impl Scheduler {
fn new(main_task: Arc<Task>) -> Self {
Scheduler {
run_queues: [
VecDeque::new(), VecDeque::new(),
VecDeque::new(), VecDeque::new(),
VecDeque::new(), VecDeque::new(),
VecDeque::new(), VecDeque::new(),
],
current: main_task.clone(),
all_tasks: alloc::vec![main_task],
time_slice: DEFAULT_TIME_SLICE,
switch_count: 0,
}
}
/// Добавить готовую задачу в очередь.
pub fn enqueue(&mut self, task: Arc<Task>) {
task.set_state(TaskState::Ready);
let prio = task.priority.0 as usize;
let idx = prio.min(PRIORITY_LEVELS - 1);
self.run_queues[idx].push_back(task);
}
/// Выбрать следующую задачу наивысшего приоритета.
fn pick_next(&mut self) -> Option<Arc<Task>> {
for queue in self.run_queues.iter_mut() {
if let Some(task) = queue.pop_front() {
return Some(task);
}
}
None
}
}
pub static SCHEDULER: Mutex<Option<Scheduler>> = Mutex::new(None);
/// Инициализация планировщика.
pub fn init() {
without_interrupts(|| {
let main_task = Task::new_main("kernel_shell");
let mut sched = Scheduler::new(main_task);
// Создаём Idle задачу ядра
let idle_task = Task::new_kernel_thread("idle", idle_task_entry, 0, Priority::IDLE);
sched.all_tasks.push(idle_task.clone());
sched.enqueue(idle_task);
*SCHEDULER.lock() = Some(sched);
log::info!("[sched] Preemptive scheduler initialized with idle task");
});
}
extern "C" fn idle_task_entry(_arg: usize) {
loop {
x86_64::instructions::hlt();
}
}
/// Создать и запустить новый поток ядра.
pub fn spawn(
name: &str,
entry: extern "C" fn(usize),
arg: usize,
priority: Priority,
) -> TaskId {
without_interrupts(|| {
let task = Task::new_kernel_thread(name, entry, arg, priority);
let id = task.id;
if let Some(sched) = SCHEDULER.lock().as_mut() {
sched.all_tasks.push(task.clone());
sched.enqueue(task);
log::debug!("[sched] Spawned new kernel thread: {} (name: {})", id, name);
}
id
})
}
/// Получить ссылку на текущую выполняющуюся задачу.
pub fn current_task() -> Option<Arc<Task>> {
without_interrupts(|| {
SCHEDULER.lock().as_ref().map(|s| s.current.clone())
})
}
/// Заблокировать текущую задачу (ожидание IPC или ресурса) и переключить контекст.
pub fn block_current() {
without_interrupts(|| {
if let Some(sched) = SCHEDULER.lock().as_mut() {
sched.current.set_state(TaskState::Blocked);
}
});
schedule();
}
/// Разблокировать задачу и вернуть её в очередь готовых.
pub fn unblock(task_id: TaskId) {
without_interrupts(|| {
if let Some(sched) = SCHEDULER.lock().as_mut() {
for task in &sched.all_tasks {
if task.id == task_id && task.get_state() == TaskState::Blocked {
let task_clone = task.clone();
sched.enqueue(task_clone);
break;
}
}
}
});
}
/// Завершить текущий поток.
pub fn exit_current() {
log::debug!("[sched] Task exited");
without_interrupts(|| {
if let Some(sched) = SCHEDULER.lock().as_mut() {
sched.current.set_state(TaskState::Dead);
}
});
schedule();
}
/// Переключить контекст на следующую готовую задачу (если есть).
pub fn schedule() {
without_interrupts(|| {
let mut guard = SCHEDULER.lock();
let sched = match guard.as_mut() {
Some(s) => s,
None => return,
};
let next = match sched.pick_next() {
Some(n) => n,
None => return, // нет других задач — продолжаем выполнять текущую
};
let prev = sched.current.clone();
// Если текущая задача не завершилась и не заблокирована — возвращаем её в очередь
if prev.get_state() == TaskState::Running {
prev.set_state(TaskState::Ready);
let prio = prev.priority.0 as usize;
let idx = prio.min(PRIORITY_LEVELS - 1);
sched.run_queues[idx].push_back(prev.clone());
}
next.set_state(TaskState::Running);
sched.current = next.clone();
sched.time_slice = DEFAULT_TIME_SLICE;
sched.switch_count += 1;
// Обновляем TSS RSP0 для новой задачи
if let Some(stack) = &next.stack {
crate::arch::x86_64::gdt::set_kernel_stack(x86_64::VirtAddr::new(stack.top() as u64));
}
// Получаем сырые указатели на сохранённый RSP до сброса блокировки
let prev_rsp_ptr: *mut usize = &mut *prev.rsp.lock();
let next_rsp: usize = *next.rsp.lock();
// Сбрасываем guard перед ассемблерным переключением стека!
drop(guard);
unsafe {
__switch_context(prev_rsp_ptr, next_rsp);
}
});
}
/// Обработчик тика таймера (вызывается из прерывания IRQ0 каждые 10мс).
pub fn tick() {
let should_schedule = {
let mut guard = SCHEDULER.lock();
if let Some(sched) = guard.as_mut() {
sched.current.ticks_run.fetch_add(1, Ordering::Relaxed);
if sched.time_slice > 0 {
sched.time_slice -= 1;
}
// Если квант времени истёк — планируем переключение
sched.time_slice == 0
} else {
false
}
};
if should_schedule {
schedule();
}
}
/// Получить снимок всех задач для команды `ps`.
pub fn get_tasks_info() -> Vec<TaskInfo> {
without_interrupts(|| {
let mut result = Vec::new();
if let Some(sched) = SCHEDULER.lock().as_mut() {
// Очищаем завершившиеся задачи (кроме текущей)
sched.all_tasks.retain(|t| {
t.id == sched.current.id || t.get_state() != TaskState::Dead
});
for t in &sched.all_tasks {
result.push(TaskInfo {
id: t.id,
name: t.name.clone(),
state: t.get_state(),
priority: t.priority,
ticks: t.ticks_run.load(Ordering::Relaxed),
});
}
}
result
})
}
+180
View File
@@ -0,0 +1,180 @@
//! Task — структура, описывающая поток выполнения (процесс или поток ядра).
use alloc::alloc::{alloc_zeroed, dealloc, Layout};
use alloc::string::String;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicU64, AtomicU8, Ordering};
use spin::Mutex;
/// Размер стэка ядра для каждого потока (16 KiB = 4 страницы).
pub const KERNEL_STACK_SIZE: usize = 16 * 1024;
/// Уникальный идентификатор задачи.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TaskId(pub u64);
impl TaskId {
pub fn new() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(1);
TaskId(COUNTER.fetch_add(1, Ordering::Relaxed))
}
pub fn as_u64(self) -> u64 {
self.0
}
}
impl core::fmt::Display for TaskId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Task#{}", self.0)
}
}
/// Состояние задачи.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TaskState {
/// Готова к выполнению, ожидает в очереди планировщика.
Ready = 0,
/// Выполняется на CPU прямо сейчас.
Running = 1,
/// Заблокирована (ждёт таймера, IPC, семафора).
Blocked = 2,
/// Завершилась, ожидает очистки.
Dead = 3,
}
impl From<u8> for TaskState {
fn from(val: u8) -> Self {
match val {
0 => TaskState::Ready,
1 => TaskState::Running,
2 => TaskState::Blocked,
_ => TaskState::Dead,
}
}
}
/// Приоритет задачи (0 — наивысший, 7 — наименьший).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Priority(pub u8);
impl Priority {
pub const KERNEL: Self = Priority(0);
pub const REALTIME: Self = Priority(1);
pub const HIGH: Self = Priority(2);
pub const NORMAL: Self = Priority(4);
pub const LOW: Self = Priority(6);
pub const IDLE: Self = Priority(7);
}
/// Выделенный стэк ядра.
pub struct KernelStack {
ptr: *mut u8,
layout: Layout,
}
unsafe impl Send for KernelStack {}
unsafe impl Sync for KernelStack {}
impl KernelStack {
/// Выделить новый стек ядра размером 16 KiB.
pub fn new() -> Self {
let layout = Layout::from_size_align(KERNEL_STACK_SIZE, 4096).unwrap();
let ptr = unsafe { alloc_zeroed(layout) };
assert!(!ptr.is_null(), "KernelStack: OOM allocating kernel stack");
KernelStack { ptr, layout }
}
/// Верхушка стека (адрес для начала роста стека вниз).
pub fn top(&self) -> usize {
(self.ptr as usize) + KERNEL_STACK_SIZE
}
}
impl Drop for KernelStack {
fn drop(&mut self) {
unsafe {
dealloc(self.ptr, self.layout);
}
}
}
/// Задача (поток выполнения).
pub struct Task {
pub id: TaskId,
pub name: String,
pub priority: Priority,
pub state: AtomicU8,
pub rsp: Mutex<usize>,
pub stack: Option<KernelStack>,
pub ticks_run: AtomicU64,
}
impl Task {
/// Создать структуру для текущего главного потока ядра (он уже имеет свой стек).
pub fn new_main(name: &str) -> Arc<Self> {
Arc::new(Task {
id: TaskId(0),
name: String::from(name),
priority: Priority::NORMAL,
state: AtomicU8::new(TaskState::Running as u8),
rsp: Mutex::new(0),
stack: None,
ticks_run: AtomicU64::new(0),
})
}
/// Создать новый поток ядра со своим выделенным стеком.
pub fn new_kernel_thread(
name: &str,
entry_fn: extern "C" fn(usize),
arg: usize,
priority: Priority,
) -> Arc<Self> {
let stack = KernelStack::new();
let stack_top = stack.top();
// Формируем начальный стек для __switch_context:
// top - 8: arg (usize)
// top - 16: entry_fn (usize)
// top - 24: thread_trampoline RIP
// top - 32: rbp (0)
// top - 40: rbx (0)
// top - 48: r12 (0)
// top - 56: r13 (0)
// top - 64: r14 (0)
// top - 72: r15 (0)
let initial_rsp = unsafe {
let sp = stack_top as *mut usize;
*sp.offset(-1) = arg;
*sp.offset(-2) = entry_fn as usize;
*sp.offset(-3) = crate::arch::x86_64::context::thread_trampoline as *const () as usize;
*sp.offset(-4) = 0; // rbp
*sp.offset(-5) = 0; // rbx
*sp.offset(-6) = 0; // r12
*sp.offset(-7) = 0; // r13
*sp.offset(-8) = 0; // r14
*sp.offset(-9) = 0; // r15
(sp.offset(-9)) as usize
};
Arc::new(Task {
id: TaskId::new(),
name: String::from(name),
priority,
state: AtomicU8::new(TaskState::Ready as u8),
rsp: Mutex::new(initial_rsp),
stack: Some(stack),
ticks_run: AtomicU64::new(0),
})
}
pub fn get_state(&self) -> TaskState {
TaskState::from(self.state.load(Ordering::Relaxed))
}
pub fn set_state(&self, state: TaskState) {
self.state.store(state as u8, Ordering::Relaxed);
}
}
+587
View File
@@ -0,0 +1,587 @@
//! Встроенные команды kernel shell.
use syscall_abi::SyscallNumber;
/// Диспетчер команд — вызывает нужный обработчик.
pub fn dispatch(cmd: &str, args: &[&str]) {
match cmd {
"help" | "?" => cmd_help(),
"clear" | "cls" => cmd_clear(),
"mem" | "memory" => cmd_mem(),
"uname" => cmd_uname(args),
"echo" => cmd_echo(args),
"ps" => cmd_ps(),
"spawn" => cmd_spawn(args),
"ipc" => cmd_ipc(args),
"cpuinfo" => cmd_cpuinfo(),
"lspci" => cmd_lspci(),
"ifconfig" | "net" => cmd_ifconfig(),
"ping" => cmd_ping(args),
"disks" => cmd_disks(),
"syscall" => cmd_syscall(args),
"uptime" => cmd_uptime(),
"ls" => cmd_ls(args),
"cat" => cmd_cat(args),
"vmem" => cmd_vmem(args),
"halt" | "shutdown" => cmd_halt(),
"reboot"| "restart" => cmd_reboot(),
"panic" => cmd_panic(args),
_ => {
crate::kprintln!(
"\x1b[31mUnknown command: '{}'\x1b[0m (try 'help')",
cmd
);
}
}
}
// ── help ──────────────────────────────────────────────────────────────────────
fn cmd_help() {
crate::kprintln!("\x1b[1;36mopencoreRS Kernel Shell — Built-in Commands\x1b[0m");
crate::kprintln!("\x1b[90m─────────────────────────────────────────────\x1b[0m");
let cmds: &[(&str, &str)] = &[
("help, ?", "Show this help"),
("clear, cls", "Clear the screen"),
("mem, memory", "Physical memory statistics"),
("vmem <addr>", "Translate virtual address to physical"),
("cpuinfo", "Display CPUID vendor, model & features"),
("lspci", "Scan and list PCI hardware devices"),
("ifconfig, net", "Display network interface & packet stats"),
("ping [ip]", "Send ICMP Echo Request over Ethernet"),
("disks", "List AHCI SATA hard disks"),
("uname [-a]", "System information"),
("echo <text>", "Print text to console"),
("ps", "List preemptive multitasking threads"),
("spawn [name]", "Spawn a background kernel worker thread"),
("ipc", "Test IPC channel message passing & thread wakeup"),
("syscall <message>", "Trigger SYSCALL/SYSRET instruction"),
("uptime", "Time since kernel boot"),
("ls [path]", "List directory (VFS stub)"),
("cat <file>", "Print file contents (/proc/*)"),
("halt, shutdown", "Halt the CPU"),
("reboot, restart", "Reboot the system"),
("panic [msg]", "Trigger kernel panic (testing)"),
];
for (name, desc) in cmds {
crate::kprintln!(" \x1b[1;33m{:<22}\x1b[0m {}", name, desc);
}
crate::kprintln!();
crate::kprintln!(" \x1b[90mCtrl-C\x1b[0m Cancel current input");
crate::kprintln!(" \x1b[90mCtrl-L\x1b[0m Clear screen");
crate::kprintln!();
}
// ── clear ─────────────────────────────────────────────────────────────────────
fn cmd_clear() {
crate::kprint!("\x1b[2J\x1b[H");
}
// ── mem ───────────────────────────────────────────────────────────────────────
fn cmd_mem() {
let total = crate::mm::pmm::total_frames();
let free = crate::mm::pmm::free_frames();
let used = crate::mm::pmm::used_frames();
let pfree = if total > 0 { free * 100 / total } else { 0 };
let mb = |frames: usize| frames * crate::mm::pmm::PAGE_SIZE / 1024 / 1024;
crate::kprintln!("\x1b[1mPhysical Memory\x1b[0m");
crate::kprintln!(
" Total : \x1b[1;37m{:>6} MiB\x1b[0m ({} frames)",
mb(total), total
);
crate::kprintln!(
" Free : \x1b[1;32m{:>6} MiB\x1b[0m ({} frames, {}%)",
mb(free), free, pfree
);
crate::kprintln!(
" Used : \x1b[1;33m{:>6} MiB\x1b[0m ({} frames)",
mb(used), used
);
// Визуальная полоска
let bar_len = 40usize;
let filled = if total > 0 { used * bar_len / total } else { 0 };
crate::kprint!(" [");
for i in 0..bar_len {
if i < filled {
crate::kprint!("\x1b[1;33m█\x1b[0m");
} else {
crate::kprint!("\x1b[90m░\x1b[0m");
}
}
crate::kprintln!("] {}% used", 100 - pfree);
crate::kprintln!();
crate::kprintln!("\x1b[1mKernel Heap\x1b[0m");
crate::kprintln!(" Size : 4 MiB (static allocation)");
crate::kprintln!();
}
// ── vmem ──────────────────────────────────────────────────────────────────────
fn cmd_vmem(args: &[&str]) {
let Some(addr_str) = args.first() else {
crate::kprintln!("Usage: vmem <virtual_address>");
crate::kprintln!(" e.g. vmem 0xffff800000000000");
return;
};
let addr_str = addr_str.trim_start_matches("0x").trim_start_matches("0X");
let Ok(addr) = u64::from_str_radix(addr_str, 16) else {
crate::kprintln!("\x1b[31mError: invalid hex address '{}'\x1b[0m", addr_str);
return;
};
let virt = x86_64::VirtAddr::new(addr);
match crate::mm::vmm::translate(virt) {
Some(phys) => {
crate::kprintln!(
" virt \x1b[1;36m{:#018x}\x1b[0m → phys \x1b[1;32m{:#018x}\x1b[0m",
virt.as_u64(), phys.as_u64()
);
}
None => {
crate::kprintln!(
" virt \x1b[1;36m{:#018x}\x1b[0m → \x1b[1;31m(not mapped)\x1b[0m",
virt.as_u64()
);
}
}
}
// ── uname ─────────────────────────────────────────────────────────────────────
fn cmd_uname(args: &[&str]) {
let verbose = args.first().copied() == Some("-a");
if verbose {
crate::kprintln!(
"opencoreRS opencoreRS {} #1 SMP x86_64",
env!("CARGO_PKG_VERSION")
);
} else {
crate::kprintln!("opencoreRS");
}
}
// ── echo ──────────────────────────────────────────────────────────────────────
fn cmd_echo(args: &[&str]) {
let mut first = true;
for arg in args {
if !first { crate::kprint!(" "); }
crate::kprint!("{}", arg);
first = false;
}
crate::kprintln!();
}
// ── ps ────────────────────────────────────────────────────────────────────────
fn cmd_ps() {
crate::kprintln!("\x1b[1m PID STATE PRIO TICKS NAME\x1b[0m");
crate::kprintln!("\x1b[90m ─── ─────── ──── ────── ────────────────\x1b[0m");
let tasks = crate::sched::get_tasks_info();
for t in tasks {
let (state_str, state_color) = match t.state {
crate::sched::TaskState::Running => ("Running", "\x1b[1;32m"),
crate::sched::TaskState::Ready => ("Ready ", "\x1b[1;33m"),
crate::sched::TaskState::Blocked => ("Blocked", "\x1b[1;34m"),
crate::sched::TaskState::Dead => ("Dead ", "\x1b[90m"),
};
let prio_str = match t.priority.0 {
0 => "KERNEL",
1 => "REALTM",
2 => "HIGH ",
4 => "NORMAL",
6 => "LOW ",
7 => "IDLE ",
_ => "CUSTOM",
};
crate::kprintln!(
" {:>3} {}{}\x1b[0m {:<6} {:>6} {}",
t.id.0,
state_color,
state_str,
prio_str,
t.ticks,
t.name
);
}
crate::kprintln!();
}
// ── spawn ─────────────────────────────────────────────────────────────────────
extern "C" fn demo_worker_thread(arg: usize) {
let worker_num = arg;
log::info!("[worker #{}] Background thread started!", worker_num);
// Работает в фоне параллельно с shell
for i in 1..=5 {
// Симулируем небольшую работу
let mut count = 0u64;
for _ in 0..5_000_000 {
unsafe { core::arch::asm!("nop"); }
count += 1;
}
log::info!("[worker #{}] Step {}/5 completed (nop count: {})", worker_num, i, count);
// Добровольно уступаем квант времени
crate::sched::schedule();
}
log::info!("[worker #{}] Background thread finished successfully!", worker_num);
}
static mut WORKER_COUNTER: usize = 1;
fn cmd_spawn(args: &[&str]) {
let name = args.first().copied().unwrap_or("bg_worker");
let worker_id = unsafe {
let id = WORKER_COUNTER;
WORKER_COUNTER += 1;
id
};
let task_id = crate::sched::spawn(
name,
demo_worker_thread,
worker_id,
crate::sched::Priority::NORMAL,
);
crate::kprintln!(
"\x1b[1;32mSpawned thread {}\x1b[0m (name: '{}', worker #{})",
task_id, name, worker_id
);
crate::kprintln!("Type \x1b[1m'ps'\x1b[0m to see running threads.");
crate::kprintln!();
}
// ── ipc ───────────────────────────────────────────────────────────────────────
extern "C" fn ipc_test_receiver(channel_id: usize) {
let ch_id = channel_id as u64;
log::info!("[ipc_receiver] Waiting for message on channel #{}...", ch_id);
let ch = crate::ipc::get_channel(ch_id).expect("Channel not found");
let current = crate::sched::current_task().expect("No current task");
// Блокируемся, пока не придёт сообщение
loop {
if let Some(msg) = ch.try_recv() {
log::info!(
"\x1b[1;32m[ipc_receiver] Message received!\x1b[0m Tag: {:#x}, Words: [{}, {}, {}, {}]",
msg.tag, msg.words[0], msg.words[1], msg.words[2], msg.words[3]
);
break;
}
ch.register_waiter(current.id);
crate::sched::block_current();
}
}
fn cmd_ipc(_args: &[&str]) {
crate::kprintln!("\x1b[1;36mTesting IPC Channels and Thread Blocking/Wakeup\x1b[0m");
// 1. Создаём канал
let ch = crate::ipc::create_channel(4);
let ch_id = ch.id;
crate::kprintln!(" 1. Created IPC channel \x1b[1m#{}\x1b[0m", ch_id);
// 2. Спавним поток-получатель (он заблокируется на канале)
let rx_task = crate::sched::spawn(
"ipc_rx",
ipc_test_receiver,
ch_id as usize,
crate::sched::Priority::NORMAL,
);
crate::kprintln!(" 2. Spawned receiver task \x1b[1m{}\x1b[0m (will block on channel)", rx_task);
// 3. Отправляем сообщение через канал (это мгновенно разбудит получателя!)
let msg = crate::ipc::Message::new(0xCAFE_BABE, 42, 100, 200, 300);
crate::kprintln!(" 3. Sending message: tag={:#x}, w0={}", msg.tag, msg.words[0]);
let _ = ch.send(msg);
crate::kprintln!(" \x1b[1;32mIPC test message dispatched!\x1b[0m Check log output above.");
crate::kprintln!();
}
// ── cpuinfo ───────────────────────────────────────────────────────────────────
fn cmd_cpuinfo() {
crate::kprintln!("\x1b[1;36mHardware CPU Information (CPUID & SMP)\x1b[0m");
crate::kprintln!("\x1b[90m─────────────────────────────────────────────\x1b[0m");
if let Some(info) = crate::arch::x86_64::cpuid::CPU_INFO.get() {
crate::kprintln!(" \x1b[1;33mVendor :\x1b[0m {}", info.vendor);
crate::kprintln!(" \x1b[1;33mModel :\x1b[0m {}", info.brand_str());
crate::kprintln!(" \x1b[1;33mFeatures :\x1b[0m");
crate::kprintln!(
" APIC: {}, X2APIC: {}, SSE: {}, AVX: {}, RDRAND: {}, 1GB_Pages: {}",
if info.has_apic { "\x1b[32myes\x1b[0m" } else { "no" },
if info.has_x2apic { "\x1b[32myes\x1b[0m" } else { "no" },
if info.has_sse { "\x1b[32myes\x1b[0m" } else { "no" },
if info.has_avx { "\x1b[32myes\x1b[0m" } else { "no" },
if info.has_rdrand { "\x1b[32myes\x1b[0m" } else { "no" },
if info.has_1gb_pages { "\x1b[32myes\x1b[0m" } else { "no" }
);
} else {
crate::kprintln!(" CPUID not initialized");
}
let cpu_count = *crate::arch::x86_64::smp::CPU_COUNT.lock();
crate::kprintln!(" \x1b[1;33mActive Cores:\x1b[0m {} core(s)", cpu_count);
crate::kprintln!();
}
// ── lspci ─────────────────────────────────────────────────────────────────────
fn cmd_lspci() {
crate::kprintln!("\x1b[1;36mPCI Bus Device Enumeration\x1b[0m");
crate::kprintln!("\x1b[90m─────────────────────────────────────────────\x1b[0m");
let devices = crate::arch::x86_64::pci::PCI_DEVICES.lock();
if devices.is_empty() {
crate::kprintln!(" No PCI devices found.");
} else {
for dev in devices.iter() {
crate::kprintln!(
" \x1b[1;33m{:02x}:{:02x}.0\x1b[0m [{:04x}:{:04x}] \x1b[1m{}\x1b[0m ({})",
dev.bus,
dev.device,
dev.vendor_id,
dev.device_id,
dev.class_name(),
dev.vendor_name()
);
}
}
crate::kprintln!();
}
// ── ifconfig / net ────────────────────────────────────────────────────────────
fn cmd_ifconfig() {
crate::kprintln!("\x1b[1;36mNetwork Interface Configuration (eth0)\x1b[0m");
crate::kprintln!("\x1b[90m─────────────────────────────────────────────\x1b[0m");
let dev_lock = crate::drivers::e1000::E1000_DEVICE.lock();
if let Some(dev) = dev_lock.as_ref() {
crate::kprintln!(" \x1b[1;33mInterface :\x1b[0m eth0 (Intel E1000 Gigabit)");
crate::kprintln!(
" \x1b[1;33mMAC Address:\x1b[0m {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
dev.mac[0], dev.mac[1], dev.mac[2], dev.mac[3], dev.mac[4], dev.mac[5]
);
let ip = crate::net::MY_IP;
let gw = crate::net::GATEWAY_IP;
crate::kprintln!(" \x1b[1;33mIPv4 :\x1b[0m {}.{}.{}.{} / 24", ip[0], ip[1], ip[2], ip[3]);
crate::kprintln!(" \x1b[1;33mGateway :\x1b[0m {}.{}.{}.{}", gw[0], gw[1], gw[2], gw[3]);
let stats = *crate::net::NET_STATS.lock();
crate::kprintln!(
" \x1b[1;33mStatistics :\x1b[0m TX: {} packets ({} bytes) | RX: {} packets ({} bytes)",
stats.tx_frames, stats.tx_bytes, stats.rx_frames, stats.rx_bytes
);
} else {
crate::kprintln!(" No network interface found.");
}
crate::kprintln!();
}
// ── ping ──────────────────────────────────────────────────────────────────────
fn cmd_ping(_args: &[&str]) {
crate::kprintln!("\x1b[90mSending ICMP Echo Request to Gateway (10.0.2.2)...\x1b[0m");
match crate::net::ping(crate::net::GATEWAY_IP) {
Ok(_) => {
crate::kprintln!(" \x1b[1;32mPING 10.0.2.2 (10.0.2.2): 64 data bytes (ICMP Echo sent)\x1b[0m");
}
Err(e) => {
crate::kprintln!(" \x1b[1;31mPing error:\x1b[0m {}", e);
}
}
crate::kprintln!();
}
// ── disks ─────────────────────────────────────────────────────────────────────
fn cmd_disks() {
crate::kprintln!("\x1b[1;36mStorage Controllers & Connected Disks\x1b[0m");
crate::kprintln!("\x1b[90m─────────────────────────────────────────────\x1b[0m");
let ahci_lock = crate::drivers::ahci::AHCI_CONTROLLER.lock();
if let Some(ahci) = ahci_lock.as_ref() {
crate::kprintln!(" \x1b[1;33mAHCI Controller:\x1b[0m ABAR={:#x}", ahci.base_virt);
if ahci.disks.is_empty() {
crate::kprintln!(" No SATA disks connected.");
} else {
for disk in &ahci.disks {
let size_mb = (disk.sectors * 512) / (1024 * 1024);
crate::kprintln!(
" \x1b[1;33mPort #{}\x1b[0m: {} Drive, Size: {} MiB ({} sectors)",
disk.port,
if disk.is_sata { "SATA HDD/SSD" } else { "SATAPI CD/DVD" },
size_mb,
disk.sectors
);
}
}
} else {
crate::kprintln!(" No AHCI controller initialized.");
}
crate::kprintln!();
}
// ── syscall ───────────────────────────────────────────────────────────────────
fn cmd_syscall(args: &[&str]) {
let msg = if args.is_empty() {
"Hello from SYSCALL ABI!"
} else {
args[0]
};
crate::kprintln!("\x1b[90mDispatching SYSCALL (SyscallNumber::Log)...\x1b[0m");
let result = crate::syscall::handler::syscall_dispatch(
SyscallNumber::Log as u64,
msg.as_ptr() as u64,
msg.len() as u64,
0,
0,
0,
);
crate::kprintln!(
"\x1b[1;32mSYSCALL handled successfully!\x1b[0m Return code: {} (0 = Ok)",
result
);
crate::kprintln!();
}
// ── uptime ────────────────────────────────────────────────────────────────────
fn cmd_uptime() {
let secs = crate::time::uptime_secs();
let ms = crate::time::uptime_ms() % 1000;
let (h, m, s) = (secs / 3600, (secs % 3600) / 60, secs % 60);
crate::kprintln!(
" up \x1b[1;37m{:02}:{:02}:{:02}.{:03}\x1b[0m \
\x1b[90m(TSC-based, ~2.5 GHz assumed)\x1b[0m",
h, m, s, ms
);
crate::kprintln!();
}
// ── ls ────────────────────────────────────────────────────────────────────────
fn cmd_ls(args: &[&str]) {
let path = args.first().copied().unwrap_or("/");
crate::kprintln!(
"\x1b[1;36m{}\x1b[0m \x1b[90m(VFS server not started)\x1b[0m",
path
);
crate::kprintln!("\x1b[90m─────────────────────────────────────────\x1b[0m");
if path == "/" {
let entries = [
("bin/", "d", "binaries"),
("dev/", "d", "device files"),
("etc/", "d", "configuration"),
("proc/", "d", "process info (procfs)"),
("sys/", "d", "kernel objects (sysfs)"),
("tmp/", "d", "temporary files"),
];
for (name, kind, note) in &entries {
let color = if kind == &"d" { "\x1b[1;34m" } else { "\x1b[0m" };
crate::kprintln!(
" {}{:<10}\x1b[0m \x1b[90m# {}\x1b[0m",
color, name, note
);
}
} else {
crate::kprintln!("\x1b[33m ls: {}: filesystem not mounted\x1b[0m", path);
}
crate::kprintln!();
}
// ── cat ───────────────────────────────────────────────────────────────────────
fn cmd_cat(args: &[&str]) {
let Some(file) = args.first() else {
crate::kprintln!("Usage: cat <file>");
return;
};
match *file {
"/proc/meminfo" => {
let mb = |f: usize| f * crate::mm::pmm::PAGE_SIZE / 1024 / 1024;
crate::kprintln!("MemTotal: {} MiB", mb(crate::mm::pmm::total_frames()));
crate::kprintln!("MemFree: {} MiB", mb(crate::mm::pmm::free_frames()));
crate::kprintln!("MemUsed: {} MiB", mb(crate::mm::pmm::used_frames()));
}
"/proc/uptime" => {
crate::kprintln!(
"{}.{:03} 0.00",
crate::time::uptime_secs(),
crate::time::uptime_ms() % 1000
);
}
"/proc/version" => {
crate::kprintln!(
"opencoreRS version {} #1 SMP x86_64",
env!("CARGO_PKG_VERSION")
);
}
_ => {
crate::kprintln!(
"\x1b[31mcat: {}: No such file (VFS not mounted)\x1b[0m",
file
);
}
}
}
// ── halt ──────────────────────────────────────────────────────────────────────
fn cmd_halt() -> ! {
crate::kprintln!("\x1b[33mHalting CPU...\x1b[0m");
log::info!("[shell] User requested halt");
crate::arch::x86_64::halt_loop()
}
// ── reboot ────────────────────────────────────────────────────────────────────
fn cmd_reboot() -> ! {
crate::kprintln!("\x1b[33mRebooting...\x1b[0m");
log::info!("[shell] User requested reboot");
unsafe {
let mut port: x86_64::instructions::port::Port<u8> =
x86_64::instructions::port::Port::new(0x64);
port.write(0xFE_u8);
}
crate::arch::x86_64::halt_loop()
}
// ── panic ─────────────────────────────────────────────────────────────────────
fn cmd_panic(args: &[&str]) {
if args.is_empty() {
panic!("User-triggered kernel panic");
}
panic!("User-triggered panic: {}", args[0]);
}
+158
View File
@@ -0,0 +1,158 @@
//! Kernel Debug Shell — интерактивная консоль поверх UART.
//!
//! Запускается в конце `kernel_main` вместо halt-loop.
//! Работает в режиме polling (нет timer IRQ) — CPU не засыпает, но
//! для отладки это нормально.
//!
//! # Возможности
//! - Чтение строк с поддержкой Backspace, Ctrl-C
//! - ANSI цвета (работает в QEMU serial → terminal)
//! - Встроенные команды (см. commands.rs)
pub mod commands;
const MAX_LINE: usize = 256;
const MAX_ARGS: usize = 16;
/// Цветной prompt.
const PROMPT: &str =
"\x1b[1;32mkernel\x1b[0m@\x1b[1;36mopencoreRS\x1b[0m\x1b[33m:~\x1b[0m$ ";
/// Запустить shell — никогда не возвращается.
pub fn run() -> ! {
print_banner();
loop {
// Вывести prompt
crate::kprint!("{}", PROMPT);
// Прочитать строку
let mut buf = [0u8; MAX_LINE];
let len = read_line(&mut buf);
if len == 0 {
continue;
}
let line = match core::str::from_utf8(&buf[..len]) {
Ok(s) => s.trim(),
Err(_) => {
crate::kprintln!("\x1b[31mError: invalid UTF-8 input\x1b[0m");
continue;
}
};
if line.is_empty() {
continue;
}
// Парсим команду и аргументы (без heap — фиксированный массив)
let mut parts = line.splitn(MAX_ARGS + 1, char::is_whitespace);
let cmd = match parts.next().filter(|s| !s.is_empty()) {
Some(c) => c,
None => continue,
};
let mut args_buf = [""; MAX_ARGS];
let mut argc = 0usize;
for part in parts {
if part.is_empty() {
continue;
}
if argc < MAX_ARGS {
args_buf[argc] = part;
argc += 1;
}
}
commands::dispatch(cmd, &args_buf[..argc]);
}
}
// ── Приветственный баннер ─────────────────────────────────────────────────────
fn print_banner() {
// Очистка экрана + курсор в начало
crate::kprint!("\x1b[2J\x1b[H");
crate::kprintln!("\x1b[1;36m╔═══════════════════════════════════════════════════════╗");
crate::kprintln!("║ opencoreRS v0.1.0 — Kernel Debug Shell ║");
crate::kprintln!("╚═══════════════════════════════════════════════════════╝\x1b[0m");
crate::kprintln!();
crate::kprintln!(
" \x1b[90mArch :\x1b[0m x86_64 \
\x1b[90mBuild:\x1b[0m {} (debug)",
env!("CARGO_PKG_VERSION")
);
crate::kprintln!(
" \x1b[90mMem :\x1b[0m {} MiB free",
crate::mm::pmm::free_bytes() / 1024 / 1024
);
crate::kprintln!();
crate::kprintln!(" Type \x1b[1m'help'\x1b[0m to see available commands.");
crate::kprintln!();
}
// ── Чтение строки из UART ─────────────────────────────────────────────────────
/// Читать символы из UART до Enter/Ctrl-C.
///
/// Возвращает количество прочитанных байт в `buf`.
/// Поддерживает: Backspace (DEL), Ctrl-C.
fn read_line(buf: &mut [u8]) -> usize {
let mut len = 0usize;
loop {
match crate::logger::read_byte() {
// Enter (CR или LF)
Some(b'\r') | Some(b'\n') => {
if len == 0 {
continue;
}
crate::kprint!("\r\n");
return len;
}
// Backspace / DEL
Some(0x08) | Some(0x7F) => {
if len > 0 {
len -= 1;
// Затираем символ: назад, пробел, назад
crate::kprint!("\x08 \x08");
}
}
// Ctrl-C
Some(0x03) => {
crate::kprintln!("^C");
return 0;
}
// Ctrl-L — очистить экран
Some(0x0C) => {
crate::kprint!("\x1b[2J\x1b[H");
crate::kprint!("{}", PROMPT);
// Перепечатать уже введённое
let s = core::str::from_utf8(&buf[..len]).unwrap_or("");
crate::kprint!("{}", s);
}
// Печатаемый ASCII
Some(b) if b >= 0x20 && b < 0x7F => {
if len < buf.len() - 1 {
buf[len] = b;
len += 1;
// Echo
crate::logger::print_raw(core::str::from_utf8(&[b]).unwrap_or(""));
}
}
Some(_) => {} // Игнорируем другие управляющие символы
None => {
crate::net::poll();
core::hint::spin_loop();
}
}
}
}
+266
View File
@@ -0,0 +1,266 @@
//! Syscall handler и диспетчер.
//!
//! # Регистрация через MSR
//! - `IA32_EFER` (0xC0000080) — бит 0 (SCE) разрешает инструкцию `syscall`
//! - `IA32_STAR` (0xC0000081) — селекторы CS/SS для ядра и userspace
//! - `IA32_LSTAR` (0xC0000082) — адрес точки входа `syscall_entry`
//! - `IA32_FMASK` (0xC0000084) — маска сбрасываемых флагов RFLAGS при входе
use core::arch::naked_asm;
use syscall_abi::{SyscallError, SyscallNumber};
use x86_64::registers::model_specific::Msr;
const IA32_EFER: u32 = 0xC000_0080;
const IA32_STAR: u32 = 0xC000_0081;
const IA32_LSTAR: u32 = 0xC000_0082;
const IA32_FMASK: u32 = 0xC000_0084;
// Временное сохранение пользовательского стека при входе в syscall
static mut USER_RSP_TEMP: u64 = 0;
// Статический аварийный стек ядра для системных вызовов
const SYSCALL_STACK_SIZE: usize = 16384;
static mut SYSCALL_STACK: [u8; SYSCALL_STACK_SIZE] = [0; SYSCALL_STACK_SIZE];
/// Установить и настроить обработчик SYSCALL/SYSRET.
pub fn install_syscall_handler() {
unsafe {
// 1. Включить SCE (System Call Extensions) в EFER
let mut efer = Msr::new(IA32_EFER);
let efer_val = efer.read();
efer.write(efer_val | 1); // Бит 0 = SCE
// 2. Настроить STAR:
// STAR[47:32] = Kernel CS (0x08). Ядро получит CS=0x08, SS=0x10.
// STAR[63:48] = User Base (0x10). SYSRET 64-bit загрузит SS=0x18|3=0x1B, CS=0x20|3=0x23.
let star_val: u64 = ((0x0010u64) << 48) | ((0x0008u64) << 32);
Msr::new(IA32_STAR).write(star_val);
// 3. Установить LSTAR (адрес обработчика syscall_entry)
let lstar_addr = syscall_entry as *const () as usize as u64;
Msr::new(IA32_LSTAR).write(lstar_addr);
// 4. Настроить FMASK (маскируем IF - bit 9, DF - bit 10, TF - bit 8)
let fmask_val: u64 = 0x700; // сбрасываем IF (прерывания отключены при входе)
Msr::new(IA32_FMASK).write(fmask_val);
}
log::info!("[syscall] SYSCALL/SYSRET MSR registers configured");
}
/// Ассемблерная точка входа при инструкции `syscall`.
#[unsafe(naked)]
#[no_mangle]
pub unsafe extern "C" fn syscall_entry() {
naked_asm!(
// 1. Сохранить пользовательский RSP в регистр r12 и переключиться на per-task kernel stack
"mov r12, rsp",
"mov rsp, [{current_kernel_rsp0}]",
// 2. Сохранить регистры вызова userspace на стеке ядра текущей задачи
"push r12", // user RSP
"push r11", // user RFLAGS
"push rcx", // user RIP (возврат из syscall)
"push rbp",
"push rbx",
"push r12", // r12 placeholder
"push r13",
"push r14",
"push r15",
// 3. Подготовка аргументов для вызова syscall_dispatch (System V ABI):
// userspace передаёт: rax=num, rdi=arg0, rsi=arg1, rdx=arg2, r10=arg3, r8=arg4, r9=arg5
// C ABI: rdi=num, rsi=arg0, rdx=arg1, rcx=arg2, r8=arg3, r9=arg4
"mov r9, r8", // arg4
"mov r8, r10", // arg3
"mov rcx, rdx", // arg2
"mov rdx, rsi", // arg1
"mov rsi, rdi", // arg0
"mov rdi, rax", // number
// 4. Разрешить прерывания в ядре во время обработки syscall
"sti",
// 5. Вызов диспетчера
"call {dispatch}",
// 6. Отключить прерывания перед выходом
"cli",
// 7. Восстановить регистры (rax хранит результат системного вызова)
"pop r15",
"pop r14",
"pop r13",
"pop r12",
"pop rbx",
"pop rbp",
"pop rcx", // восстанавливаем user RIP в RCX для sysretq
"pop r11", // восстанавливаем user RFLAGS в R11 для sysretq
"pop rsp", // восстанавливаем user RSP!
// 8. Возврат в Ring 3
"sysretq",
current_kernel_rsp0 = sym crate::arch::x86_64::gdt::CURRENT_KERNEL_RSP0,
dispatch = sym syscall_dispatch,
);
}
#[no_mangle]
static mut SYSCALL_STACK_TOP_ADDR: usize = 0;
/// Инициализировать указатель на вершину стека syscall.
pub fn init_syscall_stack() {
unsafe {
SYSCALL_STACK_TOP_ADDR = (core::ptr::addr_of!(SYSCALL_STACK) as usize) + SYSCALL_STACK_SIZE;
}
}
/// Точка входа диспетчера системных вызовов.
#[no_mangle]
pub extern "C" fn syscall_dispatch(
number: u64,
arg0: u64,
arg1: u64,
arg2: u64,
arg3: u64,
arg4: u64,
) -> i64 {
let num = match SyscallNumber::try_from(number) {
Ok(n) => n,
Err(_) => {
log::warn!("[syscall] Unknown syscall number: {:#x}", number);
return SyscallError::NoSyscall as i64;
}
};
match num {
SyscallNumber::Log => sys_log(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::IpcSend => sys_ipc_send(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::IpcRecv => sys_ipc_recv(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::MemMap => sys_mem_map(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::MemUnmap => sys_mem_unmap(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::ThreadSpawn => sys_thread_spawn(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::ThreadExit => sys_thread_exit(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::Yield => sys_yield(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::CapDerive => sys_cap_derive(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::CapRevoke => sys_cap_revoke(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::ReadChar => sys_read_char(arg0, arg1, arg2, arg3, arg4),
SyscallNumber::WriteChar => sys_write_char(arg0, arg1, arg2, arg3, arg4),
}
}
// ── Реализации системных вызовов ──────────────────────────────────
fn sys_log(ptr: u64, len: u64, _: u64, _: u64, _: u64) -> i64 {
// Безопасное чтение строки
if len > 1024 {
return SyscallError::InvalidArgument as i64;
}
let slice = unsafe { core::slice::from_raw_parts(ptr as *const u8, len as usize) };
if let Ok(msg) = core::str::from_utf8(slice) {
crate::kprintln!("\x1b[1;35m[userspace log]\x1b[0m {}", msg);
0
} else {
SyscallError::InvalidArgument as i64
}
}
fn sys_ipc_send(channel_id: u64, msg_ptr: u64, _len: u64, _: u64, _: u64) -> i64 {
let ch = match crate::ipc::get_channel(channel_id) {
Some(c) => c,
None => return SyscallError::NotFound as i64,
};
if msg_ptr == 0 {
return SyscallError::InvalidArgument as i64;
}
let msg = unsafe { *(msg_ptr as *const crate::ipc::Message) };
match ch.send(msg) {
Ok(()) => 0,
Err(crate::ipc::ChannelError::Full) => SyscallError::Busy as i64,
Err(crate::ipc::ChannelError::Closed) => SyscallError::NotFound as i64,
}
}
fn sys_ipc_recv(channel_id: u64, buf_ptr: u64, _len: u64, _: u64, _: u64) -> i64 {
let ch = match crate::ipc::get_channel(channel_id) {
Some(c) => c,
None => return SyscallError::NotFound as i64,
};
if buf_ptr == 0 {
return SyscallError::InvalidArgument as i64;
}
let current = match crate::sched::current_task() {
Some(t) => t,
None => return SyscallError::NotFound as i64,
};
loop {
if let Some(msg) = ch.try_recv() {
unsafe {
*(buf_ptr as *mut crate::ipc::Message) = msg;
}
return 0;
}
// Регистрируем ожидание и блокируем текущую задачу
ch.register_waiter(current.id);
crate::sched::block_current();
}
}
fn sys_mem_map(_cap: u64, _virt: u64, _flags: u64, _: u64, _: u64) -> i64 {
SyscallError::NotImplemented as i64
}
fn sys_mem_unmap(_virt: u64, _: u64, _: u64, _: u64, _: u64) -> i64 {
SyscallError::NotImplemented as i64
}
fn sys_thread_spawn(_entry: u64, _stack: u64, _prio: u64, _: u64, _: u64) -> i64 {
SyscallError::NotImplemented as i64
}
fn sys_thread_exit(_code: u64, _: u64, _: u64, _: u64, _: u64) -> i64 {
crate::sched::exit_current();
0
}
fn sys_cap_derive(_src: u64, _rights: u64, _: u64, _: u64, _: u64) -> i64 {
SyscallError::NotImplemented as i64
}
fn sys_cap_revoke(_cap: u64, _: u64, _: u64, _: u64, _: u64) -> i64 {
SyscallError::NotImplemented as i64
}
fn sys_read_char(_: u64, _: u64, _: u64, _: u64, _: u64) -> i64 {
match crate::logger::read_byte() {
Some(b) => b as i64,
None => -1,
}
}
fn sys_write_char(ptr: u64, len: u64, _: u64, _: u64, _: u64) -> i64 {
if len > 4096 {
return SyscallError::InvalidArgument as i64;
}
let slice = unsafe { core::slice::from_raw_parts(ptr as *const u8, len as usize) };
if let Ok(s) = core::str::from_utf8(slice) {
crate::logger::print_raw(s);
0
} else {
SyscallError::InvalidArgument as i64
}
}
fn sys_yield(_: u64, _: u64, _: u64, _: u64, _: u64) -> i64 {
crate::sched::schedule();
0
}
+36
View File
@@ -0,0 +1,36 @@
//! Syscall подсистема.
pub mod handler;
/// Инициализировать syscall-диспетчер.
pub fn init() {
handler::init_syscall_stack();
handler::install_syscall_handler();
log::info!("[syscall] SYSCALL/SYSRET ready");
}
/// Выполнить тестовый системный вызов через инструкцию `syscall`.
#[inline(never)]
pub unsafe fn raw_syscall(
num: u64,
arg0: u64,
arg1: u64,
arg2: u64,
arg3: u64,
arg4: u64,
) -> i64 {
let ret: i64;
core::arch::asm!(
"syscall",
inout("rax") num => ret,
in("rdi") arg0,
in("rsi") arg1,
in("rdx") arg2,
in("r10") arg3,
in("r8") arg4,
out("rcx") _,
out("r11") _,
options(nostack)
);
ret
}
+51
View File
@@ -0,0 +1,51 @@
//! Простой таймер на базе TSC (Time Stamp Counter).
//!
//! RDTSC — самый дешёвый способ получить метку времени в ядре.
//! Точность: ±5% (зависит от частоты CPU). Для uptime — достаточно.
//!
//! В будущем: HPET / APIC timer для точного времени.
use core::sync::atomic::{AtomicU64, Ordering};
static BOOT_TSC: AtomicU64 = AtomicU64::new(0);
/// Инициализировать таймер — сохранить TSC в момент загрузки.
/// Вызывать как можно раньше в kernel_main.
pub fn init() {
BOOT_TSC.store(rdtsc(), Ordering::Relaxed);
log::debug!("[time] Boot TSC: {}", BOOT_TSC.load(Ordering::Relaxed));
}
/// Количество TSC-тиков с момента запуска.
pub fn elapsed_ticks() -> u64 {
rdtsc().saturating_sub(BOOT_TSC.load(Ordering::Relaxed))
}
/// Приблизительное время работы в секундах.
///
/// Предполагает частоту CPU ≈ 2.5 GHz (для QEMU это близко к истине).
/// TODO: определять частоту через PIT calibration.
pub fn uptime_secs() -> u64 {
elapsed_ticks() / 2_500_000_000
}
/// Приблизительное время в миллисекундах.
pub fn uptime_ms() -> u64 {
elapsed_ticks() / 2_500_000
}
/// Прочитать TSC.
#[inline]
pub fn rdtsc() -> u64 {
let lo: u32;
let hi: u32;
unsafe {
core::arch::asm!(
"rdtsc",
out("eax") lo,
out("edx") hi,
options(nomem, nostack, preserves_flags)
);
}
((hi as u64) << 32) | lo as u64
}