Syscall API design
This commit is contained in:
parent
a6c141dda4
commit
114e87d11f
35
Cargo.lock
generated
35
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
31
DESIGN.md
31
DESIGN.md
@ -5,7 +5,7 @@ Gila is a microkernel, and almost all functionality of the OS is relegated to
|
||||
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
|
||||
@ -35,14 +35,17 @@ The system, hence, can be configured in two ways:
|
||||
- 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
|
||||
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:
|
||||
|
||||
@ -55,3 +58,25 @@ Device driver assignment is performed like so:
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
@ -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;
|
||||
|
@ -4,7 +4,7 @@
|
||||
use core::arch::asm;
|
||||
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe fn halt() {
|
||||
pub fn halt() {
|
||||
unsafe {
|
||||
asm!("wfi");
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:#?}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use intbits::Bits;
|
||||
use x86_64::structures::paging::page_table;
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct PageMapLevel5 {
|
||||
entries: [PageMapLevel5Entry; 512],
|
||||
}
|
||||
@ -64,7 +64,6 @@ impl PageMapLevel5Entry {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct PageMapLevel4 {
|
||||
entries: [PageMapLevel4Entry; 512],
|
||||
}
|
||||
@ -129,13 +128,12 @@ impl PageMapLevel4Entry {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct PageDirectoryPointerTable {
|
||||
entries: [*mut PageDirectory; 4],
|
||||
}
|
||||
|
||||
pub struct PageDirectoryPointer {
|
||||
value: u64
|
||||
value: u64,
|
||||
}
|
||||
|
||||
impl PageDirectoryPointer {
|
||||
@ -255,12 +253,4 @@ impl PageDirectoryEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(align(4096))]
|
||||
pub struct PageTable {
|
||||
entries: [PageTableEntry; 512],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct PageTableEntry {
|
||||
value: u64,
|
||||
}
|
||||
pub use page_table::PageTable;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -19,14 +19,9 @@ mod process;
|
||||
#[macro_use]
|
||||
mod util;
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
use arch::x86_64::gdt::gdt;
|
||||
use arch::x86_64::paging::{self, PageDirectory, PageMapLevel4Entry, PageMapLevel5, PageMapLevel5Entry};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use arch::x86_64::serial::Serialport;
|
||||
|
||||
use arch::asm::*;
|
||||
use boot::{modules::*, params, *};
|
||||
use constants::*;
|
||||
use log::*;
|
||||
@ -34,7 +29,6 @@ use memory::alloc::{format, string::*, vec};
|
||||
use memory::{HHDM_RESPONSE, MEMMAP_REQUEST};
|
||||
use params::*;
|
||||
|
||||
use intbits::{self, Bits};
|
||||
use limine::memory_map::EntryType;
|
||||
#[allow(unused_imports)]
|
||||
use lzma_rs::lzma_decompress;
|
||||
@ -126,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 {
|
||||
@ -178,56 +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_trace!(
|
||||
"Size of page directory: {}",
|
||||
size_of::<PageMapLevel4Entry>()
|
||||
);
|
||||
}
|
||||
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");
|
||||
|
@ -1,16 +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 core::alloc::{Allocator, Layout};
|
||||
use talc::*;
|
||||
|
||||
pub extern crate alloc;
|
||||
@ -34,36 +30,10 @@ lazy_static! {
|
||||
.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();
|
||||
|
@ -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");
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -1,7 +0,0 @@
|
||||
// Copyright (c) 2025 shibedrill
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Registers {
|
||||
// Private fields
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -6,4 +6,4 @@
|
||||
mod arch;
|
||||
pub mod registers;
|
||||
pub mod syscall;
|
||||
pub use arch::current::*;
|
||||
pub use arch::*;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
41
src/lib/syscall/mod.rs
Normal 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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user