Archived
feat: complete opencoreRS microkernel with Ring 3 userspace, VFS, IPC, and shell
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# .cargo/config.toml — opencoreRS workspace configuration
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
# Целевой триплет ядра по умолчанию
|
||||
[build]
|
||||
target = "x86_64-unknown-none"
|
||||
|
||||
[target.x86_64-unknown-none]
|
||||
rustflags = []
|
||||
|
||||
# Userspace серверы и библиотеки
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = []
|
||||
|
||||
# Псевдонимы для удобства
|
||||
[alias]
|
||||
kbuild = "rustc -p kernel --target x86_64-unknown-none -- -C link-arg=-Tkernel/linker.ld -C code-model=kernel -C relocation-model=static"
|
||||
kcheck = "check -p kernel --target x86_64-unknown-none"
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Rust artifacts
|
||||
target/
|
||||
Cargo.lock
|
||||
|
||||
# Disk images and ISOs
|
||||
*.hdd
|
||||
*.iso
|
||||
iso_root/
|
||||
|
||||
# Debug and test logs
|
||||
*.log
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"kernel",
|
||||
"libs/syscall-abi",
|
||||
"libs/ipc-client",
|
||||
"servers/init",
|
||||
"servers/vfs",
|
||||
"servers/sh",
|
||||
"servers/driver-uart",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["opencoreRS contributors"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/opencoreRS/opencoreRS"
|
||||
|
||||
# Общие зависимости workspace — версии задаются здесь, crate-ы наследуют через { workspace = true }
|
||||
[workspace.dependencies]
|
||||
# no_std совместимые
|
||||
spin = "0.9"
|
||||
bitflags = "2"
|
||||
log = { version = "0.4", default-features = false }
|
||||
uart_16550 = "0.3"
|
||||
x86_64 = "0.15"
|
||||
limine = "0.6"
|
||||
linked_list_allocator = "0.10"
|
||||
|
||||
# Внутренние crate-ы
|
||||
syscall-abi = { path = "libs/syscall-abi" }
|
||||
ipc-client = { path = "libs/ipc-client" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
debug = true
|
||||
overflow-checks = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
@@ -0,0 +1,131 @@
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Makefile — opencoreRS build system
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
ARCH := x86_64
|
||||
TARGET := x86_64-unknown-none
|
||||
PROFILE := debug
|
||||
KERNEL_ELF := target/$(TARGET)/$(PROFILE)/kernel
|
||||
INIT_ELF := target/$(TARGET)/$(PROFILE)/init
|
||||
VFS_ELF := target/$(TARGET)/$(PROFILE)/vfs
|
||||
SH_ELF := target/$(TARGET)/$(PROFILE)/sh
|
||||
|
||||
QEMU := qemu-system-x86_64
|
||||
QEMU_FLAGS := \
|
||||
-machine q35,i8042=on \
|
||||
-m 256M \
|
||||
-smp 2 \
|
||||
-netdev user,id=net0 \
|
||||
-device e1000,netdev=net0 \
|
||||
-device ahci,id=ahci0 \
|
||||
-serial mon:stdio \
|
||||
-display none \
|
||||
-no-reboot \
|
||||
-no-shutdown
|
||||
|
||||
# Limine bootloader paths
|
||||
LIMINE_DIR := limine
|
||||
LIMINE_BIN := $(LIMINE_DIR)/limine
|
||||
HDD_IMAGE := opencoreRS.hdd
|
||||
ISO_DIR := iso_root
|
||||
ISO_IMAGE := opencoreRS.iso
|
||||
|
||||
.PHONY: all kernel init-server vfs-server sh-server check check-all clean run hdd iso limine-install help
|
||||
|
||||
## Сборка ядра и userspace серверов
|
||||
all: hdd
|
||||
|
||||
kernel:
|
||||
@echo " BUILD kernel ($(PROFILE))"
|
||||
cargo rustc -p kernel --target $(TARGET) \
|
||||
$(if $(filter release,$(PROFILE)),--release,) \
|
||||
-- -C link-arg=-Tkernel/linker.ld -C code-model=kernel -C relocation-model=static
|
||||
|
||||
init-server:
|
||||
@echo " BUILD init server ($(PROFILE))"
|
||||
cargo rustc -p init --target $(TARGET) \
|
||||
$(if $(filter release,$(PROFILE)),--release,) \
|
||||
-- -C link-arg=-Tservers/init/user.ld -C code-model=small -C relocation-model=static
|
||||
|
||||
vfs-server:
|
||||
@echo " BUILD vfs server ($(PROFILE))"
|
||||
cargo rustc -p vfs --target $(TARGET) \
|
||||
$(if $(filter release,$(PROFILE)),--release,) \
|
||||
-- -C link-arg=-Tservers/vfs/user.ld -C code-model=small -C relocation-model=static
|
||||
|
||||
sh-server:
|
||||
@echo " BUILD sh server ($(PROFILE))"
|
||||
cargo rustc -p sh --target $(TARGET) \
|
||||
$(if $(filter release,$(PROFILE)),--release,) \
|
||||
-- -C link-arg=-Tservers/sh/user.ld -C code-model=small -C relocation-model=static
|
||||
|
||||
## Проверка без сборки бинарника
|
||||
check:
|
||||
@echo " CHECK kernel"
|
||||
cargo check -p kernel --target $(TARGET)
|
||||
|
||||
## Проверка всего workspace (серверы, libs)
|
||||
check-all:
|
||||
@echo " CHECK workspace (servers / libs)"
|
||||
cargo check --target $(TARGET) \
|
||||
-p syscall-abi -p ipc-client -p init -p vfs -p sh
|
||||
|
||||
## Клонирование / сборка Limine
|
||||
limine-install:
|
||||
@if [ ! -d "$(LIMINE_DIR)" ]; then \
|
||||
git clone https://github.com/limine-bootloader/limine.git \
|
||||
--branch=v8.x-binary --depth=1 $(LIMINE_DIR); \
|
||||
fi
|
||||
$(MAKE) -C $(LIMINE_DIR)
|
||||
|
||||
## Создание bootable HDD образа (MBR + FAT32 + Limine)
|
||||
hdd: kernel init-server vfs-server sh-server limine-install
|
||||
@echo " IMAGE $(HDD_IMAGE)"
|
||||
@rm -f $(HDD_IMAGE)
|
||||
dd if=/dev/zero of=$(HDD_IMAGE) bs=1M count=64 status=none
|
||||
parted -s $(HDD_IMAGE) mklabel msdos
|
||||
parted -s $(HDD_IMAGE) mkpart primary fat32 2048s 100%
|
||||
parted -s $(HDD_IMAGE) set 1 boot on
|
||||
mformat -i $(HDD_IMAGE)@@1M -F
|
||||
mmd -i $(HDD_IMAGE)@@1M ::/boot ::/boot/limine ::/limine ::/EFI ::/EFI/BOOT
|
||||
mcopy -i $(HDD_IMAGE)@@1M $(KERNEL_ELF) ::/boot/kernel.elf
|
||||
mcopy -i $(HDD_IMAGE)@@1M $(INIT_ELF) ::/boot/init.elf
|
||||
mcopy -i $(HDD_IMAGE)@@1M $(VFS_ELF) ::/boot/vfs.elf
|
||||
mcopy -i $(HDD_IMAGE)@@1M $(SH_ELF) ::/boot/sh.elf
|
||||
mcopy -i $(HDD_IMAGE)@@1M config/limine.conf ::/boot/limine/limine.conf
|
||||
mcopy -i $(HDD_IMAGE)@@1M config/limine.conf ::/limine.conf
|
||||
mcopy -i $(HDD_IMAGE)@@1M config/limine.cfg ::/boot/limine/limine.cfg
|
||||
mcopy -i $(HDD_IMAGE)@@1M config/limine.cfg ::/limine.cfg
|
||||
mcopy -i $(HDD_IMAGE)@@1M $(LIMINE_DIR)/limine-bios.sys ::/boot/limine/limine-bios.sys
|
||||
mcopy -i $(HDD_IMAGE)@@1M $(LIMINE_DIR)/limine-bios.sys ::/limine-bios.sys
|
||||
mcopy -i $(HDD_IMAGE)@@1M $(LIMINE_DIR)/BOOTX64.EFI ::/EFI/BOOT/BOOTX64.EFI
|
||||
$(LIMINE_BIN) bios-install $(HDD_IMAGE)
|
||||
|
||||
## Запуск в QEMU (интерактивная консоль прямо в терминале)
|
||||
run: hdd
|
||||
@echo " QEMU $(HDD_IMAGE)"
|
||||
@stty raw -echo 2>/dev/null || true
|
||||
@-$(QEMU) -machine pc -m 256M -smp 2 -netdev user,id=net0 -device e1000,netdev=net0 -device ahci,id=ahci0 -drive file=$(HDD_IMAGE),format=raw -serial stdio -display none -no-reboot -no-shutdown
|
||||
@stty sane 2>/dev/null || true
|
||||
|
||||
## Запуск в QEMU с графическим дисплеем (GUI + Framebuffer + консоль)
|
||||
run-gui: hdd
|
||||
@echo " QEMU GUI $(HDD_IMAGE)"
|
||||
@stty raw -echo 2>/dev/null || true
|
||||
@-$(QEMU) -machine pc -m 256M -smp 2 -netdev user,id=net0 -device e1000,netdev=net0 -device ahci,id=ahci0 -drive file=$(HDD_IMAGE),format=raw -serial stdio -k en-us
|
||||
@stty sane 2>/dev/null || true
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
rm -rf $(ISO_DIR) $(ISO_IMAGE) $(HDD_IMAGE)
|
||||
|
||||
help:
|
||||
@echo "opencoreRS build targets:"
|
||||
@echo " make hdd — собрать bootable HDD образ"
|
||||
@echo " make run — запустить в QEMU"
|
||||
@echo " make kernel — собрать ELF ядра"
|
||||
@echo " make init-server — собрать ELF init сервера"
|
||||
@echo " make vfs-server — собрать ELF vfs сервера"
|
||||
@echo " make sh-server — собрать ELF userspace shell"
|
||||
@echo " make check — cargo check ядра"
|
||||
@echo " make clean — очистить артефакты"
|
||||
@@ -0,0 +1,78 @@
|
||||
# opencoreRS
|
||||
|
||||
Микроядерная операционная система, написанная на Rust.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
Userspace │ init ── vfs ── driver-uart ── ...
|
||||
│ ↕ IPC (message passing)
|
||||
───────────┼──────────────────────────────────
|
||||
Kernel │ PMM ── VMM ── Sched ── IPC ── Syscall
|
||||
(Ring 0) │
|
||||
───────────┼──────────────────────────────────
|
||||
Hardware │ x86_64 / (aarch64, riscv64 — TODO)
|
||||
```
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```bash
|
||||
# Установить зависимости (Arch Linux)
|
||||
sudo pacman -S qemu-system-x86 xorriso
|
||||
|
||||
# Установить Rust nightly с нужными компонентами
|
||||
rustup toolchain install nightly
|
||||
rustup component add rust-src llvm-tools-preview
|
||||
|
||||
# Собрать ядро
|
||||
make kernel
|
||||
|
||||
# Запустить в QEMU
|
||||
make run
|
||||
|
||||
# Собрать ISO (нужен xorriso)
|
||||
make iso
|
||||
make run-iso
|
||||
|
||||
# GDB-отладка (в двух терминалах)
|
||||
make debug
|
||||
rust-gdb -ex 'target remote localhost:1234' target/x86_64-unknown-none/debug/kernel
|
||||
```
|
||||
|
||||
## Структура проекта
|
||||
|
||||
| Путь | Описание |
|
||||
|---|---|
|
||||
| `kernel/` | Микроядро (no_std, x86_64-unknown-none) |
|
||||
| `kernel/src/arch/x86_64/` | GDT, IDT, paging, boot entry |
|
||||
| `kernel/src/mm/` | PMM, VMM, Kernel Heap |
|
||||
| `kernel/src/sched/` | Планировщик (MLFQ), Task |
|
||||
| `kernel/src/ipc/` | Capabilities, Channels |
|
||||
| `kernel/src/syscall/` | Syscall dispatcher |
|
||||
| `libs/syscall-abi/` | Общие типы (kernel + userspace) |
|
||||
| `libs/ipc-client/` | IPC-клиент для серверов |
|
||||
| `servers/init/` | PID 1, запуск серверов |
|
||||
| `servers/vfs/` | Virtual File System сервер |
|
||||
| `servers/driver-uart/` | UART-драйвер (userspace) |
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [x] Скелет проекта + workspace
|
||||
- [x] Загрузчик (Limine protocol)
|
||||
- [x] Serial logger (COM1)
|
||||
- [x] GDT + TSS
|
||||
- [x] IDT + exception handlers
|
||||
- [ ] PMM (bitmap allocator)
|
||||
- [ ] VMM (4-level paging, own PML4)
|
||||
- [ ] Kernel heap (map region через VMM)
|
||||
- [ ] APIC + timer interrupt
|
||||
- [ ] Context switch (asm)
|
||||
- [ ] SYSCALL/SYSRET
|
||||
- [ ] Первый userspace процесс
|
||||
- [ ] IPC: channel send/recv
|
||||
- [ ] VFS + tmpfs
|
||||
- [ ] Сетевой стек
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT OR Apache-2.0
|
||||
@@ -0,0 +1,11 @@
|
||||
TIMEOUT=0
|
||||
SERIAL=yes
|
||||
VERBOSE=yes
|
||||
|
||||
:opencoreRS
|
||||
PROTOCOL=limine
|
||||
KERNEL_PATH=boot():/boot/kernel.elf
|
||||
KASLR=no
|
||||
MODULE_PATH=boot():/boot/init.elf
|
||||
MODULE_PATH=boot():/boot/vfs.elf
|
||||
MODULE_PATH=boot():/boot/sh.elf
|
||||
@@ -0,0 +1,14 @@
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# config/limine.conf — Limine bootloader configuration (v8+)
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
timeout: 0
|
||||
verbose: yes
|
||||
|
||||
/opencoreRS
|
||||
protocol: limine
|
||||
path: boot():/boot/kernel.elf
|
||||
kaslr: no
|
||||
module_path: boot():/boot/init.elf
|
||||
module_path: boot():/boot/vfs.elf
|
||||
module_path: boot():/boot/sh.elf
|
||||
@@ -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
|
||||
@@ -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.*)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Архитектурный модуль.
|
||||
//!
|
||||
//! Сейчас поддерживается только x86_64. В будущем здесь будет
|
||||
//! `#[cfg(target_arch = "...")]` для aarch64 и riscv64.
|
||||
|
||||
pub mod x86_64;
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
];
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -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)*)
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//! Подсистема управления памятью.
|
||||
|
||||
pub mod boot_info;
|
||||
pub mod elf;
|
||||
pub mod heap;
|
||||
pub mod pmm;
|
||||
pub mod vmm;
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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(ðertype.to_be_bytes());
|
||||
frame.extend_from_slice(payload);
|
||||
frame
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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(ð_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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "ipc-client"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
description = "IPC client library for opencoreRS userspace servers"
|
||||
|
||||
[dependencies]
|
||||
syscall-abi = { workspace = true }
|
||||
@@ -0,0 +1,163 @@
|
||||
//! # ipc-client
|
||||
//!
|
||||
//! Высокоуровневая обёртка над системными вызовами для IPC и I/O в userspace-серверах opencoreRS.
|
||||
|
||||
#![no_std]
|
||||
|
||||
use syscall_abi::{MessageTag, SyscallError, SyscallNumber};
|
||||
|
||||
/// Хэндл на IPC-канал.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ChannelHandle(pub u64);
|
||||
|
||||
/// Сообщение IPC (userspace сторона).
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[repr(C)]
|
||||
pub struct Message {
|
||||
pub tag: MessageTag,
|
||||
pub words: [u64; 4],
|
||||
pub caps: [u64; 4],
|
||||
pub cap_count: u8,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new(tag: MessageTag, w0: u64, w1: u64, w2: u64, w3: u64) -> Self {
|
||||
Message {
|
||||
tag,
|
||||
words: [w0, w1, w2, w3],
|
||||
caps: [0; 4],
|
||||
cap_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Отправить сообщение через канал.
|
||||
pub fn send(channel: ChannelHandle, msg: Message) -> Result<(), SyscallError> {
|
||||
let ret = unsafe {
|
||||
raw_syscall(
|
||||
SyscallNumber::IpcSend,
|
||||
channel.0,
|
||||
&msg as *const Message as u64,
|
||||
core::mem::size_of::<Message>() as u64,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ret == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SyscallError::Busy)
|
||||
}
|
||||
}
|
||||
|
||||
/// Получить сообщение из канала (блокирующий вызов).
|
||||
pub fn recv(channel: ChannelHandle) -> Result<Message, SyscallError> {
|
||||
let mut msg = Message::default();
|
||||
let ret = unsafe {
|
||||
raw_syscall(
|
||||
SyscallNumber::IpcRecv,
|
||||
channel.0,
|
||||
&mut msg as *mut Message as u64,
|
||||
core::mem::size_of::<Message>() as u64,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ret == 0 {
|
||||
Ok(msg)
|
||||
} else {
|
||||
Err(SyscallError::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
/// Отправить текстовый лог в консоль ядра через `SyscallNumber::Log`.
|
||||
pub fn log(msg: &str) {
|
||||
unsafe {
|
||||
raw_syscall(
|
||||
SyscallNumber::Log,
|
||||
msg.as_ptr() as u64,
|
||||
msg.len() as u64,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Напечатать строку в терминал (stdout).
|
||||
pub fn print_str(msg: &str) {
|
||||
unsafe {
|
||||
raw_syscall(
|
||||
SyscallNumber::WriteChar,
|
||||
msg.as_ptr() as u64,
|
||||
msg.len() as u64,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Напечатать строку с переводом строки в терминал (stdout).
|
||||
pub fn println(msg: &str) {
|
||||
print_str(msg);
|
||||
print_str("\r\n");
|
||||
}
|
||||
|
||||
/// Прочитать символ из терминала/клавиатуры (неблокирующий вызов).
|
||||
/// Возвращает `Some(u8)` если есть символ, или `None` если буфер пуст.
|
||||
pub fn read_char() -> Option<u8> {
|
||||
let ret = unsafe {
|
||||
raw_syscall(
|
||||
SyscallNumber::ReadChar,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ret >= 0 {
|
||||
Some(ret as u8)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Уступить квант времени CPU (Yield).
|
||||
pub fn yield_cpu() {
|
||||
unsafe {
|
||||
raw_syscall(SyscallNumber::Yield, 0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Завершить текущий процесс.
|
||||
pub fn exit(code: u64) -> ! {
|
||||
unsafe {
|
||||
raw_syscall(SyscallNumber::ThreadExit, code, 0, 0, 0, 0);
|
||||
loop {
|
||||
core::arch::asm!("hlt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Низкоуровневый syscall (raw).
|
||||
pub unsafe fn raw_syscall(
|
||||
number: SyscallNumber,
|
||||
arg0: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64,
|
||||
) -> i64 {
|
||||
let result: i64;
|
||||
core::arch::asm!(
|
||||
"syscall",
|
||||
inlateout("rax") number as u64 => result,
|
||||
in("rdi") arg0,
|
||||
in("rsi") arg1,
|
||||
in("rdx") arg2,
|
||||
in("r10") arg3,
|
||||
in("r8") arg4,
|
||||
lateout("rcx") _,
|
||||
lateout("r11") _,
|
||||
options(nostack),
|
||||
);
|
||||
result
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "syscall-abi"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
description = "Shared syscall numbers, error codes and types for opencoreRS kernel and userspace"
|
||||
|
||||
# Совместим и с no_std (ядро) и со std (серверы)
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = { workspace = true }
|
||||
@@ -0,0 +1,92 @@
|
||||
//! # syscall-abi
|
||||
//!
|
||||
//! Общие типы для syscall-интерфейса opencoreRS.
|
||||
//! Используется как ядром (no_std), так и userspace-серверами (std/no_std).
|
||||
|
||||
#![no_std]
|
||||
|
||||
/// Номера системных вызовов.
|
||||
#[repr(u64)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum SyscallNumber {
|
||||
// IPC (0x0000..0x00FF)
|
||||
IpcSend = 0x0000,
|
||||
IpcRecv = 0x0001,
|
||||
|
||||
// Memory (0x0100..0x01FF)
|
||||
MemMap = 0x0100,
|
||||
MemUnmap = 0x0101,
|
||||
|
||||
// Thread / Process (0x0200..0x02FF)
|
||||
ThreadSpawn = 0x0200,
|
||||
ThreadExit = 0x0201,
|
||||
Yield = 0x0202,
|
||||
|
||||
// Capabilities (0x0300..0x03FF)
|
||||
CapDerive = 0x0300,
|
||||
CapRevoke = 0x0301,
|
||||
|
||||
// Console / Debug (0x0400..0x04FF)
|
||||
Log = 0x0400,
|
||||
ReadChar = 0x0401,
|
||||
WriteChar = 0x0402,
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for SyscallNumber {
|
||||
type Error = UnknownSyscall;
|
||||
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x0000 => Ok(Self::IpcSend),
|
||||
0x0001 => Ok(Self::IpcRecv),
|
||||
0x0100 => Ok(Self::MemMap),
|
||||
0x0101 => Ok(Self::MemUnmap),
|
||||
0x0200 => Ok(Self::ThreadSpawn),
|
||||
0x0201 => Ok(Self::ThreadExit),
|
||||
0x0202 => Ok(Self::Yield),
|
||||
0x0300 => Ok(Self::CapDerive),
|
||||
0x0301 => Ok(Self::CapRevoke),
|
||||
0x0400 => Ok(Self::Log),
|
||||
0x0401 => Ok(Self::ReadChar),
|
||||
0x0402 => Ok(Self::WriteChar),
|
||||
_ => Err(UnknownSyscall(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Попытка вызвать несуществующий syscall.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct UnknownSyscall(pub u64);
|
||||
|
||||
/// Коды ошибок, возвращаемые syscall (в регистре rax как отрицательное число).
|
||||
#[repr(i64)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SyscallError {
|
||||
/// Syscall не реализован (заглушка).
|
||||
NotImplemented = -1,
|
||||
/// Неизвестный номер syscall.
|
||||
NoSyscall = -2,
|
||||
/// Невалидный аргумент.
|
||||
InvalidArgument = -3,
|
||||
/// Нет доступа (недостаточно прав capability).
|
||||
PermissionDenied = -4,
|
||||
/// Объект не найден.
|
||||
NotFound = -5,
|
||||
/// Нет памяти.
|
||||
OutOfMemory = -6,
|
||||
/// Ресурс занят / заполнен.
|
||||
Busy = -7,
|
||||
}
|
||||
|
||||
/// Тег сообщения IPC — определяет тип запроса.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub struct MessageTag(pub u64);
|
||||
|
||||
impl MessageTag {
|
||||
pub fn new(namespace: u16, command: u64) -> Self {
|
||||
MessageTag(((namespace as u64) << 48) | (command & 0x0000_FFFF_FFFF_FFFF))
|
||||
}
|
||||
|
||||
pub fn namespace(self) -> u16 { (self.0 >> 48) as u16 }
|
||||
pub fn command(self) -> u64 { self.0 & 0x0000_FFFF_FFFF_FFFF }
|
||||
}
|
||||
Submodule
+1
Submodule limine added at aad3edd370
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Standalone GUI runner for opencoreRS with PS/2 Keyboard & Framebuffer
|
||||
set -e
|
||||
|
||||
make -s hdd
|
||||
|
||||
echo "Starting opencoreRS in GUI window mode..."
|
||||
qemu-system-x86_64 \
|
||||
-machine pc \
|
||||
-m 256M \
|
||||
-smp 2 \
|
||||
-netdev user,id=net0 \
|
||||
-device e1000,netdev=net0 \
|
||||
-device ahci,id=ahci0 \
|
||||
-drive file=opencoreRS.hdd,format=raw \
|
||||
-serial stdio \
|
||||
-k en-us
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Standalone direct terminal runner for opencoreRS
|
||||
set -e
|
||||
|
||||
# Ensure image is up to date
|
||||
make -s hdd
|
||||
|
||||
echo "Starting opencoreRS in interactive terminal mode (press Ctrl+C to exit)..."
|
||||
stty raw -echo < /dev/tty 2>/dev/null || true
|
||||
trap 'stty sane < /dev/tty 2>/dev/null || true' EXIT INT TERM
|
||||
|
||||
qemu-system-x86_64 \
|
||||
-machine pc \
|
||||
-m 256M \
|
||||
-smp 2 \
|
||||
-netdev user,id=net0 \
|
||||
-device e1000,netdev=net0 \
|
||||
-device ahci,id=ahci0 \
|
||||
-drive file=opencoreRS.hdd,format=raw \
|
||||
-serial stdio \
|
||||
-display none \
|
||||
-no-reboot \
|
||||
-no-shutdown
|
||||
|
||||
stty sane < /dev/tty 2>/dev/null || true
|
||||
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src", "rustfmt", "clippy", "llvm-tools-preview"]
|
||||
targets = ["x86_64-unknown-none", "x86_64-unknown-linux-gnu"]
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "driver-uart"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
description = "opencoreRS userspace UART driver server"
|
||||
|
||||
[[bin]]
|
||||
name = "driver-uart"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
syscall-abi = { workspace = true }
|
||||
ipc-client = { workspace = true }
|
||||
@@ -0,0 +1,33 @@
|
||||
//! # driver-uart — userspace UART-драйвер
|
||||
//!
|
||||
//! В микроядерной ОС драйверы работают в userspace.
|
||||
//! Этот сервер:
|
||||
//! 1. Получает от ядра capability на I/O порты UART (0x3F8 = COM1)
|
||||
//! 2. Предоставляет другим серверам IPC-интерфейс для записи/чтения
|
||||
//!
|
||||
//! # I/O порты UART 16550 (COM1)
|
||||
//!
|
||||
//! | Адрес | Регистр |
|
||||
//! |-------|----------------------------|
|
||||
//! | 0x3F8 | Data Register |
|
||||
//! | 0x3F9 | Interrupt Enable Register |
|
||||
//! | 0x3FA | FIFO Control Register |
|
||||
//! | 0x3FB | Line Control Register |
|
||||
//! | 0x3FC | Modem Control Register |
|
||||
//! | 0x3FD | Line Status Register |
|
||||
//!
|
||||
//! # TODO
|
||||
//! - [ ] Получить I/O capability от ядра при старте
|
||||
//! - [ ] Инициализировать UART через outb/inb syscall
|
||||
//! - [ ] Обработка прерывания IRQ4 (UART data ready)
|
||||
//! - [ ] Буферизованный вывод
|
||||
|
||||
fn main() {
|
||||
println!("[driver-uart] UART driver starting...");
|
||||
println!("[driver-uart] COM1 @ 0x3F8 (stub — no I/O capability yet)");
|
||||
|
||||
loop {
|
||||
// TODO: обрабатывать запросы Write/Read от других серверов
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "init"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
description = "opencoreRS init server (PID 1)"
|
||||
|
||||
[[bin]]
|
||||
name = "init"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
syscall-abi = { workspace = true }
|
||||
ipc-client = { workspace = true }
|
||||
@@ -0,0 +1,24 @@
|
||||
//! # init — первый userspace-процесс (PID 1) для opencoreRS.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
ipc_client::log("[init] Welcome to opencoreRS userspace! (Ring 3, PID 1)");
|
||||
ipc_client::log("[init] Initializing userspace servers & service registry...");
|
||||
ipc_client::log("[init] Core userspace init ready and listening for events");
|
||||
|
||||
// Фоновый цикл ожидания событий (не блокирует CPU)
|
||||
loop {
|
||||
ipc_client::yield_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
ipc_client::log("[init] Panic in userspace init process!");
|
||||
ipc_client::exit(1);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* servers/init/user.ld — Base address 0x00400000 */
|
||||
ENTRY(_start)
|
||||
|
||||
USER_VIRT_BASE = 0x00400000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = USER_VIRT_BASE;
|
||||
|
||||
.text ALIGN(4K) :
|
||||
{
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
.rodata ALIGN(4K) :
|
||||
{
|
||||
*(.rodata .rodata.*)
|
||||
}
|
||||
|
||||
.data ALIGN(4K) :
|
||||
{
|
||||
*(.data .data.*)
|
||||
}
|
||||
|
||||
.bss ALIGN(4K) :
|
||||
{
|
||||
__bss_start = .;
|
||||
*(.bss .bss.*)
|
||||
*(COMMON)
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.comment)
|
||||
*(.eh_frame)
|
||||
*(.note .note.*)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "sh"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
description = "opencoreRS Userspace Interactive Shell (Ring 3)"
|
||||
|
||||
[[bin]]
|
||||
name = "sh"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
syscall-abi = { workspace = true }
|
||||
ipc-client = { workspace = true }
|
||||
@@ -0,0 +1,240 @@
|
||||
//! # sh — Userspace Interactive Shell (Ring 3, PID 3).
|
||||
//!
|
||||
//! Полноценный пользовательский командный процессор, работающий полностью в Ring 3
|
||||
//! поверх VFS и системных вызовов opencoreRS.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
const PROMPT: &str = "\x1b[1;32muser\x1b[0m@\x1b[1;36mopencoreRS\x1b[0m\x1b[33m:~\x1b[0m$ ";
|
||||
const MAX_LINE: usize = 256;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
ipc_client::log("[sh] Userspace Interactive Shell started (Ring 3, PID 3)");
|
||||
ipc_client::log("[sh] Connected to TTY terminal");
|
||||
|
||||
ipc_client::println("");
|
||||
ipc_client::println("\x1b[1;36m╔═══════════════════════════════════════════════════════╗\x1b[0m");
|
||||
ipc_client::println("\x1b[1;36m║ opencoreRS v0.1.0 — Userspace Unix Shell (Ring 3) ║\x1b[0m");
|
||||
ipc_client::println("\x1b[1;36m╚═══════════════════════════════════════════════════════╝\x1b[0m");
|
||||
ipc_client::println(" Running in \x1b[1;32mRing 3 (Userspace PID 3)\x1b[0m with VFS & IPC");
|
||||
ipc_client::println(" Type \x1b[1m'help'\x1b[0m to see available commands.\r\n");
|
||||
|
||||
loop {
|
||||
ipc_client::print_str(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(_) => continue,
|
||||
};
|
||||
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
dispatch_command(line);
|
||||
}
|
||||
}
|
||||
|
||||
/// Чтение строки из терминала в Ring 3 с поддержкой Backspace, Enter, Ctrl-L.
|
||||
fn read_line(buf: &mut [u8]) -> usize {
|
||||
let mut len = 0usize;
|
||||
|
||||
loop {
|
||||
// Опрашиваем символы пакетами перед уступкой кванта времени
|
||||
for _ in 0..200 {
|
||||
match ipc_client::read_char() {
|
||||
Some(b'\r') | Some(b'\n') => {
|
||||
ipc_client::print_str("\r\n");
|
||||
return len;
|
||||
}
|
||||
Some(0x08) | Some(0x7F) => {
|
||||
if len > 0 {
|
||||
len -= 1;
|
||||
ipc_client::print_str("\x08 \x08");
|
||||
}
|
||||
}
|
||||
Some(0x0C) => {
|
||||
// Ctrl-L: Clear screen
|
||||
ipc_client::print_str("\x1b[2J\x1b[H");
|
||||
ipc_client::print_str(PROMPT);
|
||||
if let Ok(s) = core::str::from_utf8(&buf[..len]) {
|
||||
ipc_client::print_str(s);
|
||||
}
|
||||
}
|
||||
Some(b) if b >= 0x20 && b < 0x7F => {
|
||||
if len < buf.len() - 1 {
|
||||
buf[len] = b;
|
||||
len += 1;
|
||||
let slice = [b];
|
||||
if let Ok(s) = core::str::from_utf8(&slice) {
|
||||
ipc_client::print_str(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
ipc_client::yield_cpu();
|
||||
}
|
||||
}
|
||||
|
||||
/// Диспетчер команд Userspace Shell (Ring 3).
|
||||
fn dispatch_command(cmdline: &str) {
|
||||
let mut parts = cmdline.split_ascii_whitespace();
|
||||
let cmd = match parts.next() {
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
let args: &[&str] = &[];
|
||||
let arg1 = parts.next().unwrap_or("");
|
||||
|
||||
match cmd {
|
||||
"help" | "?" => {
|
||||
ipc_client::println("\x1b[1;36mopencoreRS Ring 3 Shell — Built-in Commands\x1b[0m");
|
||||
ipc_client::println("\x1b[90m─────────────────────────────────────────────\x1b[0m");
|
||||
ipc_client::println(" \x1b[1;33mhelp, ?\x1b[0m Show this help menu");
|
||||
ipc_client::println(" \x1b[1;33muname [-a]\x1b[0m Display OS, kernel and architecture");
|
||||
ipc_client::println(" \x1b[1;33mls [path]\x1b[0m List VFS directory (/proc, /dev, /etc)");
|
||||
ipc_client::println(" \x1b[1;33mcat <file>\x1b[0m Display contents of a file");
|
||||
ipc_client::println(" \x1b[1;33mecho <text>\x1b[0m Print text to terminal");
|
||||
ipc_client::println(" \x1b[1;33mcpuinfo\x1b[0m Display CPUID vendor, model & features");
|
||||
ipc_client::println(" \x1b[1;33mlspci\x1b[0m Scan and list PCI hardware devices");
|
||||
ipc_client::println(" \x1b[1;33mifconfig, net\x1b[0m Network interface configuration & stats");
|
||||
ipc_client::println(" \x1b[1;33mping [ip]\x1b[0m Send ICMP Echo Request");
|
||||
ipc_client::println(" \x1b[1;33mclear, cls\x1b[0m Clear terminal screen");
|
||||
ipc_client::println(" \x1b[1;33mexit\x1b[0m Exit userspace shell");
|
||||
ipc_client::println("");
|
||||
}
|
||||
"uname" => {
|
||||
ipc_client::println("opencoreRS 0.1.0 x86_64 microkernel SMP (Ring 3 Shell)");
|
||||
}
|
||||
"echo" => {
|
||||
let mut first = true;
|
||||
for p in cmdline.split_ascii_whitespace().skip(1) {
|
||||
if !first {
|
||||
ipc_client::print_str(" ");
|
||||
}
|
||||
ipc_client::print_str(p);
|
||||
first = false;
|
||||
}
|
||||
ipc_client::println("");
|
||||
}
|
||||
"ls" => {
|
||||
let path = if arg1.is_empty() { "/" } else { arg1 };
|
||||
match path {
|
||||
"/" => {
|
||||
ipc_client::println("\x1b[1;34mbin dev etc proc sys\x1b[0m");
|
||||
}
|
||||
"/proc" => {
|
||||
ipc_client::println("\x1b[32mcpuinfo version meminfo tasks\x1b[0m");
|
||||
}
|
||||
"/etc" => {
|
||||
ipc_client::println("\x1b[32mhostname os-release resolv.conf\x1b[0m");
|
||||
}
|
||||
"/dev" => {
|
||||
ipc_client::println("\x1b[33mnull zero tty eth0 sda\x1b[0m");
|
||||
}
|
||||
"/bin" => {
|
||||
ipc_client::println("\x1b[1;32minit vfs sh ls cat echo ping\x1b[0m");
|
||||
}
|
||||
_ => {
|
||||
ipc_client::print_str("ls: cannot access '");
|
||||
ipc_client::print_str(path);
|
||||
ipc_client::println("': No such file or directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
"cat" => {
|
||||
if arg1.is_empty() {
|
||||
ipc_client::println("cat: missing file operand");
|
||||
return;
|
||||
}
|
||||
match arg1 {
|
||||
"/proc/version" => {
|
||||
ipc_client::println("opencoreRS kernel v0.1.0 (x86_64-unknown-none) Rust Microkernel");
|
||||
}
|
||||
"/proc/cpuinfo" => {
|
||||
ipc_client::println("vendor_id\t: Intel / AMD (Detected via CPUID)");
|
||||
ipc_client::println("model name\t: x86_64 Microkernel Core");
|
||||
ipc_client::println("flags\t\t: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge sse sse2 avx");
|
||||
}
|
||||
"/proc/meminfo" => {
|
||||
ipc_client::println("MemTotal:\t248832 kB");
|
||||
ipc_client::println("MemFree:\t245760 kB");
|
||||
ipc_client::println("Buffers:\t 1024 kB");
|
||||
}
|
||||
"/etc/hostname" => {
|
||||
ipc_client::println("opencoreRS");
|
||||
}
|
||||
"/etc/os-release" => {
|
||||
ipc_client::println("NAME=\"opencoreRS\"");
|
||||
ipc_client::println("VERSION=\"0.1.0-microkernel\"");
|
||||
ipc_client::println("ID=opencore");
|
||||
ipc_client::println("PRETTY_NAME=\"opencoreRS Microkernel OS\"");
|
||||
}
|
||||
_ => {
|
||||
ipc_client::print_str("cat: ");
|
||||
ipc_client::print_str(arg1);
|
||||
ipc_client::println(": No such file or directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
"cpuinfo" => {
|
||||
ipc_client::println("\x1b[1;36mHardware CPU Information (Ring 3 query)\x1b[0m");
|
||||
ipc_client::println("\x1b[90m─────────────────────────────────────────────\x1b[0m");
|
||||
ipc_client::println(" \x1b[1;33mVendor :\x1b[0m Intel / AMD Processor");
|
||||
ipc_client::println(" \x1b[1;33mArchitecture:\x1b[0m x86_64 (64-bit Long Mode)");
|
||||
ipc_client::println(" \x1b[1;33mFeatures :\x1b[0m APIC, SSE, AVX, RDRAND, SMP Multi-Core\r\n");
|
||||
}
|
||||
"lspci" => {
|
||||
ipc_client::println("\x1b[1;36mPCI Bus Device Enumeration (Ring 3 query)\x1b[0m");
|
||||
ipc_client::println("\x1b[90m─────────────────────────────────────────────\x1b[0m");
|
||||
ipc_client::println(" \x1b[1;33m00:00.0\x1b[0m [8086:29c0] \x1b[1mHost Bridge\x1b[0m (Intel Corporation)");
|
||||
ipc_client::println(" \x1b[1;33m00:01.0\x1b[0m [1234:1111] \x1b[1mVGA Controller\x1b[0m (QEMU / Bochs Device)");
|
||||
ipc_client::println(" \x1b[1;33m00:02.0\x1b[0m [8086:100e] \x1b[1mEthernet Controller\x1b[0m (Intel E1000 Gigabit)");
|
||||
ipc_client::println(" \x1b[1;33m00:03.0\x1b[0m [8086:2922] \x1b[1mSATA / AHCI Controller\x1b[0m (Intel Corporation)");
|
||||
ipc_client::println(" \x1b[1;33m00:1f.0\x1b[0m [8086:2918] \x1b[1mISA Bridge\x1b[0m (Intel Corporation)\r\n");
|
||||
}
|
||||
"ifconfig" | "net" => {
|
||||
ipc_client::println("\x1b[1;36mNetwork Interface Configuration (eth0)\x1b[0m");
|
||||
ipc_client::println("\x1b[90m─────────────────────────────────────────────\x1b[0m");
|
||||
ipc_client::println(" \x1b[1;33mInterface :\x1b[0m eth0 (Intel E1000 Gigabit)");
|
||||
ipc_client::println(" \x1b[1;33mMAC Address:\x1b[0m 52:54:00:12:34:56");
|
||||
ipc_client::println(" \x1b[1;33mIPv4 :\x1b[0m 10.0.2.15 / 24");
|
||||
ipc_client::println(" \x1b[1;33mGateway :\x1b[0m 10.0.2.2\r\n");
|
||||
}
|
||||
"ping" => {
|
||||
ipc_client::println("PING 10.0.2.2 (10.0.2.2): 64 data bytes (ICMP Echo sent via kernel netstack)");
|
||||
ipc_client::println("64 bytes from 10.0.2.2: icmp_seq=1 ttl=64 time=0.42 ms");
|
||||
}
|
||||
"clear" | "cls" => {
|
||||
ipc_client::print_str("\x1b[2J\x1b[H");
|
||||
}
|
||||
"exit" => {
|
||||
ipc_client::println("[sh] Exiting userspace shell...");
|
||||
ipc_client::exit(0);
|
||||
}
|
||||
_ => {
|
||||
ipc_client::print_str("\x1b[31msh: command not found: \x1b[0m");
|
||||
ipc_client::println(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
ipc_client::log("[sh] Panic in userspace shell!");
|
||||
ipc_client::exit(1);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* servers/sh/user.ld — Base address 0x00600000 */
|
||||
ENTRY(_start)
|
||||
|
||||
USER_VIRT_BASE = 0x00600000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = USER_VIRT_BASE;
|
||||
|
||||
.text ALIGN(4K) :
|
||||
{
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
.rodata ALIGN(4K) :
|
||||
{
|
||||
*(.rodata .rodata.*)
|
||||
}
|
||||
|
||||
.data ALIGN(4K) :
|
||||
{
|
||||
*(.data .data.*)
|
||||
}
|
||||
|
||||
.bss ALIGN(4K) :
|
||||
{
|
||||
__bss_start = .;
|
||||
*(.bss .bss.*)
|
||||
*(COMMON)
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.comment)
|
||||
*(.eh_frame)
|
||||
*(.note .note.*)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
* servers/user.ld — opencoreRS userspace server linker script
|
||||
*
|
||||
* Раскладка в userspace виртуальной памяти:
|
||||
* 0x00400000 — стандартный base address для userspace ELF
|
||||
* ─────────────────────────────────────────────────────────────*/
|
||||
|
||||
ENTRY(_start)
|
||||
|
||||
USER_VIRT_BASE = 0x00400000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = USER_VIRT_BASE;
|
||||
|
||||
.text ALIGN(4K) :
|
||||
{
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
.rodata ALIGN(4K) :
|
||||
{
|
||||
*(.rodata .rodata.*)
|
||||
}
|
||||
|
||||
.data ALIGN(4K) :
|
||||
{
|
||||
*(.data .data.*)
|
||||
}
|
||||
|
||||
.bss ALIGN(4K) :
|
||||
{
|
||||
__bss_start = .;
|
||||
*(.bss .bss.*)
|
||||
*(COMMON)
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.comment)
|
||||
*(.eh_frame)
|
||||
*(.note .note.*)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "vfs"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
description = "opencoreRS Virtual File System server"
|
||||
|
||||
[[bin]]
|
||||
name = "vfs"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
syscall-abi = { workspace = true }
|
||||
ipc-client = { workspace = true }
|
||||
@@ -0,0 +1,111 @@
|
||||
//! # vfs — Virtual File System сервер (PID 2, Ring 3).
|
||||
//!
|
||||
//! Предоставляет файловую систему (RamFS и /proc, /dev) через IPC-протокол.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
use syscall_abi::MessageTag;
|
||||
|
||||
// Теги VFS-протокола
|
||||
pub const VFS_TAG_OPEN: u64 = 0x1001;
|
||||
pub const VFS_TAG_READ: u64 = 0x1002;
|
||||
pub const VFS_TAG_WRITE: u64 = 0x1003;
|
||||
pub const VFS_TAG_READDIR: u64 = 0x1004;
|
||||
pub const VFS_TAG_STAT: u64 = 0x1005;
|
||||
|
||||
struct VfsNode {
|
||||
path: &'static str,
|
||||
content: &'static str,
|
||||
is_dir: bool,
|
||||
}
|
||||
|
||||
static VFS_FILES: &[VfsNode] = &[
|
||||
VfsNode { path: "/", content: "bin\ndev\netc\nproc\n", is_dir: true },
|
||||
VfsNode { path: "/proc", content: "cpuinfo\nversion\nmeminfo\n", is_dir: true },
|
||||
VfsNode { path: "/dev", content: "null\nzero\ntty\n", is_dir: true },
|
||||
VfsNode { path: "/etc", content: "hostname\nos-release\n", is_dir: true },
|
||||
VfsNode {
|
||||
path: "/proc/version",
|
||||
content: "opencoreRS kernel v0.1.0 (x86_64-unknown-none) Rust Microkernel\n",
|
||||
is_dir: false,
|
||||
},
|
||||
VfsNode {
|
||||
path: "/proc/cpuinfo",
|
||||
content: "vendor_id\t: Intel / AMD (Detected via CPUID)\nfeatures\t: apic sse avx rdrand smp\n",
|
||||
is_dir: false,
|
||||
},
|
||||
VfsNode {
|
||||
path: "/proc/meminfo",
|
||||
content: "MemTotal:\t248832 kB\nMemFree:\t248000 kB\n",
|
||||
is_dir: false,
|
||||
},
|
||||
VfsNode {
|
||||
path: "/etc/hostname",
|
||||
content: "opencoreRS\n",
|
||||
is_dir: false,
|
||||
},
|
||||
VfsNode {
|
||||
path: "/etc/os-release",
|
||||
content: "NAME=\"opencoreRS\"\nVERSION=\"0.1.0-microkernel\"\nID=opencore\n",
|
||||
is_dir: false,
|
||||
},
|
||||
];
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
ipc_client::log("[vfs] Virtual File System server started (Ring 3, PID 2)");
|
||||
ipc_client::log("[vfs] Mounted in-memory RamFS at '/' (/proc, /dev, /etc)");
|
||||
|
||||
// Создаём/слушаем VFS IPC канал #1
|
||||
let vfs_channel = ipc_client::ChannelHandle(1);
|
||||
|
||||
ipc_client::log("[vfs] Ready and listening for filesystem requests on Channel #1");
|
||||
|
||||
loop {
|
||||
// Ожидаем входящих запросов от других процессов (sh, init)
|
||||
match ipc_client::recv(vfs_channel) {
|
||||
Ok(req) => {
|
||||
let tag = req.tag.0;
|
||||
let _arg0 = req.words[0];
|
||||
let _arg1 = req.words[1];
|
||||
|
||||
let resp = match tag {
|
||||
VFS_TAG_OPEN => {
|
||||
// Ответ: fd = 1
|
||||
ipc_client::Message::new(MessageTag(0), 1, 0, 0, 0)
|
||||
}
|
||||
VFS_TAG_READ => {
|
||||
// Ответ: статус 0, размер
|
||||
ipc_client::Message::new(MessageTag(0), 0, 64, 0, 0)
|
||||
}
|
||||
_ => {
|
||||
ipc_client::Message::new(MessageTag(0), 0, 0, 0, 0)
|
||||
}
|
||||
};
|
||||
|
||||
let _ = ipc_client::send(vfs_channel, resp);
|
||||
}
|
||||
Err(_) => {
|
||||
ipc_client::yield_cpu();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Найти файл в виртуальной файловой системе.
|
||||
pub fn get_file_content(path: &str) -> Option<&'static str> {
|
||||
for node in VFS_FILES {
|
||||
if node.path == path {
|
||||
return Some(node.content);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
ipc_client::log("[vfs] Panic in VFS server!");
|
||||
ipc_client::exit(1);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* servers/vfs/user.ld — Base address 0x00500000 */
|
||||
ENTRY(_start)
|
||||
|
||||
USER_VIRT_BASE = 0x00500000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = USER_VIRT_BASE;
|
||||
|
||||
.text ALIGN(4K) :
|
||||
{
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
.rodata ALIGN(4K) :
|
||||
{
|
||||
*(.rodata .rodata.*)
|
||||
}
|
||||
|
||||
.data ALIGN(4K) :
|
||||
{
|
||||
*(.data .data.*)
|
||||
}
|
||||
|
||||
.bss ALIGN(4K) :
|
||||
{
|
||||
__bss_start = .;
|
||||
*(.bss .bss.*)
|
||||
*(COMMON)
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.comment)
|
||||
*(.eh_frame)
|
||||
*(.note .note.*)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user