Compare commits
31 Commits
main
..
85cf698aae
| Author | SHA1 | Date | |
|---|---|---|---|
| 85cf698aae | |||
| 710772efee | |||
| de822fa48d | |||
| 5d672d8132 | |||
| f8eb4f4e54 | |||
| 134343d2a8 | |||
| c201b43d8c | |||
| 947d8931a4 | |||
| 685f911431 | |||
| 57b1d338ac | |||
| fd8f59649e | |||
| 301b5b6e6c | |||
| 4dd59ef79a | |||
| 8f0c73f4dc | |||
| 91b035c2a2 | |||
| 4a20a5b0af | |||
| 09926f45ee | |||
| 32ee3895e6 | |||
| ff4362ca5b | |||
| 1dc41fe7ff | |||
| 6e92817f3f | |||
| b9ee87aed4 | |||
| 77f228df11 | |||
| 831308b2ad | |||
| 1e9a1ebf4d | |||
| 734ff8dd59 | |||
| f4cc0c17a6 | |||
| 27fe15f83d | |||
| 6719239036 | |||
| 3e9bdf72c9 | |||
| e71245c082 |
+12
-8
@@ -51,12 +51,16 @@ SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
IncludeBlocks: Merge
|
||||
IncludeCategories:
|
||||
- Regex: "^<zinnia/.*>"
|
||||
Priority: 1
|
||||
- Regex: "^<kernel/.*>"
|
||||
Priority: 2
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 3
|
||||
- Regex: '^".*\.h"'
|
||||
Priority: 4
|
||||
- Regex: "^<zinnia/.*>"
|
||||
Priority: 1
|
||||
- Regex: "^<common/.*>"
|
||||
Priority: 2
|
||||
- Regex: "^<kernel/.*>"
|
||||
Priority: 3
|
||||
- Regex: "^<vdso/.*>"
|
||||
Priority: 4
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 5
|
||||
- Regex: '^".*\.h"'
|
||||
Priority: 6
|
||||
SortIncludes: CaseInsensitive
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Style:
|
||||
AngledHeaders: ["zinnia/.*", " kernel/.*"]
|
||||
AngledHeaders: [ "zinnia/.*", "uapi/.*" ]
|
||||
|
||||
+4
-9
@@ -1,13 +1,8 @@
|
||||
# IDE Files
|
||||
.vscode/
|
||||
.idea/
|
||||
.config*
|
||||
build/
|
||||
.cache/
|
||||
|
||||
# Build artifacts
|
||||
.wraplock
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
compile_commands.json
|
||||
|
||||
# Default build dir
|
||||
build/
|
||||
__pycache__/
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "subprojects/uacpi"]
|
||||
path = subprojects/uacpi
|
||||
url = https://github.com/uACPI/uacpi
|
||||
@@ -0,0 +1,9 @@
|
||||
mainmenu "Zinnia Kernel Configuration"
|
||||
|
||||
config KERNEL_FLAVOR
|
||||
string "Kernel flavor name"
|
||||
default "vanilla"
|
||||
|
||||
source "arch/$(ARCH)/Kconfig"
|
||||
source "core/Kconfig"
|
||||
source "dev/Kconfig"
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Marvin Friedrich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,7 @@
|
||||
ISC License
|
||||
|
||||
Copyright 2026 Marvin Friedrich and contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -0,0 +1,10 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
@@ -1,30 +1,26 @@
|
||||
<img src="zinnia.svg" width="10%"/>
|
||||
<img src="logo.svg" width="10%"/>
|
||||
|
||||

