Compare commits

..

2 Commits

Author SHA1 Message Date
114e87d11f
Syscall API design 2025-06-05 00:19:02 -04:00
a6c141dda4
I DONT KNOW WHAT IM DOING AND IM SCARED 2025-05-28 23:07:33 -04:00
28 changed files with 631 additions and 251 deletions

35
Cargo.lock generated
View File

@ -67,26 +67,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "enumflags2"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147"
dependencies = [
"enumflags2_derive",
]
[[package]]
name = "enumflags2_derive"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "fdt"
version = "0.2.0-alpha1"
@ -103,13 +83,15 @@ name = "gila"
version = "0.3.0"
dependencies = [
"acpi",
"enumflags2",
"bitflags",
"fdt",
"flagset",
"intbits",
"lazy_static",
"limine",
"lzma-rs",
"num-derive",
"num-traits",
"once_cell",
"spin 0.10.0",
"talc",
@ -173,6 +155,17 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-traits"
version = "0.2.19"

View File

@ -5,13 +5,15 @@ edition = "2024"
[dependencies]
acpi = "5.1.0"
enumflags2 = "0.7.11"
bitflags = "2.9.1"
fdt = { git = "https://github.com/repnop/fdt", version = "0.2.0-alpha1" }
flagset = "0.4.7"
intbits = "0.2.0"
lazy_static = { version = "1.5.0", default-features = false, features = ["spin_no_std"] }
limine = "0.4.0"
lzma-rs = { git = "https://github.com/glaeqen/lzma-no-std-rs/", version = "0.2.0", default-features = false }
num-derive = "0.4.2"
num-traits = { version = "0.2.19", default-features = false }
once_cell = { version = "1.20.3", default-features = false, features = ["alloc", "critical-section"] }
spin = "0.10.0"
talc = "4.4.2"

View File

@ -1,11 +1,11 @@
# Design Outline
Gila is a microkernel, and almost all functionality of the OS is relegated to
"server" processes. A server is a process that provides a specific
functionality, using a kernel-provided interface, and it is given a "seat" by
Gila is a microkernel, and almost all functionality of the OS is relegated to
"server" processes. A server is a process that provides a specific
functionality, using a kernel-provided interface, and it is given a "seat" by
the kernel which processes may query to reference it and invoke its functions.
# Boot Process
## Boot Process
Gila initializes as a bare kernel, with the bootloader providing an init RAM
filesystem in the form of a .tar.lzma archive. The kernel reads this file, and
@ -32,17 +32,20 @@ The benefit of this approach is threefold:
The system, hence, can be configured in two ways:
- The drivers can all be included in the initramfs for diskless operation.
- The bare minimum drivers needed for disk access are included in the
- The bare minimum drivers needed for disk access are included in the
initramfs, and all other drivers are included in the root filesystem.
# APIs
## APIs
Processes access services similarly to the way the init process accesses
data from the file system. The process requests the ID of the server that
data from the file system. The process requests the ID of the server that
performs the function, and then communicates with it via IPC and shared
memory regions. APIs are to be defined in the standard library.
# Device Drivers
Conventions for local procedure calls and message busing will be worked on
soon.
## Device Drivers
Device driver assignment is performed like so:
@ -54,4 +57,26 @@ Device driver assignment is performed like so:
into the process's memory space, and returns information on the new mappings
to the process.
- The driver then makes its functions available via IPC and shared memory.
- The kernel stores information about the driver to avoid spawning duplicates.
- The kernel stores information about the driver to avoid spawning duplicates.
## Servers vs. Shared Libraries
Servers and shared libraries serve similar purposes: They make some
functionality usable by any process without code duplication. However, there
are times when processes and developers should prefer one or the other.
A server should be used when:
- The function must somehow acquire a mutually exclusive lock on a resource.
- The function should complete asynchronously.
A shared library should be used when:
- No resources involved need to be mutually exclusive.
- The function is non-blocking and synchronous.
Hence, servers are very important for things like disk drivers and file
system drivers, where non-synchronized writes could cause data loss. It should
also be noted that libraries *can*, and often will, call local procedures
from servers. The functionality of calling a procedure will be handled by the
standard library and shared across processes.

View File

@ -5,6 +5,7 @@
pub mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64::asm;
pub use x86_64::paging;
#[cfg(target_arch = "aarch64")]
pub mod aarch64;

View File

@ -4,7 +4,7 @@
use core::arch::asm;
#[allow(clippy::missing_safety_doc)]
pub unsafe fn halt() {
pub fn halt() {
unsafe {
asm!("wfi");
}

View File

@ -6,31 +6,53 @@
use core::arch::asm;
pub unsafe fn cr0() -> u64 {
let mut cr0: u64;
pub fn read_cr0() -> u64 {
let cr0: u64;
unsafe { asm!("mov {0:r}, cr0", out(reg) cr0) }
cr0
}
pub unsafe fn interrupt_disable() {
pub fn read_cr2() -> usize {
let cr2: usize;
unsafe { asm!("mov {0:r}, cr2", out(reg) cr2) }
cr2
}
pub fn read_cr3() -> u64 {
let cr3: u64;
unsafe { asm!("mov {0:r}, cr3", out(reg) cr3) }
cr3
}
pub fn write_cr3(val: u64) {
unsafe { asm!("mov cr3, {0:r}", in(reg) val) }
}
pub fn interrupt_disable() {
unsafe {
asm!("cli");
}
}
pub unsafe fn interrupt_enable() {
pub fn interrupt_enable() {
unsafe {
asm!("sti");
}
}
pub unsafe fn halt() {
pub fn halt() {
unsafe {
asm!("hlt");
}
}
pub unsafe fn port_read_u8(port: u16) -> u8 {
pub fn nop() {
unsafe {
asm!("nop");
}
}
pub fn port_read_u8(port: u16) -> u8 {
let result: u8;
unsafe {
asm! {
@ -42,7 +64,7 @@ pub unsafe fn port_read_u8(port: u16) -> u8 {
result
}
pub unsafe fn port_read_u16(port: u16) -> u16 {
pub fn port_read_u16(port: u16) -> u16 {
let result: u16;
unsafe {
asm! {
@ -54,7 +76,7 @@ pub unsafe fn port_read_u16(port: u16) -> u16 {
result
}
pub unsafe fn port_read_u32(port: u16) -> u32 {
pub fn port_read_u32(port: u16) -> u32 {
let result: u32;
unsafe {
asm! {
@ -66,7 +88,7 @@ pub unsafe fn port_read_u32(port: u16) -> u32 {
result
}
pub unsafe fn port_write_u8(port: u16, data: u8) {
pub fn port_write_u8(port: u16, data: u8) {
unsafe {
asm! {
"out dx, al",
@ -76,7 +98,7 @@ pub unsafe fn port_write_u8(port: u16, data: u8) {
}
}
pub unsafe fn port_write_u16(port: u16, data: u16) {
pub fn port_write_u16(port: u16, data: u16) {
unsafe {
asm! {
"out dx, ax",
@ -86,7 +108,7 @@ pub unsafe fn port_write_u16(port: u16, data: u16) {
}
}
pub unsafe fn port_write_u32(port: u16, data: u32) {
pub fn port_write_u32(port: u16, data: u32) {
unsafe {
asm! {
"out dx eax",

View File

@ -5,8 +5,33 @@ use core::arch::asm;
use core::ptr::addr_of_mut;
use intbits::Bits;
use x86_64::structures::gdt::*;
pub unsafe fn gdt() -> (u8, u64) {
#[allow(dead_code)]
pub trait GdtEntryRead {
fn base(&self) -> usize;
fn limit(&self) -> usize;
fn flags(&self) -> u8;
fn access(&self) -> u8;
}
impl GdtEntryRead for Entry {
fn base(&self) -> usize {
((self.raw().bits(56..=63) << 24) & self.raw().bits(16..=39)) as usize
}
fn limit(&self) -> usize {
((self.raw().bits(48..=51) << 16) & self.raw().bits(16..=31)) as usize
}
fn access(&self) -> u8 {
self.raw().bits(40..=47) as u8
}
fn flags(&self) -> u8 {
self.raw().bits(52..=55) as u8
}
}
#[allow(dead_code)]
pub fn read_gdt() -> GlobalDescriptorTable {
let mut addr: u128 = 0;
unsafe {
asm! {
@ -14,5 +39,10 @@ pub unsafe fn gdt() -> (u8, u64) {
in("rax") addr_of_mut!(addr),
}
}
(addr.bits(0..=15) as u8, addr.bits(16..=79) as u64)
// x86 gdtr: Lower 2 8-bit bytes (lower 16 bits) is the size of the GDT,
// in 8-bit bytes, minus one.
// Bits 16 through 79 are the virtual address of the GDT.
let (limit, addr) = (addr.bits(0..=15) as usize + 1, addr.bits(16..=79) as u64);
let gdt_raw: &[u64] = unsafe { core::slice::from_raw_parts(addr as *const u64, limit / 8) };
GlobalDescriptorTable::from_raw_entries(gdt_raw)
}

View File

@ -10,6 +10,7 @@ lazy_static! {
pub static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.double_fault.set_handler_fn(double_fault);
idt.page_fault.set_handler_fn(page_fault);
idt
};
}
@ -17,3 +18,24 @@ lazy_static! {
extern "x86-interrupt" fn double_fault(info: InterruptStackFrame, _: u64) -> ! {
crate::interrupt::double_fault(&format!("{info:#?}"));
}
extern "x86-interrupt" fn page_fault(info: InterruptStackFrame, errcode: PageFaultErrorCode) {
if errcode.contains(PageFaultErrorCode::USER_MODE) {
// Fault occurred in usermode. Non fatal.
todo!()
} else {
// Fault occurred in kernel mode. This is possibly fatal.
// This is recoverable if we simply hit an unavailable page,
// but unrecoverable if we hit a nonexistent or invalid page.
if errcode.contains(PageFaultErrorCode::PROTECTION_VIOLATION)
| errcode.contains(PageFaultErrorCode::MALFORMED_TABLE)
{
let addr = unsafe {
let a: usize;
core::arch::asm! {"mov {}, cr2", out(reg) a};
a
};
crate::interrupt::page_fault(addr, &format!("{info:#?}"))
}
}
}

View File

@ -4,4 +4,5 @@
pub mod asm;
pub mod gdt;
pub mod interrupts;
pub mod paging;
pub mod serial;

View File

@ -0,0 +1,256 @@
#![allow(dead_code)]
use intbits::Bits;
use x86_64::structures::paging::page_table;
pub struct PageMapLevel5 {
entries: [PageMapLevel5Entry; 512],
}
pub struct PageMapLevel5Entry {
value: u64,
}
impl PageMapLevel5Entry {
pub fn execute_disable(&self) -> bool {
self.value.bit(63)
}
pub fn present(&self) -> bool {
self.value.bit(0)
}
pub fn set_present(&mut self, present: bool) {
self.value.set_bit(0, present);
}
pub fn writable(&self) -> bool {
self.value.bit(1)
}
pub fn set_writable(&mut self, writable: bool) {
self.value.set_bit(1, writable);
}
pub fn user_accessible(&self) -> bool {
self.value.bit(2)
}
pub fn set_user_accessible(&mut self, accessible: bool) {
self.value.set_bit(2, accessible);
}
pub fn write_through(&self) -> bool {
self.value.bit(3)
}
pub fn set_write_through(&mut self, write_through: bool) {
self.value.set_bit(3, write_through);
}
pub fn cache_disable(&self) -> bool {
self.value.bit(4)
}
pub fn set_cache_disable(&mut self, cache_disable: bool) {
self.value.set_bit(4, cache_disable);
}
pub fn accessed(&self) -> bool {
self.value.bit(5)
}
pub fn set_accessed(&mut self, accessed: bool) {
self.value.set_bit(5, accessed);
}
pub fn physical_address(&self) -> usize {
self.value.bits(12..=51) as usize
}
pub fn set_physical_address(&mut self, address: u64) {
self.value &= 0xFFF0000000000FFF;
self.value |= (address & 0xFFFFFFFFFF) << 12;
}
pub fn available(&self) -> usize {
(self.value.bits(52..=62) << 4 & self.value.bits(8..=11) << 1 & self.value.bits(6..=6))
as usize
}
}
pub struct PageMapLevel4 {
entries: [PageMapLevel4Entry; 512],
}
pub struct PageMapLevel4Entry {
value: u64,
}
impl PageMapLevel4Entry {
pub fn execute_disable(&self) -> bool {
self.value.bit(63)
}
pub fn present(&self) -> bool {
self.value.bit(0)
}
pub fn set_present(&mut self, present: bool) {
self.value.set_bit(0, present);
}
pub fn writable(&self) -> bool {
self.value.bit(1)
}
pub fn set_writable(&mut self, writable: bool) {
self.value.set_bit(1, writable);
}
pub fn user_accessible(&self) -> bool {
self.value.bit(2)
}
pub fn set_user_accessible(&mut self, accessible: bool) {
self.value.set_bit(2, accessible);
}
pub fn write_through(&self) -> bool {
self.value.bit(3)
}
pub fn set_write_through(&mut self, write_through: bool) {
self.value.set_bit(3, write_through);
}
pub fn cache_disable(&self) -> bool {
self.value.bit(4)
}
pub fn set_cache_disable(&mut self, cache_disable: bool) {
self.value.set_bit(4, cache_disable);
}
pub fn accessed(&self) -> bool {
self.value.bit(5)
}
pub fn set_accessed(&mut self, accessed: bool) {
self.value.set_bit(5, accessed);
}
pub fn physical_address(&self) -> usize {
self.value.bits(12..=51) as usize
}
pub fn set_physical_address(&mut self, address: u64) {
self.value &= 0xFFF0000000000FFF;
self.value |= (address & 0xFFFFFFFFFF) << 12;
}
pub fn set_pointer_table(&mut self, table: &PageDirectoryPointerTable) {
self.set_physical_address(&raw const table as u64);
}
pub fn available(&self) -> usize {
(self.value.bits(52..=62) << 4 & self.value.bits(8..=11) << 1 & self.value.bits(6..=6))
as usize
}
}
pub struct PageDirectoryPointerTable {
entries: [*mut PageDirectory; 4],
}
pub struct PageDirectoryPointer {
value: u64,
}
impl PageDirectoryPointer {
pub fn execute_disable(&self) -> bool {
self.value.bit(63)
}
pub fn present(&self) -> bool {
self.value.bit(0)
}
pub fn set_present(&mut self, present: bool) {
self.value.set_bit(0, present);
}
pub fn writable(&self) -> bool {
self.value.bit(1)
}
pub fn set_writable(&mut self, writable: bool) {
self.value.set_bit(1, writable);
}
pub fn user_accessible(&self) -> bool {
self.value.bit(2)
}
pub fn set_user_accessible(&mut self, accessible: bool) {
self.value.set_bit(2, accessible);
}
pub fn write_through(&self) -> bool {
self.value.bit(3)
}
pub fn set_write_through(&mut self, write_through: bool) {
self.value.set_bit(3, write_through);
}
pub fn cache_disable(&self) -> bool {
self.value.bit(4)
}
pub fn set_cache_disable(&mut self, cache_disable: bool) {
self.value.set_bit(4, cache_disable);
}
pub fn accessed(&self) -> bool {
self.value.bit(5)
}
pub fn set_accessed(&mut self, accessed: bool) {
self.value.set_bit(5, accessed);
}
pub fn physical_address(&self) -> *mut PageDirectory {
self.value.bits(12..=51) as *mut PageDirectory
}
pub fn set_physical_address(&mut self, address: u64) {
self.value &= 0xFFF0000000000FFF;
self.value |= (address & 0xFFFFFFFFFF) << 12;
}
pub fn set_page_directory(&mut self, dir: &PageDirectory) {
self.set_physical_address(&raw const dir as u64);
}
pub fn available(&self) -> usize {
(self.value.bits(52..=62) << 4 & self.value.bits(8..=11) << 1 & self.value.bits(6..=6))
as usize
}
}
pub struct PageDirectory {
entries: [PageDirectoryEntry; 512],
}
pub struct PageDirectoryEntry {
value: u64,
}
impl PageDirectoryEntry {
pub fn execute_disable(&self) -> bool {
self.value.bit(63)
}
pub fn present(&self) -> bool {
self.value.bit(0)
}
pub fn set_present(&mut self, present: bool) {
self.value.set_bit(0, present);
}
pub fn writable(&self) -> bool {
self.value.bit(1)
}
pub fn set_writable(&mut self, writable: bool) {
self.value.set_bit(1, writable);
}
pub fn user_accessible(&self) -> bool {
self.value.bit(2)
}
pub fn set_user_accessible(&mut self, accessible: bool) {
self.value.set_bit(2, accessible);
}
pub fn write_through(&self) -> bool {
self.value.bit(3)
}
pub fn set_write_through(&mut self, write_through: bool) {
self.value.set_bit(3, write_through);
}
pub fn cache_disable(&self) -> bool {
self.value.bit(4)
}
pub fn set_cache_disable(&mut self, cache_disable: bool) {
self.value.set_bit(4, cache_disable);
}
pub fn accessed(&self) -> bool {
self.value.bit(5)
}
pub fn set_accessed(&mut self, accessed: bool) {
self.value.set_bit(5, accessed);
}
pub fn physical_address(&self) -> usize {
self.value.bits(12..=51) as usize
}
pub fn set_physical_address(&mut self, address: u64) {
self.value &= 0xFFF0000000000FFF;
self.value |= (address & 0xFFFFFFFFFF) << 12;
}
pub fn available(&self) -> usize {
(self.value.bits(52..=62) << 4 & self.value.bits(8..=11) << 1 & self.value.bits(6..=6))
as usize
}
}
pub use page_table::PageTable;

View File

@ -12,7 +12,7 @@ pub struct Serialport {
impl LogSubscriber for Serialport {
fn write(&self, msg: &str) {
for c in msg.chars() {
unsafe { port_write_u8(self.port, c as u8) };
port_write_u8(self.port, c as u8);
}
}
}

View File

@ -5,6 +5,7 @@ use crate::format;
use crate::log::LogLevel;
use crate::memory::alloc::string::String;
#[allow(dead_code)]
pub static INITRAMFS_DEFAULT_PATH: &str = "/boot/initramfs.tar.lzma";
pub static LOG_DEFAULT_LEVEL: LogLevel = LogLevel::Info;

View File

@ -10,7 +10,7 @@ use limine::response::{DeviceTreeBlobResponse, RsdpResponse};
use crate::boot::DTB_REQUEST;
use crate::device::acpi::RSDP_REQUEST;
use crate::log::{LOGGER, LogLevel};
use crate::{format, log_info, log_trace, log_warning};
use crate::{format, log_error, log_info, log_trace, log_warning};
lazy_static! {
pub static ref DTB: Option<&'static DeviceTreeBlobResponse> = match DTB_REQUEST.get_response() {
@ -37,8 +37,11 @@ lazy_static! {
// Just to check
pub fn init_statics() {
let _ = DTB.is_some();
let _ = RSDP.is_some();
let dtb = DTB.is_some();
let rdsp = RSDP.is_some();
if !dtb & !rdsp {
log_error!("Device: Neither DTB nor ACPI available, booted system will be useless")
}
}
#[allow(dead_code)]

View File

@ -4,3 +4,7 @@
pub fn double_fault(info: &str) -> ! {
panic!("Double fault: {}", info);
}
pub fn page_fault(addr: usize, info: &str) -> ! {
panic!("Page fault at 0x{:X}: {}", addr, info);
}

View File

@ -67,7 +67,7 @@ impl Logger {
}
}
/// The logger exists for the entire lifetime of the kernel.
// The logger exists for the entire lifetime of the kernel.
pub static LOGGER: Logger = Logger {
inner: Mutex::new(LoggerInner::new()),
};
@ -97,8 +97,8 @@ impl LoggerInner {
}
}
/// Calling log will sequentially acquire lock on all logging subscribers
/// to write to them with a formatted log message.
// Calling log will sequentially acquire lock on all logging subscribers
// to write to them with a formatted log message.
pub fn log(&self, level: LogLevel, msg: &str) {
// Nobody is EVER allowed to call log with the Disabled log level. It is a placeholder.
if level == LogLevel::Disabled {

View File

@ -19,22 +19,16 @@ mod process;
#[macro_use]
mod util;
use core::arch::asm;
use arch::x86_64::gdt::gdt;
#[cfg(target_arch = "x86_64")]
use arch::x86_64::serial::Serialport;
use arch::asm::*;
use boot::{modules::*, params, *};
use constants::*;
use log::*;
use memory::{MEMMAP_REQUEST, HHDM_RESPONSE};
use memory::alloc::{format, string::*, vec};
use memory::{HHDM_RESPONSE, MEMMAP_REQUEST};
use params::*;
use intbits::{self, Bits};
use limine::file::File;
use limine::memory_map::EntryType;
#[allow(unused_imports)]
use lzma_rs::lzma_decompress;
@ -113,27 +107,6 @@ unsafe extern "C" fn main() -> ! {
log_trace!("{log_msg}")
}
let irfs_path = {
if let Some(key) = PARAMS.get("-initramfs") {
key
} else {
INITRAMFS_DEFAULT_PATH
}
};
log_trace!("Boot: initramfs path requested: {irfs_path}");
let _initramfs = {
let mut result: Option<&File> = None;
for file in MODULE_RESPONSE.modules() {
if file.path().to_string_lossy() == irfs_path {
result = Some(file);
log_info!("Boot: initramfs found");
}
}
result
}
.expect("initramfs not found in modules list");
// Panic if this is absent. It's needed to set up the GDT and paging
let mmap_response = MEMMAP_REQUEST
.get_response()
@ -147,7 +120,7 @@ unsafe extern "C" fn main() -> ! {
let mut unusable: u64 = 0;
for entry in mmap_response.entries() {
log_msg.push_str(&format!(
"\n\t\t0x{:x} bytes @ 0x{:x}: {}",
"\n\t\t0x{:X} bytes @ 0x{:X}: {}",
entry.length,
entry.base,
match entry.entry_type {
@ -199,43 +172,14 @@ unsafe extern "C" fn main() -> ! {
panic!("Memory map contains no entries");
}
log_info!("HHDM offset: 0x{:x}", HHDM_RESPONSE.offset());
#[cfg(target_arch = "x86_64")]
{
log_trace!(
"Boot: Protected mode enable: {}",
unsafe { cr0() & 0b1 } == 1
);
log_trace!(
"Boot: Paging enable: {}",
unsafe { cr0() & (0b1 << 31) } == 1
);
let pd_addr = {
let cr3: u64;
unsafe {
asm! {
"mov {}, cr3",
out(reg) cr3,
}
}
log_trace!("Boot: cr3 value: 0b{:b}", cr3);
cr3.bits(12..=31)
};
log_trace!("Boot: Physical address of page directory: 0x{:x}", pd_addr);
log_trace!("Boot: Virtual address of page directory: 0x{:x}", pd_addr + HHDM_RESPONSE.offset());
log_trace!("Boot: GDT: 0x{:x} bytes @ 0x{:x}", unsafe { gdt().0 }, unsafe { gdt().1} );
log_trace!("Boot: GDT: 0x{:x}", unsafe { *((gdt().1 + 0x0) as *const u64) })
}
log_info!("HHDM offset: 0x{:X}", HHDM_RESPONSE.offset());
panic!("Bailing");
#[allow(unreachable_code)]
loop {
for _i in 0..50000000u64 {
unsafe {
core::arch::asm!("nop");
}
crate::arch::asm::nop();
}
core::hint::black_box(());
log_trace!("Heartbeat");

View File

@ -1,13 +1,12 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
#![allow(unused_imports)]
use enumflags2::*;
use lazy_static::lazy_static;
use limine::{request::{ExecutableAddressRequest, HhdmRequest, MemoryMapRequest}, response::HhdmResponse};
use limine::{
request::{ExecutableAddressRequest, HhdmRequest, MemoryMapRequest},
response::HhdmResponse,
};
use core::alloc::{Allocator, Layout};
use talc::*;
pub extern crate alloc;
@ -26,39 +25,15 @@ pub static MEMMAP_REQUEST: MemoryMapRequest = limine::request::MemoryMapRequest:
pub static HHDM_REQUEST: HhdmRequest = limine::request::HhdmRequest::new();
lazy_static! {
pub static ref HHDM_RESPONSE: &'static HhdmResponse = HHDM_REQUEST.get_response().expect("Did not get HHDM response from bootloader");
pub static ref HHDM_RESPONSE: &'static HhdmResponse = HHDM_REQUEST
.get_response()
.expect("Did not get HHDM response from bootloader");
}
// TODO: 10kb kernel heap. Need to figure out how to make this less stupid...
static mut ARENA: [u8; 10000] = [0; 10000];
#[global_allocator]
static ALLOCATOR: Talck<spin::Mutex<()>, ClaimOnOom> = Talc::new(unsafe {
// if we're in a hosted environment, the Rust runtime may allocate before
// main() is called, so we need to initialize the arena automatically
ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut()))
})
.lock();
#[allow(dead_code)]
pub struct MemoryRegion {
start_address: usize,
end_address: usize,
flags: BitFlags<MemoryRegionFlags>,
}
impl MemoryRegion {
// TODO: Memory allocation and virtual addressing
#[allow(unused_variables)]
pub fn new(flags: BitFlags<MemoryRegionFlags>) -> Self {
todo!()
}
}
#[bitflags]
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum MemoryRegionFlags {
Readable,
Writable,
Executable,
}
static ALLOCATOR: Talck<spin::Mutex<()>, ClaimOnOom> =
Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) })
.lock();

View File

@ -11,6 +11,8 @@ use crate::{LOGGER, LogLevel};
#[panic_handler]
pub fn panic(info: &PanicInfo) -> ! {
log_critical!("Panic in {}: {}", info.location().unwrap(), info.message());
// TODO: If any userspace facilities are still working, *attempt* to flush panic info and
// logs to disk or whatever else. Then kill all processes and reboot.
loop {
unsafe {
asm!("nop");

View File

@ -1,19 +1,18 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
#![allow(unused_imports, dead_code)]
use enumflags2::{BitFlags, bitflags, make_bitflags};
use crate::arch::asm;
use crate::memory::MemoryRegion;
use crate::memory::MemoryRegionFlags;
use crate::arch::paging::{PageDirectory, PageTable};
use crate::memory::alloc;
use alloc::string::String;
use alloc::vec::Vec;
use spin::Mutex;
#[allow(dead_code)]
pub struct ProcessTable {}
pub static PROCESS_TABLE: Mutex<Vec<Process>> = Mutex::new(Vec::new());
// Currently running process. Invalid once the referenced process dies and
// is destructed.
#[allow(dead_code)]
pub static CURRENT_PROCESS: Mutex<Option<&Process>> = Mutex::new(None);
#[allow(dead_code)]
pub struct Process {
@ -21,8 +20,6 @@ pub struct Process {
proc_id: u32,
/// ID of the parent process.
parent_proc_id: u32,
/// List of all child processes.
child_proc_ids: Vec<u32>,
/// Human readable name of the process, should be the command.
name: String,
/// The base address of the process stack within its memory region.
@ -30,62 +27,30 @@ pub struct Process {
/// The length of the stack. This, plus the start address, is the stack size.
stack_length: usize,
/// The stack pointer, which is relative to the memory region start.
stack_ptr: usize,
/// The region of memory allocated to the process.
mem_region: MemoryRegion,
stack_pointer: usize,
/// The page direcory pointing to this process's pages.
page_directory: PageDirectory,
/// All pages owned by the process.
/// When creating a process, we should create one read-write data page,
/// with execution disabled, and one read-only executable page.
page_tables: Vec<PageTable>,
/// Process priority. Lower number is higher priority.
prio: u16,
priority: u16,
}
#[allow(dead_code)]
pub unsafe fn context_switch() -> ! {
#[allow(unused_unsafe)]
unsafe {
loop {
asm::halt();
}
}
pub fn context_switch() -> ! {
loop {}
}
#[allow(dead_code)]
pub enum ProcessState {
// The process is the one being currently executed.
Running,
// The process is blocked by a syscall.
Waiting,
// The process is voluntarily napping until an alarm goes off.
Sleeping,
// The process has been "paused", and can be resumed or killed.
Suspended,
}
// Interprocess communication system:
// Processes communicate through "sessions" which are mediated by the kernel.
// Sessions can only be opened if two processes both request a session with
// each other (mutual operation). Submitting a session request is a blocking
// system call that will return either once the other process accepts, or the
// kernel denies the request due to missing capability, or the other process
// not existing yet.
pub struct ProcessSession {
proc_id_a: u32,
proc_id_b: u32,
shared_mem: MemoryRegion,
}
impl ProcessSession {
pub fn new(a: ProcessSessionRequest, b: ProcessSessionRequest) -> Self {
ProcessSession {
proc_id_a: a.proc_id,
proc_id_b: b.proc_id,
shared_mem: MemoryRegion::new(
MemoryRegionFlags::Readable | MemoryRegionFlags::Writable,
),
}
}
}
pub struct ProcessSessionRequest {
proc_id: u32,
}
impl ProcessSessionRequest {
pub fn accept(self, b: ProcessSessionRequest) -> ProcessSession {
ProcessSession::new(self, b)
}
}

View File

@ -1,11 +1,13 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
/// Critical section: Mark a section of code as critical.
/// Interrupts will be disabled until the section completes.
#[macro_export]
macro_rules! critical_section {
($b:block) => {
unsafe { $crate::arch::asm::interrupt_disable(); }
$crate::arch::asm::interrupt_disable();
$b
unsafe { $crate::arch::asm::interrupt_enable(); }
$crate::arch::asm::interrupt_enable();
};
}

View File

@ -4,7 +4,7 @@
#[cfg(target_arch = "x86_64")]
pub mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64 as current;
pub use x86_64::syscall_impl;
#[cfg(target_arch = "aarch64")]
pub mod aarch64;

View File

@ -1,5 +1,4 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
mod registers_impl;
mod syscall_impl;
pub mod syscall_impl;

View File

@ -1,7 +0,0 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
#[allow(dead_code)]
pub struct Registers {
// Private fields
}

View File

@ -6,3 +6,131 @@
// The system call API for x86_64.
use crate::syscall::*;
#[inline(always)]
pub fn caller_syscall_0(call: u64) -> u64 {
let returncode: u64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") call => returncode,
out("rcx") _,
out("r11") _,
options(nostack, preserves_flags)
);
}
returncode
}
#[inline(always)]
pub fn caller_syscall_1(call: u64, one: u64) -> u64 {
let returncode: u64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") call => returncode,
in("rdi") one,
out("rcx") _,
out("r11") _,
options(nostack, preserves_flags)
);
}
returncode
}
#[inline(always)]
pub fn caller_syscall_2(call: u64, one: u64, two: u64) -> u64 {
let returncode: u64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") call => returncode,
in("rdi") one,
in("rsi") two,
out("rcx") _,
out("r11") _,
options(nostack, preserves_flags)
);
}
returncode
}
#[inline(always)]
pub fn caller_syscall_3(call: u64, one: u64, two: u64, three: u64) -> u64 {
let returncode: u64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") call => returncode,
in("rdi") one,
in("rsi") two,
in("rdx") three,
out("rcx") _,
out("r11") _,
options(nostack, preserves_flags)
);
}
returncode
}
#[inline(always)]
pub fn caller_syscall_4(call: u64, one: u64, two: u64, three: u64, four: u64) -> u64 {
let returncode: u64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") call => returncode,
in("rdi") one,
in("rsi") two,
in("rdx") three,
in("r10") four,
out("rcx") _,
out("r11") _,
options(nostack, preserves_flags)
);
}
returncode
}
#[inline(always)]
pub fn caller_syscall_5(call: u64, one: u64, two: u64, three: u64, four: u64, five: u64) -> u64 {
let returncode: u64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") call => returncode,
in("rdi") one,
in("rsi") two,
in("rdx") three,
in("r10") four,
in("r8") five,
out("rcx") _,
out("r11") _,
options(nostack, preserves_flags)
);
}
returncode
}
#[inline(always)]
pub fn caller_syscall_6(
call: u64,
one: u64,
two: u64,
three: u64,
four: u64,
five: u64,
six: u64,
) -> u64 {
let returncode: u64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") call => returncode,
in("rdi") one,
in("rsi") two,
in("rdx") three,
in("r10") four,
in("r8") five,
in("r9") six,
out("rcx") _,
out("r11") _,
options(nostack, preserves_flags)
);
}
returncode
}

View File

@ -6,4 +6,4 @@
mod arch;
pub mod registers;
pub mod syscall;
pub use arch::current::*;
pub use arch::*;

View File

@ -4,8 +4,7 @@
// Every architecture MUST implement this as part of the ABI.
// Additional registers can be implemented with architecture-specific traits.
#[allow(clippy::missing_safety_doc)]
pub unsafe trait RegStoreLoad
pub trait RegStoreLoad
where
Self: Sized,
Self: Default,
@ -28,14 +27,14 @@ where
// Get the six argument or return register values.
// This MUST read registers in the same order as set_syscall_args sets them.
fn read_syscall_args(&self) -> [usize; 6];
fn read_syscall_args(&self) -> [u64; 6];
// Get the syscall number or error number.
// This MUST NOT zero or otherwise overwrite the register.
fn read_syscall_num(&self) -> usize;
fn read_syscall_num(&self) -> u64;
// Set the six argument or return register values.
// This MUST write registers in the same order as read_syscall_args reads them.
fn set_syscall_args(&mut self, new_values: [usize; 6]);
fn set_syscall_args(&mut self, new_values: [u64; 6]);
// Set the syscall number or error number.
// This MUST NOT change any other registers.
fn set_syscall_num(&mut self, new_value: usize);
fn set_syscall_num(&mut self, new_value: u64);
}

View File

@ -1,28 +0,0 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
#![allow(dead_code)]
// TODO: Implement a nice API for system calls.
// I don't want to have to define the argument/return value registers twice
// per architecture. How do I make this work?
pub enum SyscallArgs {
Args0(),
Args1(usize),
Args2(usize, usize),
Args3(usize, usize, usize),
Args4(usize, usize, usize, usize),
Args5(usize, usize, usize, usize, usize),
Args6(usize, usize, usize, usize, usize, usize),
}
#[repr(usize)]
pub enum SyscallError {
Ok, // No error.
Unspecified, // Unspecified error occurred.
SyscallNotExist, // System call does not exist.
ProcessNotExist, // The process mentioned does not exist.
PermissionDenied, // The process lacks capabilities.
Aborted, // The kernel gave up on a blocking request.
}

41
src/lib/syscall/mod.rs Normal file
View File

@ -0,0 +1,41 @@
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use crate::arch;
#[derive(FromPrimitive, Debug)]
pub enum Syscall {
Exit,
Pid,
Message,
MemMap,
MemUnmap,
Resolve,
Spawn,
Version,
}
#[derive(FromPrimitive, Debug)]
pub enum SyscallStatus {
Success,
NoSuchCall,
NoSuchProcess,
NoSuchServer,
NoPermission,
OutOfMemory,
Aborted,
Unspecified,
Unknown,
}
pub struct UnknownStatus {}
impl From<u64> for SyscallStatus {
fn from(value: u64) -> Self {
SyscallStatus::from_u64(value).unwrap_or(Self::Unknown)
}
}
pub fn exit(code: u64) -> SyscallStatus {
arch::syscall_impl::caller_syscall_1(Syscall::Exit as u64, code).into()
}