Initial commit

This commit is contained in:
2026-02-04 01:58:49 +01:00
commit 0e01345abf
117 changed files with 5076 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
---
AlignAfterOpenBracket: BlockIndent
BinPackArguments: false
BinPackParameters: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowAllArgumentsOnNextLine: false
BreakAfterReturnType: Automatic
PenaltyReturnTypeOnItsOwnLine: 1000
AlignArrayOfStructures: None
IndentWidth: 4
InsertNewlineAtEOF: true
IntegerLiteralSeparator:
HexMinDigits: 8
IndentCaseBlocks: false
IndentCaseLabels: false
IndentPPDirectives: None
IndentGotoLabels: false
TabWidth: 4
Standard: Latest
Language: Cpp
LineEnding: LF
AlignOperands: DontAlign
BitFieldColonSpacing: None
ColumnLimit: 120
MaxEmptyLinesToKeep: 1
PointerAlignment: Left
QualifierAlignment: Leave
SpaceAfterLogicalNot: false
SpaceAfterCStyleCast: false
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortBlocksOnASingleLine: Empty
AlwaysBreakBeforeMultilineStrings: true
BreakAfterAttributes: Always
BreakConstructorInitializers: BeforeComma
BreakBeforeBraces: Attach
AllowShortLoopsOnASingleLine: false
SpacesInSquareBrackets: false
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignCompound: true
AlignFunctionPointers: false
AlignEscapedNewlines: DontAlign
BreakBeforeInlineASMColon: OnlyMultiline
AccessModifierOffset: -4
SpacesInParens: Never
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
IncludeBlocks: Merge
IncludeCategories:
- Regex: "^<zinnia/.*>"
Priority: 1
- Regex: "^<kernel/.*>"
Priority: 2
- Regex: '^<.*\.h>'
Priority: 3
- Regex: '^".*\.h"'
Priority: 4
SortIncludes: CaseInsensitive
+2
View File
@@ -0,0 +1,2 @@
Style:
AngledHeaders: ["zinnia/.*", " kernel/.*"]
+16
View File
@@ -0,0 +1,16 @@
# IDE Files
.vscode/
.idea/
.cache/
# Build artifacts
*.o
*.a
*.so
compile_commands.json
# Default build dir
build/
# Generated files
.config*
+21
View File
@@ -0,0 +1,21 @@
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.
+42
View File
@@ -0,0 +1,42 @@
<img src="zinnia.svg" width="10%"/>
![GitHub License](https://img.shields.io/github/license/zinnia-os/zinnia?style=flat&color=blue)
![GitHub Repo stars](https://img.shields.io/github/stars/zinnia-os/zinnia?style=flat)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/zinnia-os/zinnia/ci.yml)
![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/zinnia-os/zinnia?style=flat)
# zinnia
zinnia is a lightweight Unix-like microkernel targeting 64-bit devices.
> [!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 configure, run:
```sh
meson setup $build_dir
```
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**
+19
View File
@@ -0,0 +1,19 @@
#ifndef ZINNIA_ARCHCTL_H
#define ZINNIA_ARCHCTL_H
#include <zinnia/status.h>
#include <stddef.h>
typedef enum {
// Does nothing.
ZN_ARCHCTL_NONE = 0,
#ifdef __x86_64__
// On x86_64, sets the FSBASE register to the value.
ZN_ARCHCTL_SET_FSBASE = 1,
#endif
} zn_archctl_t;
// Performs an architecture-dependent operation identified by `op`.
zn_status_t zn_archctl(zn_archctl_t op, size_t value);
#endif
+35
View File
@@ -0,0 +1,35 @@
#ifndef ZINNIA_CHANNEL_H
#define ZINNIA_CHANNEL_H
#include <zinnia/handle.h>
#include <zinnia/status.h>
#include <stddef.h>
// Handle which always points to a channel connected to the init server.
#define ZN_INIT_CHANNEL ((zn_handle_t)(-1))
enum zn_channel_flags {
// Allow sending messages even if one endpoint is not connected.
ZN_CHANNEL_NONBLOCK = 1 << 0,
};
// Creates a new channel.
zn_status_t zn_channel_create(enum zn_channel_flags flags, zn_handle_t* endpoint0, zn_handle_t* endpoint1);
// Maps the message buffer in the address space and returns its base address.
// There may only be one message buffer per channel and process.
zn_status_t zn_channel_open(
zn_handle_t channel,
size_t num_handles,
size_t num_bytes,
zn_handle_t** out_handle_buf,
void** out_data_buf
);
// Waits for a new message to appear in the channel buffer.
zn_status_t zn_channel_read(zn_handle_t channel);
// Writes a message to the
zn_status_t zn_channel_write(zn_handle_t channel);
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef ZINNIA_HANDLE_H
#define ZINNIA_HANDLE_H
#include <zinnia/rights.h>
#include <zinnia/status.h>
#include <stdint.h>
// A generic object handle.
typedef uint32_t zn_handle_t;
#define ZN_HANDLE_INVALID ((zn_handle_t)(0))
// Checks an object handle for validity.
zn_status_t zn_handle_validate(zn_handle_t handle);
// Drops a an object handle.
// All further references using this handle are invalid. The numerical value
// may become valid again, but it is an error to keep using it.
// The underlying object gets freed only once no other handle references it.
zn_status_t zn_handle_drop(zn_handle_t handle);
// Creates a new handle pointing to the same object as pointed to
// by the original handle. The new handle may obtain new rights.
zn_status_t zn_handle_clone(zn_handle_t handle, zn_rights_t cloned_rights, zn_handle_t* cloned);
#endif
+15
View File
@@ -0,0 +1,15 @@
#ifndef ZINNIA_IPC_POSIX_H
#define ZINNIA_IPC_POSIX_H
#include <stdint.h>
enum {
ZNI_POSIX_OPEN_REQ,
ZNI_POSIX_OPEN_RES,
};
struct zni_posix_open {
uint32_t id;
};
#endif
+11
View File
@@ -0,0 +1,11 @@
#ifndef ZINNIA_LOG_H
#define ZINNIA_LOG_H
#include <zinnia/status.h>
// Panics and returns an error status to the parent process.
void zn_panic(zn_status_t status);
void zn_log(const char* message);
#endif
+32
View File
@@ -0,0 +1,32 @@
#ifndef ZINNIA_MEM_H
#define ZINNIA_MEM_H
#include <zinnia/handle.h>
#include <zinnia/status.h>
#include <stddef.h>
// Virtual memory flags.
enum zn_vm_flags {
ZN_VM_READ = 1 << 0,
ZN_VM_WRITE = 1 << 1,
ZN_VM_EXEC = 1 << 2,
ZN_VM_SHARED = 1 << 3,
};
enum zn_cache_type {
// Generic memory
ZN_CACHE_NORMAL,
// Write combining
ZN_CACHE_WC,
// Memory-mapped IO
ZN_CACHE_MMIO,
};
// Allocates memory
zn_status_t zn_mem_alloc(size_t length, zn_handle_t* out);
zn_status_t zn_mem_map(zn_handle_t mem, void* addr, size_t len, enum zn_vm_flags flags);
zn_status_t zn_mem_unmap(zn_handle_t mem, void* addr, size_t len, enum zn_vm_flags flags);
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef ZINNIA_RIGHTS_H
#define ZINNIA_RIGHTS_H
typedef enum {
// Can be read from.
ZN_RIGHT_READ = 1u << 0,
// Can be written to.
ZN_RIGHT_WRITE = 1u << 1,
// Can be executed.
ZN_RIGHT_EXECUTE = 1u << 2,
// Can be mapped in an address space.
ZN_RIGHT_MAP = 1u << 3,
// Can be moved to another process.
ZN_RIGHT_MOVE = 1u << 4,
// Can be cloned.
ZN_RIGHT_CLONE = 1u << 5,
// Can be deleted.
ZN_RIGHT_DELETE = 1u << 6,
// When cloning, use the same rights as the original.
ZN_RIGHTS_IDENTICAL = 1u << 31,
ZN_RIGHTS_COMMON = (ZN_RIGHT_MOVE | ZN_RIGHT_CLONE),
ZN_RIGHTS_RW = (ZN_RIGHT_READ | ZN_RIGHT_WRITE),
} zn_rights_t;
#endif
+35
View File
@@ -0,0 +1,35 @@
#ifndef ZINNIA_STATUS_H
#define ZINNIA_STATUS_H
// Status values that may be returned by the kernel.
// A status value of 0 means everything is okay.
// A negative value indicates an error from the kernel.
// Positive values are available to user processes.
typedef enum {
ZN_OK = 0,
// An internal error occured.
ZN_ERR_INTERNAL = -1,
// Syscall number is not a recognized syscall.
ZN_ERR_BAD_SYSCALL = -2,
// This operation is not supported.
ZN_ERR_UNSUPPORTED = -3,
// System does not have enough free memory for this operation.
ZN_ERR_NO_MEMORY = -4,
// Process can not own any more handles.
ZN_ERR_NO_HANDLES = -5,
// One or more of the provided arguments is not valid.
ZN_ERR_BAD_ARG = -6,
// Argument is outside of the range for valid values.
ZN_ERR_BAD_RANGE = -7,
// Object handle does not name a valid object or correct type.
ZN_ERR_BAD_OBJECT = -8,
// Object has insufficient permissions for this operation.
ZN_ERR_BAD_PERMS = -9,
// Buffer is not large enough or doesn't point to a valid memory region.
ZN_ERR_BAD_BUFFER = -10,
} zn_status_t;
// Returns a string describing the error code.
const char* zn_status_to_string(zn_status_t status);
#endif
+27
View File
@@ -0,0 +1,27 @@
#ifndef ZINNIA_SYSCALL_NUMBERS_H
#define ZINNIA_SYSCALL_NUMBERS_H
enum {
ZN_SYSCALL_PANIC,
ZN_SYSCALL_LOG,
ZN_SYSCALL_ARCHCTL,
ZN_SYSCALL_HANDLE_VALIDATE,
ZN_SYSCALL_HANDLE_DROP,
ZN_SYSCALL_HANDLE_CLONE,
ZN_SYSCALL_MEM_ALLOC,
ZN_SYSCALL_MEM_FREE,
ZN_SYSCALL_CHANNEL_CREATE,
ZN_SYSCALL_CHANNEL_OPEN,
ZN_SYSCALL_CHANNEL_READ,
ZN_SYSCALL_CHANNEL_WRITE,
ZN_SYSCALL_THREAD_CREATE,
ZN_SYSCALL_THREAD_DESTROY,
};
#endif
+14
View File
@@ -0,0 +1,14 @@
#ifndef ZINNIA_THREAD_H
#define ZINNIA_THREAD_H
#include <zinnia/handle.h>
#include <zinnia/status.h>
enum zn_thread_flags {
ZN_THREAD_NONE,
};
// Creates a new thread.
zn_status_t zn_thread_create(const char* name, zn_handle_t address_space, enum zn_thread_flags flags);
#endif
+51
View File
@@ -0,0 +1,51 @@
#include <kernel/percpu.h>
#include <kernel/syscalls.h>
#include <asm.h>
#include <defs.h>
#include <gdt.h>
#include <stddef.h>
#include <string.h>
struct gdt main_gdt = {
.null = 0,
.kernel_code32 = 0x00cf9b000000ffff,
.kernel_data32 = 0x00cf93000000ffff,
.kernel_code64 = 0x00a09b0000000000,
.kernel_data64 = 0x0000930000000000,
.user_code = 0x00cffb000000ffff,
.user_data = 0x0000f30000000000,
.user_code64 = 0x0000fb0000000000,
.tss = {0x0000890000000000, 0},
};
void gdt_init() {
// Set initial values.
memcpy(&percpu_get()->arch.gdt, &main_gdt, sizeof(struct gdt));
struct gdtr gdtr = {
.limit = sizeof(struct gdt) - 1,
.base = &percpu_get()->arch.gdt,
};
asm volatile("lgdt [%0]" ::"r"(&gdtr));
// Save the contents of MSR_GS_BASE, as they get cleared by a write to `gs`.
virt_t gs = asm_rdmsr(MSR_GS_BASE);
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);
}
+7
View File
@@ -0,0 +1,7 @@
#include <kernel/compiler.h>
#include <stdint.h>
struct [[__packed]] idtr {
uint16_t limit;
struct idt* base;
};
+78
View File
@@ -0,0 +1,78 @@
#pragma once
#include <stdint.h>
static inline uint64_t asm_rdmsr(uint32_t msr) {
uint32_t eax;
uint32_t edx;
asm volatile("rdmsr" : "=a"(eax), "=d"(edx) : "c"(msr) : "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;
uint32_t edx = val >> 32;
asm volatile("wrmsr" : : "a"(eax), "d"(edx), "c"(msr) : "memory");
}
// Writes a 64-bit value to a control register using XSETBV.
static inline void asm_wrxcr(uint32_t reg, uint64_t val) {
uint32_t eax = val;
uint32_t edx = val >> 32;
asm volatile("xsetbv" ::"a"(eax), "d"(edx), "c"(reg) : "memory");
}
// 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");
}
// 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");
}
// 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");
}
// 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 uint8_t asm_inb(uint16_t port) {
uint8_t result;
asm volatile("in %0, %1" : "=a"(result) : "Nd"(port));
return result;
}
static inline uint16_t asm_inw(uint16_t port) {
uint16_t result;
asm volatile("in %0, %1" : "=a"(result) : "Nd"(port));
return result;
}
static inline uint32_t asm_inl(uint16_t port) {
uint32_t result;
asm volatile("in %0, %1" : "=a"(result) : "Nd"(port));
return result;
}
static inline void asm_outb(uint16_t port, uint8_t value) {
asm volatile("out %0, %1" : : "Nd"(port), "a"(value));
}
static inline void asm_outw(uint16_t port, uint16_t value) {
asm volatile("out %0, %1" : : "Nd"(port), "a"(value));
}
static inline void asm_outl(uint16_t port, uint32_t value) {
asm volatile("out %0, %1" : : "Nd"(port), "a"(value));
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#define ELF_ARCH_CLASS ELFCLASS64
#define ELF_ARCH_DATA ELFDATA2LSB
#define ELF_ARCH_MACHINE EM_X86_64
#define elf_hdr elf64_hdr
#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
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <defs.h>
#include <stdint.h>
static inline bool arch_irq_get_state() {
uint64_t flags;
asm volatile("pushf; pop %0" : "=r"(flags)::"memory");
return flags & RFLAGS_IF;
}
static inline void arch_irq_set_state(bool state) {
if (state)
asm volatile("sti");
else
asm volatile("cli");
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <kernel/types.h>
#include <stddef.h>
#include <stdint.h>
static inline size_t arch_mem_page_bits() {
return 12;
}
static inline size_t arch_mem_level_bits() {
return 9;
}
static inline size_t arch_mem_num_levels() {
return 4;
}
static inline virt_t arch_mem_hhdm_addr() {
return 0xFFFF'8000'0000'0000;
}
static inline virt_t arch_mem_pfndb_addr() {
return 0xFFFF'A000'0000'0000;
}
static inline virt_t arch_mem_mapping_addr() {
return 0xFFFF'C000'0000'0000;
}
typedef uint64_t pte_t;
enum pte_flags;
enum cache_mode;
struct page_table;
void arch_mem_pte_clear(pte_t* pte);
pte_t arch_mem_pte_build(phys_t addr, enum pte_flags flags, enum cache_mode cache);
bool arch_mem_pte_is_present(pte_t* pte);
bool arch_mem_pte_is_dir(pte_t* pte);
phys_t arch_mem_pte_address(pte_t* pte);
void arch_mem_pt_set(struct page_table* pt);
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <gdt.h>
#include <stdint.h>
static inline struct percpu* arch_percpu_get() {
struct percpu* result;
asm volatile("mov %0, gs:0" : "=r"(result)::"memory");
return result;
}
struct arch_percpu {
uint32_t lapic_id;
struct gdt gdt;
struct tss tss;
};
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <stdint.h>
struct arch_task_context {
uint64_t rsp;
uint8_t* fpu_region;
uint16_t ds, es, fs, gs;
uint64_t fs_base, gs_base;
};
struct arch_context {
uint64_t r15;
uint64_t r14;
uint64_t r13;
uint64_t r12;
uint64_t r11;
uint64_t r10;
uint64_t r9;
uint64_t r8;
uint64_t rsi;
uint64_t rdi;
uint64_t rbp;
uint64_t rdx;
uint64_t rcx;
uint64_t rbx;
uint64_t rax;
uint64_t isr; // Pushed onto the stack by the interrupt handler stubs.
uint64_t error; // Pushed onto the stack by the CPU if the interrupt has an error code.
uint64_t rip; // The rest is pushed onto the stack by the CPU during an interrupt.
uint64_t cs;
uint64_t rflags;
uint64_t rsp;
uint64_t ss;
};
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <stdint.h>
typedef uint64_t phys_t;
typedef uint64_t virt_t;
typedef uint64_t reg_t;
@@ -0,0 +1,9 @@
#pragma once
#include <kernel/compiler.h>
#include <stddef.h>
#include <stdint.h>
bool arch_usercopy_read(uint8_t* dst, const __user uint8_t* src, size_t len);
bool arch_usercopy_write(__user uint8_t* dst, const uint8_t* src, size_t len);
bool arch_usercopy_strlen(const __user uint8_t* str, size_t max, size_t* len);
+191
View File
@@ -0,0 +1,191 @@
#pragma once
#define CPUID_1C_SSE3 (1 << 0)
#define CPUID_1C_PCLMUL (1 << 1)
#define CPUID_1C_DTES64 (1 << 2)
#define CPUID_1C_MONITOR (1 << 3)
#define CPUID_1C_DS_CPL (1 << 4)
#define CPUID_1C_VMX (1 << 5)
#define CPUID_1C_SMX (1 << 6)
#define CPUID_1C_EST (1 << 7)
#define CPUID_1C_TM2 (1 << 8)
#define CPUID_1C_SSSE3 (1 << 9)
#define CPUID_1C_CID (1 << 10)
#define CPUID_1C_SDBG (1 << 11)
#define CPUID_1C_FMA (1 << 12)
#define CPUID_1C_CX16 (1 << 13)
#define CPUID_1C_XTPR (1 << 14)
#define CPUID_1C_PDCM (1 << 15)
#define CPUID_1C_PCID (1 << 17)
#define CPUID_1C_DCA (1 << 18)
#define CPUID_1C_SSE4_1 (1 << 19)
#define CPUID_1C_SSE4_2 (1 << 20)
#define CPUID_1C_X2APIC (1 << 21)
#define CPUID_1C_MOVBE (1 << 22)
#define CPUID_1C_POPCNT (1 << 23)
#define CPUID_1C_TSC (1 << 24)
#define CPUID_1C_AES (1 << 25)
#define CPUID_1C_XSAVE (1 << 26)
#define CPUID_1C_OSXSAVE (1 << 27)
#define CPUID_1C_AVX (1 << 28)
#define CPUID_1C_F16C (1 << 29)
#define CPUID_1C_RDRAND (1 << 30)
#define CPUID_1C_HYPERVISOR (1 << 31)
#define CPUID_1D_FPU (1 << 0)
#define CPUID_1D_VME (1 << 1)
#define CPUID_1D_DE (1 << 2)
#define CPUID_1D_PSE (1 << 3)
#define CPUID_1D_TSC (1 << 4)
#define CPUID_1D_MSR (1 << 5)
#define CPUID_1D_PAE (1 << 6)
#define CPUID_1D_MCE (1 << 7)
#define CPUID_1D_CX8 (1 << 8)
#define CPUID_1D_APIC (1 << 9)
#define CPUID_1D_SEP (1 << 11)
#define CPUID_1D_MTRR (1 << 12)
#define CPUID_1D_PGE (1 << 13)
#define CPUID_1D_MCA (1 << 14)
#define CPUID_1D_CMOV (1 << 15)
#define CPUID_1D_PAT (1 << 16)
#define CPUID_1D_PSE36 (1 << 17)
#define CPUID_1D_PSN (1 << 18)
#define CPUID_1D_CLFLUSH (1 << 19)
#define CPUID_1D_DS (1 << 21)
#define CPUID_1D_ACPI (1 << 22)
#define CPUID_1D_MMX (1 << 23)
#define CPUID_1D_FXSR (1 << 24)
#define CPUID_1D_SSE (1 << 25)
#define CPUID_1D_SSE2 (1 << 26)
#define CPUID_1D_SS (1 << 27)
#define CPUID_1D_HTT (1 << 28)
#define CPUID_1D_TM (1 << 29)
#define CPUID_1D_IA64 (1 << 30)
#define CPUID_1D_PBE (1 << 31)
#define CPUID_7B_FSGSBASE (1 << 0)
#define CPUID_7B_SGX (1 << 2)
#define CPUID_7B_BMI (1 << 3)
#define CPUID_7B_HLE (1 << 4)
#define CPUID_7B_AVX2 (1 << 5)
#define CPUID_7B_SMEP (1 << 7)
#define CPUID_7B_BMI2 (1 << 8)
#define CPUID_7B_RTM (1 << 11)
#define CPUID_7B_AVX512F (1 << 16)
#define CPUID_7B_AVX512DQ (1 << 17)
#define CPUID_7B_RDSEED (1 << 18)
#define CPUID_7B_ADX (1 << 19)
#define CPUID_7B_SMAP (1 << 20)
#define CPUID_7B_AVX512IFMA (1 << 21)
#define CPUID_7B_CLFLUSHOPT (1 << 23)
#define CPUID_7B_CLWB (1 << 24)
#define CPUID_7B_AVX512CD (1 << 28)
#define CPUID_7B_SHA (1 << 29)
#define CPUID_7B_AVX512BW (1 << 30)
#define CPUID_7B_AVX512VL (1 << 31)
#define CPUID_7C_AVX512VBMI (1 << 1)
#define CPUID_7C_UMIP (1 << 2)
#define CPUID_7C_PKU (1 << 3)
#define CPUID_7C_OSPKE (1 << 4)
#define CPUID_7C_WAITPKG (1 << 5)
#define CPUID_7C_AVX512VBMI2 (1 << 6)
#define CPUID_7C_SHSTK (1 << 7)
#define CPUID_7C_GFNI (1 << 8)
#define CPUID_7C_VAES (1 << 9)
#define CPUID_7C_VPCLMULQDQ (1 << 10)
#define CPUID_7C_AVX512VNNI (1 << 11)
#define CPUID_7C_AVX512BITALG (1 << 12)
#define CPUID_7C_TME_EN (1 << 13)
#define CPUID_7C_AVX512VPOPCNTDQ (1 << 14)
#define CPUID_7C_RDPID (1 << 22)
#define CPUID_7C_KL (1 << 23)
#define CPUID_7C_CLDEMOTE (1 << 25)
#define CPUID_7C_MOVDIRI (1 << 27)
#define CPUID_7C_MOVDIR64B (1 << 28)
#define CPUID_7C_ENQCMD (1 << 29)
#define CPUID_7D_UINTR (1 << 5)
#define CPUID_7D_AVX512VP2INTERSECT (1 << 8)
#define CPUID_7D_SERIALIZE (1 << 14)
#define CPUID_7D_TSXLDTRK (1 << 16)
#define CPUID_7D_PCONFIG (1 << 18)
#define CPUID_7D_IBT (1 << 20)
#define CPUID_7D_AMX_BF16 (1 << 22)
#define CPUID_7D_AVX512FP16 (1 << 23)
#define CPUID_7D_AMX_TILE (1 << 24)
#define CPUID_7D_AMX_INT8 (1 << 25)
#define RFLAGS_CF (1u << 0)
#define RFLAGS_PF (1u << 2)
#define RFLAGS_AF (1u << 4)
#define RFLAGS_ZF (1u << 6)
#define RFLAGS_SF (1u << 7)
#define RFLAGS_TF (1u << 8)
#define RFLAGS_IF (1u << 9)
#define RFLAGS_DF (1u << 10)
#define RFLAGS_OF (1u << 11)
#define RFLAGS_IOPL (3u << 12)
#define RFLAGS_NT (1u << 14)
#define RFLAGS_RF (1u << 16)
#define RFLAGS_VM (1u << 17)
#define RFLAGS_AC (1u << 18)
#define RFLAGS_VIF (1u << 19)
#define RFLAGS_VIP (1u << 20)
#define RFLAGS_ID (1u << 21)
#define CR0_PE (1 << 0)
#define CR0_MP (1 << 1)
#define CR0_EM (1 << 2)
#define CR0_TS (1 << 3)
#define CR0_ET (1 << 4)
#define CR0_NE (1 << 5)
#define CR0_WP (1 << 16)
#define CR0_AM (1 << 18)
#define CR0_NW (1 << 29)
#define CR0_CD (1 << 30)
#define CR0_PG (1 << 31)
#define CR4_VME (1 << 0)
#define CR4_PVI (1 << 1)
#define CR4_TSD (1 << 2)
#define CR4_DE (1 << 3)
#define CR4_PSE (1 << 4)
#define CR4_PAE (1 << 5)
#define CR4_MCE (1 << 6)
#define CR4_PGE (1 << 7)
#define CR4_PCE (1 << 8)
#define CR4_OSFXSR (1 << 9)
#define CR4_OSXMMEXCPT (1 << 10)
#define CR4_UMIP (1 << 11)
#define CR4_VMXE (1 << 13)
#define CR4_SMXE (1 << 14)
#define CR4_FSGSBASE (1 << 16)
#define CR4_PCIDE (1 << 17)
#define CR4_OSXSAVE (1 << 18)
#define CR4_SMEP (1 << 20)
#define CR4_SMAP (1 << 21)
#define CR4_PKE (1 << 22)
#define CR4_CET (1 << 23)
#define CR4_PKS (1 << 24)
#define MSR_EFER_SCE (1 << 0)
#define MSR_EFER_LME (1 << 8)
#define MSR_EFER_LMA (1 << 10)
#define MSR_EFER_NXE (1 << 11)
#define MSR_EFER_SVME (1 << 12)
#define MSR_EFER_LMSLE (1 << 13)
#define MSR_EFER_FFXSR (1 << 14)
#define MSR_EFER_TCE (1 << 15)
#define MSR_EFER (0xC0000080)
#define MSR_STAR (0xC0000081)
#define MSR_LSTAR (0xC0000082)
#define MSR_CSTAR (0xC0000083)
#define MSR_SFMASK (0xC0000084)
#define MSR_FS_BASE (0xC0000100)
#define MSR_GS_BASE (0xC0000101)
#define MSR_KERNEL_GS_BASE (0xC0000102)
#define CPL_USER (0b11)
#define CPL_KERNEL (0b00)
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <kernel/compiler.h>
#include <stdint.h>
#include "tss.h"
struct [[__packed]] gdt {
uint64_t null;
uint64_t kernel_code32;
uint64_t kernel_data32;
uint64_t kernel_code64;
uint64_t kernel_data64;
uint64_t user_code;
uint64_t user_data;
uint64_t user_code64;
uint64_t tss[2];
};
struct [[__packed]] gdtr {
uint16_t limit;
struct gdt* base;
};
// Initializes a GDT on the local core.
void gdt_init();
// Sets the Task State Segment in the given GDT.
void gdt_set_tss(struct gdt* gdt, struct tss* tss);
+1
View File
@@ -0,0 +1 @@
#pragma once
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#define SMP_GDTR_OFFSET 80
#define SMP_FARJMP_OFFSET 90
#define SMP_TEMP_STACK_OFFSET 98
#define SMP_TEMP_CR3_OFFSET 102
#define SMP_ENTRY_OFFSET 106
#define SMP_HHDM_OFFSET 114
#define SMP_KERNEL32_DS 16
#define SMP_KERNEL64_CS 24
#define SMP_KERNEL64_DS 32
#define SMP_INFO_SIZE 127
#ifndef __ASSEMBLER__
#include <gdt.h>
#include <kernel/common.h>
#include <kernel/compiler.h>
#include <stddef.h>
#include <stdint.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;
uint32_t lapic_id;
uint8_t booted;
};
static_assert(SMP_GDTR_OFFSET == offsetof(struct smp_info, gdtr));
static_assert(SMP_FARJMP_OFFSET == offsetof(struct smp_info, farjmp_offset));
static_assert(SMP_TEMP_STACK_OFFSET == offsetof(struct smp_info, temp_stack));
static_assert(SMP_TEMP_CR3_OFFSET == offsetof(struct smp_info, temp_cr3));
static_assert(SMP_ENTRY_OFFSET == offsetof(struct smp_info, entry));
static_assert(SMP_HHDM_OFFSET == offsetof(struct smp_info, hhdm_offset));
static_assert(SMP_KERNEL32_DS == offsetof(struct gdt, kernel_data32));
static_assert(SMP_KERNEL64_CS == offsetof(struct gdt, kernel_code64));
static_assert(SMP_KERNEL64_DS == offsetof(struct gdt, kernel_data64));
static_assert(SMP_INFO_SIZE == sizeof(struct smp_info));
// Initializes a LAPIC.
void x86_64_smp_init(uint32_t id);
#endif
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <kernel/compiler.h>
#include <stdint.h>
struct [[__packed, __aligned(0x10)]] tss {
uint32_t reserved0;
uint64_t rsp0;
uint64_t rsp1;
uint64_t rsp2;
uint32_t reserved1;
uint32_t reserved2;
uint64_t ist1;
uint64_t ist2;
uint64_t ist3;
uint64_t ist4;
uint64_t ist5;
uint64_t ist6;
uint64_t ist7;
uint32_t reserved3;
uint32_t reserved4;
uint16_t reserved5;
uint16_t iopb;
};
+56
View File
@@ -0,0 +1,56 @@
#include <kernel/mem.h>
#include <kernel/types.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 << 10,
ARCH_FLAG_EXECUTE_DISABLE = 1LU << 63,
ARCH_PTE_ADDR_MASK = 0x000F'FFFF'FFFF'F000,
};
void arch_mem_pte_clear(pte_t* pte) {
*pte = 0;
}
pte_t arch_mem_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;
}
return result;
}
bool arch_mem_pte_is_present(pte_t* pte) {
return *pte & ARCH_FLAG_PRESENT;
}
bool arch_mem_pte_is_dir(pte_t* pte) {
return *pte & ARCH_FLAG_SIZE;
}
phys_t arch_mem_pte_address(pte_t* pte) {
return (phys_t)(*pte & ARCH_PTE_ADDR_MASK);
}
void arch_mem_pt_set(struct page_table* pt) {
asm volatile("mov cr3, %0" ::"r"(pt->root) : "memory");
}
+17
View File
@@ -0,0 +1,17 @@
kernel_c_args += [
'-masm=intel',
'-mgeneral-regs-only',
'-mno-red-zone',
'-mcmodel=kernel',
]
kernel_sources += files(
'gdt.c',
'idt.c',
'mem.c',
'serial.c',
'smp.c',
'sys.c',
'trampoline.S',
'usercopy.c',
)
+47
View File
@@ -0,0 +1,47 @@
#include <asm.h>
#include <kernel/console.h>
#include <stdint.h>
constexpr uint16_t COM1_BASE = 0x3F8;
static bool is_ready() {
return asm_inb(COM1_BASE + 5) & 0x20;
}
static void serial_write(struct console* con, const char* buf, size_t count) {
for (size_t i = 0; i < count; i++) {
while (!is_ready())
asm volatile("pause");
asm_outb(COM1_BASE, buf[i]);
}
}
bool serial_setup(struct console* con) {
asm_outb(COM1_BASE + 1, 0x00); // Disable interrupts
asm_outb(COM1_BASE + 3, 0x80); // Enable DLAB (set baud rate divisor)
asm_outb(COM1_BASE, 0x01); // Set divisor low byte (115200 baud if 1)
asm_outb(COM1_BASE + 1, 0x00); // Set divisor high byte
asm_outb(COM1_BASE + 3, 0x03); // 8 bits, no parity, one stop bit (8n1)
asm_outb(COM1_BASE + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
asm_outb(COM1_BASE + 4, 0x0B); // IRQs enabled, RTS/DSR set
asm_outb(COM1_BASE + 4, 0x1E); // Set to loopback mode.
asm_outb(COM1_BASE, 0xAE); // Send a test byte.
// If we don't get the same value back, this serial port doesn't work.
if (asm_inb(COM1_BASE) != 0xAE) {
return false;
}
asm_outb(COM1_BASE + 4, 0x0F); // Disable loopback mode.
return true;
}
static struct console serial_con = {
.name = "com1",
.write = serial_write,
.init = serial_setup,
};
DEFINE_CONSOLE(&serial_con);
+17
View File
@@ -0,0 +1,17 @@
#include <kernel/compiler.h>
#include <kernel/types.h>
#include <smp.h>
extern uint8_t smp_trampoline_start[];
extern uint8_t smp_trampoline_end[];
[[noreturn]]
static void ap_entry(phys_t info) {
// TODO
while (1) {
asm volatile("hlt");
}
}
void x86_64_smp_init(uint32_t id) {}
+42
View File
@@ -0,0 +1,42 @@
#include <zinnia/archctl.h>
#include <zinnia/status.h>
#include <kernel/compiler.h>
#include <kernel/init.h>
#include <kernel/percpu.h>
#include <gdt.h>
#include "asm.h"
#include "defs.h"
[[__init, __naked]]
void _start() {
asm volatile(
"lea rsp, [rip + %0]\n"
"jmp %1"
:
: "i"(__ld_stack_top), "r"(kernel_entry)
);
}
void percpu_bsp_early_init() {
asm_wrmsr(MSR_GS_BASE, (uint64_t)&percpu_bsp);
asm_wrmsr(MSR_FS_BASE, 0);
asm_wrmsr(MSR_KERNEL_GS_BASE, 0);
gdt_init();
}
[[noreturn]]
void arch_panic() {
asm volatile("cli; hlt");
__unreachable();
}
zn_status_t arch_archctl(zn_archctl_t op, uintptr_t arg) {
switch (op) {
case ZN_ARCHCTL_SET_FSBASE:
asm_wrmsr(MSR_FS_BASE, arg);
return 0;
default:
return ZN_ERR_BAD_ARG;
}
}
+107
View File
@@ -0,0 +1,107 @@
#include <smp.h>
#include <defs.h>
.intel_syntax noprefix
.global smp_trampoline_start
.global smp_trampoline_end
.section .rodata
.code16
smp_trampoline_start:
cli
cld
jmp 1f
smp_trampoline_data:
.skip SMP_INFO_SIZE
.set data_offset, (smp_trampoline_data - smp_trampoline_start)
.set gdtr_offset, (data_offset + SMP_GDTR_OFFSET)
.set farjmp_offset, (data_offset + SMP_FARJMP_OFFSET)
.set temp_stack_offset, (data_offset + SMP_TEMP_STACK_OFFSET)
.set temp_cr3_offset, (data_offset + SMP_TEMP_CR3_OFFSET)
.set entry_offset, (data_offset + SMP_ENTRY_OFFSET)
.set hhdm_offset, (data_offset + SMP_HHDM_OFFSET)
1:
mov bx, cs
shl ebx, 4
.set idtr_offset, (invalid_idtr - smp_trampoline_start)
lidtd cs:idtr_offset
lgdtd cs:gdtr_offset
.set mode32_offset, (mode32 - smp_trampoline_start)
lea eax, [ebx + mode32_offset]
mov dword ptr cs:farjmp_offset, eax
mov eax, CR0_PE | CR0_ET
mov cr0, eax
jmp fword ptr cs:farjmp_offset
.code32
mode32:
mov ax, SMP_KERNEL32_DS
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
xor eax, eax
lldt ax
mov esp, dword ptr [ebx + temp_stack_offset]
xor eax, eax
mov cr4, eax
or eax, CR4_PAE
mov cr4, eax
mov ecx, MSR_EFER
mov eax, MSR_EFER_LME
xor edx, edx
wrmsr
mov eax, dword ptr [ebx + temp_cr3_offset]
mov cr3, eax
mov eax, cr0
or eax, CR0_PG
mov cr0, eax
.set mode64_offset, (mode64 - smp_trampoline_start)
lea eax, [ebx + mode64_offset]
push SMP_KERNEL64_CS
push eax
retf
.code64
mode64:
mov ax, SMP_KERNEL64_DS
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov ecx, MSR_EFER
rdmsr
or eax, MSR_EFER_NXE
wrmsr
xor ebp, ebp
lea rdi, [rbx + data_offset]
mov rax, [rbx + entry_offset]
add rsp, [rbx + hhdm_offset]
jmp rax
invalid_idtr:
.word 0
.quad 0
smp_trampoline_end:
+128
View File
@@ -0,0 +1,128 @@
#include <kernel/compiler.h>
#include <kernel/usercopy.h>
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(uint8_t* dst, const __user uint8_t* src, size_t len) {
asm volatile(
// Setup regs for `rep movsb`.
"xchg rcx, rdx\n"
"mov rax, [rip + %0]\n"
"mov [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"
"mov [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 uint8_t* dst, const uint8_t* src, size_t len) {
asm volatile(
// Setup regs for `rep movsb`.
"xchg rcx, rdx\n"
"mov 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"
"mov [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"(&read_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 uint8_t* str, size_t max, size_t* len) {
asm volatile(
"mov rax, [rip + %0]\n"
"mov [rcx], rax\n"
".global x86_64_strlen_start\n"
"x86_64_strlen_start:\n"
"xor r8, r8\n"
".Lloop:\n"
"cmp byte ptr [rdi + r8], 0\n"
"je .Lleave\n"
"inc r8\n"
"cmp rsi, r8\n"
"jne .Lloop\n"
".Lleave:\n"
"mov [rdx], r8\n"
".global x86_64_strlen_end\n"
"x86_64_strlen_end:\n"
"xor rax, rax\n"
"mov [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"(&read_region)
: "memory"
);
}
+70
View File
@@ -0,0 +1,70 @@
#include <kernel/cmdline.h>
#include <kernel/init.h>
#include <stddef.h>
#include <string.h>
// Defined in linker script.
extern struct cmdline_option __ld_cmdline_start[];
extern struct cmdline_option __ld_cmdline_end[];
static char cmdline_buf[CMDLINE_MAX];
[[__init]]
void cmdline_parse(const char* cmdline) {
struct cmdline_option* opt = __ld_cmdline_start;
const size_t len = strnlen(cmdline, CMDLINE_MAX - 1);
size_t idx = 0;
memcpy(cmdline_buf, cmdline, len);
while (1) {
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) {
if (!strcmp(opt->name, name))
opt->func(value);
opt++;
}
if (idx >= len)
break;
}
}
+85
View File
@@ -0,0 +1,85 @@
#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 "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);
[[__initdata_sorted("limine.2")]]
static volatile uint64_t limine_end[] = LIMINE_REQUESTS_END_MARKER;
#define LIMINE_REQUEST(request, tag, rev) \
[[__initdata_sorted("limine.1")]] \
static volatile struct limine_##request request = { \
.id = tag, \
.revision = rev, \
.response = nullptr, \
}
LIMINE_REQUEST(memmap_request, LIMINE_MEMMAP_REQUEST_ID, 0);
LIMINE_REQUEST(hhdm_request, LIMINE_HHDM_REQUEST_ID, 0);
LIMINE_REQUEST(executable_address_request, LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID, 0);
LIMINE_REQUEST(executable_file_request, LIMINE_EXECUTABLE_FILE_REQUEST_ID, 0);
LIMINE_REQUEST(framebuffer_request, LIMINE_FRAMEBUFFER_REQUEST_ID, 1);
LIMINE_REQUEST(module_request, LIMINE_MODULE_REQUEST_ID, 0);
LIMINE_REQUEST(dtb_request, LIMINE_DTB_REQUEST_ID, 0);
LIMINE_REQUEST(rsdp_request, LIMINE_RSDP_REQUEST_ID, 0);
[[__initdata]]
static struct phys_mem mem[128];
[[__initdata]]
static struct boot_file files[8];
[[__init]]
void kernel_entry() {
kernel_early_init();
struct boot_info info;
struct limine_memmap_response* mm_res = memmap_request.response;
struct limine_module_response* module_res = module_request.response;
struct limine_executable_address_response* exec_res = executable_address_request.response;
struct limine_executable_file_response* exec_file_res = executable_file_request.response;
info.cmdline = exec_file_res->executable_file->string;
info.num_mem_maps = MIN(ARRAY_SIZE(mem), mm_res->entry_count);
for (size_t i = 0; i < info.num_mem_maps; i++) {
mem[i].address = mm_res->entries[i]->base;
mem[i].length = mm_res->entries[i]->length;
switch (mm_res->entries[i]->type) {
case LIMINE_MEMMAP_USABLE:
mem[i].usage = PHYS_USABLE;
break;
case LIMINE_MEMMAP_ACPI_RECLAIMABLE:
case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE:
mem[i].usage = PHYS_RECLAIMABLE;
break;
default:
mem[i].usage = PHYS_RESERVED;
break;
}
}
if (module_res) {
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].length = module_res->modules[i]->size;
files[i].path = module_res->modules[i]->path;
}
}
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;
kernel_main(&info);
}
+576
View File
@@ -0,0 +1,576 @@
/* BSD Zero Clause License */
/* Copyright (C) 2022-2025 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
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* 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;
uint32_t tftp_ip;
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;
};
/* Paging mode */
#define LIMINE_PAGING_MODE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x95c1a0edab0944cb, 0xa4e5cb3842f7488a}
#if defined(__x86_64__) || defined(__i386__)
#define LIMINE_PAGING_MODE_X86_64_4LVL 0
#define LIMINE_PAGING_MODE_X86_64_5LVL 1
#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_X86_64_4LVL
#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_X86_64_4LVL
#elif defined(__aarch64__)
#define LIMINE_PAGING_MODE_AARCH64_4LVL 0
#define LIMINE_PAGING_MODE_AARCH64_5LVL 1
#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_AARCH64_4LVL
#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_AARCH64_4LVL
#elif defined(__riscv) && (__riscv_xlen == 64)
#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_MIN LIMINE_PAGING_MODE_RISCV_SV39
#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_RISCV_SV48
#elif defined(__loongarch__) && (__loongarch_grlen == 64)
#define LIMINE_PAGING_MODE_LOONGARCH64_4LVL 0
#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_LOONGARCH64_4LVL
#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_LOONGARCH64_4LVL
#else
#error Unknown architecture
#endif
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_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 reserved;
};
struct limine_mp_response {
uint64_t cpu_count;
LIMINE_PTR(struct limine_mp_info**) cpus;
};
#else
#error Unknown architecture
#endif
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_ACPI_TABLES 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;
uint64_t entry_32;
uint64_t 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;
uint64_t address;
};
struct limine_efi_system_table_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_efi_system_table_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;
};
#ifdef __cplusplus
}
#endif
#endif
+1
View File
@@ -0,0 +1 @@
kernel_sources += files('entry.c')
+42
View File
@@ -0,0 +1,42 @@
#include <kernel/assert.h>
#include <kernel/cmdline.h>
#include <kernel/compiler.h>
#include <kernel/console.h>
#include <kernel/init.h>
#include <kernel/irq.h>
#include <kernel/mem.h>
#include <kernel/percpu.h>
#include <kernel/print.h>
#include <config.h>
const char zinnia_banner[] =
"zinnia " ZINNIA_VERSION " (" ZINNIA_ARCH ", " ZINNIA_COMPILER_ID ", " ZINNIA_LINKER_ID ")";
[[__init]]
void kernel_early_init() {
percpu_bsp_early_init();
percpu_get()->online = true;
irq_lock();
}
[[noreturn]]
void kernel_main(struct boot_info* info) {
cmdline_parse(info->cmdline);
console_init();
kprintf("%s\n", zinnia_banner); // Say hello!
kprintf("Command line: \"%s\"\n", info->cmdline);
mem_init(info->mem_map, info->num_mem_maps, info->virt_base, info->phys_base, info->hhdm_base);
sched_init();
irq_unlock();
while (1) {}
}
[[noreturn]]
void kernel_main_task() {
while (1) {}
ASSERT(false, "Nothing to do");
}
+6
View File
@@ -0,0 +1,6 @@
kernel_sources += files(
'cmdline.c',
'main.c',
)
subdir('limine')
+1
View File
@@ -0,0 +1 @@
+44
View File
@@ -0,0 +1,44 @@
#include <kernel/cmdline.h>
#include <kernel/console.h>
#include <kernel/print.h>
#include <kernel/syscalls.h>
#include <string.h>
extern struct console* const __ld_console_start[];
extern struct console* const __ld_console_end[];
struct console* active = nullptr;
void console_write(const char* buf, size_t len) {
if (__likely(active))
active->write(active, buf, len);
}
static void console_setup(const char* value) {
if (!value)
return;
// Find a suitable boot console.
struct console* const* cur = __ld_console_start;
while (cur < __ld_console_end) {
if (!strcmp(value, (*cur)->name)) {
active = *cur;
}
cur++;
}
}
CMDLINE_OPTION("console", console_setup);
void console_init() {
// If we have no console set and at least one set, use that as a default.
if (!active && &__ld_console_start[0] != &__ld_console_end[0])
active = __ld_console_start[0];
if (active) {
if (active->init(active) == false)
active = nullptr;
else
kprintf("Logging on console \"%s\"\n", active->name);
}
}
+1
View File
@@ -0,0 +1 @@
#include <kernel/elf.h>
+16
View File
@@ -0,0 +1,16 @@
#include <kernel/irq.h>
#include <kernel/percpu.h>
#include <stdatomic.h>
void irq_lock() {
irq_set_state(false);
atomic_fetch_add_explicit(&percpu_get()->irq.level, 1, memory_order_acq_rel);
}
void irq_unlock() {
uint32_t old_level = atomic_fetch_sub_explicit(&percpu_get()->irq.level, 1, memory_order_acq_rel);
// If it was 1, the new IRQ level is now 0.
if (old_level == 1) {
irq_set_state(true);
}
}
+11
View File
@@ -0,0 +1,11 @@
kernel_sources += files(
'clock.c',
'console.c',
'elf.c',
'irq.c',
'panic.c',
'percpu.c',
'print.c',
'sched.c',
'spin.c',
)
+7
View File
@@ -0,0 +1,7 @@
#include <kernel/panic.h>
[[noreturn]]
void panic() {
// TODO
while (1) {}
}
+14
View File
@@ -0,0 +1,14 @@
#include <kernel/compiler.h>
#include <kernel/percpu.h>
[[__used, __section(".percpu")]]
struct percpu percpu_bsp = {
.self = &percpu_bsp,
.id = 0,
.online = false,
};
struct percpu* percpu_new() {
// TODO
return nullptr;
}
+372
View File
@@ -0,0 +1,372 @@
#include <kernel/console.h>
#include <kernel/panic.h>
#include <kernel/print.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) {
// Amount of bytes written.
int32_t written = 0;
while (*fmt != '\0') {
size_t maxrem = INT32_MAX - written;
if (fmt[0] != '%' || fmt[1] == '%') {
if (fmt[0] == '%')
fmt++;
size_t amount = 1;
while (fmt[amount] && fmt[amount] != '%')
amount++;
if (maxrem < amount) {
return;
}
callback(ctx, fmt, amount);
fmt += amount;
written += amount;
continue;
}
const char* format_begun_at = fmt++;
bool force_sign = false;
bool blank_sign = false;
bool write_prefix = false;
bool pad_zero = false;
bool has_width = false;
size_t width = 0;
bool has_precision = false;
size_t precision = 0;
size_t size = sizeof(uint32_t);
char number[32];
check_fmt:
memset(number, 0, sizeof(number));
switch (*fmt) {
// Flags
case '+': {
force_sign = true;
fmt++;
goto check_fmt;
}
case ' ': {
blank_sign = true;
fmt++;
goto check_fmt;
}
case '#': {
write_prefix = true;
fmt++;
goto check_fmt;
}
case '0': {
pad_zero = true;
fmt++;
goto check_fmt;
}
// Width
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
has_width = true;
char number[10 + 1] = {0};
size_t idx = 0;
while (*fmt >= '0' && *fmt <= '9') {
number[idx++] = *fmt;
fmt++;
}
width = atolu(number, 10);
goto check_fmt;
}
// Precision
case '.': {
has_precision = true;
fmt++;
if (*fmt == '*') {
precision = (char)va_arg(args, int32_t);
} else {
char number[10 + 1] = {0};
size_t idx = 0;
while (*fmt >= '0' && *fmt <= '9') {
number[idx++] = *fmt;
fmt++;
}
precision = atolu(number, 10);
}
goto check_fmt;
}
// Length
case 'h': {
if (fmt[1] == 'h') {
size = sizeof(uint8_t);
fmt++;
} else
size = sizeof(uint16_t);
fmt++;
goto check_fmt;
}
case 'l': {
// There is no difference between `long int` and `long long int`.
if (fmt[1] == 'l')
fmt++;
fmt++;
size = sizeof(uint64_t);
goto check_fmt;
}
case 'z': {
size = sizeof(size_t);
fmt++;
goto check_fmt;
}
// Character
case 'c': {
char c = (char)va_arg(args, int32_t);
callback(ctx, &c, sizeof(c));
written++;
fmt++;
break;
}
// String of characters
case 's': {
const char* str = va_arg(args, const char*);
if (!str)
str = "(null)";
size_t len = 0;
if (has_width)
len = strnlen(str, width);
else
len = strlen(str);
callback(ctx, str, len);
written += len;
fmt++;
break;
}
// Signed decimal integer
case 'i':
case 'd': {
switch (size) {
case sizeof(int8_t):
ltoa((int8_t)va_arg(args, int32_t), number, 10);
break;
case sizeof(int16_t):
ltoa((int16_t)va_arg(args, int32_t), number, 10);
break;
case sizeof(int32_t):
ltoa((int32_t)va_arg(args, int32_t), number, 10);
break;
default:
ltoa(va_arg(args, int64_t), number, 10);
break;
}
goto print_num;
}
// Unsigned decimal integer
case 'u': {
switch (size) {
case sizeof(uint8_t):
lutoa((uint8_t)va_arg(args, uint32_t), number, 10);
break;
case sizeof(uint16_t):
lutoa((uint16_t)va_arg(args, uint32_t), number, 10);
break;
case sizeof(uint32_t):
lutoa(va_arg(args, uint32_t), number, 10);
break;
case sizeof(uint64_t):
lutoa(va_arg(args, uint64_t), number, 10);
break;
}
goto print_num;
}
// Unsigned hexadecimal integer
case 'X':
case 'x': {
switch (size) {
case sizeof(uint8_t):
lutoa((uint8_t)va_arg(args, uint32_t), number, 16);
break;
case sizeof(uint16_t):
lutoa((uint16_t)va_arg(args, uint32_t), number, 16);
break;
case sizeof(uint32_t):
lutoa(va_arg(args, uint32_t), number, 16);
break;
case sizeof(uint64_t):
lutoa(va_arg(args, uint64_t), number, 16);
break;
}
print_num:
size_t len = strlen(number);
if (has_precision) {
for (size_t i = 0; i < precision - len; i++)
callback(ctx, " ", 1);
}
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);
}
}
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);
written += len;
fmt++;
break;
}
// Pointer address
case 'p': {
const size_t num = va_arg(args, size_t);
const size_t buf_size = sizeof(size_t) * 2 + 1;
char str[buf_size];
lutoa(num, number, 0x10);
const size_t len = strlen(number); // Get the length of the final number.
for (size_t i = 0; i < buf_size; i++)
str[i] = '0';
size_t offset = sizeof(str) - len - 1;
callback(ctx, str, offset);
callback(ctx, number, len);
fmt++;
written += len;
break;
}
// The number of characters written so far is stored in the pointed location.
case 'n': {
int32_t* const ptr = va_arg(args, int32_t*);
*ptr = written;
fmt++;
break;
}
// No format, just normal text.
default: {
fmt = format_begun_at;
size_t len = strlen(fmt);
callback(ctx, fmt, len);
written += len;
fmt += len;
break;
}
}
}
}
static void printf_console(void*, const char* msg, size_t len) {
console_write(msg, len);
}
void kprintf(const char* message, ...) {
va_list args;
va_start(args, message);
kvprintf(printf_console, nullptr, message, args);
va_end(args);
}
+6
View File
@@ -0,0 +1,6 @@
#include <kernel/print.h>
#include <kernel/sched.h>
void sched_init() {
kprintf("Scheduler initialized\n");
}
+16
View File
@@ -0,0 +1,16 @@
#include <kernel/spin.h>
#include <stdatomic.h>
// TODO: Ticketing system
void spin_lock(struct spinlock* spin) {
while (atomic_exchange_explicit(&spin->locked, true, memory_order_acquire)) {
asm volatile("pause");
}
atomic_store_explicit(&spin->locked, true, memory_order_release);
}
void spin_unlock(struct spinlock* spin) {
atomic_store_explicit(&spin->locked, false, memory_order_release);
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <kernel/compiler.h>
#include <kernel/panic.h>
#include <kernel/print.h>
#define ASSERT(expr, msg, ...) \
({ \
if (__unlikely(!(expr))) { \
kprintf( \
"\e[31mKernel panic - Environment is unsound!\n" \
"In function \"%s\" (%s:%u):\n" \
"Assertion \"%s\" failed! " msg "\n", \
__FUNCTION__, \
__FILE__, \
__LINE__, \
#expr, \
##__VA_ARGS__ \
); \
panic(); \
} \
})
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <kernel/common.h>
#include <kernel/compiler.h>
#define CMDLINE_MAX 0x1000
#define CMDLINE_OPTION(opt_name, opt_func) \
[[__used, __section(".cmdline")]] \
static const struct cmdline_option UNIQUE_IDENT(__cmline_option_) = { \
.name = opt_name, \
.func = opt_func, \
}
struct cmdline_option {
// The name of this option.
const char* name;
// Gets called if this option is present on the command line.
// If an option is specified as `name=value`, then the `value` is passed as well.
// Otherwise, it's NULL.
void (*func)(const char* value);
};
// Parses the command line and invokes all options.
void cmdline_parse(const char* cmdline);
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#define ROUND_UP(value, to) (((value) + ((to) - 1)) / (to))
#define ALIGN_DOWN(value, align) (((value) / (align)) * (align))
#define ALIGN_UP(value, align) (ROUND_UP(value, align) * align)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define BIT_TEST(value, bit) (((value) & (1 << bit)) == (1 << bit))
#define CONTAINER_OF(value, p, field) ((p*)((char*)(&(value)) + offsetof(p, field)))
#define CONCAT(A, B) _CONCAT(A, B)
#define _CONCAT(A, B) A##B
// Creates a unique identifier for a compilation unit.
#define UNIQUE_IDENT(ident) CONCAT(__unique_##ident, __COUNTER__)
// Ensures that a type is properly defined at this point.
// This is used to ensure that e.g. architecture-specific structures are defined.
#define ASSERT_TYPE(type) static_assert(sizeof(type) == sizeof(type))
+46
View File
@@ -0,0 +1,46 @@
#pragma once
// These hints may improve code generation, but are optional and can be stubbed.
#if defined(__GNUC__) || defined(__clang__)
#define __cold gnu::cold
#define __hot gnu::hot
#define __used gnu::used
#define __unused gnu::unused
#define __format(like, ...) gnu::format(like, __VA_ARGS__)
#define __likely(x) __builtin_expect(!!(x), 1)
#define __unlikely(x) __builtin_expect(!!(x), 0)
#define __always_inline gnu::always_inline
#else
#define __cold
#define __hot
#define __used
#define __unused
#define __format(like, ...)
#define __likely(x) x
#define __unlikely(x) x
#define __always_inline
#endif
// Only clang has these hints, GCC will throw a warning here.
// They also don't support the C23 attribute format.
#ifdef __clang__
#define __user __attribute__((noderef, address_space(1)))
#define __phys __attribute__((noderef, address_space(2)))
#else
#define __user
#define __phys
#endif
// Attributes that affect data layout. Not optional.
#if defined(__GNUC__) || defined(__clang__)
#define __weak gnu::weak
#define __section(x) gnu::section(x)
#define __aligned(x) gnu::aligned(x)
#define __packed gnu::packed
#define __naked gnu::naked
#else
#error "Unsupported compiler!"
#endif
// Intrinsics
#define __unreachable() __builtin_unreachable()
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <kernel/common.h>
#include <kernel/compiler.h>
#include <stddef.h>
struct console {
const char* name;
void* private;
// Initializes the console.
bool (*init)(struct console* con);
// Writes to the console.
void (*write)(struct console* con, const char* buf, size_t count);
};
// Initializes a console for logging.
void console_init();
// Writes a message to a console.
void console_write(const char* buf, size_t len);
// Defines a new early console which can be used to log early boot messages.
// An early console must not make any heap allocations.
#define DEFINE_CONSOLE(con) \
[[__used, __section(".console")]] \
static struct console* UNIQUE_IDENT(console) = (con)
+227
View File
@@ -0,0 +1,227 @@
#pragma once
#include <bits/elf.h>
#include <stdint.h>
constexpr char ELF_MAG[4] = {0x7F, 'E', 'L', 'F'};
typedef uint64_t elf64_addr_t;
typedef uint64_t elf64_off_t;
typedef uint32_t elf32_addr_t;
typedef uint32_t elf32_off_t;
enum : uint8_t {
EI_MAG0 = 0,
EI_MAG1 = 1,
EI_MAG2 = 2,
EI_MAG3 = 3,
EI_CLASS = 4,
EI_DATA = 5,
EI_VERSION = 6,
EI_OSABI = 7,
EI_ABIVERSION = 8,
EI_PAD = 9,
EI_NIDENT = 16,
};
enum : uint16_t {
EM_X86_64 = 62,
EM_AARCH64 = 183,
EM_RISCV = 243,
EM_LOONGARCH = 258,
};
enum : uint32_t {
ELFCLASS32 = 1,
ELFCLASS64 = 2,
};
enum : uint32_t {
ELFDATA2LSB = 1,
ELFDATA2MSB = 2,
};
enum : uint32_t {
EV_NONE = 0,
EV_CURRENT = 1,
EV_NUM = 2,
};
enum : uint32_t {
ELFOSABI_SYSV = 0,
ELFOSABI_HPUX = 1,
ELFOSABI_STANDALONE = 255,
};
enum : uint32_t {
ET_NONE = 0,
ET_REL = 1,
ET_EXEC = 2,
ET_DYN = 3,
ET_CORE = 4,
};
enum : uint32_t {
PT_NULL = 0x00000000,
PT_LOAD = 0x00000001,
PT_DYNAMIC = 0x00000002,
PT_INTERP = 0x00000003,
PT_NOTE = 0x00000004,
PT_SHLIB = 0x00000005,
PT_PHDR = 0x00000006,
PT_TLS = 0x00000007,
};
enum : uint32_t {
PF_X = 0x00000001,
PF_W = 0x00000002,
PF_R = 0x00000004,
};
enum : uint32_t {
DT_NULL = 0,
DT_NEEDED = 1,
DT_PLTRELSZ = 2,
DT_PLTGOT = 3,
DT_HASH = 4,
DT_STRTAB = 5,
DT_SYMTAB = 6,
DT_RELA = 7,
DT_RELASZ = 8,
DT_RELAENT = 9,
DT_STRSZ = 10,
DT_SYMENT = 11,
DT_INIT = 12,
DT_FINI = 13,
DT_SONAME = 14,
DT_RPATH = 15,
DT_SYMBOLIC = 16,
DT_REL = 17,
DT_RELSZ = 18,
DT_RELENT = 19,
DT_PLTREL = 20,
DT_DEBUG = 21,
DT_TEXTREL = 22,
DT_JMPREL = 23,
DT_BIND_NOW = 24,
DT_INIT_ARRAY = 25,
DT_FINI_ARRAY = 26,
DT_INIT_ARRAYSZ = 27,
DT_FINI_ARRAYSZ = 28,
DT_LOOS = 0x60000000,
DT_HIOS = 0x6FFFFFFF,
DT_LOPROC = 0x70000000,
DT_HIPROC = 0x7FFFFFFF,
};
enum : uint32_t {
AT_NULL = 0,
AT_IGNORE = 1,
AT_EXECFD = 2,
AT_PHDR = 3,
AT_PHENT = 4,
AT_PHNUM = 5,
AT_PAGESZ = 6,
AT_BASE = 7,
AT_FLAGS = 8,
AT_ENTRY = 9,
AT_NOTELF = 10,
AT_UID = 11,
AT_EUID = 12,
AT_GID = 13,
AT_EGID = 14,
};
struct elf64_ehdr {
uint8_t e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
elf64_addr_t e_entry;
elf64_off_t e_phoff;
elf64_off_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct elf32_ehdr {
uint8_t e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
elf32_addr_t e_entry;
elf32_off_t e_phoff;
elf32_off_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct elf64_phdr {
uint32_t p_type;
uint32_t p_flags;
elf64_off_t p_offset;
elf64_addr_t p_vaddr;
elf64_addr_t p_paddr;
uint64_t p_filesz;
uint64_t p_memsz;
uint64_t p_align;
};
struct elf32_phdr {
uint32_t p_type;
elf32_off_t p_offset;
elf32_addr_t p_vaddr;
elf32_addr_t p_paddr;
uint32_t p_filesz;
uint32_t p_memsz;
uint32_t p_flags;
uint32_t p_align;
};
struct elf64_dyn {
int64_t d_tag;
union {
uint64_t d_val;
elf64_addr_t d_ptr;
} d_un;
};
struct elf32_dyn {
int32_t r_offset;
union {
uint32_t d_val;
elf32_addr_t d_ptr;
} d_un;
};
struct elf64_nhdr {
uint64_t n_namesz;
uint64_t n_descsz;
uint64_t n_type;
};
struct elf32_nhdr {
uint32_t n_namesz;
uint32_t n_descsz;
uint32_t n_type;
};
struct elf64_auxv {
uint64_t atype;
uint64_t avalue;
};
struct elf32_auxv {
uint32_t atype;
uint32_t avalue;
};
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include <zinnia/handle.h>
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <kernel/common.h>
#include <kernel/compiler.h>
#include <kernel/types.h>
#include <stddef.h>
#include <stdint.h>
#define __init __used, __section(".init.text"), __cold
#define __initdata __used, __section(".init.data")
#define __initdata_sorted(name) __used, __section(".init.data." name)
struct boot_file {
uint8_t* data;
size_t length;
const char* path;
};
struct boot_info {
char* cmdline;
struct phys_mem* mem_map;
size_t num_mem_maps;
phys_t phys_base;
virt_t virt_base;
virt_t hhdm_base;
struct boot_file* files;
size_t num_files;
};
extern uint8_t __ld_stack_top[];
extern uint8_t __ld_stack_bottom[];
// Entry point for the kernel after arch-specific setup has finished.
void kernel_entry();
// Initializes the early parts of the kernel.
void kernel_early_init();
[[noreturn]]
void kernel_main(struct boot_info* info);
[[noreturn]]
void kernel_main_task();
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <bits/irq.h>
#include <kernel/compiler.h>
#include <kernel/list.h>
#include <kernel/vector.h>
#include <stdint.h>
enum irq_status : uint8_t {
IRQ_STATUS_HANDLED,
IRQ_STATUS_IGNORED,
};
enum irq_polarity : uint8_t {
IRQ_POLARITY_LOW,
IRQ_POLARITY_HIGH,
};
enum irq_trigger_mode : uint8_t {
IRQ_TRIGGER_EDGE,
IRQ_TRIGGER_LEVEL,
};
struct irq_handler {
enum irq_status (*handler)(void* self);
SLIST_LINK(struct irq_handler*) next;
};
struct irq_line {
int (*set_config)(enum irq_trigger_mode mode, enum irq_polarity polarity);
void (*mask)(struct irq_line* self);
void (*unmask)(struct irq_line* self);
void (*eoi)(struct irq_line* self);
SLIST_HEAD(struct irq_handler*) handlers;
bool is_busy;
};
void irq_line_attach(struct irq_line* self, struct irq_handler* handler);
struct irq_percpu {
uint32_t level;
};
void irq_lock();
void irq_unlock();
// Enables or disables interrupts on this CPU.
// Don't use this function directly.
static inline void irq_set_state(bool state) {
return arch_irq_set_state(state);
}
// Returns true if interrupts are enabled on this CPU.
// Don't use this function directly.
static inline bool irq_get_state() {
return arch_irq_get_state();
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#define SLIST_HEAD(type) \
struct { \
type sl_head; \
}
#define SLIST_LINK(type) \
struct { \
type sl_link; \
}
#define SLIST_EMPTY(head) ((head)->sl_head == NULL)
#define SLIST_FIRST(head) ((head)->sl_head)
#define SLIST_NEXT(elm, field) ((elm)->field.sl_link)
#define SLIST_INSERT_END(slistelm, elm, field) \
do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) \
do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_FOREACH(var, head, field) for ((var) = SLIST_FIRST((head)); (var); (var) = SLIST_NEXT((var), field))
+180
View File
@@ -0,0 +1,180 @@
#pragma once
#include <zinnia/status.h>
#include <kernel/compiler.h>
#include <kernel/spin.h>
#include <kernel/types.h>
#include <bits/mem.h>
#include <stddef.h>
ASSERT_TYPE(pte_t);
enum pte_flags {
PTE_READ = 1 << 0, // Can read from this page.
PTE_WRITE = 1 << 1, // Can write to this page.
PTE_EXEC = 1 << 2, // Can execute code on this page.
PTE_USER = 1 << 3, // Can be accessed by the user.
PTE_DIR = 1 << 4, // Is a non-leaf page.
};
enum cache_mode {
CACHE_NONE,
CACHE_WRITE_COMBINE,
CACHE_WRITE_THROUGH,
CACHE_WRITE_BACK,
CACHE_MMIO,
};
struct page_table {
phys_t root;
struct spinlock lock;
};
enum alloc_flags {
ALLOC_NOZERO = 1 << 0, // Don't zero out the allocated memory.
ALLOC_MEM32 = 1 << 1, // Allocated memory needs to fit inside 32 bits.
ALLOC_MEM20 = 1 << 2, // Allocated memory needs to fit inside 20 bits.
};
enum phys_mem_usage {
PHYS_RESERVED,
PHYS_USABLE,
PHYS_RECLAIMABLE,
};
struct phys_mem {
phys_t address;
size_t length;
enum phys_mem_usage usage;
};
enum page_type {
PAGE_PHYS = 0, // Regular physical memory.
};
struct page {
enum page_type type;
int32_t flags;
size_t refcount;
union {
struct {
struct page* next; // Pointer to the next chunk.
size_t count; // Amount of free pages.
} freelist;
};
};
static_assert(0x1000 % sizeof(struct page) == 0, "struct must be a multiple of the page size!");
static_assert(sizeof(struct page) <= 64, "struct must be smaller than 64 bytes!");
struct address_space {
// TODO
};
extern struct page_table mem_kernel_table;
// Base address of the `struct page` array.
extern struct page* mem_pfndb;
// Base address of the HHDM.
extern virt_t mem_hhdm_base;
#define HHDM_PTR(paddr) (void*)((paddr) + mem_hhdm_base)
void mem_init(struct phys_mem* map, size_t length, virt_t kernel_virt, phys_t kernel_phys, virt_t tmp_hhdm);
void mem_phys_bootstrap(struct phys_mem* mem);
void mem_phys_init(struct phys_mem* map, size_t length);
// Allocates a region of memory which can be smaller than the page size.
// Returns `nullptr` if the allocator cannot provide an allocation for the
// given `length` + `flags`. Always returns `nullptr` if `length` is 0.
zn_status_t mem_alloc(size_t length, enum alloc_flags flags, void** out);
// Frees an allocation created by `mem_alloc`.
// Passing `nullptr` is a no-op.
zn_status_t mem_free(void* mem);
// Allocates an amount of contiguous physical pages.
zn_status_t mem_phys_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out);
// Frees an allocated region of physical pages.
zn_status_t mem_phys_free(phys_t start, size_t num_pages);
// Creates a new page table for the kernel.
zn_status_t mem_pt_new_kernel(struct page_table* pt, enum alloc_flags flags);
// Creates a new page table for a user process.
zn_status_t mem_pt_new_user(struct page_table* pt, enum alloc_flags flags);
// Sets a page table on the current processor.
static inline void mem_pt_set(struct page_table* pt) {
arch_mem_pt_set(pt);
}
// Maps a single page to a virtual address in the given page table.
zn_status_t mem_pt_map(struct page_table* pt, virt_t vaddr, phys_t paddr, enum pte_flags flags, enum cache_mode cache);
// Changes the protection of a page.
zn_status_t mem_pt_protect(struct page_table* pt, virt_t vaddr, enum pte_flags flags);
// Unmaps a page.
zn_status_t mem_pt_unmap(struct page_table* pt, virt_t vaddr);
// Base address used to access physical memory.
static inline virt_t mem_hhdm_addr() {
return arch_mem_hhdm_addr();
}
// Base address used to access the page array.
static inline virt_t mem_pfndb_addr() {
return arch_mem_pfndb_addr();
}
// Base address used to access kernel memory mappings.
static inline virt_t mem_mapping_addr() {
return arch_mem_mapping_addr();
}
// Bit shift in a page.
static inline size_t mem_page_bits() {
return arch_mem_page_bits();
}
// Gets the page size in bytes.
static inline size_t mem_page_size() {
return 1 << mem_page_bits();
}
// The amount of bits in a level.
static inline size_t mem_level_bits() {
return arch_mem_level_bits();
}
// The amount of levels in an address.
static inline size_t mem_num_levels() {
return arch_mem_num_levels();
}
// Clears out a page table entry.
static inline void mem_pte_clear(pte_t* pte) {
return arch_mem_pte_clear(pte);
}
// Builds a page table entry from the given info.
static inline pte_t mem_pte_build(phys_t addr, enum pte_flags flags, enum cache_mode cache) {
return arch_mem_pte_build(addr, flags, cache);
}
// Returns true if the given PTE is present and valid.
static inline bool mem_pte_is_present(pte_t* pte) {
return arch_mem_pte_is_present(pte);
}
// Returns true if the given PTE contains another level.
static inline bool mem_pte_is_dir(pte_t* pte) {
return arch_mem_pte_is_dir(pte);
}
// Returns the address component of the page table entry.
static inline phys_t mem_pte_address(pte_t* pte) {
return arch_mem_pte_address(pte);
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <kernel/sched.h>
struct mutex {
struct task* owner;
bool flag;
};
void mutex_lock(struct mutex* mutex);
void mutex_unlock(struct mutex* mutex);
+5
View File
@@ -0,0 +1,5 @@
#pragma once
// Stop all execution upon panic.
[[noreturn]]
void panic();
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <bits/percpu.h>
#include <kernel/compiler.h>
#include <kernel/irq.h>
#include <kernel/sched.h>
#include <stddef.h>
ASSERT_TYPE(struct arch_percpu);
// CPU-relative information.
struct percpu {
struct percpu* self; // A pointer to this structure.
size_t id; // The virtual ID of this CPU.
bool online; // Whether this CPU is initialized and active.
virt_t kernel_stack; // The kernel mode stack.
virt_t user_stack; // The user mode stack.
struct arch_percpu arch; // Architecture-specific fields.
struct irq_percpu irq; // IRQ information.
struct sched_percpu sched; // Scheduler information.
};
// Per-CPU data for the bootstrap processor.
extern struct percpu percpu_bsp;
// Gets the per-CPU data on the current CPU.
static inline struct percpu* percpu_get() {
return arch_percpu_get();
}
// Allocates a block of memory for a new CPU.
struct percpu* percpu_new();
// Initializes the bootstrap processor.
void percpu_bsp_early_init();
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <kernel/compiler.h>
[[__format(printf, 1, 2)]]
void kprintf(const char* message, ...);
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <bits/sched.h>
#include <kernel/mem.h>
#include <kernel/types.h>
#include <stddef.h>
#include <stdint.h>
ASSERT_TYPE(struct arch_context);
ASSERT_TYPE(struct arch_task_context);
typedef size_t tid_t;
enum task_state {
TASK_STATE_RUNNING, // Task is active and running.
TASK_STATE_READY, // Task is active, but not currently scheduled.
TASK_STATE_BLOCKED, // Task is waiting on another object.
};
// A task is the smallest unit of the scheduler.
struct task {
tid_t id;
struct process* process;
enum task_state state;
struct arch_context context;
virt_t kernel_stack;
virt_t user_stack;
size_t time_slice;
int8_t priority;
};
// Per-CPU data for scheduling.
struct sched_percpu {
struct task* current;
};
void sched_init();
+13
View File
@@ -0,0 +1,13 @@
#pragma once
// Busy-waits in a loop until the lock is freed.
// Does not put the CPU to sleep.
struct spinlock {
bool locked;
};
// Attempts to lock a spinlock. If it's already locked, waits until it's freed.
void spin_lock(struct spinlock* spin);
// Unlocks a spinlock.
void spin_unlock(struct spinlock* spin);
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <zinnia/status.h>
#include <kernel/compiler.h>
#include <kernel/types.h>
#include <stddef.h>
#include <stdint.h>
zn_status_t syscall_dispatch(reg_t num, reg_t a0, reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6);
[[noreturn]]
void syscall_panic(zn_status_t err);
zn_status_t syscall_log(__user const char* msg, size_t len);
zn_status_t syscall_archctl(uint32_t op, size_t value);
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <bits/types.h>
#include <kernel/common.h>
ASSERT_TYPE(phys_t);
ASSERT_TYPE(virt_t);
ASSERT_TYPE(reg_t);
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <uapi/uio.h>
struct iovec_iter {};
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <bits/usercopy.h>
#include <kernel/compiler.h>
#include <stddef.h>
#include <stdint.h>
struct usercopy_region {
void (*start_ip)();
void (*end_ip)();
void (*fault_ip)();
};
// Copies a block of data from user to kernel memory.
bool usercopy_read(uint8_t* dst, const __user uint8_t* src, size_t len) {
return arch_usercopy_read(dst, src, len);
}
// Copies a block of data from kernel to user memory.
bool usercopy_write(__user uint8_t* dst, const uint8_t* src, size_t len) {
return arch_usercopy_write(dst, src, len);
}
// Performs a strlen() on a user string.
bool usercopy_strlen(const __user uint8_t* str, size_t max, size_t* len) {
return arch_usercopy_strlen(str, max, len);
}
+85
View File
@@ -0,0 +1,85 @@
#pragma once
#include <kernel/mem.h>
#include <stddef.h>
#include <string.h>
#define VEC(type) \
struct { \
type* items; \
size_t capacity; \
size_t length; \
}
// Initializes a vector that can hold `cap` entries.
#define VEC_INIT(vec, cap) \
do { \
*(vec) = (typeof(*(vec))){ \
.items = nullptr, \
.capacity = (cap), \
.length = 0, \
}; \
} while (0)
// Frees the memory associated with the `vec`.
#define VEC_FREE(vec) \
do { \
kfree((vec)->items); \
(vec)->items = NULL; \
(vec)->capacity = 0; \
(vec)->length = 0; \
} while (0)
// Pushes a new `item` to the `vec`.
#define VEC_PUSH(vec, item) \
do { \
auto __vec = (vec); \
/* Default capacity. */ \
if (__vec->capacity == 0) \
__vec->capacity = 16; \
/* If no container was allocated, do it now.*/ \
if (__vec->items == nullptr) { \
__vec->items = kmalloc(sizeof(typeof(*(__vec->items))) * __vec->capacity, KMF_ZEROED); \
__vec->length = 0; \
} \
/* If the new entry would overflow the buffer, double the capacity. */ \
if (__vec->length + 1 > __vec->capacity) { \
__vec->capacity *= 2; \
__vec->items = krealloc(__vec->items, sizeof(typeof(*(__vec->items))) * __vec->capacity); \
} \
__vec->items[__vec->length] = (item); \
__vec->length++; \
} while (0)
// Removes the element at index `idx` and moves all other members so the vec is contiguous again.
#define VEC_POP(vec, idx) \
do { \
auto __vec = (vec); \
if ((idx) >= __vec->length) \
break; \
/* Move all data back by one entry. */ \
memmove( \
__vec->items + (idx), \
__vec->items + (idx) + 1, \
sizeof(typeof(*(__vec->items))) * (__vec->length - (idx)) \
); \
__vec->length--; \
} while (0)
// Iterates over `vec` with iterator variable `var_name`.
#define VEC_ITER(vec, var_name) \
for (typeof(((vec)->items)) var_name = (vec)->items + 0; (var_name) < (vec)->items + (vec)->length; var_name++)
// Looks for a `value` in the `vec` and returns the index of the first match in `result`.
// If unsuccessful, returns -1.
#define VEC_FIND(vec, result, value) \
do { \
result = -1; \
for (usize __i = 0; __i < (vec)->length; __i++) { \
if ((vec)->items[__i] == value) { \
result = __i; \
break; \
} \
} \
true; \
} while (0)
+10
View File
@@ -0,0 +1,10 @@
#ifndef ZINNIA_STDARG_H
#define ZINNIA_STDARG_H
typedef __builtin_va_list va_list;
#define va_start(v, l) __builtin_va_start(v, l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v, T) __builtin_va_arg(v, T)
#define va_copy(d, s) __builtin_va_copy(d, s)
#endif
+37
View File
@@ -0,0 +1,37 @@
#ifndef ZINNIA_STDATOMIC_H
#define ZINNIA_STDATOMIC_H
typedef enum memory_order {
memory_order_relaxed = __ATOMIC_RELAXED,
memory_order_consume = __ATOMIC_CONSUME,
memory_order_acquire = __ATOMIC_ACQUIRE,
memory_order_release = __ATOMIC_RELEASE,
memory_order_acq_rel = __ATOMIC_ACQ_REL,
memory_order_seq_cst = __ATOMIC_SEQ_CST
} memory_order;
#define atomic_store_explicit(ptr, des, ord) __atomic_store_n(ptr, des, ord)
#define atomic_store(ptr, des) atomic_store_explicit(ptr, des, __ATOMIC_SEQ_CST)
#define atomic_load_explicit(ptr, ord) __atomic_load_n(ptr, ord)
#define atomic_load(ptr) atomic_load_explicit(ptr, __ATOMIC_SEQ_CST)
#define atomic_exchange_explicit(ptr, des, ord) __atomic_exchange_n(ptr, des, ord)
#define atomic_exchange(ptr, des) atomic_exchange_explicit(ptr, des, __ATOMIC_SEQ_CST)
#define atomic_fetch_add_explicit(ptr, op, ord) __atomic_fetch_add(ptr, op, ord)
#define atomic_fetch_add(ptr, op) atomic_fetch_add_explicit(ptr, op, __ATOMIC_SEQ_CST)
#define atomic_fetch_sub_explicit(ptr, op, ord) __atomic_fetch_sub(ptr, op, ord)
#define atomic_fetch_sub(ptr, op) atomic_fetch_sub_explicit(ptr, op, __ATOMIC_SEQ_CST)
#define atomic_fetch_or_explicit(ptr, op, ord) __atomic_fetch_or(ptr, op, ord)
#define atomic_fetch_or(ptr, op) atomic_fetch_or_explicit(ptr, op, __ATOMIC_SEQ_CST)
#define atomic_fetch_xor_explicit(ptr, op, ord) __atomic_fetch_xor(ptr, op, ord)
#define atomic_fetch_xor(ptr, op) atomic_fetch_xor_explicit(ptr, op, __ATOMIC_SEQ_CST)
#define atomic_fetch_and_explicit(ptr, op, ord) __atomic_fetch_and(ptr, op, ord)
#define atomic_fetch_and(ptr, op) atomic_fetch_and_explicit(ptr, op, __ATOMIC_SEQ_CST)
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef ZINNIA_STDDEF_H
#define ZINNIA_STDDEF_H
typedef unsigned long size_t;
typedef signed long ssize_t;
#define offsetof(type, member) __builtin_offsetof(type, member)
#endif
+29
View File
@@ -0,0 +1,29 @@
#ifndef ZINNIA_STDINT_H
#define ZINNIA_STDINT_H
typedef __INT8_TYPE__ int8_t;
typedef __UINT8_TYPE__ uint8_t;
#define INT8_MAX __INT8_MAX__
#define UINT8_MAX __UINT8_MAX__
typedef __INT16_TYPE__ int16_t;
typedef __UINT16_TYPE__ uint16_t;
#define INT16_MAX __INT16_MAX__
#define UINT16_MAX __UINT16_MAX__
typedef __INT32_TYPE__ int32_t;
typedef __UINT32_TYPE__ uint32_t;
#define INT32_MAX __INT32_MAX__
#define UINT32_MAX __UINT32_MAX__
typedef __INT64_TYPE__ int64_t;
typedef __UINT64_TYPE__ uint64_t;
#define INT64_MAX __INT64_MAX__
#define UINT64_MAX __UINT64_MAX__
typedef __INTPTR_TYPE__ intptr_t;
typedef __UINTPTR_TYPE__ uintptr_t;
#define INTPTR_MAX __INTPTR_MAX__
#define UINTPTR_MAX __UINTPTR_MAX__
#endif
+17
View File
@@ -0,0 +1,17 @@
#ifndef ZINNIA_STRING_H
#define ZINNIA_STRING_H
#include <stddef.h>
size_t strlen(const char* str);
size_t strnlen(const char* str, size_t len);
int memcmp(const void* s1, const void* s2, size_t size);
void* memcpy(void* restrict dest, const void* restrict src, size_t n);
void* memmove(void* dstptr, const void* srcptr, size_t size);
void* memset(void* dest, int value, size_t n);
char* strncpy(char* restrict dst, const char* restrict src, size_t len);
int strcmp(const char* str1, const char* str2);
int strncmp(const char* str1, const char* str2, size_t len);
char* strchr(const char* c, int s);
#endif
+105
View File
@@ -0,0 +1,105 @@
ENTRY(_start)
PHDRS {
text PT_LOAD FLAGS(5);
rodata PT_LOAD FLAGS(4);
data PT_LOAD FLAGS(6);
dynamic PT_DYNAMIC FLAGS(6);
percpu PT_LOAD FLAGS(6);
}
SECTIONS
{
. = 0xffffffff80000000;
__ld_kernel_start = .;
.text ALIGN(CONSTANT(MAXPAGESIZE)) : {
__ld_text_start = .;
KEEP(*(.init.text))
*(.text .text.*)
*(.plt .plt.*)
__ld_text_end = .;
} :text
.rodata 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.*)
*(.got .got.*)
__ld_rodata_end = .;
} :rodata
.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
.dynsym : {
__ld_dynsym_start = .;
*(.dynsym)
__ld_dynsym_end = .;
} :data
.dynstr : {
__ld_dynstr_start = .;
*(.dynstr)
__ld_dynstr_end = .;
} :data
.dynamic : {
*(.dynamic)
} :data :dynamic
.bss : {
/* Small stack to bootstrap the allocator */
__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))
. = ALIGN(CONSTANT(MAXPAGESIZE));
__ld_percpu_end = .;
__ld_data_end = .;
} :percpu
__ld_kernel_end = .;
/DISCARD/ :
{
*(.comment*)
*(.eh_frame*)
*(.rel.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.interp)
}
}
+3
View File
@@ -0,0 +1,3 @@
kernel_sources += files(
'string.c',
)
+164
View File
@@ -0,0 +1,164 @@
#include <kernel/common.h>
#include <kernel/compiler.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
[[__hot]]
size_t strlen(const char* str) {
size_t result = 0;
while (*str++) {
result++;
}
return result;
}
[[__hot]]
size_t strnlen(const char* str, size_t len) {
size_t result = 0;
while (result < len && *str++) {
result++;
}
return result;
}
[[__hot]]
int memcmp(const void* s1, const void* s2, size_t size) {
int diff = 0;
char* s1ptr = (char*)s1;
char* s2ptr = (char*)s2;
for (size_t i = 0; i < size; i++) {
if (s1ptr[i] != s2ptr[i])
diff++;
}
return diff;
}
[[__hot]]
void* memcpy(void* restrict dest, const void* restrict src, size_t n) {
if (__unlikely(n == 0))
return dest;
size_t d = (size_t)dest;
size_t s = (size_t)src;
if (d % sizeof(size_t) != 0 || s % sizeof(size_t) != 0) {
while (n && (d % sizeof(size_t) != 0) && (s % sizeof(size_t) != 0)) {
*((uint8_t*)d) = *((uint8_t*)s);
d++;
s++;
n--;
}
}
size_t* qword_dest = (size_t*)d;
const size_t* qword_src = (const size_t*)s;
const size_t word_count = n / sizeof(size_t);
for (size_t i = 0; i < word_count; i++) {
qword_dest[i] = qword_src[i];
}
size_t remaining_bytes = n % sizeof(size_t);
d = (size_t)(qword_dest + word_count);
s = (size_t)(qword_src + word_count);
while (remaining_bytes--) {
*((uint8_t*)d) = *((uint8_t*)s);
d++;
s++;
}
return dest;
}
[[__hot]]
void* memmove(void* dstptr, const void* srcptr, size_t size) {
uint8_t* dst = (uint8_t*)dstptr;
const uint8_t* src = (const uint8_t*)srcptr;
if (dst < src) {
for (size_t i = 0; i < size; i++)
dst[i] = src[i];
} else {
for (size_t i = size; i != 0; i--)
dst[i - 1] = src[i - 1];
}
return dstptr;
}
[[__hot]]
void* memset(void* dest, int value, size_t n) {
if (__unlikely(n == 0))
return dest;
size_t d = (size_t)dest;
if (d % sizeof(size_t) != 0) {
while (n && (d % sizeof(size_t) != 0)) {
*((uint8_t*)d) = value;
d++;
n--;
}
}
size_t* qword_dest = (size_t*)d;
size_t qword_value = value;
qword_value |= (qword_value << 8);
qword_value |= (qword_value << 16);
qword_value |= (qword_value << 32);
const size_t word_count = n / sizeof(size_t);
for (size_t i = 0; i < word_count; i++) {
qword_dest[i] = qword_value;
}
size_t remaining_bytes = n % sizeof(size_t);
d = (size_t)(qword_dest + word_count);
while (remaining_bytes--) {
*((uint8_t*)d) = value;
d++;
}
return dest;
}
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));
}
int strncmp(const char* str1, const char* str2, size_t len) {
while (len && *str1 && (*str1 == *str2)) {
++str1;
++str2;
--len;
}
if (len == 0) {
return 0;
} else {
return (*(unsigned char*)str1 - *(unsigned char*)str2);
}
}
int strcmp(const char* str1, const char* str2) {
while (*str1 && (*str1 == *str2)) {
++str1;
++str2;
}
return (*(unsigned char*)str1 - *(unsigned char*)str2);
}
char* strchr(const char* s, int c) {
char ch = (char)c;
while (true) {
char cur = *s;
if (cur == ch)
return (char*)s;
if (cur == 0)
return nullptr;
s++;
}
}
+147
View File
@@ -0,0 +1,147 @@
#include <kernel/assert.h>
#include <kernel/common.h>
#include <kernel/mem.h>
#include <kernel/panic.h>
#include <kernel/print.h>
#include <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 page_table mem_kernel_table = {0};
void mem_init(struct phys_mem* map, size_t map_len, virt_t kernel_virt, phys_t kernel_phys, virt_t tmp_hhdm) {
// This function creates a kernel page table and initializes all memory managers.
const size_t pgsz = mem_page_size();
kprintf("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_RECLAIMABLE:
label = "Reclaimable";
break;
}
kprintf("[%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);
kprintf(
"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(mem_pt_new_kernel(&mem_kernel_table, 0) == 0, "Unable to allocate the kernel page table");
// text
kprintf("Mapping text segment at %p\n", __ld_text_start);
for (uint8_t* p = __ld_text_start; p <= __ld_text_end; p += pgsz) {
zn_status_t status = mem_pt_map(
&mem_kernel_table,
(virt_t)p,
(phys_t)(p - __ld_kernel_start + kernel_phys),
PTE_READ | PTE_EXEC,
CACHE_NONE
);
ASSERT(!status, "Failed to map %p with error %i", p, status);
}
// rodata
kprintf("Mapping rodata segment at %p\n", __ld_rodata_start);
for (uint8_t* p = __ld_rodata_start; p < __ld_rodata_end; p += pgsz) {
zn_status_t status = mem_pt_map(
&mem_kernel_table,
(virt_t)p,
(phys_t)(p - __ld_kernel_start + kernel_phys),
PTE_READ,
CACHE_NONE
);
ASSERT(!status, "Failed to map %p with error %i", p, status);
}
// data
kprintf("Mapping data segment at %p\n", __ld_data_start);
for (uint8_t* p = __ld_data_start; p < __ld_data_end; p += pgsz) {
zn_status_t status = mem_pt_map(
&mem_kernel_table,
(virt_t)p,
(phys_t)(p - __ld_kernel_start + kernel_phys),
PTE_READ | PTE_WRITE,
CACHE_NONE
);
ASSERT(!status, "Failed to map %p with error %i", p, status);
}
// The kernel address space is divided into 3 segments (plus kernel).
// On x86_64 for example, they live at:
// HHDM FFFF'8000'0000'0000
// PFNDB FFFF'A000'0000'0000
// Mappings FFFF'C000'0000'0000
// Map all physical memory to the HHDM address.
tmp_hhdm = mem_hhdm_addr();
for (size_t i = 0; i < map_len; i++) {
for (size_t p = 0; p <= map[i].length; p += pgsz) {
virt_t vaddr = (virt_t)(map[i].address + p + tmp_hhdm);
phys_t paddr = (phys_t)(map[i].address + p);
zn_status_t status = mem_pt_map(&mem_kernel_table, vaddr, paddr, PTE_READ | PTE_WRITE, CACHE_NONE);
ASSERT(!status, "Failed to map HHDM page %p to %p with error %i", (void*)vaddr, (void*)paddr, status);
}
}
mem_hhdm_base = tmp_hhdm;
// Switch to our own page table.
mem_pt_set(&mem_kernel_table);
// 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;
size_t length = ALIGN_UP((map[i].length / pgsz) * sizeof(struct page), pgsz);
virt_t vaddr = ALIGN_DOWN(mem_pfndb_addr() + (map[i].address / pgsz * sizeof(struct page)), pgsz);
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!");
mem_pt_map(&mem_kernel_table, vaddr + page, paddr, PTE_READ | PTE_WRITE, CACHE_NONE);
}
}
mem_pfndb = (struct page*)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);
kprintf("Memory initialization complete\n");
}
+7
View File
@@ -0,0 +1,7 @@
kernel_sources += files(
'init.c',
'pmm.c',
'pt.c',
'slab.c',
'vmm.c',
)
+155
View File
@@ -0,0 +1,155 @@
#include <kernel/assert.h>
#include <kernel/compiler.h>
#include <kernel/mem.h>
#include <kernel/print.h>
#include <kernel/spin.h>
#include <stdint.h>
#include <string.h>
virt_t mem_hhdm_base = 0;
struct page* mem_pfndb = nullptr;
static zn_status_t (*alloc_fn)(size_t num_pages, enum alloc_flags flags, phys_t* out);
static zn_status_t (*free_fn)(phys_t start, size_t num_pages);
zn_status_t mem_phys_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
return alloc_fn(num_pages, flags, out);
}
zn_status_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 zn_status_t bump_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
const size_t bytes = num_pages * mem_page_size();
if (((intptr_t)bump_mem.length - bytes) <= 0)
return ZN_ERR_NO_MEMORY;
*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 zn_status_t bump_free(phys_t addr, size_t num_pages) {
// We don't free bootstrap memory.
ASSERT(false, "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 zn_status_t freelist_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out) {
spin_lock(&pmm_lock);
const size_t bytes = num_pages * 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)mem_pfndb) / sizeof(struct page)) * mem_page_size();
// If the region is too high up or doesn't have enough pages left, continue searching.
if (page_addr + bytes >= limit || __unlikely(it->freelist.count < num_pages)) {
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 {
it->freelist.count -= num_pages;
if (__likely(out))
*out = page_addr + (it->freelist.count * mem_page_size());
}
goto success;
}
spin_unlock(&pmm_lock);
return ZN_ERR_NO_MEMORY;
success:
// If the NOZERO flag is *not* specified, zero memory.
if (__likely(out) && __likely(!(flags & ALLOC_NOZERO))) {
memcpy(HHDM_PTR(*out), 0, bytes);
}
pmm_total_free -= num_pages;
spin_unlock(&pmm_lock);
return 0;
}
static zn_status_t freelist_free(phys_t addr, size_t num_pages) {
spin_lock(&pmm_lock);
struct page* page = &mem_pfndb[addr / mem_page_size()];
page->freelist.count = num_pages;
page->freelist.next = pmm_head;
pmm_head = page;
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 < mem_page_size() || map[i].usage != PHYS_USABLE)
continue;
struct page* page = &mem_pfndb[map[i].address / mem_page_size()];
page->freelist.count = map[i].length / mem_page_size();
page->freelist.next = pmm_head;
pmm_head = page;
pmm_total_free += page->freelist.count;
}
const size_t free_bytes = (pmm_total_free * mem_page_size());
kprintf("Total available memory: %zu MiB\n", free_bytes / 1024 / 1024);
}
+82
View File
@@ -0,0 +1,82 @@
#include <kernel/assert.h>
#include <kernel/mem.h>
#include <kernel/print.h>
#include <kernel/spin.h>
zn_status_t mem_pt_new_kernel(struct page_table* pt, enum alloc_flags flags) {
flags &= ~ALLOC_NOZERO;
phys_t addr;
zn_status_t status = mem_phys_alloc(1, flags, &addr);
if (status)
return status;
pt->root = addr;
return 0;
}
zn_status_t mem_pt_new_user(struct page_table* pt, enum alloc_flags flags) {
// TODO
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 zn_status_t get_pte(struct page_table* pt, virt_t vaddr, bool is_user, bool check_only, pte_t** ret) {
pte_t* current_head = HHDM_PTR(pt->root);
size_t index = 0;
for (int8_t level = mem_num_levels() - 1; level >= 0; level--) {
const size_t addr_mask = (1 << mem_level_bits()) - 1;
const size_t addr_shift = mem_page_bits() + (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 = &current_head[index];
if (mem_pte_is_present(pte)) {
// Get the next level.
*pte = mem_pte_build(mem_pte_address(pte), level_flags, CACHE_NONE);
current_head = HHDM_PTR(mem_pte_address(pte));
} else {
// If the current level isn't present, we can skip the rest.
if (check_only)
return ZN_ERR_NO_MEMORY;
phys_t addr;
zn_status_t alloc_status = mem_phys_alloc(1, 0, &addr);
if (alloc_status)
return alloc_status;
*pte = mem_pte_build(addr, level_flags, CACHE_NONE);
current_head = HHDM_PTR(addr);
}
}
*ret = &current_head[index];
return 0;
}
zn_status_t mem_pt_map(struct page_table* pt, virt_t vaddr, phys_t paddr, enum pte_flags flags, enum cache_mode cache) {
spin_lock(&pt->lock);
pte_t* pte;
zn_status_t status = get_pte(pt, vaddr, flags & PTE_USER, false, &pte);
if (status) {
goto fail;
}
*pte = mem_pte_build(paddr, flags, cache);
fail:
spin_unlock(&pt->lock);
return status;
}
+1
View File
@@ -0,0 +1 @@
#include <kernel/mem.h>
View File
+45
View File
@@ -0,0 +1,45 @@
kernel_c_args = [
'-ffreestanding',
'-fno-stack-protector',
'-fno-omit-frame-pointer',
'-fno-pie',
'-nostdinc',
'-D__KERNEL__=1',
]
kernel_lds = meson.current_source_dir() / 'kernel.ld'
kernel_link_args = [
'-ffreestanding',
'-nostdlib',
'-Wl,-nostdlib',
'-static',
'-T' + kernel_lds,
]
kernel_includes = include_directories(
'arch' / target_machine.cpu_family() / 'include',
'include',
)
kernel_sources = []
subdir('arch' / target_machine.cpu_family())
subdir('boot')
subdir('klibc')
subdir('mem')
subdir('core')
subdir('syscalls')
configure_file(configuration: common_cfg, output: 'config.h')
executable(
'zinnia',
include_directories: [common_includes, kernel_includes],
c_args: kernel_c_args,
link_args: kernel_link_args,
link_depends: kernel_lds,
sources: kernel_sources,
install: true,
install_dir: get_option('datadir') / 'zinnia',
)
+21
View File
@@ -0,0 +1,21 @@
#include <zinnia/channel.h>
#include <zinnia/status.h>
#include <kernel/compiler.h>
zn_status_t syscall_channel_create(
enum zn_channel_flags flags,
__user zn_handle_t* endpoint0,
__user zn_handle_t* endpoint1
) {
return ZN_ERR_UNSUPPORTED;
}
zn_status_t syscall_channel_open(
zn_handle_t channel,
size_t num_handles,
size_t num_bytes,
__user zn_handle_t** out_handle_buf,
__user void** out_data_buf
) {
return ZN_ERR_UNSUPPORTED;
}
+18
View File
@@ -0,0 +1,18 @@
#include <zinnia/status.h>
#include <zinnia/syscall_numbers.h>
#include <kernel/compiler.h>
#include <kernel/syscalls.h>
#include <kernel/types.h>
zn_status_t syscall_dispatch(reg_t num, reg_t a0, reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6) {
switch (num) {
case ZN_SYSCALL_PANIC:
syscall_panic((zn_status_t)a0);
case ZN_SYSCALL_LOG:
return syscall_log((__user const char*)a0, a1);
case ZN_SYSCALL_ARCHCTL:
return syscall_archctl(a0, a1);
}
return ZN_ERR_BAD_SYSCALL;
}
+22
View File
@@ -0,0 +1,22 @@
#include <zinnia/handle.h>
#include <zinnia/status.h>
#include <kernel/percpu.h>
#include <kernel/syscalls.h>
zn_status_t zn_handle_clone(zn_handle_t handle, zn_rights_t cloned_rights, zn_handle_t* cloned) {
struct task* current = percpu_get()->sched.current;
current->process;
return ZN_ERR_UNSUPPORTED;
}
zn_status_t zn_handle_drop(zn_handle_t handle) {
struct task* current = percpu_get()->sched.current;
return ZN_ERR_UNSUPPORTED;
}
zn_status_t zn_handle_validate(zn_handle_t handle) {
struct task* current = percpu_get()->sched.current;
return ZN_ERR_UNSUPPORTED;
}
View File
+6
View File
@@ -0,0 +1,6 @@
kernel_sources += files(
'channel.c',
'dispatch.c',
'handle.c',
'log.c',
)
+1
View File
@@ -0,0 +1 @@
subdir('zinnia')
+28
View File
@@ -0,0 +1,28 @@
#include <zinnia/channel.h>
#include <zinnia/syscall_numbers.h>
#include "syscall_stubs.h"
zn_status_t zn_channel_create(enum zn_channel_flags flags, zn_handle_t* endpoint0, zn_handle_t* endpoint1) {
return syscall3(ZN_SYSCALL_CHANNEL_CREATE, (arg_t)flags, (arg_t)endpoint0, (arg_t)endpoint1);
}
zn_status_t zn_channel_open(
zn_handle_t channel,
size_t num_handles,
size_t num_bytes,
zn_handle_t** out_handle_buf,
void** out_data_buf
) {
return syscall5(
ZN_SYSCALL_CHANNEL_OPEN,
(arg_t)channel,
(arg_t)num_handles,
(arg_t)num_bytes,
(arg_t)out_handle_buf,
(arg_t)out_data_buf
);
}
zn_status_t zn_channel_write(zn_handle_t channel) {
return syscall1(ZN_SYSCALL_CHANNEL_WRITE, (arg_t)channel);
}
+15
View File
@@ -0,0 +1,15 @@
#include <zinnia/handle.h>
#include <zinnia/syscall_numbers.h>
#include "syscall_stubs.h"
zn_status_t zn_handle_validate(zn_handle_t handle) {
return syscall1(ZN_SYSCALL_HANDLE_VALIDATE, (arg_t)handle);
}
zn_status_t zn_handle_drop(zn_handle_t handle) {
return syscall1(ZN_SYSCALL_HANDLE_DROP, (arg_t)handle);
}
zn_status_t zn_handle_clone(zn_handle_t object, zn_rights_t cloned_rights, zn_handle_t* cloned) {
return syscall3(ZN_SYSCALL_HANDLE_CLONE, (arg_t)object, (arg_t)cloned_rights, (arg_t)cloned);
}
+9
View File
@@ -0,0 +1,9 @@
#include <zinnia/log.h>
#include <zinnia/status.h>
#include <zinnia/syscall_numbers.h>
#include <string.h>
#include "syscall_stubs.h"
void zn_log(const char* message) {
syscall2(ZN_SYSCALL_LOG, (arg_t)message, (arg_t)strlen(message));
}
+9
View File
@@ -0,0 +1,9 @@
libzinnia = both_libraries(
'zinnia',
'channel.c',
'handle.c',
'log.c',
'status.c',
include_directories: common_includes,
install: true,
)

Some files were not shown because too many files have changed in this diff Show More