|
||||
# Zinnia
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# zinnia
|
||||
Zinnia is a lightweight Unix-like monolithic kernel targeting 64-bit devices.
|
||||
|
||||
zinnia is a lightweight Unix-like microkernel targeting 64-bit devices.
|
||||
## Building Zinnia
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please note that this project is a work in progress.
|
||||
> Some parts may not work at all and/or will drastically change over time.
|
||||
|
||||
## Building
|
||||
|
||||
> [!NOTE]
|
||||
> This repository contains only the kernel and drivers.
|
||||
> If you want to get a bootable image, you might want to check out
|
||||
> **https://github.com/zinnia-os/bootstrap** instead.
|
||||
|
||||
To build the kernel you need `meson` and a C23-compatible GNU-like compiler.
|
||||
Currently supported are GCC and Clang toolchains.
|
||||
To build the kernel you need:
|
||||
- `meson`
|
||||
- a `kconfig` frontend (like `kconfiglib` via `pip`)
|
||||
- a GNU-like C compiler with support for C23 (e.g. `gcc` or `clang`)
|
||||
|
||||
To configure, run:
|
||||
```sh
|
||||
alldefconfig
|
||||
meson setup $build_dir
|
||||
```
|
||||
|
||||
@@ -32,9 +28,3 @@ And to build:
|
||||
```sh
|
||||
meson compile -C $build_dir
|
||||
```
|
||||
|
||||
To cross-compile, you should follow the Meson cross-compilation guide.
|
||||
|
||||
## Debugging
|
||||
|
||||
Follow the debugging guide at **https://github.com/zinnia-os/bootstrap**
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
menu "x86_64 Architecture options"
|
||||
|
||||
config X86_64
|
||||
bool
|
||||
default y
|
||||
select ACPI
|
||||
select HAVE_MMU
|
||||
select HAVE_MMU_GENERIC
|
||||
help
|
||||
64-bit x86 CPU support for AMD and Intel CPUs.
|
||||
|
||||
config TSC
|
||||
bool "Invariant Timestamp Counter support"
|
||||
default y
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,139 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/mmio.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <stdint.h>
|
||||
#include <x86_64/apic.h>
|
||||
#include <x86_64/asm.h>
|
||||
#include <x86_64/defs.h>
|
||||
|
||||
static uint64_t lapic_read_reg(struct local_apic* lapic, uint32_t reg) {
|
||||
if (lapic->xapic_regs) {
|
||||
if (reg == APIC_REG_ICR) {
|
||||
uint64_t lo, hi;
|
||||
lo = mmio_read32(lapic->xapic_regs + APIC_REG_ICR);
|
||||
hi = mmio_read32(lapic->xapic_regs + APIC_REG_ICR_HI);
|
||||
return (hi << 32) | lo;
|
||||
} else {
|
||||
return mmio_read32(lapic->xapic_regs + reg);
|
||||
}
|
||||
} else {
|
||||
return asm_rdmsr(0x800 + (reg >> 4));
|
||||
}
|
||||
}
|
||||
|
||||
static void lapic_write_reg(struct local_apic* lapic, uint32_t reg, uint64_t value) {
|
||||
if (lapic->xapic_regs) {
|
||||
if (reg == APIC_REG_ICR) {
|
||||
mmio_write32(lapic->xapic_regs + APIC_REG_ICR_HI, value >> 32);
|
||||
mmio_write32(lapic->xapic_regs + APIC_REG_ICR, (uint32_t)value);
|
||||
} else {
|
||||
mmio_write32(lapic->xapic_regs + reg, (uint32_t)value);
|
||||
}
|
||||
} else {
|
||||
asm_wrmsr(0x800 + (reg >> 4), value);
|
||||
}
|
||||
}
|
||||
|
||||
void lapic_init(struct local_apic* lapic) {
|
||||
uint64_t apic_msr = asm_rdmsr(0x1B);
|
||||
apic_msr |= 1 << 11; // Enable APIC.
|
||||
|
||||
struct cpuid cpuid = asm_cpuid(1, 0);
|
||||
if (cpuid.ecx & CPUID_1C_X2APIC)
|
||||
apic_msr |= 1 << 10;
|
||||
else {
|
||||
uintptr_t vaddr = vm_alloc(0x1000);
|
||||
pmap_map(&kernel_vma.pmap, vaddr, apic_msr & 0xFFFF'F000, PTE_READ | PTE_WRITE, CACHE_MMIO);
|
||||
lapic->xapic_regs = (void*)vaddr;
|
||||
assert(lapic->xapic_regs, "Failed to allocate virtual memory!\n");
|
||||
}
|
||||
|
||||
asm_wrmsr(0x1B, apic_msr);
|
||||
|
||||
lapic->id = lapic_read_reg(lapic, APIC_REG_ID);
|
||||
if (lapic->xapic_regs)
|
||||
lapic->id >>= 24;
|
||||
|
||||
// Reset the TPR.
|
||||
lapic_write_reg(lapic, APIC_REG_TPR, 0);
|
||||
// Enable APIC bit in the SIVR.
|
||||
lapic_write_reg(lapic, APIC_REG_SIVR, (lapic_read_reg(lapic, APIC_REG_SIVR) & ~0xFF) | 0x100 | IDT_SPURIOUS);
|
||||
|
||||
if (lapic->xapic_regs) {
|
||||
lapic_write_reg(lapic, APIC_REG_DFR, 0xF000'0000);
|
||||
// Logical destination = LAPIC ID.
|
||||
lapic_write_reg(lapic, APIC_REG_LDR, lapic_read_reg(lapic, APIC_REG_ID));
|
||||
}
|
||||
|
||||
// TODO: Parse MADT and setup NMI sources.
|
||||
|
||||
// Tell the APIC timer to divide by 16.
|
||||
lapic_write_reg(lapic, APIC_REG_DCR, 3);
|
||||
// Set the timer to the highest value possible.
|
||||
lapic_write_reg(lapic, APIC_REG_ICR_TIMER, 0xFFFF'FFFF);
|
||||
|
||||
// Sleep for 10 milliseconds.
|
||||
clock_spin_ns(10'000'000);
|
||||
|
||||
lapic->ticks_per_10ms = 0xFFFF'FFFF - lapic_read_reg(lapic, APIC_REG_CCR);
|
||||
lapic_write_reg(lapic, APIC_REG_LVT_TR, IDT_IPI_RESCHED | 0x20000);
|
||||
lapic_write_reg(lapic, APIC_REG_ICR_TIMER, lapic->ticks_per_10ms);
|
||||
}
|
||||
|
||||
void lapic_eoi(struct local_apic* lapic) {
|
||||
lapic_write_reg(lapic, APIC_REG_EOI, 0);
|
||||
}
|
||||
|
||||
void lapic_send_ipi(
|
||||
struct local_apic* lapic,
|
||||
uint32_t target,
|
||||
uint8_t vector,
|
||||
enum apic_delivery_mode delivery_mode,
|
||||
enum apic_dest_mode dest_mode,
|
||||
enum apic_level level,
|
||||
enum apic_trigger_mode trigger_mode
|
||||
) {
|
||||
uint64_t icr = vector;
|
||||
icr |= (uint64_t)delivery_mode << 8;
|
||||
icr |= (uint64_t)dest_mode << 11;
|
||||
icr |= (uint64_t)level << 14;
|
||||
icr |= (uint64_t)trigger_mode << 15;
|
||||
|
||||
if (lapic->xapic_regs)
|
||||
icr |= (uint64_t)(uint8_t)target << 56;
|
||||
else
|
||||
icr |= (uint64_t)target << 32;
|
||||
|
||||
lapic_write_reg(lapic, APIC_REG_ICR, icr);
|
||||
|
||||
if (lapic->xapic_regs) {
|
||||
while (lapic_read_reg(lapic, APIC_REG_ICR) & (APIC_DELIVERY_STATUS_PENDING << 12))
|
||||
asm volatile("pause");
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint16_t PIC1_COMMAND_PORT = 0x20;
|
||||
constexpr uint16_t PIC1_DATA_PORT = 0x21;
|
||||
constexpr uint16_t PIC2_COMMAND_PORT = 0xA0;
|
||||
constexpr uint16_t PIC2_DATA_PORT = 0xA1;
|
||||
|
||||
void pic_disable() {
|
||||
// We initialize the PIC properly, but completely disable it and use the APIC in favor of it.
|
||||
// Remap IRQs so they start at 0x20 since interrupts 0x00..0x1F are used by CPU exceptions.
|
||||
asm_outb(PIC1_COMMAND_PORT, 0x11);
|
||||
asm_outb(PIC1_DATA_PORT, 0x20);
|
||||
asm_outb(PIC1_DATA_PORT, 0x04);
|
||||
asm_outb(PIC1_DATA_PORT, 0x01);
|
||||
asm_outb(PIC1_DATA_PORT, 0xFF);
|
||||
|
||||
// Same for the slave PIC.
|
||||
asm_outb(PIC2_COMMAND_PORT, 0x11);
|
||||
asm_outb(PIC2_DATA_PORT, 0x28);
|
||||
asm_outb(PIC2_DATA_PORT, 0x02);
|
||||
asm_outb(PIC2_DATA_PORT, 0x01);
|
||||
asm_outb(PIC2_DATA_PORT, 0xFF);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/syscall.h>
|
||||
#include <arch/archctl.h>
|
||||
#include <arch/mem.h>
|
||||
#include <uapi/archctl.h>
|
||||
#include <x86_64/asm.h>
|
||||
#include <x86_64/defs.h>
|
||||
|
||||
struct syscall_ret arch_archctl(uintptr_t op, uintptr_t arg) {
|
||||
struct syscall_ret ret = {};
|
||||
|
||||
switch (op) {
|
||||
case __ARCHCTL_SET_FSBASE:
|
||||
if (!arch_mem_addr_is_user(arg)) {
|
||||
ret.error = EINVAL;
|
||||
return ret;
|
||||
}
|
||||
asm_wrmsr(MSR_FS_BASE, arg);
|
||||
return ret;
|
||||
case __ARCHCTL_SET_GSBASE:
|
||||
if (!arch_mem_addr_is_user(arg)) {
|
||||
ret.error = EINVAL;
|
||||
return ret;
|
||||
}
|
||||
// Update the inactive GSBASE MSR, not ours.
|
||||
asm_wrmsr(MSR_KERNEL_GS_BASE, arg);
|
||||
return ret;
|
||||
default:
|
||||
ret.error = EINVAL;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <stddef.h>
|
||||
#include <x86_64/asm.h>
|
||||
#include <x86_64/defs.h>
|
||||
#include <x86_64/gdt.h>
|
||||
#include <x86_64/percpu.h>
|
||||
#include <x86_64/tss.h>
|
||||
|
||||
const struct gdt gdt_base = {
|
||||
.null = {0},
|
||||
.kernel_code32 = {.val = 0x00cf9b000000ffff},
|
||||
.kernel_data32 = {.val = 0x00cf93000000ffff},
|
||||
.kernel_code64 = {.val = 0x00a09b0000000000},
|
||||
.kernel_data64 = {.val = 0x0000930000000000},
|
||||
.user_code32 = {.val = 0x00cffb000000ffff},
|
||||
.user_data = {.val = 0x0000f30000000000},
|
||||
.user_code64 = {.val = 0x00a0fb0000000000},
|
||||
.tss = {
|
||||
.limit0 = sizeof(struct tss) - 1,
|
||||
.access = 0x89, // Present | DPL=0 | System | 64-bit TSS
|
||||
.limit1_flags = ((sizeof(struct tss) - 1) >> 16) & 0x0F,
|
||||
.reserved = 0,
|
||||
},
|
||||
};
|
||||
|
||||
void gdt_load() {
|
||||
struct gdt* gdt = &percpu_ref(arch_data)->gdt;
|
||||
memcpy(gdt, &gdt_base, sizeof(gdt_base));
|
||||
|
||||
struct tss* tss = &percpu_ref(arch_data)->tss;
|
||||
tss->iopb = sizeof(struct tss);
|
||||
|
||||
uintptr_t tss_addr = (uintptr_t)tss;
|
||||
gdt->tss.base0 = tss_addr;
|
||||
gdt->tss.base1 = tss_addr >> 16;
|
||||
gdt->tss.base2 = tss_addr >> 24;
|
||||
gdt->tss.base3 = tss_addr >> 32;
|
||||
|
||||
struct gdtr gdtr = {
|
||||
.limit = sizeof(struct gdt) - 1,
|
||||
.base = gdt,
|
||||
};
|
||||
|
||||
asm volatile("lgdt %0" ::"m"(gdtr));
|
||||
|
||||
// Save the contents of MSR_GS_BASE, as they get cleared by a write to `gs`.
|
||||
uintptr_t gs = asm_rdmsr(MSR_GS_BASE);
|
||||
|
||||
// Flush and reload the segment registers.
|
||||
asm volatile(
|
||||
"push %0\n"
|
||||
"lea rax, [rip + 1f]\n"
|
||||
"push rax\n"
|
||||
"retfq\n"
|
||||
"1:\n"
|
||||
"mov ax, %1\n"
|
||||
"mov ds, ax\n"
|
||||
"mov es, ax\n"
|
||||
"mov fs, ax\n"
|
||||
"mov gs, ax\n"
|
||||
"mov ss, ax\n"
|
||||
:
|
||||
: "i"(offsetof(struct gdt, kernel_code64)), "i"(offsetof(struct gdt, kernel_data64))
|
||||
: "rax", "memory"
|
||||
);
|
||||
|
||||
asm_wrmsr(MSR_GS_BASE, gs);
|
||||
|
||||
asm volatile(
|
||||
"mov ax, %c0\n"
|
||||
"ltr ax"
|
||||
:
|
||||
: "i"(offsetof(struct gdt, tss))
|
||||
: "rax", "memory"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/mmio.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <uacpi/acpi.h>
|
||||
#include <uacpi/status.h>
|
||||
#include <uacpi/tables.h>
|
||||
#include <uacpi/uacpi.h>
|
||||
|
||||
static bool use_hpet = true;
|
||||
static void hpet_option(bool use) {
|
||||
use_hpet = use;
|
||||
}
|
||||
CMDLINE_OPTION("x86_64.hpet", "Enable High Precision Event Timer clock", hpet_option);
|
||||
|
||||
struct hpet {
|
||||
struct clock clock;
|
||||
void* regs;
|
||||
uint32_t period;
|
||||
};
|
||||
|
||||
constexpr uint32_t HPET_CAP = 0x0;
|
||||
constexpr uint32_t HPET_CFG = 0x10;
|
||||
constexpr uint32_t HPET_COUNTER = 0xF0;
|
||||
|
||||
static void hpet_reset(struct clock* c) {
|
||||
struct hpet* hpet = containerof(c, struct hpet, clock);
|
||||
mmio_write64(hpet->regs + HPET_COUNTER, 0);
|
||||
}
|
||||
|
||||
static uint64_t hpet_get_elapsed_ns(struct clock* c) {
|
||||
struct hpet* hpet = containerof(c, struct hpet, clock);
|
||||
uint64_t counter = mmio_read64(hpet->regs + HPET_COUNTER);
|
||||
return (uint64_t)((__uint128_t)counter * (__uint128_t)hpet->period / 1'000'000);
|
||||
}
|
||||
|
||||
static struct hpet hpet_clock = {
|
||||
.clock = {
|
||||
.name = "hpet",
|
||||
.priority = 128,
|
||||
.reset = hpet_reset,
|
||||
.get_elapsed_ns = hpet_get_elapsed_ns,
|
||||
},
|
||||
};
|
||||
|
||||
static char table_buf[0x1000];
|
||||
|
||||
void hpet_init() {
|
||||
if (!use_hpet)
|
||||
return;
|
||||
|
||||
uacpi_setup_early_table_access(table_buf, sizeof(table_buf));
|
||||
uacpi_table table = {0};
|
||||
assert(!uacpi_table_find_by_signature("HPET", &table), "HPET table not found!\n");
|
||||
|
||||
struct acpi_hpet* hpet = table.ptr;
|
||||
|
||||
uintptr_t vaddr = vm_alloc(0x1000);
|
||||
pmap_map(&kernel_vma.pmap, vaddr, hpet->address.address, PTE_READ | PTE_WRITE, CACHE_MMIO);
|
||||
hpet_clock.regs = (void*)vaddr;
|
||||
|
||||
uacpi_table_unref(&table);
|
||||
log("x86_64/hpet: HPET registers mapped at %p\n", hpet_clock.regs);
|
||||
|
||||
mmio_write64(hpet_clock.regs + HPET_CFG, mmio_read64(hpet_clock.regs + HPET_CFG) | 1);
|
||||
|
||||
uint64_t caps = mmio_read64(hpet_clock.regs + HPET_CAP);
|
||||
hpet_clock.period = (caps >> 32) & 0xFFFF'FFFF;
|
||||
|
||||
assert(clock_switch(&hpet_clock.clock), "Unable to switch to HPET!\n");
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/usercopy.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <arch/sched.h>
|
||||
#include <bits/sched.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <x86_64/apic.h>
|
||||
#include <x86_64/defs.h>
|
||||
#include <x86_64/gdt.h>
|
||||
#include <x86_64/percpu.h>
|
||||
|
||||
struct [[__packed]] idt_entry {
|
||||
uint16_t base0;
|
||||
uint16_t selector;
|
||||
uint8_t ist;
|
||||
uint8_t attrib;
|
||||
uint16_t base1;
|
||||
uint32_t base2;
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
static_assert(sizeof(struct idt_entry) == 16);
|
||||
|
||||
struct [[__packed]] idtr {
|
||||
uint16_t limit;
|
||||
struct idt_entry* base;
|
||||
};
|
||||
|
||||
static_assert(sizeof(struct idtr) == 10);
|
||||
|
||||
void interrupt_handler(struct arch_context* ctx) {
|
||||
struct percpu* cpu = percpu_get();
|
||||
struct arch_percpu* arch_cpu = percpu_ref(arch_data);
|
||||
uint64_t isr = ctx->isr;
|
||||
|
||||
switch (isr) {
|
||||
case IDT_PF:
|
||||
uint64_t cr2;
|
||||
asm volatile("mov %0, cr2" : "=r"(cr2));
|
||||
|
||||
const struct vma_fault_info info = {
|
||||
.ip = ctx->rip,
|
||||
.addr = cr2,
|
||||
.caused_by_user = (ctx->cs & CPL_USER) == CPL_USER,
|
||||
.caused_by_write = ctx->error & (1 << 1),
|
||||
.caused_by_fetch = ctx->error & (1 << 4),
|
||||
.page_was_present = ctx->error & (1 << 0),
|
||||
};
|
||||
|
||||
struct task* current = cpu->sched.current;
|
||||
const bool irqs_were_on = ctx->rflags & RFLAGS_IF;
|
||||
if (irqs_were_on)
|
||||
irq_set_state(true);
|
||||
|
||||
const bool handled = current && current->space && vma_page_fault(current->space, &info);
|
||||
|
||||
if (irqs_were_on)
|
||||
irq_set_state(false);
|
||||
|
||||
if (handled)
|
||||
break;
|
||||
|
||||
if (current && current->usercopy_region) {
|
||||
struct usercopy_region* region = current->usercopy_region;
|
||||
if (ctx->rip >= (uintptr_t)region->start_ip && ctx->rip < (uintptr_t)region->end_ip) {
|
||||
ctx->rip = (uintptr_t)region->fault_ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
panic(
|
||||
"Unrecoverable page fault. Attempted to %s a %s page at %#lx (IP: %#lx)\n",
|
||||
info.caused_by_write ? "write to"
|
||||
: info.caused_by_fetch ? "execute on"
|
||||
: "read from",
|
||||
info.page_was_present ? "present" : "non-present",
|
||||
info.addr,
|
||||
info.ip
|
||||
);
|
||||
case IDT_SPURIOUS:
|
||||
break;
|
||||
case IDT_IPI_RESCHED:
|
||||
lapic_eoi(&arch_cpu->lapic);
|
||||
arch_sched_preempt_disable();
|
||||
if (arch_sched_preempt_enable())
|
||||
sched_reschedule(&cpu->sched);
|
||||
break;
|
||||
default:
|
||||
if (isr < 0x20)
|
||||
panic("Unhandled exception %lu at %#lx, error: %#lx\n", isr, ctx->rip, ctx->error);
|
||||
assert(isr - 0x20 < ARRAY_SIZE(arch_cpu->irq_lines), "Unhandled interrupt on ISR %lu\n", isr);
|
||||
struct irq_line* line = arch_cpu->irq_lines[isr - 0x20];
|
||||
assert(line, "Unhandled interrupt on ISR %lu\n", isr);
|
||||
irq_line_raise(line);
|
||||
}
|
||||
}
|
||||
|
||||
#define CS_OFFSET (sizeof(struct arch_context) - sizeof(uint64_t) - offsetof(struct arch_context, cs))
|
||||
|
||||
[[__naked]]
|
||||
void interrupt_return() {
|
||||
asm volatile(
|
||||
"pop r15\n"
|
||||
"pop r14\n"
|
||||
"pop r13\n"
|
||||
"pop r12\n"
|
||||
"pop r11\n"
|
||||
"pop r10\n"
|
||||
"pop r9\n"
|
||||
"pop r8\n"
|
||||
"pop rsi\n"
|
||||
"pop rdi\n"
|
||||
"pop rbp\n"
|
||||
"pop rdx\n"
|
||||
"pop rcx\n"
|
||||
"pop rbx\n"
|
||||
"pop rax\n"
|
||||
// Change GS back if we came from user mode.
|
||||
"cmp word ptr [rsp + %c0], %c1\n"
|
||||
"je 2f\n"
|
||||
"swapgs\n"
|
||||
"2:\n"
|
||||
// Skip .error and .isr fields.
|
||||
"add rsp, 0x10\n"
|
||||
"iretq\n"
|
||||
:
|
||||
: "i"(CS_OFFSET), "i"(offsetof(struct gdt, kernel_code64))
|
||||
);
|
||||
}
|
||||
|
||||
[[__naked]]
|
||||
void interrupt_stub_internal() {
|
||||
asm volatile(
|
||||
// Load the kernel GS base if we're coming from user space.
|
||||
"cmp word ptr [rsp + %c0], %c1\n"
|
||||
"je 2f\n"
|
||||
"swapgs\n"
|
||||
"2:\n"
|
||||
"push rax\n"
|
||||
"push rbx\n"
|
||||
"push rcx\n"
|
||||
"push rdx\n"
|
||||
"push rbp\n"
|
||||
"push rdi\n"
|
||||
"push rsi\n"
|
||||
"push r8\n"
|
||||
"push r9\n"
|
||||
"push r10\n"
|
||||
"push r11\n"
|
||||
"push r12\n"
|
||||
"push r13\n"
|
||||
"push r14\n"
|
||||
"push r15\n"
|
||||
"cld\n"
|
||||
// Zero out the base pointer since we can't trust it.
|
||||
"xor rbp, rbp\n"
|
||||
// Load the frame as first argument.
|
||||
"mov rdi, rsp\n"
|
||||
"call %c2\n"
|
||||
"jmp %c3\n"
|
||||
:
|
||||
: "i"(CS_OFFSET), "i"(offsetof(struct gdt, kernel_code64)), "i"(interrupt_handler), "i"(interrupt_return)
|
||||
);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
#define REPEAT_256(M) \
|
||||
M(0) M(1) M(2) M(3) M(4) M(5) M(6) M(7) \
|
||||
M(8) M(9) M(10) M(11) M(12) M(13) M(14) M(15) \
|
||||
M(16) M(17) M(18) M(19) M(20) M(21) M(22) M(23) \
|
||||
M(24) M(25) M(26) M(27) M(28) M(29) M(30) M(31) \
|
||||
M(32) M(33) M(34) M(35) M(36) M(37) M(38) M(39) \
|
||||
M(40) M(41) M(42) M(43) M(44) M(45) M(46) M(47) \
|
||||
M(48) M(49) M(50) M(51) M(52) M(53) M(54) M(55) \
|
||||
M(56) M(57) M(58) M(59) M(60) M(61) M(62) M(63) \
|
||||
M(64) M(65) M(66) M(67) M(68) M(69) M(70) M(71) \
|
||||
M(72) M(73) M(74) M(75) M(76) M(77) M(78) M(79) \
|
||||
M(80) M(81) M(82) M(83) M(84) M(85) M(86) M(87) \
|
||||
M(88) M(89) M(90) M(91) M(92) M(93) M(94) M(95) \
|
||||
M(96) M(97) M(98) M(99) M(100) M(101) M(102) M(103) \
|
||||
M(104) M(105) M(106) M(107) M(108) M(109) M(110) M(111) \
|
||||
M(112) M(113) M(114) M(115) M(116) M(117) M(118) M(119) \
|
||||
M(120) M(121) M(122) M(123) M(124) M(125) M(126) M(127) \
|
||||
M(128) M(129) M(130) M(131) M(132) M(133) M(134) M(135) \
|
||||
M(136) M(137) M(138) M(139) M(140) M(141) M(142) M(143) \
|
||||
M(144) M(145) M(146) M(147) M(148) M(149) M(150) M(151) \
|
||||
M(152) M(153) M(154) M(155) M(156) M(157) M(158) M(159) \
|
||||
M(160) M(161) M(162) M(163) M(164) M(165) M(166) M(167) \
|
||||
M(168) M(169) M(170) M(171) M(172) M(173) M(174) M(175) \
|
||||
M(176) M(177) M(178) M(179) M(180) M(181) M(182) M(183) \
|
||||
M(184) M(185) M(186) M(187) M(188) M(189) M(190) M(191) \
|
||||
M(192) M(193) M(194) M(195) M(196) M(197) M(198) M(199) \
|
||||
M(200) M(201) M(202) M(203) M(204) M(205) M(206) M(207) \
|
||||
M(208) M(209) M(210) M(211) M(212) M(213) M(214) M(215) \
|
||||
M(216) M(217) M(218) M(219) M(220) M(221) M(222) M(223) \
|
||||
M(224) M(225) M(226) M(227) M(228) M(229) M(230) M(231) \
|
||||
M(232) M(233) M(234) M(235) M(236) M(237) M(238) M(239) \
|
||||
M(240) M(241) M(242) M(243) M(244) M(245) M(246) M(247) \
|
||||
M(248) M(249) M(250) M(251) M(252) M(253) M(254) M(255)
|
||||
// clang-format on
|
||||
|
||||
#define INTERRUPT_STUB(n) \
|
||||
[[__naked]] \
|
||||
static void interrupt_stub_##n() { \
|
||||
asm volatile( \
|
||||
".if (%c0 == 8 || (%c0 >= 10 && %c0 <= 14) || %c0 == 17 || %c0 == 21 || %c0 == 29 || %c0 == 30)\n" \
|
||||
".else\n" \
|
||||
"push 0\n" \
|
||||
".endif\n" \
|
||||
"push %c0\n" \
|
||||
"jmp %c1\n" \
|
||||
: \
|
||||
: "i"(n), "i"(interrupt_stub_internal) \
|
||||
); \
|
||||
}
|
||||
|
||||
#define INTERRUPT_SETUP(n) \
|
||||
do { \
|
||||
uint64_t addr = (uint64_t)interrupt_stub_##n; \
|
||||
idt[n].base0 = addr & 0xFFFF; \
|
||||
idt[n].base1 = (addr >> 16) & 0xFFFF; \
|
||||
idt[n].base2 = (addr >> 32) & 0xFFFF'FFFF; \
|
||||
idt[n].ist = 0; \
|
||||
idt[n].attrib = (1 << 7) | 0xE; \
|
||||
idt[n].selector = offsetof(struct gdt, kernel_code64); \
|
||||
idt[n].reserved = 0; \
|
||||
} while (0);
|
||||
|
||||
REPEAT_256(INTERRUPT_STUB)
|
||||
|
||||
static struct idt_entry idt[256] = {};
|
||||
|
||||
void idt_init() {
|
||||
REPEAT_256(INTERRUPT_SETUP)
|
||||
}
|
||||
|
||||
void idt_load() {
|
||||
struct idtr idtr = {
|
||||
.limit = sizeof(idt) - 1,
|
||||
.base = idt,
|
||||
};
|
||||
|
||||
asm volatile("lidt %0" ::"m"(idtr));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <zinnia/elf.h>
|
||||
|
||||
#define ELF_ARCH_CLASS ELFCLASS64
|
||||
#define ELF_ARCH_DATA ELFDATA2LSB
|
||||
#define ELF_ARCH_MACHINE EM_X86_64
|
||||
|
||||
#define ELF_R_SYM ELF64_R_SYM
|
||||
#define ELF_R_TYPE ELF64_R_TYPE
|
||||
#define ELF_ST_BIND ELF64_ST_BIND
|
||||
#define ELF_ST_TYPE ELF64_ST_TYPE
|
||||
|
||||
#define elf_ehdr elf64_ehdr
|
||||
#define elf_phdr elf64_phdr
|
||||
#define elf_dyn elf64_dyn
|
||||
#define elf_addr elf64_addr
|
||||
#define elf_off elf64_off
|
||||
#define elf_nhdr elf64_nhdr
|
||||
#define elf_auxv elf64_auxv
|
||||
#define elf_sym elf64_sym
|
||||
#define elf_rela elf64_rela
|
||||
#define elf_rel elf64_rel
|
||||
|
||||
enum {
|
||||
R_NONE = 0,
|
||||
R_COPY = 5,
|
||||
R_GLOB_DAT = 6,
|
||||
R_JUMP_SLOT = 7,
|
||||
R_RELATIVE = 8,
|
||||
R_IRELATIVE = 37,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint64_t pte_t;
|
||||
@@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <zinnia/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct arch_task_context {
|
||||
uint64_t rsp;
|
||||
uint8_t* fpu_region;
|
||||
phys_t fpu_phys;
|
||||
uint16_t ds, es, fs, gs;
|
||||
uint64_t fs_base, gs_base;
|
||||
};
|
||||
@@ -33,3 +35,13 @@ struct arch_context {
|
||||
uint64_t rsp;
|
||||
uint64_t ss;
|
||||
};
|
||||
|
||||
#define ARCH_CTX_NUM rax
|
||||
#define ARCH_CTX_A0 rdi
|
||||
#define ARCH_CTX_A1 rsi
|
||||
#define ARCH_CTX_A2 rdx
|
||||
#define ARCH_CTX_A3 r10
|
||||
#define ARCH_CTX_A4 r8
|
||||
#define ARCH_CTX_A5 r9
|
||||
#define ARCH_CTX_RET0 rax
|
||||
#define ARCH_CTX_RET1 rdx
|
||||
@@ -3,5 +3,3 @@
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint64_t phys_t;
|
||||
typedef uint64_t virt_t;
|
||||
typedef uint64_t reg_t;
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct local_apic {
|
||||
uint32_t id;
|
||||
uint32_t ticks_per_10ms;
|
||||
void* xapic_regs;
|
||||
};
|
||||
|
||||
enum apic_delivery_mode {
|
||||
APIC_DELIVERY_FIXED = 0b000,
|
||||
APIC_DELIVERY_LOWESTPRIO = 0b001,
|
||||
APIC_DELIVERY_SMI = 0b010,
|
||||
APIC_DELIVERY_NMI = 0b100,
|
||||
APIC_DELIVERY_INIT = 0b101,
|
||||
APIC_DELIVERY_STARTUP = 0b110,
|
||||
};
|
||||
|
||||
enum apic_dest_mode {
|
||||
APIC_DEST_PHYSICAL = 0,
|
||||
APIC_DEST_LOGICAL = 1,
|
||||
};
|
||||
|
||||
enum apic_delivery_status {
|
||||
APIC_DELIVERY_STATUS_IDLE = 0,
|
||||
APIC_DELIVERY_STATUS_PENDING = 1,
|
||||
};
|
||||
|
||||
enum apic_level {
|
||||
APIC_LEVEL_DEASSERT = 0,
|
||||
APIC_LEVEL_ASSERT = 1,
|
||||
};
|
||||
|
||||
enum apic_trigger_mode {
|
||||
APIC_TRIGGER_EDGE = 0,
|
||||
APIC_TRIGGER_LEVEL = 1,
|
||||
};
|
||||
|
||||
enum apic_regs {
|
||||
APIC_REG_ID = 0x20,
|
||||
APIC_REG_TPR = 0x80,
|
||||
APIC_REG_EOI = 0xB0,
|
||||
APIC_REG_LDR = 0xD0,
|
||||
APIC_REG_DFR = 0xE0,
|
||||
APIC_REG_SIVR = 0xF0,
|
||||
APIC_REG_ESR = 0x280,
|
||||
APIC_REG_ICR = 0x300,
|
||||
APIC_REG_ICR_HI = 0x310,
|
||||
APIC_REG_LVT_TR = 0x320,
|
||||
APIC_REG_ICR_TIMER = 0x380,
|
||||
APIC_REG_CCR = 0x390,
|
||||
APIC_REG_DCR = 0x3E0,
|
||||
};
|
||||
|
||||
void lapic_init(struct local_apic* lapic);
|
||||
void lapic_eoi(struct local_apic* lapic);
|
||||
|
||||
void lapic_send_ipi(
|
||||
struct local_apic* lapic,
|
||||
uint32_t target,
|
||||
uint8_t vector,
|
||||
enum apic_delivery_mode delivery_mode,
|
||||
enum apic_dest_mode dest_mode,
|
||||
enum apic_level level,
|
||||
enum apic_trigger_mode trigger_mode
|
||||
);
|
||||
|
||||
void pic_disable();
|
||||
@@ -2,6 +2,16 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct cpuid {
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
};
|
||||
|
||||
static inline struct cpuid asm_cpuid(uint32_t leaf, uint32_t subleaf) {
|
||||
struct cpuid out = {0};
|
||||
asm volatile("cpuid" : "=a"(out.eax), "=b"(out.ebx), "=c"(out.ecx), "=d"(out.edx) : "0"(leaf), "2"(subleaf));
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline uint64_t asm_rdmsr(uint32_t msr) {
|
||||
uint32_t eax;
|
||||
uint32_t edx;
|
||||
@@ -9,6 +19,13 @@ static inline uint64_t asm_rdmsr(uint32_t msr) {
|
||||
return ((uint64_t)edx << 32) | eax;
|
||||
}
|
||||
|
||||
static inline uint64_t asm_rdtsc() {
|
||||
uint32_t eax;
|
||||
uint32_t edx;
|
||||
asm volatile("rdtsc" : "=a"(eax), "=d"(edx) : : "memory");
|
||||
return ((uint64_t)edx << 32) | eax;
|
||||
}
|
||||
|
||||
// Writes a 64-bit value to a given MSR.
|
||||
static inline void asm_wrmsr(uint32_t msr, uint64_t val) {
|
||||
uint32_t eax = (uint32_t)val;
|
||||
@@ -26,25 +43,25 @@ static inline void asm_wrxcr(uint32_t reg, uint64_t val) {
|
||||
// Saves the FPU state to a 512-byte region of memory using FXSAVE.
|
||||
// Pointer must be 16-byte aligned.
|
||||
static inline void asm_fxsave(void* mem) {
|
||||
asm volatile("fxsave %0" : "+m"(*(uint8_t*)mem)::"memory");
|
||||
asm volatile("fxsave [%0]" ::"r"(mem) : "memory");
|
||||
}
|
||||
|
||||
// Restores the FPU state from a 512-byte region of memory using FXRSTOR.
|
||||
// Pointer must be 16-byte aligned.
|
||||
static inline void asm_fxrstor(void* mem) {
|
||||
asm volatile("fxrstor %0" ::"m"(*(uint8_t*)mem) : "memory");
|
||||
static inline void asm_fxrstor(const void* mem) {
|
||||
asm volatile("fxrstor [%0]" ::"r"(mem) : "memory");
|
||||
}
|
||||
|
||||
// Saves the FPU state to a region of memory using XSAVE.
|
||||
// Pointer must be 16-byte aligned.
|
||||
static inline void asm_xsave(void* mem) {
|
||||
asm volatile("xsave %0" : "+m"(*(uint8_t*)mem)::"memory");
|
||||
asm volatile("xsave [%0]" ::"r"(mem), "a"(0xFFFF'FFFF), "d"(0xFFFF'FFFF) : "memory");
|
||||
}
|
||||
|
||||
// Restores the FPU state from a region of memory using XRSTOR.
|
||||
// Pointer must be 16-byte aligned.
|
||||
static inline void asm_xrstor(void* mem) {
|
||||
asm volatile("xrstor %0" ::"m"(*(uint8_t*)mem) : "memory");
|
||||
static inline void asm_xrstor(const void* mem) {
|
||||
asm volatile("xrstor [%0]" ::"r"(mem), "a"(0xFFFF'FFFF), "d"(0xFFFF'FFFF) : "memory");
|
||||
}
|
||||
|
||||
static inline uint8_t asm_inb(uint16_t port) {
|
||||
@@ -76,3 +93,43 @@ static inline void asm_outw(uint16_t port, uint16_t value) {
|
||||
static inline void asm_outl(uint16_t port, uint32_t value) {
|
||||
asm volatile("out %0, %1" : : "Nd"(port), "a"(value));
|
||||
}
|
||||
|
||||
static inline uint16_t asm_read_ds() {
|
||||
uint16_t value = 0;
|
||||
asm volatile("mov %0, ds" : "=r"(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline uint16_t asm_read_es() {
|
||||
uint16_t value = 0;
|
||||
asm volatile("mov %0, es" : "=r"(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline uint16_t asm_read_fs() {
|
||||
uint16_t value = 0;
|
||||
asm volatile("mov %0, fs" : "=r"(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline uint16_t asm_read_gs() {
|
||||
uint16_t value = 0;
|
||||
asm volatile("mov %0, gs" : "=r"(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline void asm_write_ds(uint16_t value) {
|
||||
asm volatile("mov ds, %0" ::"r"(value));
|
||||
}
|
||||
|
||||
static inline void asm_write_es(uint16_t value) {
|
||||
asm volatile("mov es, %0" ::"r"(value));
|
||||
}
|
||||
|
||||
static inline void asm_write_fs(uint16_t value) {
|
||||
asm volatile("mov fs, %0" ::"r"(value));
|
||||
}
|
||||
|
||||
static inline void asm_write_gs(uint16_t value) {
|
||||
asm volatile("mov gs, %0" ::"r"(value));
|
||||
}
|
||||
@@ -134,6 +134,13 @@
|
||||
#define RFLAGS_VIP (1u << 20)
|
||||
#define RFLAGS_ID (1u << 21)
|
||||
|
||||
#define XCR0_X87 (1 << 0)
|
||||
#define XCR0_SSE (1 << 1)
|
||||
#define XCR0_AVX (1 << 2)
|
||||
#define XCR0_AXV512_OPMASK (1 << 5)
|
||||
#define XCR0_AVX512_ZMM_HI256 (1 << 6)
|
||||
#define XCR0_AVX512_HI16_ZMM (1 << 7)
|
||||
|
||||
#define CR0_PE (1 << 0)
|
||||
#define CR0_MP (1 << 1)
|
||||
#define CR0_EM (1 << 2)
|
||||
@@ -178,6 +185,7 @@
|
||||
#define MSR_EFER_FFXSR (1 << 14)
|
||||
#define MSR_EFER_TCE (1 << 15)
|
||||
|
||||
#define MSR_PAT (0x277)
|
||||
#define MSR_EFER (0xC0000080)
|
||||
#define MSR_STAR (0xC0000081)
|
||||
#define MSR_LSTAR (0xC0000082)
|
||||
@@ -187,5 +195,16 @@
|
||||
#define MSR_GS_BASE (0xC0000101)
|
||||
#define MSR_KERNEL_GS_BASE (0xC0000102)
|
||||
|
||||
#define CPL_USER (0b11)
|
||||
#define CPL_KERNEL (0b00)
|
||||
#define PAT_UC (0x0)
|
||||
#define PAT_WC (0x1)
|
||||
#define PAT_WT (0x4)
|
||||
#define PAT_WP (0x5)
|
||||
#define PAT_WB (0x6)
|
||||
#define PAT_UCM (0x7)
|
||||
|
||||
#define CPL_USER (0x3)
|
||||
#define CPL_KERNEL (0x0)
|
||||
|
||||
#define IDT_PF (0xE)
|
||||
#define IDT_SPURIOUS (0xFE)
|
||||
#define IDT_IPI_RESCHED (0xFF)
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <zinnia/compiler.h>
|
||||
#include <stdint.h>
|
||||
|
||||
union gdt_desc {
|
||||
uint64_t val;
|
||||
struct {
|
||||
uint16_t limit0;
|
||||
uint16_t base0;
|
||||
uint8_t base1;
|
||||
uint8_t access;
|
||||
uint8_t limit1_flags;
|
||||
uint8_t base2;
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(union gdt_desc) == 0x8);
|
||||
|
||||
union gdt_long_desc {
|
||||
uint64_t val[2];
|
||||
struct {
|
||||
uint16_t limit0;
|
||||
uint16_t base0;
|
||||
uint8_t base1;
|
||||
uint8_t access;
|
||||
uint8_t limit1_flags;
|
||||
uint8_t base2;
|
||||
uint32_t base3;
|
||||
uint32_t reserved;
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(union gdt_long_desc) == 0x10);
|
||||
|
||||
struct [[__packed]] gdt {
|
||||
union gdt_desc null;
|
||||
union gdt_desc kernel_code32;
|
||||
union gdt_desc kernel_data32;
|
||||
union gdt_desc kernel_code64;
|
||||
union gdt_desc kernel_data64;
|
||||
union gdt_desc user_code32;
|
||||
union gdt_desc user_data;
|
||||
union gdt_desc user_code64;
|
||||
union gdt_long_desc tss;
|
||||
};
|
||||
|
||||
struct [[__packed]] gdtr {
|
||||
uint16_t limit;
|
||||
struct gdt* base;
|
||||
};
|
||||
|
||||
extern const struct gdt gdt_base;
|
||||
|
||||
// Initializes a GDT on the local CPU.
|
||||
void gdt_load();
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void hpet_init();
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <zinnia/init.h>
|
||||
|
||||
[[__init]]
|
||||
void idt_init();
|
||||
|
||||
// Loads the IDT on this CPU.
|
||||
void idt_load();
|
||||
|
||||
void interrupt_return();
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <stddef.h>
|
||||
#include <x86_64/apic.h>
|
||||
#include <x86_64/gdt.h>
|
||||
#include <x86_64/tss.h>
|
||||
|
||||
struct arch_percpu {
|
||||
struct gdt gdt;
|
||||
struct tss tss;
|
||||
struct local_apic lapic;
|
||||
struct irq_line* irq_lines[128];
|
||||
size_t fpu_size;
|
||||
void (*fpu_save)(void*);
|
||||
void (*fpu_restore)(const void*);
|
||||
bool can_smap;
|
||||
};
|
||||
|
||||
extern struct arch_percpu arch_data;
|
||||
|
||||
// Initializes the calling CPU and makes `cpu` its per-CPU block.
|
||||
void x86_64_cpu_setup(struct percpu* cpu);
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <zinnia/pmap.h>
|
||||
|
||||
enum : pte_t {
|
||||
ARCH_FLAG_PRESENT = 1 << 0,
|
||||
ARCH_FLAG_READ_WRITE = 1 << 1,
|
||||
ARCH_FLAG_USER_MODE = 1 << 2,
|
||||
ARCH_FLAG_WRITE_THROUGH = 1 << 3,
|
||||
ARCH_FLAG_CACHE_DISABLE = 1 << 4,
|
||||
ARCH_FLAG_ACCESSED = 1 << 5,
|
||||
ARCH_FLAG_DIRTY = 1 << 6,
|
||||
ARCH_FLAG_SIZE = 1 << 7,
|
||||
ARCH_FLAG_GLOBAL = 1 << 8,
|
||||
ARCH_FLAG_AVAILABLE = 1 << 9,
|
||||
ARCH_FLAG_ATTRIBUTE_TABLE = 1 << 7,
|
||||
ARCH_FLAG_EXECUTE_DISABLE = 1LU << 63,
|
||||
|
||||
ARCH_PTE_ADDR_MASK = 0x000F'FFFF'FFFF'F000,
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <x86_64/gdt.h>
|
||||
|
||||
struct [[__packed]] smp_info {
|
||||
struct gdt gdt;
|
||||
struct gdtr gdtr;
|
||||
uint32_t farjmp_offset;
|
||||
uint32_t farjmp_segment;
|
||||
uint32_t temp_stack;
|
||||
uint32_t temp_cr3;
|
||||
uint64_t entry;
|
||||
uint64_t hhdm_offset;
|
||||
uint64_t percpu;
|
||||
uint32_t lapic_id;
|
||||
_Atomic(uint8_t) booted;
|
||||
};
|
||||
|
||||
// Discovers all APs and starts them.
|
||||
void x86_64_smp_init();
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void arch_syscall_stub();
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void tsc_init();
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <kernel/compiler.h>
|
||||
#include <stdint.h>
|
||||
#include <zinnia/compiler.h>
|
||||
|
||||
struct [[__packed, __aligned(0x10)]] tss {
|
||||
struct [[__packed]] tss {
|
||||
uint32_t reserved0;
|
||||
uint64_t rsp0;
|
||||
uint64_t rsp1;
|
||||
@@ -22,3 +22,5 @@ struct [[__packed, __aligned(0x10)]] tss {
|
||||
uint16_t reserved5;
|
||||
uint16_t iopb;
|
||||
};
|
||||
|
||||
static_assert(sizeof(struct tss) == 0x68);
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <arch/irq.h>
|
||||
#include <stdint.h>
|
||||
#include <x86_64/defs.h>
|
||||
|
||||
bool arch_irq_get_state() {
|
||||
uint64_t flags;
|
||||
asm volatile("pushf; pop %0" : "=r"(flags)::"memory");
|
||||
return flags & RFLAGS_IF;
|
||||
}
|
||||
|
||||
bool arch_irq_set_state(bool state) {
|
||||
bool old = arch_irq_get_state();
|
||||
if (state)
|
||||
asm volatile("sti" ::: "memory");
|
||||
else
|
||||
asm volatile("cli" ::: "memory");
|
||||
return old;
|
||||
}
|
||||
|
||||
void arch_irq_wait() {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <zinnia/types.h>
|
||||
#include <arch/mem.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
size_t arch_mem_page_size() {
|
||||
return 1 << 12;
|
||||
}
|
||||
|
||||
size_t arch_mem_page_bits() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
size_t arch_mem_level_bits() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
size_t arch_mem_num_levels() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
uintptr_t arch_mem_hhdm_addr() {
|
||||
return 0xFFFF'8000'0000'0000;
|
||||
}
|
||||
|
||||
uintptr_t arch_mem_pfndb_addr() {
|
||||
return 0xFFFF'A000'0000'0000;
|
||||
}
|
||||
|
||||
uintptr_t arch_mem_mapping_addr() {
|
||||
return 0xFFFF'C000'0000'0000;
|
||||
}
|
||||
|
||||
bool arch_mem_addr_is_user(uintptr_t addr) {
|
||||
return addr < 0x0000'7FFF'FFFF'F000;
|
||||
}
|
||||
@@ -1,17 +1,25 @@
|
||||
kernel_c_args += [
|
||||
'-masm=intel',
|
||||
'-mgeneral-regs-only',
|
||||
'-mno-red-zone',
|
||||
'-mcmodel=kernel',
|
||||
'-masm=intel',
|
||||
]
|
||||
|
||||
kernel_sources += files(
|
||||
'apic.c',
|
||||
'archctl.c',
|
||||
'gdt.c',
|
||||
'hpet.c',
|
||||
'idt.c',
|
||||
'irq.c',
|
||||
'mem.c',
|
||||
'percpu.c',
|
||||
'pmap.c',
|
||||
'pmap.c',
|
||||
'sched.c',
|
||||
'serial.c',
|
||||
'smp.c',
|
||||
'sys.c',
|
||||
'trampoline.S',
|
||||
'start.c',
|
||||
'syscall.c',
|
||||
'tsc.c',
|
||||
'usercopy.c',
|
||||
)
|
||||
@@ -0,0 +1,145 @@
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <config.h>
|
||||
#include <stddef.h>
|
||||
#include <x86_64/apic.h>
|
||||
#include <x86_64/asm.h>
|
||||
#include <x86_64/defs.h>
|
||||
#include <x86_64/gdt.h>
|
||||
#include <x86_64/hpet.h>
|
||||
#include <x86_64/idt.h>
|
||||
#include <x86_64/percpu.h>
|
||||
#include <x86_64/smp.h>
|
||||
#include <x86_64/syscall.h>
|
||||
#include <x86_64/tsc.h>
|
||||
|
||||
struct percpu* arch_percpu_get() {
|
||||
struct percpu* result;
|
||||
asm volatile("mov %0, gs:0" : "=r"(result)::"memory");
|
||||
return result;
|
||||
}
|
||||
|
||||
void arch_percpu_bsp_init() {
|
||||
asm_wrmsr(MSR_GS_BASE, (uint64_t)&percpu_bsp);
|
||||
asm_wrmsr(MSR_FS_BASE, 0);
|
||||
asm_wrmsr(MSR_KERNEL_GS_BASE, 0);
|
||||
|
||||
pic_disable();
|
||||
gdt_load();
|
||||
idt_init();
|
||||
idt_load();
|
||||
}
|
||||
|
||||
union ia32_star {
|
||||
uint64_t value;
|
||||
struct {
|
||||
uint32_t rsvd;
|
||||
uint16_t kernel_cs_ss;
|
||||
uint16_t user_cs_ss;
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(union ia32_star) == 8);
|
||||
|
||||
[[__percpu]]
|
||||
struct arch_percpu arch_data = {0};
|
||||
|
||||
void x86_64_cpu_setup(struct percpu* cpu) {
|
||||
asm_wrmsr(MSR_KERNEL_GS_BASE, 0);
|
||||
asm_wrmsr(MSR_GS_BASE, (uint64_t)cpu);
|
||||
asm_wrmsr(MSR_FS_BASE, 0);
|
||||
|
||||
struct arch_percpu* arch_cpu = percpu_ref(arch_data);
|
||||
|
||||
gdt_load();
|
||||
|
||||
// TODO: not if FRED
|
||||
idt_load();
|
||||
|
||||
asm_wrmsr(
|
||||
MSR_PAT,
|
||||
PAT_WB | PAT_WT << 8 | PAT_UCM << 16 | PAT_UC << 24 | (uint64_t)PAT_WP << 32 | (uint64_t)PAT_WC << 40 |
|
||||
(uint64_t)PAT_UCM << 48 | (uint64_t)PAT_UC << 56
|
||||
);
|
||||
|
||||
// Syscall extension.
|
||||
asm_wrmsr(MSR_EFER, asm_rdmsr(MSR_EFER) | MSR_EFER_SCE);
|
||||
union ia32_star star = {0};
|
||||
star.kernel_cs_ss = offsetof(struct gdt, kernel_code64);
|
||||
star.user_cs_ss = offsetof(struct gdt, user_code32);
|
||||
asm_wrmsr(MSR_STAR, star.value);
|
||||
asm_wrmsr(MSR_LSTAR, (uint64_t)arch_syscall_stub);
|
||||
asm_wrmsr(MSR_SFMASK, RFLAGS_AC | RFLAGS_DF | RFLAGS_IF);
|
||||
|
||||
// TODO: FRED
|
||||
|
||||
uint64_t cr0, cr4;
|
||||
asm volatile("mov %0, cr0" : "=r"(cr0));
|
||||
asm volatile("mov %0, cr4" : "=r"(cr4));
|
||||
|
||||
struct cpuid cpuid1 = asm_cpuid(1, 0);
|
||||
struct cpuid cpuid7 = asm_cpuid(7, 0);
|
||||
struct cpuid cpuid13 = asm_cpuid(13, 0);
|
||||
|
||||
// Enable SSE.
|
||||
cr0 &= ~CR0_EM;
|
||||
cr0 |= CR0_MP;
|
||||
cr4 |= CR4_OSFXSR | CR4_OSXMMEXCPT;
|
||||
|
||||
if (cpuid1.ecx & CPUID_1C_XSAVE) {
|
||||
cr4 |= CR4_OSXSAVE;
|
||||
asm volatile("mov cr4, %0" ::"r"(cr4));
|
||||
|
||||
uint64_t xcr0 = XCR0_X87 | XCR0_SSE;
|
||||
// AVX
|
||||
if (cpuid1.ecx & CPUID_1C_AVX)
|
||||
xcr0 |= XCR0_AVX;
|
||||
|
||||
// AVX-512
|
||||
if (cpuid7.ebx & CPUID_7B_AVX512F)
|
||||
xcr0 |= XCR0_AXV512_OPMASK | XCR0_AVX512_ZMM_HI256 | XCR0_AVX512_HI16_ZMM;
|
||||
|
||||
asm_wrxcr(0, xcr0);
|
||||
|
||||
arch_cpu->fpu_size = cpuid13.ecx;
|
||||
arch_cpu->fpu_save = asm_xsave;
|
||||
arch_cpu->fpu_restore = asm_xrstor;
|
||||
} else {
|
||||
arch_cpu->fpu_size = 512;
|
||||
arch_cpu->fpu_save = asm_fxsave;
|
||||
arch_cpu->fpu_restore = asm_fxrstor;
|
||||
}
|
||||
|
||||
if (cpuid7.ecx & CPUID_7C_UMIP)
|
||||
cr4 |= CR4_UMIP;
|
||||
|
||||
if (cpuid7.ebx & CPUID_7B_SMEP)
|
||||
cr4 |= CR4_SMEP;
|
||||
|
||||
if (cpuid7.ebx & CPUID_7B_SMAP) {
|
||||
cr4 |= CR4_SMAP;
|
||||
arch_cpu->can_smap = true;
|
||||
}
|
||||
|
||||
if (cpuid7.ebx & CPUID_7B_FSGSBASE)
|
||||
cr4 |= CR4_FSGSBASE;
|
||||
|
||||
asm volatile("mov cr0, %0" ::"r"(cr0));
|
||||
asm volatile("mov cr4, %0" ::"r"(cr4));
|
||||
|
||||
lapic_init(&arch_cpu->lapic);
|
||||
cpu->online = true;
|
||||
}
|
||||
|
||||
void arch_percpu_init() {
|
||||
hpet_init();
|
||||
#if CONFIG_TSC
|
||||
tsc_init();
|
||||
#endif
|
||||
|
||||
// Init the BSP.
|
||||
x86_64_cpu_setup(&percpu_bsp);
|
||||
|
||||
// Init any APs.
|
||||
x86_64_smp_init();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/types.h>
|
||||
#include <x86_64/pmap.h>
|
||||
|
||||
void pte_clear(pte_t* pte) {
|
||||
*pte = 0;
|
||||
}
|
||||
|
||||
void pte_flush(uintptr_t vaddr) {
|
||||
asm volatile("invlpg [%0]" ::"r"(vaddr) : "memory");
|
||||
}
|
||||
|
||||
pte_t pte_build(phys_t addr, enum pte_flags flags, enum cache_mode cache) {
|
||||
pte_t result = ((pte_t)addr & ARCH_PTE_ADDR_MASK) | ARCH_FLAG_PRESENT;
|
||||
|
||||
if (flags & PTE_USER)
|
||||
result |= ARCH_FLAG_USER_MODE;
|
||||
|
||||
if (flags & PTE_DIR)
|
||||
result |= ARCH_FLAG_READ_WRITE;
|
||||
else {
|
||||
if (flags & PTE_WRITE)
|
||||
result |= ARCH_FLAG_READ_WRITE;
|
||||
if (!(flags & PTE_EXEC))
|
||||
result |= ARCH_FLAG_EXECUTE_DISABLE;
|
||||
|
||||
switch (cache) {
|
||||
case CACHE_ANY:
|
||||
case CACHE_WRITE_BACK:
|
||||
break;
|
||||
case CACHE_WRITE_THROUGH:
|
||||
result |= ARCH_FLAG_WRITE_THROUGH;
|
||||
break;
|
||||
case CACHE_MMIO:
|
||||
result |= ARCH_FLAG_CACHE_DISABLE | ARCH_FLAG_WRITE_THROUGH;
|
||||
break;
|
||||
case CACHE_WRITE_COMBINE:
|
||||
result |= ARCH_FLAG_ATTRIBUTE_TABLE | ARCH_FLAG_WRITE_THROUGH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool pte_is_present(pte_t* pte) {
|
||||
return *pte & ARCH_FLAG_PRESENT;
|
||||
}
|
||||
|
||||
bool pte_is_dir(pte_t* pte) {
|
||||
return *pte & ARCH_FLAG_SIZE;
|
||||
}
|
||||
|
||||
phys_t pte_address(pte_t* pte) {
|
||||
return (phys_t)(*pte & ARCH_PTE_ADDR_MASK);
|
||||
}
|
||||
|
||||
void pmap_set(struct pmap* pt) {
|
||||
asm volatile("mov cr3, %0" ::"r"(pt->root) : "memory");
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <arch/sched.h>
|
||||
#include <bits/sched.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <x86_64/asm.h>
|
||||
#include <x86_64/defs.h>
|
||||
#include <x86_64/gdt.h>
|
||||
#include <x86_64/idt.h>
|
||||
#include <x86_64/percpu.h>
|
||||
|
||||
// The task frame consists of registers that the C ABI marks as callee-saved.
|
||||
// If we don't save them, these registers are lost during a context switch.
|
||||
struct task_frame {
|
||||
uint64_t rbx, rbp, r12, r13, r14, r15, rip;
|
||||
};
|
||||
|
||||
[[__naked]]
|
||||
static struct task* perform_switch(uint64_t* old_rsp, uint64_t new_rsp, struct task* prev) {
|
||||
asm volatile(
|
||||
"sub rsp, 0x30\n" // Make room for all regs (except RIP).
|
||||
"mov [rsp + %c[rbx]], rbx\n"
|
||||
"mov [rsp + %c[rbp]], rbp\n"
|
||||
"mov [rsp + %c[r12]], r12\n"
|
||||
"mov [rsp + %c[r13]], r13\n"
|
||||
"mov [rsp + %c[r14]], r14\n"
|
||||
"mov [rsp + %c[r15]], r15\n"
|
||||
"mov [rdi], rsp\n" // rdi = old_rsp
|
||||
"mov rsp, rsi\n" // rsi = new_rsp
|
||||
"mov rbx, [rsp + %c[rbx]]\n"
|
||||
"mov rbp, [rsp + %c[rbp]]\n"
|
||||
"mov r12, [rsp + %c[r12]]\n"
|
||||
"mov r13, [rsp + %c[r13]]\n"
|
||||
"mov r14, [rsp + %c[r14]]\n"
|
||||
"mov r15, [rsp + %c[r15]]\n"
|
||||
"add rsp, 0x30\n"
|
||||
"mov rax, rdx\n"
|
||||
"ret" // This will conveniently move us to the RIP we put at this stack entry.
|
||||
:
|
||||
: [rbx] "i"(offsetof(struct task_frame, rbx)),
|
||||
[rbp] "i"(offsetof(struct task_frame, rbp)),
|
||||
[r12] "i"(offsetof(struct task_frame, r12)),
|
||||
[r13] "i"(offsetof(struct task_frame, r13)),
|
||||
[r14] "i"(offsetof(struct task_frame, r14)),
|
||||
[r15] "i"(offsetof(struct task_frame, r15))
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
struct task* arch_sched_switch(struct task* from, struct task* to) {
|
||||
struct arch_percpu* cpu = percpu_ref(arch_data);
|
||||
struct arch_task_context* from_ctx = &from->context;
|
||||
struct arch_task_context* to_ctx = &to->context;
|
||||
|
||||
cpu->tss.rsp0 = kernel_stack_top(&to->kernel_stack);
|
||||
|
||||
if (from_ctx->fpu_region != nullptr) {
|
||||
cpu->fpu_save(from_ctx->fpu_region);
|
||||
from_ctx->ds = asm_read_ds();
|
||||
from_ctx->es = asm_read_es();
|
||||
from_ctx->fs = asm_read_fs();
|
||||
from_ctx->gs = asm_read_gs();
|
||||
from_ctx->fs_base = asm_rdmsr(MSR_FS_BASE);
|
||||
from_ctx->gs_base = asm_rdmsr(MSR_KERNEL_GS_BASE);
|
||||
}
|
||||
|
||||
if (to_ctx->fpu_region != nullptr) {
|
||||
cpu->fpu_restore(to_ctx->fpu_region);
|
||||
asm_write_ds(to_ctx->ds);
|
||||
asm_write_es(to_ctx->es);
|
||||
asm_write_fs(to_ctx->fs);
|
||||
|
||||
// If we have to change the GS segment we need to reload the MSR, otherwise we lose its value.
|
||||
if (to_ctx->gs != asm_read_gs()) {
|
||||
uint64_t percpu = asm_rdmsr(MSR_GS_BASE);
|
||||
asm_write_gs(to_ctx->gs);
|
||||
asm_wrmsr(MSR_GS_BASE, percpu);
|
||||
}
|
||||
|
||||
asm_wrmsr(MSR_FS_BASE, to_ctx->fs_base);
|
||||
asm_wrmsr(MSR_KERNEL_GS_BASE, to_ctx->gs_base);
|
||||
}
|
||||
|
||||
return perform_switch(&from_ctx->rsp, to_ctx->rsp, from);
|
||||
}
|
||||
|
||||
// Calls task_entry with the correct parameters.
|
||||
[[__naked]]
|
||||
void task_entry_thunk() {
|
||||
asm volatile(
|
||||
"mov rdi, rbx\n"
|
||||
"mov rsi, r12\n"
|
||||
"mov rdx, rax\n"
|
||||
"jmp %0"
|
||||
:
|
||||
: "r"(task_entry)
|
||||
: "memory", "rdi", "rsi", "rdx"
|
||||
);
|
||||
}
|
||||
|
||||
errno_t arch_task_init(
|
||||
struct arch_task_context* context,
|
||||
void* entry,
|
||||
uintptr_t arg0,
|
||||
uintptr_t stack_start,
|
||||
bool is_user
|
||||
) {
|
||||
struct arch_percpu* cpu = percpu_ref(arch_data);
|
||||
|
||||
assert((stack_start & 0xF) == 0, "Task stack is not 16-byte aligned!\n");
|
||||
|
||||
// Prepare a dummy stack with an entry point function to return to.
|
||||
struct task_frame* frame = (struct task_frame*)(stack_start - sizeof(uint64_t)) - 1;
|
||||
frame->rbx = (uint64_t)entry;
|
||||
frame->r12 = arg0;
|
||||
frame->rip = (uint64_t)task_entry_thunk;
|
||||
context->rsp = (uint64_t)frame;
|
||||
|
||||
if (is_user) {
|
||||
// Allocate FPU space.
|
||||
const size_t fpu_size = cpu->fpu_size;
|
||||
uintptr_t fpu_base = vm_alloc(fpu_size);
|
||||
if (!fpu_base)
|
||||
return ENOMEM;
|
||||
phys_t pages;
|
||||
errno_t s = mem_phys_alloc(ALIGN_UP(fpu_size, arch_mem_page_size()) / arch_mem_page_size(), 0, &pages);
|
||||
if (s) {
|
||||
vm_free(fpu_base, fpu_size);
|
||||
return s;
|
||||
}
|
||||
for (size_t i = 0; i < fpu_size; i += arch_mem_page_size())
|
||||
pmap_map(&kernel_vma.pmap, fpu_base + i, pages + i, PTE_WRITE | PTE_READ, CACHE_WRITE_BACK);
|
||||
context->fpu_region = (void*)fpu_base;
|
||||
context->fpu_phys = pages;
|
||||
|
||||
context->ds = asm_read_ds();
|
||||
context->es = asm_read_es();
|
||||
context->fs = asm_read_fs();
|
||||
context->gs = asm_read_gs();
|
||||
context->fs_base = asm_rdmsr(MSR_FS_BASE);
|
||||
context->gs_base = asm_rdmsr(MSR_KERNEL_GS_BASE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void arch_task_fini(struct arch_task_context* context) {
|
||||
if (context->fpu_region == nullptr)
|
||||
return;
|
||||
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
const size_t fpu_size = percpu_ref(arch_data)->fpu_size;
|
||||
const uintptr_t fpu_base = (uintptr_t)context->fpu_region;
|
||||
for (size_t i = 0; i < fpu_size; i += page_size)
|
||||
pmap_unmap(&kernel_vma.pmap, fpu_base + i);
|
||||
mem_phys_free(context->fpu_phys, ALIGN_UP(fpu_size, page_size) / page_size);
|
||||
vm_free(fpu_base, fpu_size);
|
||||
|
||||
context->fpu_region = nullptr;
|
||||
}
|
||||
|
||||
[[__naked]]
|
||||
void arch_sched_preempt_disable() {
|
||||
asm volatile(
|
||||
"inc qword ptr gs:%c0\n"
|
||||
"ret"
|
||||
:
|
||||
: "i"(offsetof(struct percpu, sched.preempt_level))
|
||||
);
|
||||
}
|
||||
|
||||
[[__naked]]
|
||||
bool arch_sched_preempt_enable() {
|
||||
asm volatile(
|
||||
"dec qword ptr gs:%c0\n"
|
||||
"setz al\n"
|
||||
"movzx eax, al\n"
|
||||
"ret\n"
|
||||
:
|
||||
: "i"(offsetof(struct percpu, sched.preempt_level))
|
||||
: "cc"
|
||||
);
|
||||
}
|
||||
|
||||
[[__naked]]
|
||||
void arch_sched_jump_to_context(struct arch_context* context) {
|
||||
asm volatile(
|
||||
"mov rsp, rdi\n"
|
||||
"jmp %0\n" ::"r"(interrupt_return)
|
||||
);
|
||||
}
|
||||
|
||||
void arch_sched_jump_to_user(uintptr_t ip, uintptr_t sp, uintptr_t arg) {
|
||||
struct arch_context context = {
|
||||
.rip = ip,
|
||||
.rsp = sp,
|
||||
.rdi = arg,
|
||||
.rflags = 0x202,
|
||||
.cs = offsetof(struct gdt, user_code64) | CPL_USER,
|
||||
.ss = offsetof(struct gdt, user_data) | CPL_USER,
|
||||
};
|
||||
|
||||
uintptr_t percpu = asm_rdmsr(MSR_GS_BASE);
|
||||
asm volatile(
|
||||
"mov ds, %0\n"
|
||||
"mov es, %0\n"
|
||||
"mov fs, %0\n"
|
||||
"mov gs, %0\n"
|
||||
:
|
||||
: "a"(0)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
asm_wrmsr(MSR_FS_BASE, 0);
|
||||
asm_wrmsr(MSR_GS_BASE, percpu);
|
||||
asm_wrmsr(MSR_KERNEL_GS_BASE, 0);
|
||||
|
||||
arch_sched_jump_to_context(&context);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <asm.h>
|
||||
#include <kernel/console.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <stdint.h>
|
||||
#include <x86_64/asm.h>
|
||||
|
||||
constexpr uint16_t COM1_BASE = 0x3F8;
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/types.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <uacpi/acpi.h>
|
||||
#include <uacpi/tables.h>
|
||||
#include <x86_64/apic.h>
|
||||
#include <x86_64/defs.h>
|
||||
#include <x86_64/percpu.h>
|
||||
#include <x86_64/pmap.h>
|
||||
#include <x86_64/smp.h>
|
||||
|
||||
extern uint8_t smp_trampoline_start[];
|
||||
extern uint8_t smp_trampoline_data[];
|
||||
extern uint8_t smp_trampoline_end[];
|
||||
|
||||
[[__naked, __used]]
|
||||
static void smp_trampoline() {
|
||||
asm volatile(
|
||||
".pushsection .rodata\n"
|
||||
".code16\n"
|
||||
".global smp_trampoline_start\n"
|
||||
"smp_trampoline_start:\n"
|
||||
" cli\n"
|
||||
" cld\n"
|
||||
" jmp .Lmode16\n"
|
||||
|
||||
".global smp_trampoline_data\n"
|
||||
"smp_trampoline_data:\n"
|
||||
" .skip %c[info_size]\n"
|
||||
|
||||
".set .Ldata_offset, (smp_trampoline_data - smp_trampoline_start)\n"
|
||||
".set .Lgdtr_offset, (.Ldata_offset + %c[gdtr])\n"
|
||||
".set .Lfarjmp_offset, (.Ldata_offset + %c[farjmp])\n"
|
||||
".set .Ltemp_stack_offset, (.Ldata_offset + %c[temp_stack])\n"
|
||||
".set .Ltemp_cr3_offset, (.Ldata_offset + %c[temp_cr3])\n"
|
||||
".set .Lentry_offset, (.Ldata_offset + %c[entry])\n"
|
||||
".set .Lhhdm_offset, (.Ldata_offset + %c[hhdm])\n"
|
||||
|
||||
".Lmode16:\n"
|
||||
" mov bx, cs\n"
|
||||
" shl ebx, 4\n"
|
||||
|
||||
" lidtd cs:(.Linvalid_idtr - smp_trampoline_start)\n"
|
||||
" lgdtd cs:.Lgdtr_offset\n"
|
||||
|
||||
" lea eax, [ebx + (.Lmode32 - smp_trampoline_start)]\n"
|
||||
" mov dword ptr cs:.Lfarjmp_offset, eax\n"
|
||||
|
||||
" mov eax, %c[cr0]\n"
|
||||
" mov cr0, eax\n"
|
||||
" jmp fword ptr cs:.Lfarjmp_offset\n"
|
||||
|
||||
".code32\n"
|
||||
".Lmode32:\n"
|
||||
" mov ax, %c[kernel32_ds]\n"
|
||||
" mov ds, ax\n"
|
||||
" mov es, ax\n"
|
||||
" mov fs, ax\n"
|
||||
" mov gs, ax\n"
|
||||
" mov ss, ax\n"
|
||||
|
||||
" xor eax, eax\n"
|
||||
" lldt ax\n"
|
||||
|
||||
" mov esp, dword ptr [ebx + .Ltemp_stack_offset]\n"
|
||||
|
||||
" xor eax, eax\n"
|
||||
" mov cr4, eax\n"
|
||||
|
||||
" or eax, %c[cr4_pae]\n"
|
||||
" mov cr4, eax\n"
|
||||
|
||||
" mov ecx, %c[msr_efer]\n"
|
||||
" mov eax, %c[efer_lme]\n"
|
||||
" xor edx, edx\n"
|
||||
" wrmsr\n"
|
||||
|
||||
" mov eax, dword ptr [ebx + .Ltemp_cr3_offset]\n"
|
||||
" mov cr3, eax\n"
|
||||
|
||||
" mov eax, cr0\n"
|
||||
" or eax, %c[cr0_pg]\n"
|
||||
" mov cr0, eax\n"
|
||||
|
||||
" lea eax, [ebx + (.Lmode64 - smp_trampoline_start)]\n"
|
||||
" push %c[kernel64_cs]\n"
|
||||
" push eax\n"
|
||||
" retf\n"
|
||||
|
||||
".code64\n"
|
||||
".Lmode64:\n"
|
||||
" mov ax, %c[kernel64_ds]\n"
|
||||
" mov ds, ax\n"
|
||||
" mov es, ax\n"
|
||||
" mov fs, ax\n"
|
||||
" mov gs, ax\n"
|
||||
" mov ss, ax\n"
|
||||
|
||||
" mov ecx, %c[msr_efer]\n"
|
||||
" rdmsr\n"
|
||||
" or eax, %c[efer_nxe]\n"
|
||||
" wrmsr\n"
|
||||
|
||||
" xor ebp, ebp\n"
|
||||
|
||||
" lea rdi, [rbx + .Ldata_offset]\n"
|
||||
" mov rax, [rbx + .Lentry_offset]\n"
|
||||
" add rsp, [rbx + .Lhhdm_offset]\n"
|
||||
" jmp rax\n"
|
||||
|
||||
".Linvalid_idtr:\n"
|
||||
" .word 0\n"
|
||||
" .quad 0\n"
|
||||
|
||||
".global smp_trampoline_end\n"
|
||||
"smp_trampoline_end:\n"
|
||||
".popsection\n"
|
||||
" ud2\n"
|
||||
:
|
||||
: [info_size] "i"(sizeof(struct smp_info)),
|
||||
[gdtr] "i"(offsetof(struct smp_info, gdtr)),
|
||||
[farjmp] "i"(offsetof(struct smp_info, farjmp_offset)),
|
||||
[temp_stack] "i"(offsetof(struct smp_info, temp_stack)),
|
||||
[temp_cr3] "i"(offsetof(struct smp_info, temp_cr3)),
|
||||
[entry] "i"(offsetof(struct smp_info, entry)),
|
||||
[hhdm] "i"(offsetof(struct smp_info, hhdm_offset)),
|
||||
[kernel32_ds] "i"(offsetof(struct gdt, kernel_data32)),
|
||||
[kernel64_cs] "i"(offsetof(struct gdt, kernel_code64)),
|
||||
[kernel64_ds] "i"(offsetof(struct gdt, kernel_data64)),
|
||||
[cr0] "i"(CR0_PE | CR0_ET),
|
||||
[cr0_pg] "i"(CR0_PG),
|
||||
[cr4_pae] "i"(CR4_PAE),
|
||||
[msr_efer] "i"(MSR_EFER),
|
||||
[efer_lme] "i"(MSR_EFER_LME),
|
||||
[efer_nxe] "i"(MSR_EFER_NXE)
|
||||
);
|
||||
}
|
||||
|
||||
constexpr size_t AP_BOOT_STACK_SIZE = 0x8000;
|
||||
constexpr uint64_t AP_BOOT_TIMEOUT_NS = 1'000'000'000;
|
||||
|
||||
[[noreturn]]
|
||||
static void ap_entry(phys_t info_addr) {
|
||||
// We're still running on the temporary page table.
|
||||
pmap_set(&kernel_vma.pmap);
|
||||
|
||||
struct smp_info* info = HHDM_PTR(info_addr);
|
||||
struct percpu* cpu = (struct percpu*)info->percpu;
|
||||
|
||||
x86_64_cpu_setup(cpu);
|
||||
irq_lock();
|
||||
|
||||
// This task keeps running on the boot stack, but is never scheduled again after the first yield.
|
||||
sched_init(&cpu->sched);
|
||||
|
||||
// Let the BSP know that we're alive.
|
||||
// `info` must not be touched after this point.
|
||||
atomic_store_explicit(&info->booted, 1, memory_order_release);
|
||||
|
||||
log("smp: CPU %zu online\n", cpu->id);
|
||||
|
||||
sched_yield(&cpu->sched);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static bool start_ap(phys_t trampoline, uint32_t temp_cr3, uint32_t lapic_id) {
|
||||
const size_t trampoline_size = smp_trampoline_end - smp_trampoline_start;
|
||||
|
||||
phys_t stack;
|
||||
if (mem_phys_alloc(AP_BOOT_STACK_SIZE / arch_mem_page_size(), ALLOC_MEM32 | ALLOC_NOZERO, &stack)) {
|
||||
log("smp: Failed to allocate a boot stack for LAPIC %u\n", lapic_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct percpu* cpu = percpu_new();
|
||||
if (!cpu) {
|
||||
log("smp: Failed to allocate per-CPU data for LAPIC %u\n", lapic_id);
|
||||
mem_phys_free(stack, AP_BOOT_STACK_SIZE / arch_mem_page_size());
|
||||
return false;
|
||||
}
|
||||
|
||||
log("smp: Starting CPU %zu\n", cpu->id);
|
||||
|
||||
memcpy(HHDM_PTR(trampoline), smp_trampoline_start, trampoline_size);
|
||||
|
||||
const phys_t info_addr = trampoline + (smp_trampoline_data - smp_trampoline_start);
|
||||
struct smp_info* info = HHDM_PTR(info_addr);
|
||||
memcpy(&info->gdt, &gdt_base, sizeof(info->gdt));
|
||||
info->gdtr.limit = sizeof(struct gdt) - 1;
|
||||
info->gdtr.base = (struct gdt*)(info_addr + offsetof(struct smp_info, gdt));
|
||||
info->farjmp_offset = 0; // Filled in by the trampoline.
|
||||
info->farjmp_segment = offsetof(struct gdt, kernel_code32);
|
||||
info->temp_stack = stack + AP_BOOT_STACK_SIZE;
|
||||
info->temp_cr3 = temp_cr3;
|
||||
info->entry = (uint64_t)ap_entry;
|
||||
info->hhdm_offset = mem_hhdm_base;
|
||||
info->percpu = (uint64_t)cpu;
|
||||
info->lapic_id = lapic_id;
|
||||
atomic_init(&info->booted, 0);
|
||||
|
||||
struct local_apic* lapic = &percpu_ref(arch_data)->lapic;
|
||||
|
||||
lapic_send_ipi(lapic, lapic_id, 0, APIC_DELIVERY_INIT, APIC_DEST_PHYSICAL, APIC_LEVEL_ASSERT, APIC_TRIGGER_EDGE);
|
||||
clock_spin_ns(10'000'000);
|
||||
|
||||
lapic_send_ipi(
|
||||
lapic,
|
||||
lapic_id,
|
||||
trampoline >> 12,
|
||||
APIC_DELIVERY_STARTUP,
|
||||
APIC_DEST_PHYSICAL,
|
||||
APIC_LEVEL_ASSERT,
|
||||
APIC_TRIGGER_EDGE
|
||||
);
|
||||
clock_spin_ns(10'000'000);
|
||||
|
||||
for (uint64_t waited = 0; !atomic_load_explicit(&info->booted, memory_order_acquire); waited += 1'000'000) {
|
||||
if (waited >= AP_BOOT_TIMEOUT_NS) {
|
||||
// The per-CPU data and boot stack are leaked on purpose, in case the AP still comes up.
|
||||
log("smp: CPU %zu (LAPIC %u) did not respond\n", cpu->id, lapic_id);
|
||||
return false;
|
||||
}
|
||||
clock_spin_ns(1'000'000);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static errno_t build_temp_table(phys_t* out) {
|
||||
const size_t pgsz = arch_mem_page_size();
|
||||
|
||||
phys_t mem;
|
||||
errno_t status = mem_phys_alloc(6, ALLOC_MEM32, &mem);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
uint64_t* l4 = HHDM_PTR(mem);
|
||||
uint64_t* l3 = HHDM_PTR(mem + pgsz);
|
||||
const phys_t l2_base = mem + 2 * pgsz;
|
||||
|
||||
// Use 2 MiB pages.
|
||||
l4[0] = (mem + pgsz) | ARCH_FLAG_PRESENT | ARCH_FLAG_READ_WRITE;
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
uint64_t* l2 = HHDM_PTR(l2_base + i * pgsz);
|
||||
l3[i] = (l2_base + i * pgsz) | ARCH_FLAG_PRESENT | ARCH_FLAG_READ_WRITE;
|
||||
for (size_t j = 0; j < 512; j++)
|
||||
l2[j] = ((i << 30) + (j << 21)) | ARCH_FLAG_PRESENT | ARCH_FLAG_READ_WRITE | ARCH_FLAG_SIZE;
|
||||
}
|
||||
|
||||
// Copy over the higher half from the kernel page table.
|
||||
const uint64_t* kernel_l4 = HHDM_PTR(kernel_vma.pmap.root);
|
||||
for (size_t i = 256; i < 512; i++)
|
||||
l4[i] = kernel_l4[i];
|
||||
|
||||
*out = mem;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void x86_64_smp_init() {
|
||||
const size_t trampoline_size = smp_trampoline_end - smp_trampoline_start;
|
||||
assert(trampoline_size <= arch_mem_page_size(), "SMP trampoline doesn't fit in a page\n");
|
||||
|
||||
const size_t max_cpus = percpu_max_cpus();
|
||||
if (max_cpus <= 1)
|
||||
return;
|
||||
|
||||
uacpi_table table;
|
||||
if (uacpi_table_find_by_signature(ACPI_MADT_SIGNATURE, &table) != UACPI_STATUS_OK) {
|
||||
log("smp: No MADT found, not starting any APs\n");
|
||||
return;
|
||||
}
|
||||
|
||||
phys_t trampoline;
|
||||
if (mem_phys_alloc(1, ALLOC_MEM20, &trampoline)) {
|
||||
log("smp: Failed to allocate 20-bit memory for the AP trampoline\n");
|
||||
uacpi_table_unref(&table);
|
||||
return;
|
||||
}
|
||||
|
||||
phys_t temp_table;
|
||||
if (build_temp_table(&temp_table)) {
|
||||
log("smp: Failed to allocate a temporary page table\n");
|
||||
mem_phys_free(trampoline, 1);
|
||||
uacpi_table_unref(&table);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t bsp_id = percpu_ref(arch_data)->lapic.id;
|
||||
bool all_booted = true;
|
||||
size_t num_cpus = 1;
|
||||
|
||||
const struct acpi_madt* madt = table.ptr;
|
||||
const uint8_t* entry = (const uint8_t*)madt->entries;
|
||||
const uint8_t* end = (const uint8_t*)madt + madt->hdr.length;
|
||||
|
||||
while (entry + sizeof(struct acpi_entry_hdr) <= end && num_cpus < max_cpus) {
|
||||
const struct acpi_entry_hdr* hdr = (const struct acpi_entry_hdr*)entry;
|
||||
|
||||
// Don't trust malformed firmware tables.
|
||||
if (hdr->length < sizeof(struct acpi_entry_hdr) || entry + hdr->length > end) {
|
||||
log("smp: MADT contains a malformed entry\n");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t lapic_id = 0;
|
||||
bool enabled = false;
|
||||
switch (hdr->type) {
|
||||
case ACPI_MADT_ENTRY_TYPE_LAPIC:
|
||||
const struct acpi_madt_lapic* lapic = (const struct acpi_madt_lapic*)hdr;
|
||||
lapic_id = lapic->id;
|
||||
enabled = lapic->flags & ACPI_PIC_ENABLED;
|
||||
break;
|
||||
case ACPI_MADT_ENTRY_TYPE_LOCAL_X2APIC:
|
||||
const struct acpi_madt_x2apic* x2apic = (const struct acpi_madt_x2apic*)hdr;
|
||||
lapic_id = x2apic->id;
|
||||
enabled = x2apic->flags & ACPI_PIC_ENABLED;
|
||||
break;
|
||||
}
|
||||
|
||||
if (enabled && lapic_id != bsp_id) {
|
||||
if (start_ap(trampoline, temp_table, lapic_id))
|
||||
num_cpus++;
|
||||
else
|
||||
all_booted = false;
|
||||
}
|
||||
|
||||
entry += hdr->length;
|
||||
}
|
||||
|
||||
uacpi_table_unref(&table);
|
||||
|
||||
// An AP which didn't respond might still be using these.
|
||||
if (all_booted) {
|
||||
mem_phys_free(temp_table, 6);
|
||||
mem_phys_free(trampoline, 1);
|
||||
}
|
||||
|
||||
log("smp: %zu CPU(s) online\n", num_cpus);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <uapi/archctl.h>
|
||||
#include <x86_64/apic.h>
|
||||
#include <x86_64/asm.h>
|
||||
#include <x86_64/defs.h>
|
||||
#include <x86_64/gdt.h>
|
||||
|
||||
[[__init, __naked, __used]]
|
||||
void _start() {
|
||||
asm volatile(
|
||||
"mov rsp, %0\n"
|
||||
"jmp %1"
|
||||
:
|
||||
: "r"(__ld_stack_top), "r"(kernel_entry)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/syscall.h>
|
||||
#include <bits/sched.h>
|
||||
#include <stddef.h>
|
||||
#include <x86_64/defs.h>
|
||||
#include <x86_64/gdt.h>
|
||||
|
||||
// TODO: Refactor to only push callee-saved registers.
|
||||
|
||||
[[__naked]]
|
||||
void arch_syscall_stub() {
|
||||
asm volatile(
|
||||
" swapgs\n"
|
||||
" mov gs:%c[user_stack], rsp\n"
|
||||
" mov rsp, gs:%c[kernel_stack]\n"
|
||||
" cld\n"
|
||||
"1:\n"
|
||||
" push %c[user_data]\n" // ss
|
||||
" push gs:%c[user_stack]\n"
|
||||
" push r11\n" // rflags
|
||||
" push %c[user_code64]\n" // cs
|
||||
" push rcx\n" // rip
|
||||
" push 0x00\n" // arch_context::error
|
||||
" push 0x00\n" // arch_context::isr
|
||||
" push rax\n"
|
||||
" push rbx\n"
|
||||
" push rcx\n"
|
||||
" push rdx\n"
|
||||
" push rbp\n"
|
||||
" push rdi\n"
|
||||
" push rsi\n"
|
||||
" push r8\n"
|
||||
" push r9\n"
|
||||
" push r10\n"
|
||||
" push r11\n"
|
||||
" push r12\n"
|
||||
" push r13\n"
|
||||
" push r14\n"
|
||||
" push r15\n"
|
||||
" xor rbp, rbp\n"
|
||||
" mov rdi, rsp\n" // SysV ABI first argument.
|
||||
" call syscall_dispatch\n" // Call syscall handler.
|
||||
" cli\n"
|
||||
" pop r15\n"
|
||||
" pop r14\n"
|
||||
" pop r13\n"
|
||||
" pop r12\n"
|
||||
" pop r11\n"
|
||||
" pop r10\n"
|
||||
" pop r9\n"
|
||||
" pop r8\n"
|
||||
" pop rsi\n"
|
||||
" pop rdi\n"
|
||||
" pop rbp\n"
|
||||
" pop rdx\n"
|
||||
" pop rcx\n"
|
||||
" pop rbx\n"
|
||||
" pop rax\n"
|
||||
" add rsp, 0x10\n" // Skip .error and .isr fields
|
||||
"2:\n"
|
||||
" mov rsp, gs:%c[user_stack]\n"
|
||||
" swapgs\n"
|
||||
" sysretq\n"
|
||||
:
|
||||
: [user_stack] "i"(offsetof(struct percpu, user_stack)),
|
||||
[kernel_stack] "i"(offsetof(struct percpu, kernel_stack)),
|
||||
[user_code64] "i"(offsetof(struct gdt, user_code64) | CPL_USER),
|
||||
[user_data] "i"(offsetof(struct gdt, user_data) | CPL_USER)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <stdint.h>
|
||||
#include <x86_64/asm.h>
|
||||
#include <x86_64/defs.h>
|
||||
|
||||
static bool use_tsc = true;
|
||||
static void tsc_option(bool opt) {
|
||||
use_tsc = opt;
|
||||
}
|
||||
CMDLINE_OPTION("x86_64.tsc", "Enable timestamp counter", tsc_option);
|
||||
|
||||
constexpr uint64_t nanos_per_sec = 1'000'000'000;
|
||||
static uint64_t tsc_freq = 0;
|
||||
static uint64_t tsc_base_cycles = 0;
|
||||
|
||||
static uint64_t cycles_to_ns(uint64_t cycles, uint64_t freq) {
|
||||
uint64_t seconds = cycles / freq;
|
||||
uint64_t remaining_cycles = cycles % freq;
|
||||
uint64_t fractional_ns = (uint64_t)((__uint128_t)remaining_cycles * (__uint128_t)nanos_per_sec / (__uint128_t)freq);
|
||||
uint64_t ns = seconds * nanos_per_sec + fractional_ns;
|
||||
|
||||
return MIN(ns, UINT64_MAX);
|
||||
}
|
||||
|
||||
static void tsc_reset(struct clock* clock) {
|
||||
tsc_base_cycles = asm_rdtsc();
|
||||
}
|
||||
|
||||
static uint64_t tsc_get_elapsed_ns(struct clock* clock) {
|
||||
uint64_t cycles = asm_rdtsc() - tsc_base_cycles;
|
||||
return cycles_to_ns(cycles, tsc_freq);
|
||||
}
|
||||
|
||||
static struct clock tsc_clock = {
|
||||
.name = "tsc",
|
||||
.priority = 255,
|
||||
.reset = tsc_reset,
|
||||
.get_elapsed_ns = tsc_get_elapsed_ns,
|
||||
};
|
||||
|
||||
void tsc_init() {
|
||||
if (!use_tsc)
|
||||
return;
|
||||
|
||||
if (!(asm_cpuid(1, 0).edx & CPUID_1D_TSC) || !(asm_cpuid(0x8000'0007, 0).edx & (1 << 8))) {
|
||||
log("x86_64/tsc: No invariant TSC detected!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for the TSC info leaf.
|
||||
bool has_tsc_leaf = false;
|
||||
struct cpuid tsc_leaf = {0};
|
||||
if (asm_cpuid(0, 0).eax >= 0x15) {
|
||||
tsc_leaf = asm_cpuid(0x15, 0);
|
||||
has_tsc_leaf = true;
|
||||
}
|
||||
|
||||
if (clock_available()) {
|
||||
// Calibrate using existing clock.
|
||||
uint64_t t1 = asm_rdtsc();
|
||||
clock_spin_ns(10'000'000);
|
||||
uint64_t t2 = asm_rdtsc();
|
||||
tsc_freq = (t2 - t1) * 100;
|
||||
} else if (has_tsc_leaf) {
|
||||
// If we have no timer (yet), the only way we can calibrate the TSC is if CPUID gives us the frequency.
|
||||
// On a normal system, this should usually never be called and is a last resort
|
||||
// since at this point we have at least the HPET timer.
|
||||
if (tsc_leaf.ecx != 0 && tsc_leaf.ebx != 0 && tsc_leaf.eax != 0) {
|
||||
log("x86_64/tsc: Calibrating TSC using CPUID 0x15");
|
||||
tsc_freq = (uint64_t)tsc_leaf.ecx * tsc_leaf.ebx / tsc_leaf.eax;
|
||||
} else {
|
||||
log("x86_64/tsc: Unable to calibrate TSC using CPUID frequency information");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
log("x86_64/tsc: Unable to calibrate the TSC without a clock or static frequency info!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
log("x86_64/tsc: Timer frequency is %lu MHz\n", tsc_freq / 1'000'000);
|
||||
clock_switch(&tsc_clock);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <arch/usercopy.h>
|
||||
#include <x86_64/percpu.h>
|
||||
|
||||
void arch_usercopy_allow(bool state) {
|
||||
if (!percpu_ref(arch_data)->can_smap)
|
||||
return;
|
||||
|
||||
if (state) {
|
||||
asm volatile("stac" ::: "memory");
|
||||
} else {
|
||||
asm volatile("clac" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
extern void x86_64_read_start();
|
||||
extern void x86_64_read_end();
|
||||
extern void x86_64_read_fault();
|
||||
|
||||
static const struct usercopy_region read_region = {
|
||||
.start_ip = x86_64_read_start,
|
||||
.end_ip = x86_64_read_end,
|
||||
.fault_ip = x86_64_read_fault,
|
||||
};
|
||||
|
||||
[[__naked]]
|
||||
bool arch_usercopy_read(void* dst, const __user void* src, size_t len, struct usercopy_region** region) {
|
||||
asm volatile(
|
||||
// Setup regs for `rep movsb`.
|
||||
" xchg rcx, rdx\n"
|
||||
" lea rax, [rip + %0]\n"
|
||||
" xchg [rdx], rax\n"
|
||||
|
||||
".global x86_64_read_start\n"
|
||||
" x86_64_read_start:\n"
|
||||
" rep movsb\n"
|
||||
|
||||
".global x86_64_read_end\n"
|
||||
"x86_64_read_end:\n"
|
||||
" xor rax, rax\n"
|
||||
" xchg [rdx], rax\n"
|
||||
" mov rax, 1\n"
|
||||
" ret\n"
|
||||
|
||||
".global x86_64_read_fault\n"
|
||||
"x86_64_read_fault:\n"
|
||||
" xor rax, rax\n"
|
||||
" mov [rdx], rax\n"
|
||||
" ret\n"
|
||||
:
|
||||
: "i"(&read_region)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
extern void x86_64_write_start();
|
||||
extern void x86_64_write_end();
|
||||
extern void x86_64_write_fault();
|
||||
|
||||
static const struct usercopy_region write_region = {
|
||||
.start_ip = x86_64_write_start,
|
||||
.end_ip = x86_64_write_end,
|
||||
.fault_ip = x86_64_write_fault,
|
||||
};
|
||||
|
||||
[[__naked]]
|
||||
bool arch_usercopy_write(__user void* dst, const void* src, size_t len, struct usercopy_region** region) {
|
||||
asm volatile(
|
||||
// Setup regs for `rep movsb`.
|
||||
" xchg rcx, rdx\n"
|
||||
" lea rax, [rip + %0]\n"
|
||||
" mov [rdx], rax\n"
|
||||
|
||||
".global x86_64_write_start\n"
|
||||
" x86_64_write_start:\n"
|
||||
" rep movsb\n"
|
||||
|
||||
".global x86_64_write_end\n"
|
||||
" x86_64_write_end:\n"
|
||||
" xor rax, rax\n"
|
||||
" xchg [rdx], rax\n"
|
||||
" mov rax, 1\n"
|
||||
" ret\n"
|
||||
|
||||
".global x86_64_write_fault\n"
|
||||
"x86_64_write_fault:\n"
|
||||
" xor rax, rax\n"
|
||||
" mov [rdx], rax\n"
|
||||
" ret\n"
|
||||
:
|
||||
: "i"(&write_region)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
extern void x86_64_strlen_start();
|
||||
extern void x86_64_strlen_end();
|
||||
extern void x86_64_strlen_fault();
|
||||
|
||||
static const struct usercopy_region strlen_region = {
|
||||
.start_ip = x86_64_strlen_start,
|
||||
.end_ip = x86_64_strlen_end,
|
||||
.fault_ip = x86_64_strlen_fault,
|
||||
};
|
||||
|
||||
[[__naked]]
|
||||
bool arch_usercopy_strlen(const __user char* str, size_t max, size_t* len, struct usercopy_region** region) {
|
||||
asm volatile(
|
||||
" lea rax, [rip + %0]\n"
|
||||
" xchg [rcx], rax\n"
|
||||
|
||||
".global x86_64_strlen_start\n"
|
||||
" x86_64_strlen_start:\n"
|
||||
" xor r8, r8\n"
|
||||
".Lloop:\n"
|
||||
" cmp r8, rsi\n"
|
||||
" je .Lleave\n"
|
||||
" cmp byte ptr [rdi + r8], 0\n"
|
||||
" je .Lleave\n"
|
||||
" inc r8\n"
|
||||
" jmp .Lloop\n"
|
||||
".Lleave:\n"
|
||||
" mov [rdx], r8\n"
|
||||
|
||||
".global x86_64_strlen_end\n"
|
||||
"x86_64_strlen_end:\n"
|
||||
" xor rax, rax\n"
|
||||
" xchg [rcx], rax\n"
|
||||
" mov rax, 1\n"
|
||||
" ret\n"
|
||||
|
||||
".global x86_64_strlen_fault\n"
|
||||
"x86_64_strlen_fault:\n"
|
||||
" xor rax, rax\n"
|
||||
" mov [rcx], rax\n"
|
||||
" ret\n"
|
||||
:
|
||||
: "i"(&strlen_region)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <stddef.h>
|
||||
#include <zinnia/stdlib.h>
|
||||
#include <zinnia/string.h>
|
||||
|
||||
// Defined in linker script.
|
||||
extern const struct cmdline_option* __ld_cmdline_start[];
|
||||
extern const struct cmdline_option* __ld_cmdline_end[];
|
||||
|
||||
static char cmdline_buf[0x1000];
|
||||
|
||||
[[__init]]
|
||||
void cmdline_parse(const char* cmdline) {
|
||||
if (cmdline == nullptr)
|
||||
cmdline = "";
|
||||
|
||||
const size_t len = strnlen(cmdline, sizeof(cmdline_buf) - 1);
|
||||
size_t idx = 0;
|
||||
memcpy(cmdline_buf, cmdline, len);
|
||||
|
||||
while (1) {
|
||||
const struct cmdline_option** opt = __ld_cmdline_start;
|
||||
char* name = nullptr;
|
||||
char* value = nullptr;
|
||||
|
||||
// Skip all leading spaces.
|
||||
while (idx < len && cmdline_buf[idx] == ' ')
|
||||
idx++;
|
||||
if (idx >= len)
|
||||
break;
|
||||
size_t name_idx = idx;
|
||||
name = cmdline_buf + name_idx;
|
||||
|
||||
// Find the next equal sign or space.
|
||||
while (idx < len && cmdline_buf[idx] != '=' && cmdline_buf[idx] != ' ')
|
||||
idx++;
|
||||
if (idx > len)
|
||||
break;
|
||||
|
||||
// Check if the option has a value (=foo).
|
||||
char seperator = cmdline_buf[idx];
|
||||
cmdline_buf[idx++] = 0;
|
||||
if (seperator == '=') {
|
||||
// Check if we need to escape the value.
|
||||
char check;
|
||||
if (cmdline_buf[idx] == '"') {
|
||||
check = '"';
|
||||
cmdline_buf[idx++] = 0;
|
||||
} else {
|
||||
check = ' ';
|
||||
}
|
||||
|
||||
value = cmdline_buf + idx;
|
||||
|
||||
// Skip the value.
|
||||
while (idx < len && cmdline_buf[idx] != check)
|
||||
idx++;
|
||||
if (idx > len)
|
||||
break;
|
||||
cmdline_buf[idx++] = 0;
|
||||
}
|
||||
|
||||
// Find the corresponding option.
|
||||
while (opt < __ld_cmdline_end) {
|
||||
const struct cmdline_option* current = *opt;
|
||||
if (!strcmp(current->name, name)) {
|
||||
if (value == nullptr)
|
||||
break;
|
||||
switch (current->option_type) {
|
||||
case CMDLINE_STRING:
|
||||
current->func_str(value);
|
||||
break;
|
||||
case CMDLINE_BOOL:
|
||||
if (!strcmp(value, "on") || !strcmp(value, "yes") || !strcmp(value, "true") || !strcmp(value, "1"))
|
||||
current->func_bool(true);
|
||||
else
|
||||
current->func_bool(false);
|
||||
break;
|
||||
case CMDLINE_INTPTR: {
|
||||
int base = 10;
|
||||
if (!strncmp(value, "0x", 2)) {
|
||||
base = 0x10;
|
||||
value += 2;
|
||||
}
|
||||
current->func_intptr(atol(value, base));
|
||||
break;
|
||||
}
|
||||
case CMDLINE_UINTPTR: {
|
||||
int base = 10;
|
||||
if (!strncmp(value, "0x", 2)) {
|
||||
base = 0x10;
|
||||
value += 2;
|
||||
}
|
||||
current->func_uintptr(atolu(value, base));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
opt++;
|
||||
}
|
||||
|
||||
if (idx >= len)
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -23,18 +23,22 @@ SECTIONS
|
||||
__ld_text_end = .;
|
||||
} :text
|
||||
|
||||
.rodata ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
.cmdline ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
__ld_rodata_start = .;
|
||||
|
||||
__ld_init_ctors_start = .;
|
||||
KEEP(*(.initgraph.ctors))
|
||||
__ld_init_ctors_end = .;
|
||||
|
||||
__ld_cmdline_start = .;
|
||||
KEEP(*(.cmdline))
|
||||
__ld_cmdline_end = .;
|
||||
} :rodata
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*)
|
||||
|
||||
/* Builtin module entry points */
|
||||
. = ALIGN(8);
|
||||
__ld_mod_entry_start = .;
|
||||
KEEP(*(.mod.entry .mod.entry.*))
|
||||
__ld_mod_entry_end = .;
|
||||
|
||||
*(.got .got.*)
|
||||
|
||||
__ld_rodata_end = .;
|
||||
@@ -43,48 +47,49 @@ SECTIONS
|
||||
.data ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
__ld_data_start = .;
|
||||
|
||||
__ld_init_start = .;
|
||||
*(.initgraph.nodes)
|
||||
__ld_init_end = .;
|
||||
|
||||
__ld_console_start = .;
|
||||
KEEP(*(.console))
|
||||
__ld_console_end = .;
|
||||
|
||||
KEEP(*(SORT_BY_NAME(.init.data.*)))
|
||||
KEEP(*(.init.data))
|
||||
*(.data .data.*)
|
||||
} :data
|
||||
|
||||
.console : {
|
||||
__ld_console_start = .;
|
||||
KEEP(*(.console))
|
||||
__ld_console_end = .;
|
||||
} :data
|
||||
|
||||
.dynsym : {
|
||||
__ld_dynsym_start = .;
|
||||
*(.dynsym)
|
||||
KEEP(*(.dynsym*))
|
||||
__ld_dynsym_end = .;
|
||||
} :data
|
||||
|
||||
.dynstr : {
|
||||
__ld_dynstr_start = .;
|
||||
*(.dynstr)
|
||||
KEEP(*(.dynstr*))
|
||||
__ld_dynstr_end = .;
|
||||
} :data
|
||||
|
||||
.dynamic : {
|
||||
*(.dynamic)
|
||||
*(.dynamic*)
|
||||
} :data :dynamic
|
||||
|
||||
.bss : {
|
||||
.bss ALIGN(CONSTANT(MAXPAGESIZE)) (NOLOAD) : {
|
||||
*(.*bss .*bss.*)
|
||||
*(COMMON)
|
||||
|
||||
/* Small stack to bootstrap the allocator */
|
||||
. = ALIGN(16);
|
||||
__ld_stack_bottom = .;
|
||||
. += 0x2000;
|
||||
__ld_stack_top = .;
|
||||
*(.*bss .*bss.*)
|
||||
*(COMMON)
|
||||
} :data
|
||||
|
||||
/* Per-CPU data block. MUST always come last. */
|
||||
.percpu ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
__ld_percpu_start = .;
|
||||
KEEP(*(.percpu))
|
||||
KEEP(*(.percpu.init .percpu.init*))
|
||||
KEEP(*(.percpu.data .percpu.data*))
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
__ld_percpu_end = .;
|
||||
|
||||
@@ -99,7 +104,7 @@ SECTIONS
|
||||
*(.eh_frame*)
|
||||
*(.rel.eh_frame*)
|
||||
*(.gcc_except_table*)
|
||||
*(.note*)
|
||||
*(.note.gnu.property)
|
||||
*(.interp)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
#include <kernel/assert.h>
|
||||
#include <kernel/cmdline.h>
|
||||
#include <kernel/common.h>
|
||||
#include <kernel/init.h>
|
||||
#include <kernel/mem.h>
|
||||
#include <kernel/print.h>
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include "limine.h"
|
||||
|
||||
[[__initdata_sorted("limine.0")]]
|
||||
static volatile uint64_t limine_start[] = LIMINE_REQUESTS_START_MARKER;
|
||||
[[__initdata_sorted("limine.1")]]
|
||||
static volatile uint64_t limine_revision[] = LIMINE_BASE_REVISION(4);
|
||||
static volatile uint64_t limine_revision[] = LIMINE_BASE_REVISION(6);
|
||||
[[__initdata_sorted("limine.2")]]
|
||||
static volatile uint64_t limine_end[] = LIMINE_REQUESTS_END_MARKER;
|
||||
|
||||
@@ -39,7 +39,7 @@ static struct boot_file files[8];
|
||||
void kernel_entry() {
|
||||
kernel_early_init();
|
||||
|
||||
struct boot_info info;
|
||||
struct boot_info info = {};
|
||||
|
||||
struct limine_memmap_response* mm_res = memmap_request.response;
|
||||
struct limine_module_response* module_res = module_request.response;
|
||||
@@ -61,6 +61,9 @@ void kernel_entry() {
|
||||
case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE:
|
||||
mem[i].usage = PHYS_RECLAIMABLE;
|
||||
break;
|
||||
case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES:
|
||||
mem[i].usage = PHYS_STATIC;
|
||||
break;
|
||||
default:
|
||||
mem[i].usage = PHYS_RESERVED;
|
||||
break;
|
||||
@@ -68,18 +71,28 @@ void kernel_entry() {
|
||||
}
|
||||
|
||||
if (module_res) {
|
||||
info.files = files;
|
||||
info.num_files = MIN(ARRAY_SIZE(files), module_res->module_count);
|
||||
for (size_t i = 0; i < info.num_files; i++) {
|
||||
files[i].data = module_res->modules[i]->address;
|
||||
files[i].data = (phys_t)module_res->modules[i]->address - hhdm_request.response->offset;
|
||||
files[i].length = module_res->modules[i]->size;
|
||||
files[i].path = module_res->modules[i]->path;
|
||||
strncpy(files[i].path, module_res->modules[i]->path, sizeof(files[i].path) - 1);
|
||||
files[i].path[sizeof(files[i].path) - 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
info.mem_map = mem;
|
||||
info.phys_base = (phys_t)exec_res->physical_base;
|
||||
info.virt_base = (virt_t)exec_res->virtual_base;
|
||||
info.hhdm_base = (virt_t)hhdm_request.response->offset;
|
||||
info.virt_base = (uintptr_t)exec_res->virtual_base;
|
||||
info.hhdm_base = (uintptr_t)hhdm_request.response->offset;
|
||||
|
||||
#if CONFIG_ACPI
|
||||
if (rsdp_request.response) {
|
||||
info.rsdp = (phys_t)rsdp_request.response->address;
|
||||
if (info.rsdp >= info.hhdm_base)
|
||||
info.rsdp -= info.hhdm_base;
|
||||
}
|
||||
#endif
|
||||
|
||||
kernel_main(&info);
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
/* SPDX-License-Identifier: 0BSD */
|
||||
|
||||
/* Copyright (C) 2022-2026 Mintsuki and contributors.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef LIMINE_H
|
||||
#define LIMINE_H 1
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Misc */
|
||||
|
||||
#ifdef LIMINE_NO_POINTERS
|
||||
# define LIMINE_PTR(TYPE) uint64_t
|
||||
#else
|
||||
# define LIMINE_PTR(TYPE) TYPE
|
||||
#endif
|
||||
|
||||
#define LIMINE_REQUESTS_START_MARKER { 0xf6b8f4b39de7d1ae, 0xfab91a6940fcb9cf, \
|
||||
0x785c6ed015d3e316, 0x181e920a7852b9d9 }
|
||||
#define LIMINE_REQUESTS_END_MARKER { 0xadc0e0531bb10d03, 0x9572709f31764c62 }
|
||||
|
||||
#define LIMINE_BASE_REVISION(N) { 0xf9562b2d5c95a6c8, 0x6a7b384944536bdc, (N) }
|
||||
|
||||
#define LIMINE_BASE_REVISION_SUPPORTED(VAR) ((VAR)[2] == 0)
|
||||
|
||||
#define LIMINE_LOADED_BASE_REVISION_VALID(VAR) ((VAR)[1] != 0x6a7b384944536bdc)
|
||||
#define LIMINE_LOADED_BASE_REVISION(VAR) ((VAR)[1])
|
||||
|
||||
#define LIMINE_COMMON_MAGIC 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b
|
||||
|
||||
struct limine_uuid {
|
||||
uint32_t a;
|
||||
uint16_t b;
|
||||
uint16_t c;
|
||||
uint8_t d[8];
|
||||
};
|
||||
|
||||
#define LIMINE_MEDIA_TYPE_GENERIC 0
|
||||
#define LIMINE_MEDIA_TYPE_OPTICAL 1
|
||||
#define LIMINE_MEDIA_TYPE_TFTP 2
|
||||
|
||||
struct limine_file {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(void *) address;
|
||||
uint64_t size;
|
||||
LIMINE_PTR(char *) path;
|
||||
LIMINE_PTR(char *) string;
|
||||
uint32_t media_type;
|
||||
uint32_t unused;
|
||||
uint8_t tftp_ipv4[4];
|
||||
uint32_t tftp_port;
|
||||
uint32_t partition_index;
|
||||
uint32_t mbr_disk_id;
|
||||
struct limine_uuid gpt_disk_uuid;
|
||||
struct limine_uuid gpt_part_uuid;
|
||||
struct limine_uuid part_uuid;
|
||||
};
|
||||
|
||||
/* Boot info */
|
||||
|
||||
#define LIMINE_BOOTLOADER_INFO_REQUEST_ID { LIMINE_COMMON_MAGIC, 0xf55038d8e2a1202f, 0x279426fcf5f59740 }
|
||||
|
||||
struct limine_bootloader_info_response {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(char *) name;
|
||||
LIMINE_PTR(char *) version;
|
||||
};
|
||||
|
||||
struct limine_bootloader_info_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_bootloader_info_response *) response;
|
||||
};
|
||||
|
||||
/* Executable command line */
|
||||
|
||||
#define LIMINE_EXECUTABLE_CMDLINE_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x4b161536e598651e, 0xb390ad4a2f1f303a }
|
||||
|
||||
struct limine_executable_cmdline_response {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(char *) cmdline;
|
||||
};
|
||||
|
||||
struct limine_executable_cmdline_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_executable_cmdline_response *) response;
|
||||
};
|
||||
|
||||
/* Firmware type */
|
||||
|
||||
#define LIMINE_FIRMWARE_TYPE_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x8c2f75d90bef28a8, 0x7045a4688eac00c3 }
|
||||
|
||||
#define LIMINE_FIRMWARE_TYPE_X86BIOS 0
|
||||
#define LIMINE_FIRMWARE_TYPE_EFI32 1
|
||||
#define LIMINE_FIRMWARE_TYPE_EFI64 2
|
||||
#define LIMINE_FIRMWARE_TYPE_SBI 3
|
||||
|
||||
struct limine_firmware_type_response {
|
||||
uint64_t revision;
|
||||
uint64_t firmware_type;
|
||||
};
|
||||
|
||||
struct limine_firmware_type_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_firmware_type_response *) response;
|
||||
};
|
||||
|
||||
/* Stack size */
|
||||
|
||||
#define LIMINE_STACK_SIZE_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x224ef0460a8e8926, 0xe1cb0fc25f46ea3d }
|
||||
|
||||
struct limine_stack_size_response {
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
struct limine_stack_size_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_stack_size_response *) response;
|
||||
uint64_t stack_size;
|
||||
};
|
||||
|
||||
/* HHDM */
|
||||
|
||||
#define LIMINE_HHDM_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x48dcf1cb8ad2b852, 0x63984e959a98244b }
|
||||
|
||||
struct limine_hhdm_response {
|
||||
uint64_t revision;
|
||||
uint64_t offset;
|
||||
};
|
||||
|
||||
struct limine_hhdm_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_hhdm_response *) response;
|
||||
};
|
||||
|
||||
/* Framebuffer */
|
||||
|
||||
#define LIMINE_FRAMEBUFFER_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x9d5827dcd881dd75, 0xa3148604f6fab11b }
|
||||
|
||||
#define LIMINE_FRAMEBUFFER_RGB 1
|
||||
|
||||
struct limine_video_mode {
|
||||
uint64_t pitch;
|
||||
uint64_t width;
|
||||
uint64_t height;
|
||||
uint16_t bpp;
|
||||
uint8_t memory_model;
|
||||
uint8_t red_mask_size;
|
||||
uint8_t red_mask_shift;
|
||||
uint8_t green_mask_size;
|
||||
uint8_t green_mask_shift;
|
||||
uint8_t blue_mask_size;
|
||||
uint8_t blue_mask_shift;
|
||||
};
|
||||
|
||||
struct limine_framebuffer {
|
||||
LIMINE_PTR(void *) address;
|
||||
uint64_t width;
|
||||
uint64_t height;
|
||||
uint64_t pitch;
|
||||
uint16_t bpp;
|
||||
uint8_t memory_model;
|
||||
uint8_t red_mask_size;
|
||||
uint8_t red_mask_shift;
|
||||
uint8_t green_mask_size;
|
||||
uint8_t green_mask_shift;
|
||||
uint8_t blue_mask_size;
|
||||
uint8_t blue_mask_shift;
|
||||
uint8_t unused[7];
|
||||
uint64_t edid_size;
|
||||
LIMINE_PTR(void *) edid;
|
||||
/* Response revision 1 */
|
||||
uint64_t mode_count;
|
||||
LIMINE_PTR(struct limine_video_mode **) modes;
|
||||
};
|
||||
|
||||
struct limine_framebuffer_response {
|
||||
uint64_t revision;
|
||||
uint64_t framebuffer_count;
|
||||
LIMINE_PTR(struct limine_framebuffer **) framebuffers;
|
||||
};
|
||||
|
||||
struct limine_framebuffer_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_framebuffer_response *) response;
|
||||
};
|
||||
|
||||
/* Flanterm FB init params */
|
||||
|
||||
#define LIMINE_FLANTERM_FB_INIT_PARAMS_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x3259399fe7c5f126, 0xe01c1c8c5db9d1a9 }
|
||||
|
||||
#define LIMINE_FLANTERM_FB_ROTATE_0 0
|
||||
#define LIMINE_FLANTERM_FB_ROTATE_90 1
|
||||
#define LIMINE_FLANTERM_FB_ROTATE_180 2
|
||||
#define LIMINE_FLANTERM_FB_ROTATE_270 3
|
||||
|
||||
struct limine_flanterm_fb_init_params {
|
||||
LIMINE_PTR(uint32_t *) canvas;
|
||||
uint64_t canvas_size;
|
||||
uint32_t ansi_colours[8];
|
||||
uint32_t ansi_bright_colours[8];
|
||||
uint32_t default_bg;
|
||||
uint32_t default_fg;
|
||||
uint32_t default_bg_bright;
|
||||
uint32_t default_fg_bright;
|
||||
LIMINE_PTR(void *) font;
|
||||
uint64_t font_width;
|
||||
uint64_t font_height;
|
||||
uint64_t font_spacing;
|
||||
uint64_t font_scale_x;
|
||||
uint64_t font_scale_y;
|
||||
uint64_t margin;
|
||||
uint64_t rotation;
|
||||
};
|
||||
|
||||
struct limine_flanterm_fb_init_params_response {
|
||||
uint64_t revision;
|
||||
uint64_t entry_count;
|
||||
LIMINE_PTR(struct limine_flanterm_fb_init_params **) entries;
|
||||
};
|
||||
|
||||
struct limine_flanterm_fb_init_params_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_flanterm_fb_init_params_response *) response;
|
||||
};
|
||||
|
||||
/* Paging mode */
|
||||
|
||||
#define LIMINE_PAGING_MODE_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x95c1a0edab0944cb, 0xa4e5cb3842f7488a }
|
||||
|
||||
#define LIMINE_PAGING_MODE_X86_64_4LVL 0
|
||||
#define LIMINE_PAGING_MODE_X86_64_5LVL 1
|
||||
#define LIMINE_PAGING_MODE_X86_64_MIN LIMINE_PAGING_MODE_X86_64_4LVL
|
||||
#define LIMINE_PAGING_MODE_X86_64_DEFAULT LIMINE_PAGING_MODE_X86_64_4LVL
|
||||
|
||||
#define LIMINE_PAGING_MODE_AARCH64_4LVL 0
|
||||
#define LIMINE_PAGING_MODE_AARCH64_5LVL 1
|
||||
#define LIMINE_PAGING_MODE_AARCH64_MIN LIMINE_PAGING_MODE_AARCH64_4LVL
|
||||
#define LIMINE_PAGING_MODE_AARCH64_DEFAULT LIMINE_PAGING_MODE_AARCH64_4LVL
|
||||
|
||||
#define LIMINE_PAGING_MODE_RISCV_SV39 0
|
||||
#define LIMINE_PAGING_MODE_RISCV_SV48 1
|
||||
#define LIMINE_PAGING_MODE_RISCV_SV57 2
|
||||
#define LIMINE_PAGING_MODE_RISCV_MIN LIMINE_PAGING_MODE_RISCV_SV39
|
||||
#define LIMINE_PAGING_MODE_RISCV_DEFAULT LIMINE_PAGING_MODE_RISCV_SV48
|
||||
|
||||
#define LIMINE_PAGING_MODE_LOONGARCH_4LVL 0
|
||||
#define LIMINE_PAGING_MODE_LOONGARCH_MIN LIMINE_PAGING_MODE_LOONGARCH_4LVL
|
||||
#define LIMINE_PAGING_MODE_LOONGARCH_DEFAULT LIMINE_PAGING_MODE_LOONGARCH_4LVL
|
||||
|
||||
struct limine_paging_mode_response {
|
||||
uint64_t revision;
|
||||
uint64_t mode;
|
||||
};
|
||||
|
||||
struct limine_paging_mode_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_paging_mode_response *) response;
|
||||
uint64_t mode;
|
||||
uint64_t max_mode;
|
||||
uint64_t min_mode;
|
||||
};
|
||||
|
||||
/* MP */
|
||||
|
||||
#define LIMINE_MP_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x95a67b819a1b857e, 0xa0b61b723b6a73e0 }
|
||||
|
||||
struct limine_mp_info;
|
||||
|
||||
typedef void (*limine_goto_address)(struct limine_mp_info *);
|
||||
|
||||
#if defined (__x86_64__) || defined (__i386__)
|
||||
|
||||
#define LIMINE_MP_RESPONSE_X86_64_X2APIC (1 << 0)
|
||||
|
||||
struct limine_mp_info {
|
||||
uint32_t processor_id;
|
||||
uint32_t lapic_id;
|
||||
uint64_t reserved;
|
||||
LIMINE_PTR(limine_goto_address) goto_address;
|
||||
uint64_t extra_argument;
|
||||
};
|
||||
|
||||
struct limine_mp_response {
|
||||
uint64_t revision;
|
||||
uint32_t flags;
|
||||
uint32_t bsp_lapic_id;
|
||||
uint64_t cpu_count;
|
||||
LIMINE_PTR(struct limine_mp_info **) cpus;
|
||||
};
|
||||
|
||||
#elif defined (__aarch64__)
|
||||
|
||||
struct limine_mp_info {
|
||||
uint32_t processor_id;
|
||||
uint32_t reserved1;
|
||||
uint64_t mpidr;
|
||||
uint64_t reserved;
|
||||
LIMINE_PTR(limine_goto_address) goto_address;
|
||||
uint64_t extra_argument;
|
||||
};
|
||||
|
||||
struct limine_mp_response {
|
||||
uint64_t revision;
|
||||
uint64_t flags;
|
||||
uint64_t bsp_mpidr;
|
||||
uint64_t cpu_count;
|
||||
LIMINE_PTR(struct limine_mp_info **) cpus;
|
||||
};
|
||||
|
||||
#elif defined (__riscv) && (__riscv_xlen == 64)
|
||||
|
||||
struct limine_mp_info {
|
||||
uint64_t processor_id;
|
||||
uint64_t hartid;
|
||||
uint64_t reserved;
|
||||
LIMINE_PTR(limine_goto_address) goto_address;
|
||||
uint64_t extra_argument;
|
||||
};
|
||||
|
||||
struct limine_mp_response {
|
||||
uint64_t revision;
|
||||
uint64_t flags;
|
||||
uint64_t bsp_hartid;
|
||||
uint64_t cpu_count;
|
||||
LIMINE_PTR(struct limine_mp_info **) cpus;
|
||||
};
|
||||
|
||||
#elif defined (__loongarch__) && (__loongarch_grlen == 64)
|
||||
|
||||
struct limine_mp_info {
|
||||
uint64_t processor_id;
|
||||
uint64_t phys_id;
|
||||
uint64_t reserved;
|
||||
LIMINE_PTR(limine_goto_address) goto_address;
|
||||
uint64_t extra_argument;
|
||||
};
|
||||
|
||||
struct limine_mp_response {
|
||||
uint64_t revision;
|
||||
uint64_t flags;
|
||||
uint64_t bsp_phys_id;
|
||||
uint64_t cpu_count;
|
||||
LIMINE_PTR(struct limine_mp_info **) cpus;
|
||||
};
|
||||
|
||||
#else
|
||||
#error Unknown architecture
|
||||
#endif
|
||||
|
||||
#define LIMINE_MP_REQUEST_X86_64_X2APIC (1 << 0)
|
||||
|
||||
struct limine_mp_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_mp_response *) response;
|
||||
uint64_t flags;
|
||||
};
|
||||
|
||||
/* Memory map */
|
||||
|
||||
#define LIMINE_MEMMAP_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x67cf3d9d378a806f, 0xe304acdfc50c3c62 }
|
||||
|
||||
#define LIMINE_MEMMAP_USABLE 0
|
||||
#define LIMINE_MEMMAP_RESERVED 1
|
||||
#define LIMINE_MEMMAP_ACPI_RECLAIMABLE 2
|
||||
#define LIMINE_MEMMAP_ACPI_NVS 3
|
||||
#define LIMINE_MEMMAP_BAD_MEMORY 4
|
||||
#define LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE 5
|
||||
#define LIMINE_MEMMAP_EXECUTABLE_AND_MODULES 6
|
||||
#define LIMINE_MEMMAP_FRAMEBUFFER 7
|
||||
#define LIMINE_MEMMAP_RESERVED_MAPPED 8
|
||||
|
||||
struct limine_memmap_entry {
|
||||
uint64_t base;
|
||||
uint64_t length;
|
||||
uint64_t type;
|
||||
};
|
||||
|
||||
struct limine_memmap_response {
|
||||
uint64_t revision;
|
||||
uint64_t entry_count;
|
||||
LIMINE_PTR(struct limine_memmap_entry **) entries;
|
||||
};
|
||||
|
||||
struct limine_memmap_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_memmap_response *) response;
|
||||
};
|
||||
|
||||
/* Entry point */
|
||||
|
||||
#define LIMINE_ENTRY_POINT_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a }
|
||||
|
||||
typedef void (*limine_entry_point)(void);
|
||||
|
||||
struct limine_entry_point_response {
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
struct limine_entry_point_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_entry_point_response *) response;
|
||||
LIMINE_PTR(limine_entry_point) entry;
|
||||
};
|
||||
|
||||
/* Executable File */
|
||||
|
||||
#define LIMINE_EXECUTABLE_FILE_REQUEST_ID { LIMINE_COMMON_MAGIC, 0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69 }
|
||||
|
||||
struct limine_executable_file_response {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_file *) executable_file;
|
||||
};
|
||||
|
||||
struct limine_executable_file_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_executable_file_response *) response;
|
||||
};
|
||||
|
||||
/* Module */
|
||||
|
||||
#define LIMINE_MODULE_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x3e7e279702be32af, 0xca1c4f3bd1280cee }
|
||||
|
||||
#define LIMINE_INTERNAL_MODULE_REQUIRED (1 << 0)
|
||||
#define LIMINE_INTERNAL_MODULE_COMPRESSED (1 << 1)
|
||||
|
||||
struct limine_internal_module {
|
||||
LIMINE_PTR(const char *) path;
|
||||
LIMINE_PTR(const char *) string;
|
||||
uint64_t flags;
|
||||
};
|
||||
|
||||
struct limine_module_response {
|
||||
uint64_t revision;
|
||||
uint64_t module_count;
|
||||
LIMINE_PTR(struct limine_file **) modules;
|
||||
};
|
||||
|
||||
struct limine_module_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_module_response *) response;
|
||||
|
||||
/* Request revision 1 */
|
||||
uint64_t internal_module_count;
|
||||
LIMINE_PTR(struct limine_internal_module **) internal_modules;
|
||||
};
|
||||
|
||||
/* RSDP */
|
||||
|
||||
#define LIMINE_RSDP_REQUEST_ID { LIMINE_COMMON_MAGIC, 0xc5e77b6b397e7b43, 0x27637845accdcf3c }
|
||||
|
||||
struct limine_rsdp_response {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(void *) address;
|
||||
};
|
||||
|
||||
struct limine_rsdp_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_rsdp_response *) response;
|
||||
};
|
||||
|
||||
/* SMBIOS */
|
||||
|
||||
#define LIMINE_SMBIOS_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x9e9046f11e095391, 0xaa4a520fefbde5ee }
|
||||
|
||||
struct limine_smbios_response {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(void *) entry_32;
|
||||
LIMINE_PTR(void *) entry_64;
|
||||
};
|
||||
|
||||
struct limine_smbios_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_smbios_response *) response;
|
||||
};
|
||||
|
||||
/* EFI system table */
|
||||
|
||||
#define LIMINE_EFI_SYSTEM_TABLE_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x5ceba5163eaaf6d6, 0x0a6981610cf65fcc }
|
||||
|
||||
struct limine_efi_system_table_response {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(void *) address;
|
||||
};
|
||||
|
||||
struct limine_efi_system_table_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_efi_system_table_response *) response;
|
||||
};
|
||||
|
||||
/* TPM event log */
|
||||
|
||||
#define LIMINE_TPM_EVENT_LOG_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x98e094fc7e76e979, 0xee8d8775c54e1d1f }
|
||||
|
||||
#define LIMINE_TPM_EVENT_LOG_FORMAT_TCG_1_2 1
|
||||
#define LIMINE_TPM_EVENT_LOG_FORMAT_TCG_2 2
|
||||
|
||||
struct limine_tpm_event_log_response {
|
||||
uint64_t revision;
|
||||
uint64_t format;
|
||||
uint64_t size;
|
||||
LIMINE_PTR(void *) address;
|
||||
};
|
||||
|
||||
struct limine_tpm_event_log_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_tpm_event_log_response *) response;
|
||||
};
|
||||
|
||||
/* EFI memory map */
|
||||
|
||||
#define LIMINE_EFI_MEMMAP_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x7df62a431d6872d5, 0xa4fcdfb3e57306c8 }
|
||||
|
||||
struct limine_efi_memmap_response {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(void *) memmap;
|
||||
uint64_t memmap_size;
|
||||
uint64_t desc_size;
|
||||
uint64_t desc_version;
|
||||
};
|
||||
|
||||
struct limine_efi_memmap_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_efi_memmap_response *) response;
|
||||
};
|
||||
|
||||
/* Date at boot */
|
||||
|
||||
#define LIMINE_DATE_AT_BOOT_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x502746e184c088aa, 0xfbc5ec83e6327893 }
|
||||
|
||||
struct limine_date_at_boot_response {
|
||||
uint64_t revision;
|
||||
int64_t timestamp;
|
||||
};
|
||||
|
||||
struct limine_date_at_boot_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_date_at_boot_response *) response;
|
||||
};
|
||||
|
||||
/* Executable address */
|
||||
|
||||
#define LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x71ba76863cc55f63, 0xb2644a48c516a487 }
|
||||
|
||||
struct limine_executable_address_response {
|
||||
uint64_t revision;
|
||||
uint64_t physical_base;
|
||||
uint64_t virtual_base;
|
||||
};
|
||||
|
||||
struct limine_executable_address_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_executable_address_response *) response;
|
||||
};
|
||||
|
||||
/* Device Tree Blob */
|
||||
|
||||
#define LIMINE_DTB_REQUEST_ID { LIMINE_COMMON_MAGIC, 0xb40ddb48fb54bac7, 0x545081493f81ffb7 }
|
||||
|
||||
struct limine_dtb_response {
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(void *) dtb_ptr;
|
||||
};
|
||||
|
||||
struct limine_dtb_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_dtb_response *) response;
|
||||
};
|
||||
|
||||
/* RISC-V Boot Hart ID */
|
||||
|
||||
#define LIMINE_RISCV_BSP_HARTID_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x1369359f025525f9, 0x2ff2a56178391bb6 }
|
||||
|
||||
struct limine_riscv_bsp_hartid_response {
|
||||
uint64_t revision;
|
||||
uint64_t bsp_hartid;
|
||||
};
|
||||
|
||||
struct limine_riscv_bsp_hartid_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_riscv_bsp_hartid_response *) response;
|
||||
};
|
||||
|
||||
/* Bootloader Performance */
|
||||
|
||||
#define LIMINE_BOOTLOADER_PERFORMANCE_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x6b50ad9bf36d13ad, 0xdc4c7e88fc759e17 }
|
||||
|
||||
struct limine_bootloader_performance_response {
|
||||
uint64_t revision;
|
||||
uint64_t reset_usec;
|
||||
uint64_t init_usec;
|
||||
uint64_t exec_usec;
|
||||
};
|
||||
|
||||
struct limine_bootloader_performance_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_bootloader_performance_response *) response;
|
||||
};
|
||||
|
||||
#define LIMINE_X86_64_KEEP_IOMMU_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x8ebaabe51f490179, 0x2aa86a59ffb4ab0f }
|
||||
|
||||
struct limine_x86_64_keep_iommu_response {
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
struct limine_x86_64_keep_iommu_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_x86_64_keep_iommu_response *) response;
|
||||
};
|
||||
|
||||
/* TSC (Timestamp Counter) Frequency */
|
||||
|
||||
#define LIMINE_TSC_FREQUENCY_REQUEST_ID { LIMINE_COMMON_MAGIC, 0x10f2ee1d87d195e4, 0xf747a2b78f6ddb31 }
|
||||
|
||||
struct limine_tsc_frequency_response {
|
||||
uint64_t revision;
|
||||
uint64_t frequency;
|
||||
};
|
||||
|
||||
struct limine_tsc_frequency_request {
|
||||
uint64_t id[4];
|
||||
uint64_t revision;
|
||||
LIMINE_PTR(struct limine_tsc_frequency_response *) response;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <zinnia/elf.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/module.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <zinnia/vmo.h>
|
||||
#include <config.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if CONFIG_ACPI
|
||||
#include <zinnia/acpi.h>
|
||||
#endif
|
||||
|
||||
static const char zinnia_banner[] = "Zinnia " ZINNIA_VERSION " " CONFIG_KERNEL_FLAVOR " (" ZINNIA_ARCH
|
||||
", " ZINNIA_COMPILER_ID ", " ZINNIA_LINKER_ID ")";
|
||||
|
||||
static void kernel_main_task(uintptr_t arg0) {
|
||||
struct boot_info* info = (struct boot_info*)arg0;
|
||||
|
||||
log("main: Hello from main task!\n");
|
||||
|
||||
// Initialize built-in modules.
|
||||
module_init();
|
||||
|
||||
assert(info->num_files >= 1, "No init executable provided");
|
||||
|
||||
struct vma* init_space;
|
||||
assert(vma_new(&init_space) == EOK, "");
|
||||
|
||||
struct boot_file* file_data = &info->files[0];
|
||||
struct paged_vmo* init_file;
|
||||
assert(vmo_new_phys(&init_file) == EOK, "");
|
||||
vmo_write(&init_file->object, 0, HHDM_PTR(file_data->data), file_data->length, nullptr);
|
||||
|
||||
// Load init.
|
||||
const char* init_argv[] = {"init", nullptr};
|
||||
const char* init_envp[] = {nullptr};
|
||||
|
||||
struct exec_info exec = {
|
||||
.space = init_space,
|
||||
.file_obj = &init_file->object,
|
||||
.argv = init_argv,
|
||||
.envp = init_envp,
|
||||
};
|
||||
|
||||
struct task* init_task = nullptr;
|
||||
errno_t status = elf_load(&exec, &init_task);
|
||||
vmo_unref(&init_file->object);
|
||||
|
||||
assert(status == 0, "Failed to load init process (%i)\n", status);
|
||||
sched_add_task(&percpu_get()->sched, init_task);
|
||||
|
||||
while (true) {
|
||||
irq_wait();
|
||||
}
|
||||
}
|
||||
|
||||
[[__init]]
|
||||
void kernel_early_init() {
|
||||
percpu_bsp_init();
|
||||
percpu_get()->online = true;
|
||||
irq_lock();
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
void kernel_main(struct boot_info* info) {
|
||||
// TODO: Initialize in-memory log ring.
|
||||
kprintf("%s\n", zinnia_banner); // Say hello!
|
||||
kprintf("Command line: %s\n", info->cmdline);
|
||||
|
||||
cmdline_parse(info->cmdline);
|
||||
console_init();
|
||||
kprintf("\ec");
|
||||
|
||||
mem_init(info->mem_map, info->num_mem_maps, info->virt_base, info->phys_base, info->hhdm_base);
|
||||
|
||||
#if CONFIG_ACPI
|
||||
acpi_init(info);
|
||||
#endif
|
||||
|
||||
percpu_init();
|
||||
sched_init(&percpu_get()->sched);
|
||||
|
||||
struct task* main_task;
|
||||
task_create("main", &kernel_vma, kernel_main_task, (uintptr_t)info, &main_task);
|
||||
sched_add_task(&percpu_get()->sched, main_task);
|
||||
|
||||
irq_unlock();
|
||||
sched_yield(&percpu_get()->sched);
|
||||
|
||||
unreachable();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
menu "Kernel Core options"
|
||||
|
||||
source "core/mem/Kconfig"
|
||||
|
||||
choice SCHED_ALG
|
||||
prompt "Scheduler algorithm"
|
||||
default SCHED_DUMB
|
||||
help
|
||||
The algorithm to use for scheduling.
|
||||
|
||||
config SCHED_DUMB
|
||||
bool "Dumb Scheduler"
|
||||
help
|
||||
Very basic scheduler, without any priorities.
|
||||
|
||||
endchoice
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,65 @@
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static struct clock* active_clock = nullptr;
|
||||
static uint64_t counter_base = 0;
|
||||
static struct spinlock lock = {};
|
||||
|
||||
static uint64_t clock_get_elapsed_ns_locked() {
|
||||
if (__unlikely(!active_clock))
|
||||
return 0;
|
||||
|
||||
return counter_base + active_clock->get_elapsed_ns(active_clock);
|
||||
}
|
||||
|
||||
bool clock_available() {
|
||||
spin_lock(&lock);
|
||||
bool available = active_clock != nullptr;
|
||||
spin_unlock(&lock);
|
||||
return available;
|
||||
}
|
||||
|
||||
uint64_t clock_get_elapsed_ns() {
|
||||
spin_lock(&lock);
|
||||
uint64_t elapsed = clock_get_elapsed_ns_locked();
|
||||
spin_unlock(&lock);
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
bool clock_switch(struct clock* clock) {
|
||||
spin_lock(&lock);
|
||||
|
||||
if (active_clock) {
|
||||
if (clock->priority <= active_clock->priority) {
|
||||
spin_unlock(&lock);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t elapsed = clock_get_elapsed_ns_locked();
|
||||
clock->reset(clock);
|
||||
|
||||
counter_base = elapsed;
|
||||
active_clock = clock;
|
||||
|
||||
spin_unlock(&lock);
|
||||
|
||||
log("clock: Switching to clock source \"%s\"\n", clock->name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void clock_spin_ns(uint64_t ns) {
|
||||
if (!clock_available()) {
|
||||
log("clock: Unable to sleep for %lu ns, no clock available!\n", ns);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t target = clock_get_elapsed_ns() + ns;
|
||||
while (clock_get_elapsed_ns() < target) {
|
||||
// TODO: Maybe pause?
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
#include <kernel/cmdline.h>
|
||||
#include <kernel/console.h>
|
||||
#include <kernel/print.h>
|
||||
#include <kernel/syscalls.h>
|
||||
#include <string.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/string.h>
|
||||
|
||||
extern struct console* const __ld_console_start[];
|
||||
extern struct console* const __ld_console_end[];
|
||||
@@ -28,7 +27,7 @@ static void console_setup(const char* value) {
|
||||
}
|
||||
}
|
||||
|
||||
CMDLINE_OPTION("console", console_setup);
|
||||
CMDLINE_OPTION("console", "Set a default console", console_setup);
|
||||
|
||||
void console_init() {
|
||||
// If we have no console set and at least one set, use that as a default.
|
||||
@@ -36,9 +35,11 @@ void console_init() {
|
||||
active = __ld_console_start[0];
|
||||
|
||||
if (active) {
|
||||
if (active->init(active) == false)
|
||||
if (active->init(active) == false) {
|
||||
active = nullptr;
|
||||
else
|
||||
kprintf("Logging on console \"%s\"\n", active->name);
|
||||
return;
|
||||
}
|
||||
|
||||
log("console: Logging on console \"%s\"\n", active->name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <zinnia/dpc.h>
|
||||
|
||||
// TODO
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/list.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <arch/irq.h>
|
||||
|
||||
[[__percpu]]
|
||||
struct irq_percpu irq_data;
|
||||
|
||||
bool irq_set_state(bool state) {
|
||||
return arch_irq_set_state(state);
|
||||
}
|
||||
|
||||
bool irq_get_state() {
|
||||
return arch_irq_get_state();
|
||||
}
|
||||
|
||||
void irq_wait() {
|
||||
arch_irq_wait();
|
||||
}
|
||||
|
||||
void irq_lock() {
|
||||
bool prev = arch_irq_set_state(false);
|
||||
struct irq_percpu* state = percpu_ref(irq_data);
|
||||
if (state->depth++ == 0)
|
||||
state->saved_state = prev;
|
||||
}
|
||||
|
||||
void irq_unlock() {
|
||||
struct irq_percpu* state = percpu_ref(irq_data);
|
||||
if (--state->depth == 0 && state->saved_state)
|
||||
arch_irq_set_state(true);
|
||||
}
|
||||
|
||||
void irq_line_program(struct irq_line* line, enum irq_trigger_mode trigger, enum irq_polarity polarity) {
|
||||
irq_lock();
|
||||
line->mode = line->set_config(line, trigger, polarity);
|
||||
irq_unlock();
|
||||
}
|
||||
|
||||
void irq_line_raise(struct irq_line* line) {
|
||||
assert(!arch_irq_get_state(), "Raised an IRQ line with interrupts enabled");
|
||||
|
||||
irq_lock();
|
||||
line->is_busy = true;
|
||||
|
||||
bool claimed = false;
|
||||
struct irq_handler* handler;
|
||||
list_foreach(handler, &line->handlers, next) {
|
||||
if (handler->raise(handler) == IRQ_STATUS_HANDLED)
|
||||
claimed = true;
|
||||
}
|
||||
|
||||
if (!claimed)
|
||||
log("irq: Spurious interrupt\n");
|
||||
|
||||
if ((line->mode & IRQ_MODE_EOI) && line->eoi)
|
||||
line->eoi(line);
|
||||
|
||||
line->is_busy = false;
|
||||
irq_unlock();
|
||||
}
|
||||
|
||||
void irq_line_attach(struct irq_line* line, struct irq_handler* handler) {
|
||||
irq_lock();
|
||||
list_insert_head(&line->handlers, handler, next);
|
||||
irq_unlock();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Freestanding libc functions that are needed in the kernel
|
||||
|
||||
kernel_sources += files(
|
||||
'runtime.c',
|
||||
'stdlib.c',
|
||||
'string.c',
|
||||
)
|
||||
@@ -0,0 +1,93 @@
|
||||
// Compiler runtime support.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t __udivdi3(uint64_t dividend, uint64_t divisor) {
|
||||
if (divisor == 0)
|
||||
return 0;
|
||||
|
||||
if (dividend < divisor)
|
||||
return 0;
|
||||
|
||||
unsigned shift = __builtin_clzll(divisor) - __builtin_clzll(dividend);
|
||||
|
||||
divisor <<= shift;
|
||||
|
||||
uint64_t quotient = 0;
|
||||
|
||||
for (;;) {
|
||||
if (dividend >= divisor) {
|
||||
dividend -= divisor;
|
||||
quotient |= (uint64_t)1 << shift;
|
||||
}
|
||||
|
||||
if (shift == 0)
|
||||
break;
|
||||
|
||||
shift--;
|
||||
divisor >>= 1;
|
||||
}
|
||||
|
||||
return quotient;
|
||||
}
|
||||
|
||||
static unsigned clz128(__uint128_t value) {
|
||||
uint64_t high = (uint64_t)(value >> 64);
|
||||
|
||||
if (high != 0)
|
||||
return (unsigned)__builtin_clzll(high);
|
||||
|
||||
return 64 + (unsigned)__builtin_clzll((uint64_t)value);
|
||||
}
|
||||
|
||||
__uint128_t __udivmodti4(__uint128_t dividend, __uint128_t divisor, __uint128_t* remainder) {
|
||||
if (divisor == 0) {
|
||||
if (remainder)
|
||||
*remainder = dividend;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (dividend < divisor) {
|
||||
if (remainder)
|
||||
*remainder = dividend;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned shift = clz128(divisor) - clz128(dividend);
|
||||
|
||||
divisor <<= shift;
|
||||
|
||||
__uint128_t quotient = 0;
|
||||
|
||||
for (;;) {
|
||||
if (dividend >= divisor) {
|
||||
dividend -= divisor;
|
||||
quotient |= (__uint128_t)1 << shift;
|
||||
}
|
||||
|
||||
if (shift == 0)
|
||||
break;
|
||||
|
||||
shift--;
|
||||
divisor >>= 1;
|
||||
}
|
||||
|
||||
if (remainder)
|
||||
*remainder = dividend;
|
||||
|
||||
return quotient;
|
||||
}
|
||||
|
||||
__uint128_t __udivti3(__uint128_t dividend, __uint128_t divisor) {
|
||||
return __udivmodti4(dividend, divisor, nullptr);
|
||||
}
|
||||
|
||||
__uint128_t __umodti3(__uint128_t dividend, __uint128_t divisor) {
|
||||
__uint128_t remainder;
|
||||
|
||||
__udivmodti4(dividend, divisor, &remainder);
|
||||
|
||||
return remainder;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#include <stdint.h>
|
||||
#include <zinnia/stdlib.h>
|
||||
#include <zinnia/string.h>
|
||||
|
||||
static void reverse(char* s) {
|
||||
int i, j;
|
||||
char c;
|
||||
|
||||
for (i = 0, j = strlen(s) - 1; i < j; i++, j--) {
|
||||
c = s[i];
|
||||
s[i] = s[j];
|
||||
s[j] = c;
|
||||
}
|
||||
}
|
||||
|
||||
static int char_to_digit(char c) {
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned long atolu(const char* str, int base) {
|
||||
unsigned long result = 0;
|
||||
|
||||
if (base == 16 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
|
||||
str += 2;
|
||||
}
|
||||
|
||||
while (*str) {
|
||||
int digit = char_to_digit(*str++);
|
||||
if (digit < 0 || digit >= base)
|
||||
break;
|
||||
|
||||
result = result * base + digit;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
long atol(const char* str, int base) {
|
||||
int neg = 0;
|
||||
|
||||
if (*str == '-') {
|
||||
neg = 1;
|
||||
str++;
|
||||
}
|
||||
|
||||
unsigned long val = atolu(str, base);
|
||||
return neg ? -(long)val : (long)val;
|
||||
}
|
||||
|
||||
char* lutoa(uint64_t value, char* str, int base) {
|
||||
if (base < 2 || base > 36)
|
||||
return nullptr;
|
||||
|
||||
size_t i = 0;
|
||||
|
||||
do {
|
||||
uint64_t digit = value % base;
|
||||
str[i++] = (digit < 10) ? '0' + digit : 'a' + (digit - 10);
|
||||
value /= base;
|
||||
} while (value);
|
||||
|
||||
str[i] = '\0';
|
||||
reverse(str);
|
||||
return str;
|
||||
}
|
||||
|
||||
char* ltoa(int64_t value, char* str, int base) {
|
||||
if (base < 2 || base > 36)
|
||||
return nullptr;
|
||||
|
||||
uint64_t magnitude;
|
||||
int negative = 0;
|
||||
|
||||
if (value < 0) {
|
||||
negative = 1;
|
||||
magnitude = (uint64_t)(-(value + 1)) + 1;
|
||||
// avoids INT64_MIN overflow
|
||||
} else {
|
||||
magnitude = (uint64_t)value;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
|
||||
do {
|
||||
uint64_t digit = magnitude % base;
|
||||
str[i++] = (digit < 10) ? '0' + digit : 'a' + (digit - 10);
|
||||
magnitude /= base;
|
||||
} while (magnitude);
|
||||
|
||||
if (negative)
|
||||
str[i++] = '-';
|
||||
|
||||
str[i] = '\0';
|
||||
reverse(str);
|
||||
return str;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <kernel/common.h>
|
||||
#include <kernel/compiler.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <zinnia/string.h>
|
||||
|
||||
[[__hot]]
|
||||
size_t strlen(const char* str) {
|
||||
@@ -24,15 +24,14 @@ size_t strnlen(const char* str, size_t len) {
|
||||
|
||||
[[__hot]]
|
||||
int memcmp(const void* s1, const void* s2, size_t size) {
|
||||
int diff = 0;
|
||||
char* s1ptr = (char*)s1;
|
||||
char* s2ptr = (char*)s2;
|
||||
const uint8_t* s1ptr = s1;
|
||||
const uint8_t* s2ptr = s2;
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (s1ptr[i] != s2ptr[i])
|
||||
diff++;
|
||||
return s1ptr[i] - s2ptr[i];
|
||||
}
|
||||
return diff;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[__hot]]
|
||||
@@ -103,7 +102,7 @@ void* memset(void* dest, int value, size_t n) {
|
||||
}
|
||||
|
||||
size_t* qword_dest = (size_t*)d;
|
||||
size_t qword_value = value;
|
||||
size_t qword_value = (uint8_t)value;
|
||||
qword_value |= (qword_value << 8);
|
||||
qword_value |= (qword_value << 16);
|
||||
qword_value |= (qword_value << 32);
|
||||
@@ -126,8 +125,10 @@ void* memset(void* dest, int value, size_t n) {
|
||||
}
|
||||
|
||||
char* strncpy(char* restrict dst, const char* restrict src, size_t len) {
|
||||
size_t src_len = strnlen(src, len) + 1;
|
||||
return memcpy(dst, src, MIN(len, src_len));
|
||||
size_t src_len = strnlen(src, len);
|
||||
memcpy(dst, src, src_len);
|
||||
memset(dst + src_len, 0, len - src_len);
|
||||
return dst;
|
||||
}
|
||||
|
||||
int strncmp(const char* str1, const char* str2, size_t len) {
|
||||
@@ -0,0 +1,8 @@
|
||||
menu "Memory Management options"
|
||||
|
||||
config HAVE_MMU
|
||||
bool
|
||||
config HAVE_MMU_GENERIC
|
||||
bool
|
||||
|
||||
endmenu
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <zinnia/string.h>
|
||||
|
||||
extern uint8_t __ld_text_start[];
|
||||
extern uint8_t __ld_text_end[];
|
||||
extern uint8_t __ld_rodata_start[];
|
||||
extern uint8_t __ld_rodata_end[];
|
||||
extern uint8_t __ld_data_start[];
|
||||
extern uint8_t __ld_data_end[];
|
||||
extern uint8_t __ld_kernel_start[];
|
||||
|
||||
struct vma kernel_vma = {0};
|
||||
|
||||
[[__init]]
|
||||
void mem_init(struct phys_mem* map, size_t map_len, uintptr_t kernel_virt, phys_t kernel_phys, uintptr_t tmp_hhdm) {
|
||||
// This function creates a kernel page table and initializes all memory managers.
|
||||
|
||||
const size_t pgsz = arch_mem_page_size();
|
||||
|
||||
log("mem: Memory map:\n");
|
||||
for (size_t i = 0; i < map_len; i++) {
|
||||
if (map[i].length == 0)
|
||||
continue;
|
||||
|
||||
const char* label = nullptr;
|
||||
switch (map[i].usage) {
|
||||
case PHYS_RESERVED:
|
||||
label = "Reserved";
|
||||
break;
|
||||
case PHYS_USABLE:
|
||||
label = "Usable";
|
||||
break;
|
||||
case PHYS_STATIC:
|
||||
label = "Static";
|
||||
break;
|
||||
case PHYS_RECLAIMABLE:
|
||||
label = "Reclaimable";
|
||||
break;
|
||||
}
|
||||
|
||||
log("mem: [%p - %p] %s\n", (void*)map[i].address, (void*)(map[i].address + map[i].length - 1), label);
|
||||
}
|
||||
|
||||
// Set up the bootstrap allocator.
|
||||
struct phys_mem* largest = map;
|
||||
for (size_t i = 0; i < map_len; i++) {
|
||||
if (map[i].length > largest->length && map[i].usage == PHYS_USABLE)
|
||||
largest = &map[i];
|
||||
}
|
||||
mem_phys_bootstrap(largest);
|
||||
log("mem: Using region [%p - %p] for bootstrap allocator\n",
|
||||
(void*)largest->address,
|
||||
(void*)(largest->address + largest->length - 1));
|
||||
|
||||
// Set the HHDM base address to the address given by the loader.
|
||||
// We must not keep any virtual addresses to this region,
|
||||
// since we're likely going to map it at a different base address.
|
||||
mem_hhdm_base = tmp_hhdm;
|
||||
|
||||
assert(pmap_new_kernel(&kernel_vma.pmap, 0) == 0, "Unable to allocate the kernel page table\n");
|
||||
tailq_init(&kernel_vma.mappings);
|
||||
|
||||
// text
|
||||
log("mem: Mapping text segment at %p\n", __ld_text_start);
|
||||
for (uint8_t* p = __ld_text_start; p < __ld_text_end; p += pgsz) {
|
||||
errno_t status = pmap_map(
|
||||
&kernel_vma.pmap,
|
||||
(uintptr_t)p,
|
||||
(phys_t)(p - __ld_kernel_start + kernel_phys),
|
||||
PTE_READ | PTE_EXEC,
|
||||
CACHE_ANY
|
||||
);
|
||||
assert(!status, "Failed to map %p with error %i\n", p, status);
|
||||
}
|
||||
|
||||
// rodata
|
||||
log("mem: Mapping rodata segment at %p\n", __ld_rodata_start);
|
||||
for (uint8_t* p = __ld_rodata_start; p < __ld_rodata_end; p += pgsz) {
|
||||
errno_t status = pmap_map(
|
||||
&kernel_vma.pmap,
|
||||
(uintptr_t)p,
|
||||
(phys_t)(p - __ld_kernel_start + kernel_phys),
|
||||
PTE_READ,
|
||||
CACHE_ANY
|
||||
);
|
||||
assert(!status, "Failed to map %p with error %i\n", p, status);
|
||||
}
|
||||
|
||||
// data
|
||||
log("mem: Mapping data segment at %p\n", __ld_data_start);
|
||||
for (uint8_t* p = __ld_data_start; p < __ld_data_end; p += pgsz) {
|
||||
errno_t status = pmap_map(
|
||||
&kernel_vma.pmap,
|
||||
(uintptr_t)p,
|
||||
(phys_t)(p - __ld_kernel_start + kernel_phys),
|
||||
PTE_READ | PTE_WRITE,
|
||||
CACHE_ANY
|
||||
);
|
||||
assert(!status, "Failed to map %p with error %i\n", p, status);
|
||||
}
|
||||
|
||||
// The kernel address space is divided into 4 segments.
|
||||
// For example, they could live at:
|
||||
// HHDM FFFF'8000'0000'0000
|
||||
// PFNDB FFFF'A000'0000'0000
|
||||
// Mappings FFFF'C000'0000'0000
|
||||
// Kernel FFFF'FFFF'8000'0000
|
||||
|
||||
// Map all physical memory to the HHDM address.
|
||||
tmp_hhdm = arch_mem_hhdm_addr();
|
||||
for (size_t i = 0; i < map_len; i++) {
|
||||
if (map[i].usage != PHYS_USABLE && map[i].usage != PHYS_STATIC)
|
||||
continue;
|
||||
for (size_t p = 0; p < map[i].length; p += pgsz) {
|
||||
uintptr_t vaddr = (uintptr_t)(map[i].address + p + tmp_hhdm);
|
||||
phys_t paddr = (phys_t)(map[i].address + p);
|
||||
errno_t status = pmap_map(&kernel_vma.pmap, vaddr, paddr, PTE_READ | PTE_WRITE, CACHE_ANY);
|
||||
assert(!status, "Failed to map HHDM page %p to %p with error %i\n", (void*)vaddr, (void*)paddr, status);
|
||||
}
|
||||
}
|
||||
mem_hhdm_base = tmp_hhdm;
|
||||
|
||||
// Switch to our own page table.
|
||||
pmap_set(&kernel_vma.pmap);
|
||||
|
||||
// We record metadata for every single page of available memory in a large array.
|
||||
// This array is contiguous in virtual memory, but is sparsely populated.
|
||||
// Only those array entries which represent usable memory are mapped.
|
||||
for (size_t i = 0; i < map_len; i++) {
|
||||
if (map[i].length == 0 || map[i].usage != PHYS_USABLE)
|
||||
continue;
|
||||
|
||||
const size_t num_pages = (map[i].length + pgsz - 1) / pgsz;
|
||||
const size_t metadata_size = num_pages * sizeof(struct page);
|
||||
const uintptr_t metadata_start_vaddr = arch_mem_pfndb_addr() + (map[i].address / pgsz * sizeof(struct page));
|
||||
const uintptr_t metadata_end_vaddr = metadata_start_vaddr + metadata_size;
|
||||
|
||||
// Find the page-aligned boundaries that contain this metadata.
|
||||
const uintptr_t aligned_start = ALIGN_DOWN(metadata_start_vaddr, pgsz);
|
||||
const uintptr_t aligned_end = ALIGN_UP(metadata_end_vaddr, pgsz);
|
||||
const size_t length = aligned_end - aligned_start;
|
||||
const uintptr_t vaddr = aligned_start;
|
||||
|
||||
for (size_t page = 0; page < length; page += pgsz) {
|
||||
phys_t paddr;
|
||||
assert(!mem_phys_alloc(1, 0, &paddr), "Failed to allocate memory for PFNDB!\n");
|
||||
pmap_map(&kernel_vma.pmap, vaddr + page, paddr, PTE_READ | PTE_WRITE, CACHE_ANY);
|
||||
}
|
||||
}
|
||||
vm_pfndb = (struct page*)arch_mem_pfndb_addr();
|
||||
|
||||
// We don't need the bootstrap allocator from this point on.
|
||||
// Initialize the real page allocator.
|
||||
mem_phys_init(map, map_len);
|
||||
|
||||
slab_init();
|
||||
|
||||
log("mem: Memory initialization complete\n");
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
kernel_sources += files(
|
||||
'fault.c',
|
||||
'init.c',
|
||||
'pmm.c',
|
||||
'slab.c',
|
||||
'stack.c',
|
||||
'usercopy.c',
|
||||
'virt.c',
|
||||
'vma.c',
|
||||
'vmo.c',
|
||||
)
|
||||
|
||||
if kconfig.get('CONFIG_HAVE_MMU', 'n') == 'y'
|
||||
kernel_sources += files(
|
||||
'pmap.c',
|
||||
)
|
||||
endif
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <config.h>
|
||||
|
||||
// TODO: For now, we assume that we can do generic PTE lookups on every arch that has a MMU.
|
||||
static_assert(CONFIG_HAVE_MMU_GENERIC);
|
||||
|
||||
errno_t pmap_new_kernel(struct pmap* pt, enum alloc_flags flags) {
|
||||
flags &= ~ALLOC_NOZERO;
|
||||
|
||||
phys_t addr;
|
||||
errno_t status = mem_phys_alloc(1, flags, &addr);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
pte_t* root = HHDM_PTR(addr);
|
||||
const size_t num_entries = (size_t)1 << arch_mem_level_bits();
|
||||
for (size_t i = num_entries / 2; i < num_entries; i++) {
|
||||
phys_t dir;
|
||||
status = mem_phys_alloc(1, flags, &dir);
|
||||
if (status)
|
||||
return status;
|
||||
root[i] = pte_build(dir, PTE_DIR, CACHE_ANY);
|
||||
}
|
||||
|
||||
struct pmap result = {
|
||||
.root = addr,
|
||||
.lock = (struct spinlock){0},
|
||||
.is_user = false,
|
||||
};
|
||||
|
||||
*pt = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno_t pmap_new_user(struct pmap* pt, enum alloc_flags flags) {
|
||||
phys_t user_l1;
|
||||
errno_t status = mem_phys_alloc(1, 0, &user_l1);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
void* user_l1_ptr = HHDM_PTR(user_l1);
|
||||
void* kernel_l1_ptr = HHDM_PTR(kernel_vma.pmap.root);
|
||||
memcpy(user_l1_ptr, kernel_l1_ptr, arch_mem_page_size());
|
||||
|
||||
struct pmap result = {
|
||||
.root = user_l1,
|
||||
.lock = (struct spinlock){0},
|
||||
.is_user = true,
|
||||
};
|
||||
|
||||
*pt = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Gets a reference to the PTE at the given virtual address.
|
||||
// If `check_only` is set, only checks if the PTE exists,
|
||||
// and doesn't allocate new levels if they don't already exist.
|
||||
// If it can't allocate a page if it has to, returns `nullptr`.
|
||||
static pte_t* get_pte(struct pmap* pt, uintptr_t vaddr, bool is_user, bool check_only) {
|
||||
pte_t* current_head = HHDM_PTR(pt->root);
|
||||
size_t index = 0;
|
||||
|
||||
for (int8_t level = arch_mem_num_levels() - 1; level >= 0; level--) {
|
||||
const size_t addr_mask = (1 << arch_mem_level_bits()) - 1;
|
||||
const size_t addr_shift = arch_mem_page_bits() + (arch_mem_level_bits() * level);
|
||||
const enum pte_flags level_flags = PTE_DIR | (is_user ? PTE_USER : 0);
|
||||
|
||||
index = (vaddr >> addr_shift) & addr_mask;
|
||||
|
||||
// The last level is used to access the actual PTE, so break the loop then.
|
||||
// We still need to update the index beforehand, that's why we can't just end early.
|
||||
if (level == 0)
|
||||
break;
|
||||
|
||||
pte_t* pte = ¤t_head[index];
|
||||
|
||||
if (pte_is_present(pte)) {
|
||||
// Get the next level.
|
||||
*pte |= pte_build(0, level_flags, CACHE_ANY);
|
||||
current_head = HHDM_PTR(pte_address(pte));
|
||||
} else {
|
||||
// If the current level isn't present, we can skip the rest.
|
||||
if (check_only)
|
||||
return nullptr;
|
||||
|
||||
phys_t addr;
|
||||
if (mem_phys_alloc(1, 0, &addr) != 0)
|
||||
return nullptr;
|
||||
|
||||
*pte = pte_build(addr, level_flags, CACHE_ANY);
|
||||
current_head = HHDM_PTR(addr);
|
||||
}
|
||||
}
|
||||
|
||||
return ¤t_head[index];
|
||||
}
|
||||
|
||||
errno_t pmap_map(struct pmap* pt, uintptr_t vaddr, phys_t paddr, enum pte_flags flags, enum cache_mode cache) {
|
||||
spin_lock(&pt->lock);
|
||||
|
||||
errno_t status = 0;
|
||||
pte_t* pte = get_pte(pt, vaddr, flags & PTE_USER, false);
|
||||
if (pte == nullptr) {
|
||||
status = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
*pte = pte_build(paddr, flags, cache);
|
||||
|
||||
fail:
|
||||
spin_unlock(&pt->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
errno_t pmap_unmap(struct pmap* pt, uintptr_t vaddr) {
|
||||
spin_lock(&pt->lock);
|
||||
|
||||
errno_t status = 0;
|
||||
pte_t* pte = get_pte(pt, vaddr, false, true);
|
||||
if (pte == nullptr || !pte_is_present(pte)) {
|
||||
status = ENOENT;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pte_clear(pte);
|
||||
pte_flush(vaddr);
|
||||
|
||||
fail:
|
||||
spin_unlock(&pt->lock);
|
||||
return status;
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/virt.h>
|
||||
#include <stdint.h>
|
||||
|
||||
uintptr_t mem_hhdm_base = 0;
|
||||
|
||||
static errno_t (*alloc_fn)(size_t num_pages, enum alloc_flags flags, phys_t* out);
|
||||
static errno_t (*free_fn)(phys_t start, size_t num_pages);
|
||||
|
||||
errno_t mem_phys_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
|
||||
return alloc_fn(num_pages, flags, out);
|
||||
}
|
||||
|
||||
errno_t mem_phys_free(phys_t start, size_t num_pages) {
|
||||
return free_fn(start, num_pages);
|
||||
}
|
||||
|
||||
static struct phys_mem bump_mem = {0};
|
||||
static struct phys_mem* bump_region = nullptr;
|
||||
|
||||
static errno_t bump_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
|
||||
const size_t bytes = num_pages * arch_mem_page_size();
|
||||
if (bytes > bump_mem.length)
|
||||
return ENOMEM;
|
||||
|
||||
*out = bump_mem.address;
|
||||
bump_mem.length -= bytes;
|
||||
bump_mem.address += bytes;
|
||||
|
||||
if (!(flags & ALLOC_NOZERO)) {
|
||||
memset(HHDM_PTR(*out), 0, bytes);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static errno_t bump_free(phys_t addr, size_t num_pages) {
|
||||
// We don't free bootstrap memory.
|
||||
panic("Attempted to free bootstrap memory!");
|
||||
}
|
||||
|
||||
void mem_phys_bootstrap(struct phys_mem* mem) {
|
||||
alloc_fn = bump_alloc;
|
||||
free_fn = bump_free;
|
||||
|
||||
// Save the region by reference and value.
|
||||
// We can't directly modify the region by reference, because that
|
||||
// interferes with e.g. HHDM mapping logic.
|
||||
bump_mem = *mem;
|
||||
bump_region = mem;
|
||||
}
|
||||
|
||||
static struct page* pmm_head = nullptr;
|
||||
static struct spinlock pmm_lock = {0};
|
||||
static size_t pmm_total_free = 0;
|
||||
|
||||
static errno_t freelist_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
|
||||
spin_lock(&pmm_lock);
|
||||
|
||||
const size_t bytes = num_pages * arch_mem_page_size();
|
||||
phys_t limit = UINTPTR_MAX;
|
||||
if (flags & ALLOC_MEM20) {
|
||||
limit = (phys_t)1 << 20;
|
||||
} else if (flags & ALLOC_MEM32) {
|
||||
limit = (phys_t)1 << 32;
|
||||
}
|
||||
|
||||
struct page* it = pmm_head;
|
||||
struct page* prev_it = nullptr;
|
||||
|
||||
while (it) {
|
||||
const phys_t page_addr = (((uintptr_t)it - (uintptr_t)vm_pfndb) / sizeof(struct page)) * arch_mem_page_size();
|
||||
// If the region is too high up or doesn't have enough pages left, continue searching.
|
||||
if (__unlikely(it->freelist.count < num_pages) || page_addr + bytes > limit) {
|
||||
prev_it = it;
|
||||
it = it->freelist.next;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the current block has exactly enough pages left, consume it entirely.
|
||||
if (__unlikely(it->freelist.count == num_pages)) {
|
||||
if (prev_it)
|
||||
prev_it->freelist.next = it->freelist.next;
|
||||
else
|
||||
pmm_head = it->freelist.next;
|
||||
|
||||
it->freelist.next = nullptr;
|
||||
it->freelist.count = 0;
|
||||
|
||||
if (__likely(out))
|
||||
*out = page_addr;
|
||||
}
|
||||
// If not, we can just shrink the block.
|
||||
else if (page_addr + it->freelist.count * arch_mem_page_size() <= limit) {
|
||||
it->freelist.count -= num_pages;
|
||||
|
||||
if (__likely(out))
|
||||
*out = page_addr + (it->freelist.count * arch_mem_page_size());
|
||||
} else {
|
||||
struct page* rest = it + num_pages;
|
||||
rest->freelist.count = it->freelist.count - num_pages;
|
||||
rest->freelist.next = it->freelist.next;
|
||||
|
||||
if (prev_it)
|
||||
prev_it->freelist.next = rest;
|
||||
else
|
||||
pmm_head = rest;
|
||||
|
||||
it->freelist.next = nullptr;
|
||||
it->freelist.count = 0;
|
||||
|
||||
if (__likely(out))
|
||||
*out = page_addr;
|
||||
}
|
||||
goto success;
|
||||
}
|
||||
|
||||
spin_unlock(&pmm_lock);
|
||||
return ENOMEM;
|
||||
|
||||
success:
|
||||
// If the NOZERO flag is *not* specified, zero memory.
|
||||
if (__likely(out) && __likely(!(flags & ALLOC_NOZERO))) {
|
||||
memset(HHDM_PTR(*out), 0, bytes);
|
||||
}
|
||||
pmm_total_free -= num_pages;
|
||||
spin_unlock(&pmm_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void freelist_insert(struct page* page, size_t num_pages) {
|
||||
struct page* prev = nullptr;
|
||||
struct page* next = pmm_head;
|
||||
while (next && next < page) {
|
||||
prev = next;
|
||||
next = next->freelist.next;
|
||||
}
|
||||
|
||||
page->freelist.count = num_pages;
|
||||
page->freelist.next = next;
|
||||
|
||||
if (next && page + page->freelist.count == next) {
|
||||
page->freelist.count += next->freelist.count;
|
||||
page->freelist.next = next->freelist.next;
|
||||
next->freelist.next = nullptr;
|
||||
next->freelist.count = 0;
|
||||
}
|
||||
|
||||
if (prev && prev + prev->freelist.count == page) {
|
||||
prev->freelist.count += page->freelist.count;
|
||||
prev->freelist.next = page->freelist.next;
|
||||
page->freelist.next = nullptr;
|
||||
page->freelist.count = 0;
|
||||
} else if (prev) {
|
||||
prev->freelist.next = page;
|
||||
} else {
|
||||
pmm_head = page;
|
||||
}
|
||||
}
|
||||
|
||||
static errno_t freelist_free(phys_t addr, size_t num_pages) {
|
||||
spin_lock(&pmm_lock);
|
||||
|
||||
freelist_insert(&vm_pfndb[addr / arch_mem_page_size()], num_pages);
|
||||
pmm_total_free += num_pages;
|
||||
|
||||
spin_unlock(&pmm_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mem_phys_init(struct phys_mem* map, size_t length) {
|
||||
alloc_fn = freelist_alloc;
|
||||
free_fn = freelist_free;
|
||||
bump_region->address = bump_mem.address;
|
||||
bump_region->length = bump_mem.length;
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
// Regions smaller than a page are useless.
|
||||
if (map[i].length < arch_mem_page_size() || map[i].usage != PHYS_USABLE)
|
||||
continue;
|
||||
|
||||
const size_t num_pages = map[i].length / arch_mem_page_size();
|
||||
freelist_insert(&vm_pfndb[map[i].address / arch_mem_page_size()], num_pages);
|
||||
pmm_total_free += num_pages;
|
||||
}
|
||||
|
||||
const size_t free_bytes = (pmm_total_free * arch_mem_page_size());
|
||||
log("pmm: Total available memory: %zu MiB\n", free_bytes / 1024 / 1024);
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/string.h>
|
||||
|
||||
typedef struct {
|
||||
size_t num_pages; // Amount of pages connected to this slab.
|
||||
size_t size; // Size of this slab.
|
||||
} slab_info;
|
||||
|
||||
struct slab {
|
||||
struct spinlock lock; // Access lock.
|
||||
size_t ent_size; // Size of one entry.
|
||||
void** head;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct slab* slab;
|
||||
} slab_header;
|
||||
|
||||
// Initializes the SLAB structures.
|
||||
|
||||
static struct slab slabs[8] = {0};
|
||||
|
||||
static errno_t slab_new(struct slab* slab, size_t size) {
|
||||
// Allocate a new page for the head.
|
||||
phys_t addr;
|
||||
errno_t status = mem_phys_alloc(1, 0, &addr);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
slab->head = (void**)(addr + arch_mem_hhdm_addr());
|
||||
slab->ent_size = size;
|
||||
|
||||
const size_t offset = ALIGN_UP(sizeof(slab_header), size);
|
||||
const size_t available_size = arch_mem_page_size() - offset;
|
||||
|
||||
slab_header* ptr = (slab_header*)slab->head;
|
||||
ptr->slab = slab;
|
||||
slab->head = (void**)((void*)slab->head + offset);
|
||||
|
||||
void** arr = slab->head;
|
||||
const size_t max = available_size / size - 1;
|
||||
const size_t fact = size / sizeof(void*);
|
||||
|
||||
for (size_t i = 0; i < max; i++) {
|
||||
arr[i * fact] = &arr[(i + 1) * fact];
|
||||
}
|
||||
arr[max * fact] = nullptr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void slab_init(void) {
|
||||
slab_new(&slabs[0], 16);
|
||||
slab_new(&slabs[1], 32);
|
||||
slab_new(&slabs[2], 64);
|
||||
slab_new(&slabs[3], 128);
|
||||
slab_new(&slabs[4], 256);
|
||||
slab_new(&slabs[5], 512);
|
||||
slab_new(&slabs[6], 1024);
|
||||
slab_new(&slabs[7], 2048);
|
||||
}
|
||||
|
||||
static void* slab_do_alloc(struct slab* slab) {
|
||||
spin_lock(&slab->lock);
|
||||
|
||||
if (__unlikely(slab->head == nullptr) && slab_new(slab, slab->ent_size) != 0) {
|
||||
spin_unlock(&slab->lock);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void** old_free = slab->head;
|
||||
slab->head = *old_free;
|
||||
memset(old_free, 0, slab->ent_size);
|
||||
|
||||
spin_unlock(&slab->lock);
|
||||
return old_free;
|
||||
}
|
||||
|
||||
static void slab_do_free(struct slab* slab, void* addr) {
|
||||
spin_lock(&slab->lock);
|
||||
|
||||
if (__unlikely(addr == nullptr))
|
||||
goto cleanup;
|
||||
|
||||
void** new_head = addr;
|
||||
*new_head = slab->head;
|
||||
slab->head = new_head;
|
||||
|
||||
cleanup:
|
||||
spin_unlock(&slab->lock);
|
||||
}
|
||||
|
||||
static inline struct slab* slab_find_size(size_t size) {
|
||||
for (size_t i = 0; i < ARRAY_SIZE(slabs); i++) {
|
||||
if (slabs[i].ent_size >= size)
|
||||
return &slabs[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* mem_alloc(size_t size, enum alloc_flags flags) {
|
||||
if (__unlikely(size == 0))
|
||||
return nullptr;
|
||||
|
||||
struct slab* slab = slab_find_size(size);
|
||||
if (slab != nullptr) {
|
||||
return slab_do_alloc(slab);
|
||||
}
|
||||
|
||||
size_t num_pages = ROUND_UP(size, arch_mem_page_size());
|
||||
|
||||
// Allocate the pages plus an additional page for metadata.
|
||||
phys_t ret;
|
||||
errno_t status = mem_phys_alloc(num_pages + 1, 0, &ret);
|
||||
if (__unlikely(status != 0))
|
||||
return nullptr;
|
||||
|
||||
ret = ret + (phys_t)arch_mem_hhdm_addr();
|
||||
// Write metadata into the first page.
|
||||
slab_info* info = (slab_info*)ret;
|
||||
info->num_pages = num_pages;
|
||||
info->size = size;
|
||||
// Skip the first page and return the next one.
|
||||
return (void*)(ret + arch_mem_page_size());
|
||||
}
|
||||
|
||||
void mem_free(void* addr) {
|
||||
if (__unlikely(addr == nullptr))
|
||||
return;
|
||||
|
||||
// If the address is page aligned.
|
||||
if ((size_t)addr == ALIGN_DOWN((size_t)addr, arch_mem_page_size())) {
|
||||
slab_info* info = (slab_info*)(addr - arch_mem_page_size());
|
||||
mem_phys_free(((phys_t)info - (phys_t)arch_mem_hhdm_addr()), info->num_pages + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
slab_header* header = (slab_header*)(ALIGN_DOWN((size_t)addr, arch_mem_page_size()));
|
||||
slab_do_free(header->slab, addr);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/stack.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/virt.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <arch/mem.h>
|
||||
|
||||
errno_t stack_init(struct stack* ks, size_t size) {
|
||||
if (!ks || size == 0)
|
||||
return EINVAL;
|
||||
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
size = ALIGN_UP(size, page_size);
|
||||
|
||||
uintptr_t va = vm_alloc(size + page_size);
|
||||
if (!va)
|
||||
return ENOMEM;
|
||||
|
||||
phys_t phys;
|
||||
errno_t status = mem_phys_alloc(size / page_size, 0, &phys);
|
||||
if (status)
|
||||
goto err_vm;
|
||||
|
||||
const uintptr_t base = va + page_size;
|
||||
size_t mapped = 0;
|
||||
for (; mapped < size; mapped += page_size) {
|
||||
status = pmap_map(&kernel_vma.pmap, base + mapped, phys + mapped, PTE_READ | PTE_WRITE, CACHE_WRITE_BACK);
|
||||
if (status)
|
||||
goto err_map;
|
||||
}
|
||||
|
||||
ks->base = base;
|
||||
ks->phys = phys;
|
||||
ks->size = size;
|
||||
return 0;
|
||||
|
||||
err_map:
|
||||
for (size_t i = 0; i < mapped; i += page_size)
|
||||
pmap_unmap(&kernel_vma.pmap, base + i);
|
||||
mem_phys_free(phys, size / page_size);
|
||||
err_vm:
|
||||
vm_free(va, size + page_size);
|
||||
return status;
|
||||
}
|
||||
|
||||
void stack_destroy(struct stack* ks) {
|
||||
if (!ks || !ks->base)
|
||||
return;
|
||||
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
for (size_t i = 0; i < ks->size; i += page_size)
|
||||
pmap_unmap(&kernel_vma.pmap, ks->base + i);
|
||||
|
||||
mem_phys_free(ks->phys, ks->size / page_size);
|
||||
vm_free(ks->base - page_size, ks->size + page_size);
|
||||
|
||||
*ks = (struct stack){0};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/usercopy.h>
|
||||
#include <arch/mem.h>
|
||||
#include <arch/usercopy.h>
|
||||
|
||||
static bool is_user_range(uintptr_t addr, size_t len) {
|
||||
if (len == 0)
|
||||
return arch_mem_addr_is_user(addr);
|
||||
return addr + len > addr && arch_mem_addr_is_user(addr) && arch_mem_addr_is_user(addr + len - 1);
|
||||
}
|
||||
|
||||
bool usercopy_read(void* dst, const __user void* src, size_t len) {
|
||||
if (!is_user_range((uintptr_t)src, len))
|
||||
return false;
|
||||
|
||||
arch_usercopy_allow(true);
|
||||
bool success = arch_usercopy_read(dst, src, len, &percpu_get()->sched.current->usercopy_region);
|
||||
arch_usercopy_allow(false);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool usercopy_write(__user void* dst, const void* src, size_t len) {
|
||||
if (!is_user_range((uintptr_t)dst, len))
|
||||
return false;
|
||||
|
||||
arch_usercopy_allow(true);
|
||||
bool success = arch_usercopy_write(dst, src, len, &percpu_get()->sched.current->usercopy_region);
|
||||
arch_usercopy_allow(false);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool usercopy_strlen(const __user char* str, size_t max, size_t* len) {
|
||||
if (!arch_mem_addr_is_user((uintptr_t)str))
|
||||
return false;
|
||||
|
||||
size_t unused;
|
||||
if (len == nullptr)
|
||||
len = &unused;
|
||||
|
||||
arch_usercopy_allow(true);
|
||||
bool success = arch_usercopy_strlen(str, max, len, &percpu_get()->sched.current->usercopy_region);
|
||||
arch_usercopy_allow(false);
|
||||
return success;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/virt.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct page* vm_pfndb = nullptr;
|
||||
|
||||
// TODO: Replace with actual allocator.
|
||||
|
||||
static _Atomic(size_t) mapping_offset = 0;
|
||||
|
||||
uintptr_t vm_alloc(size_t length) {
|
||||
uintptr_t base = arch_mem_mapping_addr();
|
||||
size_t offset =
|
||||
atomic_fetch_add_explicit(&mapping_offset, ALIGN_UP(length, arch_mem_page_size()), memory_order_acquire);
|
||||
|
||||
return base + offset;
|
||||
}
|
||||
|
||||
void vm_free(uintptr_t ptr, size_t length) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
uintptr_t vm_page_addr(struct page* page) {
|
||||
return (((uintptr_t)page - (uintptr_t)vm_pfndb) / sizeof(struct page)) * arch_mem_page_size();
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/mman.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <stdckdint.h>
|
||||
|
||||
// Try to create a new, empty virtual address space.
|
||||
errno_t vma_new(struct vma** out) {
|
||||
debug_assert(out != nullptr, "Missing out");
|
||||
|
||||
errno_t res;
|
||||
if (!out)
|
||||
return EINVAL;
|
||||
|
||||
struct vma* vmspace = mem_alloc(sizeof(struct vma), 0);
|
||||
if (!vmspace)
|
||||
return ENOMEM;
|
||||
|
||||
res = pmap_new_user(&vmspace->pmap, 0);
|
||||
if (res)
|
||||
goto err1;
|
||||
|
||||
tailq_init(&vmspace->mappings);
|
||||
|
||||
*out = vmspace;
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
mem_free(vmspace);
|
||||
err0:
|
||||
return res;
|
||||
}
|
||||
|
||||
// Delete a virtual address space.
|
||||
void vma_free(struct vma* v) {
|
||||
struct vma_mapping* mapping;
|
||||
while ((mapping = tailq_first(&v->mappings)) != nullptr) {
|
||||
tailq_remove(&v->mappings, mapping, link);
|
||||
vmo_unref(mapping->object);
|
||||
mem_free(mapping);
|
||||
}
|
||||
}
|
||||
|
||||
errno_t vma_unmap(struct vma* v, uintptr_t vaddr, size_t size) {
|
||||
panic("TODO");
|
||||
}
|
||||
|
||||
errno_t vma_protect(struct vma* v, uintptr_t vaddr, size_t size, enum prot_flags flags) {
|
||||
panic("TODO");
|
||||
}
|
||||
|
||||
static struct vma_mapping* find_mapping(struct vma* v, size_t page) {
|
||||
for (struct vma_mapping* m = tailq_first(&v->mappings); m; m = tailq_next(m, link)) {
|
||||
if (page >= m->start_page && page < m->end_page)
|
||||
return m;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool can_access(enum prot_flags prot, const struct vma_fault_info* info) {
|
||||
if (info->caused_by_write)
|
||||
return prot & PROT_WRITE;
|
||||
if (info->caused_by_fetch)
|
||||
return prot & PROT_EXEC;
|
||||
return prot & (PROT_READ | PROT_WRITE);
|
||||
}
|
||||
|
||||
static enum pte_flags prot_to_pte(struct vma* v, enum prot_flags prot) {
|
||||
enum pte_flags pf = 0;
|
||||
if (prot & PROT_READ)
|
||||
pf |= PTE_READ;
|
||||
if (prot & PROT_WRITE)
|
||||
pf |= PTE_WRITE;
|
||||
if (prot & PROT_EXEC)
|
||||
pf |= PTE_EXEC;
|
||||
if (v->pmap.is_user)
|
||||
pf |= PTE_USER;
|
||||
return pf;
|
||||
}
|
||||
|
||||
bool vma_page_fault(struct vma* v, const struct vma_fault_info* info) {
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
const size_t faulty_page = info->addr / page_size;
|
||||
|
||||
spin_lock(&v->lock);
|
||||
struct vma_mapping* mapping = find_mapping(v, faulty_page);
|
||||
if (!mapping || !can_access(mapping->prot, info)) {
|
||||
spin_unlock(&v->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct vmo* object = mapping->object;
|
||||
const size_t object_page = (faulty_page - mapping->start_page) + mapping->offset_page;
|
||||
const enum prot_flags prot = mapping->prot;
|
||||
vmo_ref(object);
|
||||
spin_unlock(&v->lock);
|
||||
|
||||
struct page* page;
|
||||
if (object->get_page(object, object_page, &page)) {
|
||||
vmo_unref(object);
|
||||
return false;
|
||||
}
|
||||
|
||||
spin_lock(&v->lock);
|
||||
mapping = find_mapping(v, faulty_page);
|
||||
const bool still_valid = mapping && mapping->object == object &&
|
||||
(faulty_page - mapping->start_page) + mapping->offset_page == object_page && can_access(mapping->prot, info);
|
||||
|
||||
if (still_valid) {
|
||||
errno_t status =
|
||||
pmap_map(&v->pmap, faulty_page * page_size, vm_page_addr(page), prot_to_pte(v, prot), CACHE_ANY);
|
||||
assert(status == 0, "Failed to map a demand-loaded page\n");
|
||||
}
|
||||
spin_unlock(&v->lock);
|
||||
|
||||
vmo_unref(object);
|
||||
return true;
|
||||
}
|
||||
|
||||
errno_t vma_map(
|
||||
struct vma* vma,
|
||||
struct vmo* vmo,
|
||||
uintptr_t addr,
|
||||
size_t len,
|
||||
enum prot_flags flags,
|
||||
uintptr_t vmo_offset
|
||||
) {
|
||||
if (!vma || !vmo)
|
||||
return EINVAL;
|
||||
|
||||
uintptr_t end;
|
||||
if (ckd_add(&end, addr, len))
|
||||
return ENOMEM;
|
||||
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
if (addr % page_size != vmo_offset % page_size)
|
||||
return EINVAL;
|
||||
|
||||
struct vma_mapping* mapping = mem_alloc(sizeof(struct vma_mapping), 0);
|
||||
if (!mapping)
|
||||
return ENOMEM;
|
||||
|
||||
vmo_ref(vmo);
|
||||
mapping->start_page = addr / page_size;
|
||||
mapping->end_page = ALIGN_UP(end, page_size) / page_size;
|
||||
mapping->offset_page = vmo_offset / page_size;
|
||||
mapping->object = vmo;
|
||||
mapping->prot = flags;
|
||||
|
||||
spin_lock(&vma->lock);
|
||||
tailq_insert_head(&vma->mappings, mapping, link);
|
||||
spin_unlock(&vma->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/list.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vmo.h>
|
||||
|
||||
void vmo_ref(struct vmo* obj) {
|
||||
refcount_inc(&obj->rc);
|
||||
}
|
||||
|
||||
void vmo_unref(struct vmo* obj) {
|
||||
if (refcount_dec(&obj->rc))
|
||||
obj->destroy(obj);
|
||||
}
|
||||
|
||||
errno_t vmo_read(struct vmo* obj, uintptr_t offset, void* buf, size_t len, size_t* actual_read) {
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
size_t progress = 0;
|
||||
errno_t status = 0;
|
||||
|
||||
while (progress < len) {
|
||||
const size_t misalign = (progress + offset) % page_size;
|
||||
const size_t page_index = (progress + offset) / page_size;
|
||||
const size_t copy_size = MIN(page_size - misalign, len - progress);
|
||||
|
||||
struct page* p;
|
||||
status = obj->get_page(obj, page_index, &p);
|
||||
if (status != 0)
|
||||
break;
|
||||
|
||||
uintptr_t page_addr = vm_page_addr(p);
|
||||
|
||||
memcpy(buf + progress, HHDM_PTR(page_addr) + misalign, copy_size);
|
||||
progress += copy_size;
|
||||
}
|
||||
|
||||
if (actual_read)
|
||||
*actual_read = progress;
|
||||
return status;
|
||||
}
|
||||
|
||||
errno_t vmo_write(struct vmo* obj, uintptr_t offset, const void* buf, size_t len, size_t* actual_written) {
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
size_t progress = 0;
|
||||
errno_t status = 0;
|
||||
|
||||
while (progress < len) {
|
||||
const size_t misalign = (progress + offset) % page_size;
|
||||
const size_t page_index = (progress + offset) / page_size;
|
||||
const size_t copy_size = MIN(page_size - misalign, len - progress);
|
||||
|
||||
struct page* p;
|
||||
status = obj->get_page(obj, page_index, &p);
|
||||
if (status != 0)
|
||||
break;
|
||||
|
||||
uintptr_t page_addr = vm_page_addr(p);
|
||||
|
||||
memcpy(HHDM_PTR(page_addr) + misalign, buf + progress, copy_size);
|
||||
progress += copy_size;
|
||||
}
|
||||
|
||||
if (actual_written)
|
||||
*actual_written = progress;
|
||||
return status;
|
||||
}
|
||||
|
||||
errno_t vmo_copy(
|
||||
struct vmo* target,
|
||||
uintptr_t target_offset,
|
||||
struct vmo* src,
|
||||
uintptr_t src_offset,
|
||||
size_t len,
|
||||
size_t* actual_copied
|
||||
) {
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
size_t progress = 0;
|
||||
errno_t status = 0;
|
||||
|
||||
while (progress < len) {
|
||||
const size_t target_misalign = (progress + target_offset) % page_size;
|
||||
const size_t src_misalign = (progress + src_offset) % page_size;
|
||||
const size_t target_page_index = (progress + target_offset) / page_size;
|
||||
const size_t src_page_index = (progress + src_offset) / page_size;
|
||||
|
||||
const size_t copy_size = MIN(MIN(page_size - target_misalign, page_size - src_misalign), len - progress);
|
||||
|
||||
struct page* tpage;
|
||||
status = target->get_page(target, target_page_index, &tpage);
|
||||
if (status != 0)
|
||||
break;
|
||||
|
||||
struct page* spage;
|
||||
status = src->get_page(src, src_page_index, &spage);
|
||||
if (status != 0)
|
||||
break;
|
||||
|
||||
uintptr_t taddr = vm_page_addr(tpage);
|
||||
uintptr_t saddr = vm_page_addr(spage);
|
||||
|
||||
memcpy(HHDM_PTR(taddr) + target_misalign, HHDM_PTR(saddr) + src_misalign, copy_size);
|
||||
progress += copy_size;
|
||||
}
|
||||
|
||||
if (actual_copied)
|
||||
*actual_copied = progress;
|
||||
return status;
|
||||
}
|
||||
|
||||
static errno_t paged_get_page(struct vmo* vmo, uintptr_t offset_idx, struct page** out) {
|
||||
struct paged_vmo* paged = containerof(vmo, struct paged_vmo, object);
|
||||
|
||||
// Try to find the requested page in the cache.
|
||||
struct page_list* iter;
|
||||
spin_lock(&paged->lock);
|
||||
list_foreach(iter, &paged->cache, next) {
|
||||
if (iter->offset != offset_idx)
|
||||
continue;
|
||||
|
||||
// We found a cached page!
|
||||
*out = iter->value;
|
||||
spin_unlock(&paged->lock);
|
||||
return 0;
|
||||
}
|
||||
spin_unlock(&paged->lock);
|
||||
|
||||
// Get page from out pager if we don't have one already.
|
||||
struct page* new_page;
|
||||
errno_t status = paged->source.get_page(&paged->source, offset_idx, &new_page);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
struct page_list* entry = mem_alloc(sizeof(struct page_list), 0);
|
||||
if (!entry) {
|
||||
paged->source.put_page(&paged->source, offset_idx, new_page);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
entry->offset = offset_idx;
|
||||
entry->value = new_page;
|
||||
|
||||
spin_lock(&paged->lock);
|
||||
list_foreach(iter, &paged->cache, next) {
|
||||
if (iter->offset != offset_idx)
|
||||
continue;
|
||||
|
||||
*out = iter->value;
|
||||
spin_unlock(&paged->lock);
|
||||
mem_free(entry);
|
||||
paged->source.put_page(&paged->source, offset_idx, new_page);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add page to the object cache.
|
||||
list_insert_head(&paged->cache, entry, next);
|
||||
spin_unlock(&paged->lock);
|
||||
|
||||
*out = new_page;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void paged_destroy(struct vmo* vmo) {
|
||||
struct paged_vmo* paged = containerof(vmo, struct paged_vmo, object);
|
||||
|
||||
struct page_list* iter = list_first(&paged->cache);
|
||||
while (iter) {
|
||||
struct page_list* next = list_next(iter, next);
|
||||
paged->source.put_page(&paged->source, iter->offset, iter->value);
|
||||
mem_free(iter);
|
||||
iter = next;
|
||||
}
|
||||
|
||||
mem_free(paged);
|
||||
}
|
||||
|
||||
errno_t vmo_new_paged(struct pager_ops pager, struct paged_vmo** out) {
|
||||
struct paged_vmo* result = mem_alloc(sizeof(struct paged_vmo), 0);
|
||||
if (!result)
|
||||
return ENOMEM;
|
||||
|
||||
refcount_init(&result->object.rc);
|
||||
result->source = pager;
|
||||
result->object.get_page = paged_get_page;
|
||||
result->object.destroy = paged_destroy;
|
||||
|
||||
*out = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static errno_t phys_get_page(struct pager_ops* pager, uintptr_t offset, struct page** out) {
|
||||
phys_t addr;
|
||||
errno_t status = mem_phys_alloc(1, 0, &addr);
|
||||
if (status != 0)
|
||||
return status;
|
||||
size_t idx = addr / arch_mem_page_size();
|
||||
*out = &vm_pfndb[idx];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static errno_t phys_put_page(struct pager_ops* pager, uintptr_t offset, struct page* page) {
|
||||
return mem_phys_free(vm_page_addr(page), 1);
|
||||
}
|
||||
|
||||
static const struct pager_ops phys_pager = {
|
||||
.get_page = phys_get_page,
|
||||
.put_page = phys_put_page,
|
||||
};
|
||||
|
||||
errno_t vmo_new_phys(struct paged_vmo** out) {
|
||||
return vmo_new_paged(phys_pager, out);
|
||||
}
|
||||
|
||||
void vmo_truncate_paged(struct paged_vmo* paged, size_t length) {
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
const size_t keep_pages = ROUND_UP(length, page_size) / page_size;
|
||||
|
||||
spin_lock(&paged->lock);
|
||||
struct page_list** pp = &list_first(&paged->cache);
|
||||
while (*pp) {
|
||||
struct page_list* entry = *pp;
|
||||
if (entry->offset >= keep_pages) {
|
||||
*pp = list_next(entry, next);
|
||||
paged->source.put_page(&paged->source, entry->offset, entry->value);
|
||||
mem_free(entry);
|
||||
} else {
|
||||
pp = &list_next(entry, next);
|
||||
}
|
||||
}
|
||||
spin_unlock(&paged->lock);
|
||||
}
|
||||
|
||||
struct phys_range_vmo {
|
||||
struct vmo object;
|
||||
uintptr_t start;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static errno_t phys_range_get_page(struct vmo* vmo, uintptr_t offset_idx, struct page** out) {
|
||||
struct phys_range_vmo* p = containerof(vmo, struct phys_range_vmo, object);
|
||||
if (offset_idx >= p->len / arch_mem_page_size())
|
||||
return EINVAL;
|
||||
*out = &vm_pfndb[(p->start / arch_mem_page_size()) + offset_idx];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void phys_range_destroy(struct vmo* vmo) {
|
||||
mem_free(containerof(vmo, struct phys_range_vmo, object));
|
||||
}
|
||||
|
||||
errno_t vmo_new_phys_range(uintptr_t addr, size_t length, struct vmo** out) {
|
||||
struct phys_range_vmo* vmo = mem_alloc(sizeof(struct phys_range_vmo), 0);
|
||||
if (!vmo)
|
||||
return ENOMEM;
|
||||
|
||||
refcount_init(&vmo->object.rc);
|
||||
vmo->object.get_page = phys_range_get_page;
|
||||
vmo->object.destroy = phys_range_destroy;
|
||||
vmo->start = ALIGN_DOWN(addr, arch_mem_page_size());
|
||||
vmo->len = ALIGN_UP(length, arch_mem_page_size());
|
||||
|
||||
*out = &vmo->object;
|
||||
return 0;
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
kernel_sources += files(
|
||||
'clock.c',
|
||||
'console.c',
|
||||
'elf.c',
|
||||
'irq.c',
|
||||
'module.c',
|
||||
'panic.c',
|
||||
'percpu.c',
|
||||
'print.c',
|
||||
'sched.c',
|
||||
'spin.c',
|
||||
'wait.c',
|
||||
)
|
||||
|
||||
subdir('lib')
|
||||
subdir('mem')
|
||||
subdir('sched')
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/module.h>
|
||||
|
||||
extern errno_t (*__ld_mod_entry_start[])(struct module*);
|
||||
extern errno_t (*__ld_mod_entry_end[])(struct module*);
|
||||
|
||||
void module_init() {
|
||||
for (auto start = __ld_mod_entry_start; start < __ld_mod_entry_end; start += 1) {
|
||||
errno_t mod_status = (*start)(nullptr);
|
||||
assert(mod_status == EOK, "Built-in entry point returned %i", mod_status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define panic_log(msg, ...) kprintf("[ !!!! ] \e[31m" msg "\e[0m", ##__VA_ARGS__)
|
||||
|
||||
[[noreturn]]
|
||||
void panic(const char* msg, ...) {
|
||||
// TODO: Stop all other CPUs with IPI.
|
||||
irq_set_state(false);
|
||||
|
||||
struct percpu* cpu = percpu_get();
|
||||
struct task* current = cpu->sched.current;
|
||||
kprintf(
|
||||
"[ !!!! ] \e[31m"
|
||||
"Kernel panic on CPU %zu (T%zu, \"%s\"): ",
|
||||
cpu->id,
|
||||
current ? current->id : 0,
|
||||
current ? current->name : nullptr
|
||||
);
|
||||
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
kvprintf(msg, args);
|
||||
va_end(args);
|
||||
|
||||
kprintf("\e[0m");
|
||||
|
||||
panic_log("End of panic message\n");
|
||||
|
||||
while (1) {
|
||||
irq_wait();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <arch/percpu.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
extern uint8_t __ld_percpu_start[];
|
||||
extern uint8_t __ld_percpu_end[];
|
||||
|
||||
static _Atomic(size_t) num_cpus = 1;
|
||||
|
||||
static size_t max_cpus = SIZE_MAX;
|
||||
static void smp_option(uintptr_t value) {
|
||||
max_cpus = value;
|
||||
}
|
||||
CMDLINE_OPTION("smp", "Maximum number of CPUs to use", smp_option);
|
||||
|
||||
[[__used, __section(".percpu.init")]]
|
||||
struct percpu percpu_bsp = {
|
||||
.self = &percpu_bsp,
|
||||
.id = 0,
|
||||
.online = false,
|
||||
};
|
||||
|
||||
struct percpu* percpu_new() {
|
||||
const size_t size = __ld_percpu_end - __ld_percpu_start;
|
||||
const size_t num_pages = ALIGN_UP(size, arch_mem_page_size()) / arch_mem_page_size();
|
||||
|
||||
// TODO: Map pages past the end of the kernel.
|
||||
// TODO: Document how allocations work.
|
||||
|
||||
phys_t mem;
|
||||
if (mem_phys_alloc(num_pages, 0, &mem))
|
||||
return nullptr;
|
||||
|
||||
struct percpu* cpu = HHDM_PTR(mem);
|
||||
cpu->self = cpu;
|
||||
cpu->id = atomic_fetch_add_explicit(&num_cpus, 1, memory_order_relaxed);
|
||||
cpu->online = false;
|
||||
return cpu;
|
||||
}
|
||||
|
||||
size_t percpu_max_cpus() {
|
||||
return max_cpus;
|
||||
}
|
||||
|
||||
struct percpu* percpu_get() {
|
||||
return arch_percpu_get();
|
||||
}
|
||||
|
||||
void percpu_bsp_init() {
|
||||
arch_percpu_bsp_init();
|
||||
}
|
||||
|
||||
void percpu_init() {
|
||||
arch_percpu_init();
|
||||
}
|
||||
@@ -1,89 +1,22 @@
|
||||
#include <kernel/console.h>
|
||||
#include <kernel/panic.h>
|
||||
#include <kernel/print.h>
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/stdlib.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
static void reverse(char* s) {
|
||||
int i, j;
|
||||
char c;
|
||||
|
||||
for (i = 0, j = strlen(s) - 1; i < j; i++, j--) {
|
||||
c = s[i];
|
||||
s[i] = s[j];
|
||||
s[j] = c;
|
||||
}
|
||||
}
|
||||
|
||||
static int pow(int x, unsigned int y) {
|
||||
if (y == 0)
|
||||
return 1;
|
||||
else if ((y % 2) == 0)
|
||||
return pow(x, y / 2) * pow(x, y / 2);
|
||||
else
|
||||
return x * pow(x, y / 2) * pow(x, y / 2);
|
||||
}
|
||||
|
||||
long atol(const char* str, int base) {
|
||||
size_t len = strlen(str);
|
||||
int64_t result = 0;
|
||||
size_t i = 0;
|
||||
if (str[0] == '-')
|
||||
i++;
|
||||
for (; i < len; i++)
|
||||
result += (str[i] - '0') * pow(base, len - i - 1);
|
||||
if (str[0] == '-')
|
||||
result *= -1;
|
||||
return result;
|
||||
};
|
||||
|
||||
unsigned long atolu(const char* str, int base) {
|
||||
size_t len = strlen(str);
|
||||
uint64_t result = 0;
|
||||
size_t i = 0;
|
||||
for (; i < len; i++)
|
||||
result += (str[i] - '0') * pow(base, len - i - 1);
|
||||
return result;
|
||||
};
|
||||
|
||||
char* ltoa(int64_t value, char* str, int base) {
|
||||
size_t i;
|
||||
int64_t sign;
|
||||
sign = value;
|
||||
if (base == 10 && sign < 0)
|
||||
value = -value;
|
||||
i = 0;
|
||||
do {
|
||||
char c = value % base;
|
||||
if (c > 9)
|
||||
c += 7;
|
||||
str[i++] = c + '0';
|
||||
} while ((value /= base) > 0);
|
||||
if (base == 10 && sign < 0)
|
||||
str[i++] = '-';
|
||||
str[i] = '\0';
|
||||
reverse(str);
|
||||
return str;
|
||||
};
|
||||
|
||||
char* lutoa(uint64_t value, char* str, int base) {
|
||||
size_t i = 0;
|
||||
do {
|
||||
char c = value % base;
|
||||
if (c > 9)
|
||||
c += 7;
|
||||
str[i++] = c + '0';
|
||||
} while ((value /= base) > 0);
|
||||
str[i] = '\0';
|
||||
reverse(str);
|
||||
return str;
|
||||
};
|
||||
|
||||
typedef void (*log_fn_t)(void* ctx, const char* buf, size_t len);
|
||||
|
||||
static void kvprintf(log_fn_t callback, void* ctx, const char* restrict fmt, va_list args) {
|
||||
static void print_pad(log_fn_t callback, void* ctx, char c, size_t count) {
|
||||
for (size_t i = 0; i < count; i++)
|
||||
callback(ctx, &c, 1);
|
||||
}
|
||||
|
||||
static void do_print(log_fn_t callback, void* ctx, const char* restrict fmt, va_list args) {
|
||||
// Amount of bytes written.
|
||||
int32_t written = 0;
|
||||
|
||||
@@ -110,7 +43,7 @@ static void kvprintf(log_fn_t callback, void* ctx, const char* restrict fmt, va_
|
||||
bool blank_sign = false;
|
||||
bool write_prefix = false;
|
||||
bool pad_zero = false;
|
||||
bool has_width = false;
|
||||
bool left_align = false;
|
||||
size_t width = 0;
|
||||
bool has_precision = false;
|
||||
size_t precision = 0;
|
||||
@@ -141,6 +74,11 @@ check_fmt:
|
||||
fmt++;
|
||||
goto check_fmt;
|
||||
}
|
||||
case '-': {
|
||||
left_align = true;
|
||||
fmt++;
|
||||
goto check_fmt;
|
||||
}
|
||||
// Width
|
||||
case '1':
|
||||
case '2':
|
||||
@@ -151,14 +89,11 @@ check_fmt:
|
||||
case '7':
|
||||
case '8':
|
||||
case '9': {
|
||||
has_width = true;
|
||||
char number[10 + 1] = {0};
|
||||
size_t idx = 0;
|
||||
width = 0;
|
||||
while (*fmt >= '0' && *fmt <= '9') {
|
||||
number[idx++] = *fmt;
|
||||
width = width * 10 + (*fmt - '0');
|
||||
fmt++;
|
||||
}
|
||||
width = atolu(number, 10);
|
||||
|
||||
goto check_fmt;
|
||||
}
|
||||
@@ -166,16 +101,19 @@ check_fmt:
|
||||
case '.': {
|
||||
has_precision = true;
|
||||
fmt++;
|
||||
precision = 0;
|
||||
if (*fmt == '*') {
|
||||
precision = (char)va_arg(args, int32_t);
|
||||
const int32_t arg = va_arg(args, int32_t);
|
||||
if (arg < 0)
|
||||
has_precision = false;
|
||||
else
|
||||
precision = arg;
|
||||
fmt++;
|
||||
} else {
|
||||
char number[10 + 1] = {0};
|
||||
size_t idx = 0;
|
||||
while (*fmt >= '0' && *fmt <= '9') {
|
||||
number[idx++] = *fmt;
|
||||
precision = precision * 10 + (*fmt - '0');
|
||||
fmt++;
|
||||
}
|
||||
precision = atolu(number, 10);
|
||||
}
|
||||
goto check_fmt;
|
||||
}
|
||||
@@ -206,10 +144,15 @@ check_fmt:
|
||||
// Character
|
||||
case 'c': {
|
||||
char c = (char)va_arg(args, int32_t);
|
||||
const size_t pad = width > 1 ? width - 1 : 0;
|
||||
|
||||
if (!left_align)
|
||||
print_pad(callback, ctx, ' ', pad);
|
||||
callback(ctx, &c, sizeof(c));
|
||||
if (left_align)
|
||||
print_pad(callback, ctx, ' ', pad);
|
||||
|
||||
written++;
|
||||
written += 1 + pad;
|
||||
fmt++;
|
||||
break;
|
||||
}
|
||||
@@ -219,14 +162,19 @@ check_fmt:
|
||||
if (!str)
|
||||
str = "(null)";
|
||||
size_t len = 0;
|
||||
if (has_width)
|
||||
len = strnlen(str, width);
|
||||
if (has_precision)
|
||||
len = strnlen(str, precision);
|
||||
else
|
||||
len = strlen(str);
|
||||
const size_t pad = width > len ? width - len : 0;
|
||||
|
||||
if (!left_align)
|
||||
print_pad(callback, ctx, ' ', pad);
|
||||
callback(ctx, str, len);
|
||||
if (left_align)
|
||||
print_pad(callback, ctx, ' ', pad);
|
||||
|
||||
written += len;
|
||||
written += len + pad;
|
||||
fmt++;
|
||||
break;
|
||||
}
|
||||
@@ -285,36 +233,38 @@ check_fmt:
|
||||
break;
|
||||
}
|
||||
print_num:
|
||||
size_t len = strlen(number);
|
||||
if (has_precision) {
|
||||
for (size_t i = 0; i < precision - len; i++)
|
||||
callback(ctx, " ", 1);
|
||||
const char* digits = number;
|
||||
char sign = 0;
|
||||
if (digits[0] == '-') {
|
||||
sign = '-';
|
||||
digits++;
|
||||
} else if (force_sign) {
|
||||
sign = '+';
|
||||
} else if (blank_sign) {
|
||||
sign = ' ';
|
||||
}
|
||||
if (has_width) {
|
||||
char c = pad_zero ? '0' : ' ';
|
||||
|
||||
// We don't have to pad anything.
|
||||
if (len < width) {
|
||||
for (size_t i = 0; i < width - len; i++)
|
||||
callback(ctx, &c, 1);
|
||||
}
|
||||
const size_t len = strlen(digits);
|
||||
size_t zeros = (has_precision && precision > len) ? precision - len : 0;
|
||||
const size_t total = (sign ? 1 : 0) + (write_prefix ? 2 : 0) + zeros + len;
|
||||
size_t pad = width > total ? width - total : 0;
|
||||
if (pad_zero && !left_align && !has_precision) {
|
||||
zeros += pad;
|
||||
pad = 0;
|
||||
}
|
||||
if (write_prefix) {
|
||||
|
||||
if (!left_align)
|
||||
print_pad(callback, ctx, ' ', pad);
|
||||
if (sign)
|
||||
callback(ctx, &sign, 1);
|
||||
if (write_prefix)
|
||||
callback(ctx, "0x", 2);
|
||||
}
|
||||
if (force_sign) {
|
||||
char c = '+';
|
||||
if (number[0] != '-')
|
||||
callback(ctx, &c, 1);
|
||||
}
|
||||
if (blank_sign) {
|
||||
char c = ' ';
|
||||
if (number[0] != '-')
|
||||
callback(ctx, &c, 1);
|
||||
}
|
||||
callback(ctx, number, len);
|
||||
print_pad(callback, ctx, '0', zeros);
|
||||
callback(ctx, digits, len);
|
||||
if (left_align)
|
||||
print_pad(callback, ctx, ' ', pad);
|
||||
|
||||
written += len;
|
||||
written += width > total ? width : total;
|
||||
fmt++;
|
||||
break;
|
||||
}
|
||||
@@ -345,13 +295,14 @@ print_num:
|
||||
}
|
||||
// No format, just normal text.
|
||||
default: {
|
||||
fmt = format_begun_at;
|
||||
size_t len = strlen(fmt);
|
||||
size_t len = fmt - format_begun_at;
|
||||
if (*fmt)
|
||||
len++;
|
||||
|
||||
callback(ctx, fmt, len);
|
||||
callback(ctx, format_begun_at, len);
|
||||
|
||||
written += len;
|
||||
fmt += len;
|
||||
fmt = format_begun_at + len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -362,11 +313,23 @@ static void printf_console(void*, const char* msg, size_t len) {
|
||||
console_write(msg, len);
|
||||
}
|
||||
|
||||
static struct spinlock print_lock = {};
|
||||
|
||||
void kprintf(const char* message, ...) {
|
||||
spin_lock(&print_lock);
|
||||
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
|
||||
kvprintf(printf_console, nullptr, message, args);
|
||||
|
||||
do_print(printf_console, nullptr, message, args);
|
||||
va_end(args);
|
||||
|
||||
spin_unlock(&print_lock);
|
||||
}
|
||||
|
||||
void kvprintf(const char* message, va_list args) {
|
||||
spin_lock(&print_lock);
|
||||
|
||||
do_print(printf_console, nullptr, message, args);
|
||||
|
||||
spin_unlock(&print_lock);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include <zinnia/sched.h>
|
||||
@@ -0,0 +1,7 @@
|
||||
kernel_sources += files(
|
||||
'core.c',
|
||||
)
|
||||
|
||||
if kconfig.get('CONFIG_SCHED_DUMB', 'n') == 'y'
|
||||
kernel_sources += files('sched_dumb.c')
|
||||
endif
|
||||
@@ -0,0 +1,167 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/list.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/stack.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/tailq.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <arch/sched.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static void idle_func(uintptr_t) {
|
||||
while (1) {
|
||||
irq_set_state(true);
|
||||
irq_wait();
|
||||
}
|
||||
}
|
||||
|
||||
void sched_to_user(uintptr_t info_ptr) {
|
||||
struct task_start_info* info = (struct task_start_info*)info_ptr;
|
||||
uintptr_t ip = info->ip;
|
||||
uintptr_t sp = info->sp;
|
||||
uintptr_t arg = info->arg;
|
||||
mem_free(info);
|
||||
arch_sched_jump_to_user(ip, sp, arg);
|
||||
}
|
||||
|
||||
void sched_to_user_context(uintptr_t ctx) {
|
||||
arch_sched_jump_to_context((struct arch_context*)ctx);
|
||||
}
|
||||
|
||||
void sched_init(struct scheduler* sched) {
|
||||
struct task* idle_task;
|
||||
assert(task_create("idle", &kernel_vma, idle_func, 0, &idle_task) == 0, "Unable to create idle task!\n");
|
||||
sched->idle_task = idle_task;
|
||||
|
||||
struct task* boot_task = mem_alloc(sizeof(struct task), 0);
|
||||
assert(boot_task != nullptr, "Unable to create boot task!\n");
|
||||
strncpy(boot_task->name, "boot", sizeof(boot_task->name));
|
||||
boot_task->space = &kernel_vma;
|
||||
sched->current = boot_task;
|
||||
|
||||
tailq_init(&sched->run_queue);
|
||||
|
||||
log("sched: Scheduler initialized\n");
|
||||
}
|
||||
|
||||
void sched_add_task(struct scheduler* sched, struct task* task) {
|
||||
assert(task != nullptr, "Tried to add NULL to the run queue\n");
|
||||
tailq_insert_tail(&sched->run_queue, task, next);
|
||||
}
|
||||
|
||||
static struct task* next_task(struct scheduler* sched) {
|
||||
struct task* t = tailq_first(&sched->run_queue);
|
||||
|
||||
if (t == nullptr)
|
||||
return sched->idle_task;
|
||||
|
||||
tailq_remove(&sched->run_queue, t, next);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static void finish_switch(struct task* prev) {
|
||||
if (prev->state == TASK_STATE_DEAD)
|
||||
task_destroy(prev);
|
||||
}
|
||||
|
||||
static void do_reschedule(struct scheduler* sched) {
|
||||
struct task* from = sched->current;
|
||||
struct task* to = next_task(sched);
|
||||
|
||||
// We'd end up on the same task, skip switching altogether.
|
||||
if (from == to) {
|
||||
irq_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
sched->current = to;
|
||||
pmap_set(&to->space->pmap);
|
||||
|
||||
struct percpu* cpu = percpu_get();
|
||||
from->user_stack = cpu->user_stack;
|
||||
cpu->kernel_stack = kernel_stack_top(&to->kernel_stack);
|
||||
cpu->user_stack = to->user_stack;
|
||||
|
||||
struct irq_percpu irq_state = *percpu_ref(irq_data);
|
||||
struct task* prev = arch_sched_switch(from, to);
|
||||
*percpu_ref(irq_data) = irq_state;
|
||||
finish_switch(prev);
|
||||
irq_unlock();
|
||||
}
|
||||
|
||||
void sched_reschedule(struct scheduler* sched) {
|
||||
irq_lock();
|
||||
if (sched->current != sched->idle_task && sched->current->state != TASK_STATE_DEAD)
|
||||
sched_add_task(sched, sched->current);
|
||||
do_reschedule(sched);
|
||||
}
|
||||
|
||||
void sched_yield(struct scheduler* sched) {
|
||||
irq_lock();
|
||||
do_reschedule(sched);
|
||||
}
|
||||
|
||||
errno_t task_create(const char* name, struct vma* space, task_fn_t entry, uintptr_t arg0, struct task** out) {
|
||||
if (!out)
|
||||
return EINVAL;
|
||||
|
||||
struct task* new_task = mem_alloc(sizeof(struct task), 0);
|
||||
if (new_task == nullptr)
|
||||
return ENOMEM;
|
||||
|
||||
static _Atomic(size_t) last_tid = 0;
|
||||
new_task->id = atomic_fetch_add_explicit(&last_tid, 1, memory_order_acquire);
|
||||
new_task->space = space;
|
||||
strncpy(new_task->name, name, sizeof(new_task->name));
|
||||
|
||||
// Allocate a kernel stack.
|
||||
errno_t s = stack_init(&new_task->kernel_stack, KERNEL_STACK_SIZE);
|
||||
if (s != 0) {
|
||||
mem_free(new_task);
|
||||
return s;
|
||||
}
|
||||
new_task->user_stack = 0;
|
||||
|
||||
// Initialize the arch-dependent parts.
|
||||
s = arch_task_init(&new_task->context, entry, arg0, kernel_stack_top(&new_task->kernel_stack), true);
|
||||
if (s != 0) {
|
||||
stack_destroy(&new_task->kernel_stack);
|
||||
mem_free(new_task);
|
||||
return s;
|
||||
}
|
||||
|
||||
*out = new_task;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void task_destroy(struct task* task) {
|
||||
arch_task_fini(&task->context);
|
||||
stack_destroy(&task->kernel_stack);
|
||||
mem_free(task);
|
||||
}
|
||||
|
||||
void task_entry(task_fn_t entry, uintptr_t arg0, struct task* prev) {
|
||||
finish_switch(prev);
|
||||
|
||||
*percpu_ref(irq_data) = (struct irq_percpu){
|
||||
.depth = 1,
|
||||
.saved_state = true,
|
||||
};
|
||||
irq_unlock();
|
||||
|
||||
entry(arg0);
|
||||
|
||||
struct scheduler* sched = &percpu_get()->sched;
|
||||
sched->current->state = TASK_STATE_DEAD;
|
||||
sched_yield(sched);
|
||||
panic("unreachable");
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
void spin_init(struct spinlock* spin) {
|
||||
spin->next = 0;
|
||||
spin->owner = 0;
|
||||
}
|
||||
|
||||
void spin_lock(struct spinlock* spin) {
|
||||
irq_lock();
|
||||
uint32_t my = atomic_fetch_add_explicit(&spin->next, 1, memory_order_relaxed);
|
||||
|
||||
while (atomic_load_explicit(&spin->owner, memory_order_acquire) != my) {
|
||||
asm volatile("pause");
|
||||
}
|
||||
}
|
||||
|
||||
void spin_unlock(struct spinlock* spin) {
|
||||
uint32_t val = atomic_load_explicit(&spin->owner, memory_order_relaxed);
|
||||
atomic_store_explicit(&spin->owner, val + 1, memory_order_release);
|
||||
irq_unlock();
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#include <zinnia/waitq.h>
|
||||
|
||||
void waitq_init(struct waitq* wq) {
|
||||
tailq_init(&wq->waiters);
|
||||
}
|
||||
|
||||
void waitq_wait(struct waitq* wq) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void waitq_wake_one(struct waitq* wq) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void waitq_wake_all(struct waitq* wq) {
|
||||
// TODO
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
menu "Device Drivers"
|
||||
|
||||
config OF
|
||||
bool "Support for OpenFirmware"
|
||||
default y
|
||||
help
|
||||
Adds support for flattened device trees and OF platform drivers.
|
||||
|
||||
config ACPI
|
||||
bool "Support for ACPI firmware"
|
||||
default y
|
||||
help
|
||||
Adds support for ACPI firmware, tables and devices.
|
||||
|
||||
source "dev/block/Kconfig"
|
||||
source "dev/pci/Kconfig"
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint8_t uacpi_u8;
|
||||
typedef uint16_t uacpi_u16;
|
||||
typedef uint32_t uacpi_u32;
|
||||
typedef uint64_t uacpi_u64;
|
||||
|
||||
typedef int8_t uacpi_i8;
|
||||
typedef int16_t uacpi_i16;
|
||||
typedef int32_t uacpi_i32;
|
||||
typedef int64_t uacpi_i64;
|
||||
|
||||
#define UACPI_TRUE true
|
||||
#define UACPI_FALSE false
|
||||
typedef bool uacpi_bool;
|
||||
|
||||
#define UACPI_NULL nullptr
|
||||
|
||||
typedef uintptr_t uacpi_uintptr;
|
||||
typedef uacpi_uintptr uacpi_virt_addr;
|
||||
typedef size_t uacpi_size;
|
||||
|
||||
typedef va_list uacpi_va_list;
|
||||
#define uacpi_va_start va_start
|
||||
#define uacpi_va_end va_end
|
||||
#define uacpi_va_arg va_arg
|
||||
|
||||
typedef char uacpi_char;
|
||||
|
||||
#define uacpi_offsetof offsetof
|
||||
|
||||
#define UACPI_PRIu64 "llu"
|
||||
#define UACPI_PRIx64 "llx"
|
||||
#define UACPI_PRIX64 "llX"
|
||||
#define UACPI_FMT64(val) ((unsigned long long)(val))
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/types.h>
|
||||
|
||||
void acpi_init(struct boot_info* info);
|
||||
|
||||
extern phys_t acpi_rsdp_addr;
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <zinnia/acpi.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/types.h>
|
||||
|
||||
phys_t acpi_rsdp_addr = 0;
|
||||
|
||||
static bool use_acpi = true;
|
||||
void use_acpi_option(bool use) {
|
||||
use_acpi = use;
|
||||
}
|
||||
CMDLINE_OPTION("acpi", "Enable ACPI firmware subsystem", use_acpi_option);
|
||||
|
||||
void acpi_init(struct boot_info* info) {
|
||||
if (!use_acpi) {
|
||||
log("acpi: ACPI disabled on the command line!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!info->rsdp) {
|
||||
log("acpi: No RSDP was passed, unable to start ACPI subsystem!\n");
|
||||
return;
|
||||
}
|
||||
acpi_rsdp_addr = info->rsdp;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
uacpi = subproject('uacpi')
|
||||
|
||||
kernel_sources += uacpi.get_variable('sources')
|
||||
kernel_sources += files(
|
||||
'init.c',
|
||||
'uacpi_glue.c',
|
||||
)
|
||||
|
||||
kernel_includes += uacpi.get_variable('includes')
|
||||
kernel_includes += include_directories('include')
|
||||
|
||||
kernel_c_args += [
|
||||
'-DUACPI_FORMATTED_LOGGING',
|
||||
'-DUACPI_BAREBONES_MODE', # TODO: Remove
|
||||
'-DUACPI_OVERRIDE_TYPES',
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <zinnia/acpi.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/mmio.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/virt.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <arch/mem.h>
|
||||
#include <stdarg.h>
|
||||
#include <uacpi/kernel_api.h>
|
||||
#include <uacpi/status.h>
|
||||
#include <uacpi/types.h>
|
||||
|
||||
uacpi_status uacpi_kernel_get_rsdp(uacpi_phys_addr* out_rsdp_address) {
|
||||
*out_rsdp_address = (uacpi_phys_addr)acpi_rsdp_addr;
|
||||
return UACPI_STATUS_OK;
|
||||
}
|
||||
|
||||
void* uacpi_kernel_map(uacpi_phys_addr addr, uacpi_size len) {
|
||||
uintptr_t aligned_addr = ALIGN_DOWN(addr, arch_mem_page_size());
|
||||
uintptr_t difference = addr - aligned_addr;
|
||||
size_t aligned_len = ALIGN_UP(len + difference, arch_mem_page_size());
|
||||
|
||||
uintptr_t vaddr = vm_alloc(aligned_len);
|
||||
if (!vaddr)
|
||||
return nullptr;
|
||||
|
||||
for (size_t i = 0; i < aligned_len; i += arch_mem_page_size()) {
|
||||
pmap_map(&kernel_vma.pmap, vaddr + i, aligned_addr + i, PROT_READ | PROT_WRITE, CACHE_ANY);
|
||||
}
|
||||
|
||||
return (void*)(vaddr + difference);
|
||||
}
|
||||
|
||||
void uacpi_kernel_unmap(void* addr, uacpi_size len) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void uacpi_kernel_log(uacpi_log_level lvl, const uacpi_char* msg, ...) {
|
||||
log("acpi: ");
|
||||
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
|
||||
kvprintf(msg, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void uacpi_kernel_vlog(uacpi_log_level lvl, const uacpi_char* msg, uacpi_va_list args) {
|
||||
kvprintf(msg, args);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
menu "Block device drivers"
|
||||
|
||||
source "dev/block/nvme/Kconfig"
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,3 @@
|
||||
if kconfig.get('CONFIG_NVME_CORE', 'n') != 'n'
|
||||
subdir('nvme')
|
||||
endif
|
||||
@@ -0,0 +1,13 @@
|
||||
menu "NVMe Support"
|
||||
|
||||
config NVME_CORE
|
||||
bool
|
||||
default y
|
||||
|
||||
config NVME_PCI
|
||||
bool "NVMe via PCI transport"
|
||||
default y
|
||||
depends on PCI
|
||||
depends on NVME_CORE
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/module.h>
|
||||
#include <zinnia/print.h>
|
||||
|
||||
errno_t nvme_init(struct module* mod) {
|
||||
log("Hello world from NVMe!\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno_t nvme_exit(struct module* mod) {
|
||||
log("Good bye from NVMe!\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MODULE_ENTRY(nvme_init);
|
||||
MODULE_EXIT(nvme_exit);
|
||||
|
||||
MODULE_DESC("NVMe Block Devices");
|
||||
MODULE_AUTHOR("Marvin Friedrich");
|
||||
MODULE_LICENSE("ISC");
|
||||
MODULE_VERSION("1.0");
|
||||
@@ -0,0 +1,12 @@
|
||||
nvme_sources = files('core.c')
|
||||
|
||||
if kconfig.get('CONFIG_NVME_PCI', 'n') == 'y'
|
||||
nvme_sources += files('pci.c')
|
||||
endif
|
||||
|
||||
modules += {
|
||||
'nvme': {
|
||||
'modular': kconfig.get('CONFIG_NVME_CORE') == 'm',
|
||||
'sources': nvme_sources,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
kernel_sources += files(
|
||||
'memfiles.c',
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
subdir('common')
|
||||
subdir('block')
|
||||
subdir('dt')
|
||||
|
||||
if kconfig.get('CONFIG_ACPI', 'n') == 'y'
|
||||
subdir('acpi')
|
||||
endif
|
||||
|
||||
module_lds = meson.current_source_dir() / 'module.ld'
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/* Linker script for kernel modules */
|
||||
|
||||
PHDRS
|
||||
{
|
||||
text PT_LOAD FLAGS(5);
|
||||
rodata PT_LOAD FLAGS(4);
|
||||
data PT_LOAD FLAGS(6);
|
||||
dynamic PT_DYNAMIC FLAGS(6);
|
||||
modversion 0x60000001;
|
||||
modauthor 0x60000002;
|
||||
moddesc 0x60000003;
|
||||
modlicense 0x60000004;
|
||||
modcmdline 0x60000005;
|
||||
modentry 0x60000006;
|
||||
modexit 0x60000007;
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
|
||||
/* Code */
|
||||
.text : {
|
||||
*(.init .init.*)
|
||||
*(.text .text.*)
|
||||
*(.fini .fini.*)
|
||||
*(.plt .plt.*)
|
||||
} :text
|
||||
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
|
||||
/* Read-only data */
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*)
|
||||
*(.hash .hash.*)
|
||||
*(.got .got.*)
|
||||
*(.got.plt .got.plt.*)
|
||||
} :rodata
|
||||
|
||||
.mod.version : {
|
||||
KEEP(*(.mod.version))
|
||||
} :rodata :modversion
|
||||
|
||||
.mod.author : {
|
||||
KEEP(*(.mod.author))
|
||||
} :rodata :modauthor
|
||||
|
||||
.mod.desc : {
|
||||
KEEP(*(.mod.desc))
|
||||
} :rodata :moddesc
|
||||
|
||||
.mod.license : {
|
||||
KEEP(*(.mod.license))
|
||||
} :rodata :modlicense
|
||||
|
||||
.cmdline : {
|
||||
KEEP(*(.cmdline))
|
||||
} :rodata :modcmdline
|
||||
|
||||
.mod.entry : {
|
||||
KEEP(*(.mod.entry))
|
||||
} :rodata :modentry
|
||||
|
||||
.mod.exit : {
|
||||
KEEP(*(.mod.exit))
|
||||
} :rodata :modexit
|
||||
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
|
||||
/* Read-Write data */
|
||||
.data : {
|
||||
*(.data)
|
||||
*(.data.*)
|
||||
} :data
|
||||
|
||||
/* Zero-initialized memory */
|
||||
.bss : {
|
||||
*(.*bss .*bss.*)
|
||||
*(COMMON)
|
||||
} :data
|
||||
|
||||
.dynsym : {
|
||||
*(.dynsym .dynsym.*)
|
||||
} :data
|
||||
|
||||
.dynstr : {
|
||||
*(.dynstr .dynstr.*)
|
||||
} :data
|
||||
|
||||
.rela.dyn : {
|
||||
*(.rela.dyn .rela.dyn.*)
|
||||
} :data
|
||||
|
||||
/* Dynamic tags */
|
||||
.dynamic : {
|
||||
*(.dynamic)
|
||||
} :data :dynamic
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.comment*)
|
||||
*(.eh_frame*)
|
||||
*(.rel.eh_frame*)
|
||||
*(.relr.dyn .relr.dyn.*)
|
||||
*(.gcc_except_table*)
|
||||
*(.note*)
|
||||
*(.interp)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user