commit 0e01345abfcdf581bdfcade1e6a326bdde0ecc27 Author: Marvin Friedrich Date: Wed Feb 4 01:58:49 2026 +0100 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..cc44a1c --- /dev/null +++ b/.clang-format @@ -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: "^" + Priority: 1 + - Regex: "^" + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 3 + - Regex: '^".*\.h"' + Priority: 4 +SortIncludes: CaseInsensitive diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..b667c65 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +Style: + AngledHeaders: ["zinnia/.*", " kernel/.*"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce864bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# IDE Files +.vscode/ +.idea/ +.cache/ + +# Build artifacts +*.o +*.a +*.so +compile_commands.json + +# Default build dir +build/ + +# Generated files +.config* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c3c0f96 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a19d2e9 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ + + +![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** diff --git a/include/zinnia/archctl.h b/include/zinnia/archctl.h new file mode 100644 index 0000000..89a77dd --- /dev/null +++ b/include/zinnia/archctl.h @@ -0,0 +1,19 @@ +#ifndef ZINNIA_ARCHCTL_H +#define ZINNIA_ARCHCTL_H + +#include +#include + +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 diff --git a/include/zinnia/channel.h b/include/zinnia/channel.h new file mode 100644 index 0000000..4b37e59 --- /dev/null +++ b/include/zinnia/channel.h @@ -0,0 +1,35 @@ +#ifndef ZINNIA_CHANNEL_H +#define ZINNIA_CHANNEL_H + +#include +#include +#include + +// 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 diff --git a/include/zinnia/handle.h b/include/zinnia/handle.h new file mode 100644 index 0000000..9499b37 --- /dev/null +++ b/include/zinnia/handle.h @@ -0,0 +1,26 @@ +#ifndef ZINNIA_HANDLE_H +#define ZINNIA_HANDLE_H + +#include +#include +#include + +// 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 diff --git a/include/zinnia/ipc/posix.h b/include/zinnia/ipc/posix.h new file mode 100644 index 0000000..0f94144 --- /dev/null +++ b/include/zinnia/ipc/posix.h @@ -0,0 +1,15 @@ +#ifndef ZINNIA_IPC_POSIX_H +#define ZINNIA_IPC_POSIX_H + +#include + +enum { + ZNI_POSIX_OPEN_REQ, + ZNI_POSIX_OPEN_RES, +}; + +struct zni_posix_open { + uint32_t id; +}; + +#endif diff --git a/include/zinnia/log.h b/include/zinnia/log.h new file mode 100644 index 0000000..501b3f2 --- /dev/null +++ b/include/zinnia/log.h @@ -0,0 +1,11 @@ +#ifndef ZINNIA_LOG_H +#define ZINNIA_LOG_H + +#include + +// Panics and returns an error status to the parent process. +void zn_panic(zn_status_t status); + +void zn_log(const char* message); + +#endif diff --git a/include/zinnia/mem.h b/include/zinnia/mem.h new file mode 100644 index 0000000..879ecdf --- /dev/null +++ b/include/zinnia/mem.h @@ -0,0 +1,32 @@ +#ifndef ZINNIA_MEM_H +#define ZINNIA_MEM_H + +#include +#include +#include + +// 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 diff --git a/include/zinnia/rights.h b/include/zinnia/rights.h new file mode 100644 index 0000000..86559a9 --- /dev/null +++ b/include/zinnia/rights.h @@ -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 diff --git a/include/zinnia/status.h b/include/zinnia/status.h new file mode 100644 index 0000000..087e0df --- /dev/null +++ b/include/zinnia/status.h @@ -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 diff --git a/include/zinnia/syscall_numbers.h b/include/zinnia/syscall_numbers.h new file mode 100644 index 0000000..3c17a60 --- /dev/null +++ b/include/zinnia/syscall_numbers.h @@ -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 diff --git a/include/zinnia/thread.h b/include/zinnia/thread.h new file mode 100644 index 0000000..fe51180 --- /dev/null +++ b/include/zinnia/thread.h @@ -0,0 +1,14 @@ +#ifndef ZINNIA_THREAD_H +#define ZINNIA_THREAD_H + +#include +#include + +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 diff --git a/kernel/arch/x86_64/gdt.c b/kernel/arch/x86_64/gdt.c new file mode 100644 index 0000000..854238a --- /dev/null +++ b/kernel/arch/x86_64/gdt.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/kernel/arch/x86_64/idt.c b/kernel/arch/x86_64/idt.c new file mode 100644 index 0000000..f35632c --- /dev/null +++ b/kernel/arch/x86_64/idt.c @@ -0,0 +1,7 @@ +#include +#include + +struct [[__packed]] idtr { + uint16_t limit; + struct idt* base; +}; diff --git a/kernel/arch/x86_64/include/asm.h b/kernel/arch/x86_64/include/asm.h new file mode 100644 index 0000000..04f8f42 --- /dev/null +++ b/kernel/arch/x86_64/include/asm.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +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)); +} diff --git a/kernel/arch/x86_64/include/bits/elf.h b/kernel/arch/x86_64/include/bits/elf.h new file mode 100644 index 0000000..78cac9e --- /dev/null +++ b/kernel/arch/x86_64/include/bits/elf.h @@ -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 diff --git a/kernel/arch/x86_64/include/bits/irq.h b/kernel/arch/x86_64/include/bits/irq.h new file mode 100644 index 0000000..4a60994 --- /dev/null +++ b/kernel/arch/x86_64/include/bits/irq.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +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"); +} diff --git a/kernel/arch/x86_64/include/bits/mem.h b/kernel/arch/x86_64/include/bits/mem.h new file mode 100644 index 0000000..b638dee --- /dev/null +++ b/kernel/arch/x86_64/include/bits/mem.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +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); diff --git a/kernel/arch/x86_64/include/bits/percpu.h b/kernel/arch/x86_64/include/bits/percpu.h new file mode 100644 index 0000000..51f5ebb --- /dev/null +++ b/kernel/arch/x86_64/include/bits/percpu.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +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; +}; diff --git a/kernel/arch/x86_64/include/bits/sched.h b/kernel/arch/x86_64/include/bits/sched.h new file mode 100644 index 0000000..e33b139 --- /dev/null +++ b/kernel/arch/x86_64/include/bits/sched.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +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; +}; diff --git a/kernel/arch/x86_64/include/bits/types.h b/kernel/arch/x86_64/include/bits/types.h new file mode 100644 index 0000000..625168f --- /dev/null +++ b/kernel/arch/x86_64/include/bits/types.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +typedef uint64_t phys_t; +typedef uint64_t virt_t; +typedef uint64_t reg_t; diff --git a/kernel/arch/x86_64/include/bits/usercopy.h b/kernel/arch/x86_64/include/bits/usercopy.h new file mode 100644 index 0000000..f93fa63 --- /dev/null +++ b/kernel/arch/x86_64/include/bits/usercopy.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +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); diff --git a/kernel/arch/x86_64/include/defs.h b/kernel/arch/x86_64/include/defs.h new file mode 100644 index 0000000..e5e6031 --- /dev/null +++ b/kernel/arch/x86_64/include/defs.h @@ -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) diff --git a/kernel/arch/x86_64/include/gdt.h b/kernel/arch/x86_64/include/gdt.h new file mode 100644 index 0000000..eb40d2d --- /dev/null +++ b/kernel/arch/x86_64/include/gdt.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#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); diff --git a/kernel/arch/x86_64/include/idt.h b/kernel/arch/x86_64/include/idt.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/kernel/arch/x86_64/include/idt.h @@ -0,0 +1 @@ +#pragma once diff --git a/kernel/arch/x86_64/include/smp.h b/kernel/arch/x86_64/include/smp.h new file mode 100644 index 0000000..cf19246 --- /dev/null +++ b/kernel/arch/x86_64/include/smp.h @@ -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 +#include +#include +#include +#include + +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 diff --git a/kernel/arch/x86_64/include/tss.h b/kernel/arch/x86_64/include/tss.h new file mode 100644 index 0000000..4499151 --- /dev/null +++ b/kernel/arch/x86_64/include/tss.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +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; +}; diff --git a/kernel/arch/x86_64/mem.c b/kernel/arch/x86_64/mem.c new file mode 100644 index 0000000..a436abc --- /dev/null +++ b/kernel/arch/x86_64/mem.c @@ -0,0 +1,56 @@ +#include +#include + +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"); +} diff --git a/kernel/arch/x86_64/meson.build b/kernel/arch/x86_64/meson.build new file mode 100644 index 0000000..52644ff --- /dev/null +++ b/kernel/arch/x86_64/meson.build @@ -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', +) diff --git a/kernel/arch/x86_64/serial.c b/kernel/arch/x86_64/serial.c new file mode 100644 index 0000000..f9264f4 --- /dev/null +++ b/kernel/arch/x86_64/serial.c @@ -0,0 +1,47 @@ +#include +#include +#include + +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); diff --git a/kernel/arch/x86_64/smp.c b/kernel/arch/x86_64/smp.c new file mode 100644 index 0000000..0c66ffd --- /dev/null +++ b/kernel/arch/x86_64/smp.c @@ -0,0 +1,17 @@ +#include +#include +#include + +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) {} diff --git a/kernel/arch/x86_64/sys.c b/kernel/arch/x86_64/sys.c new file mode 100644 index 0000000..9eadfc8 --- /dev/null +++ b/kernel/arch/x86_64/sys.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include +#include +#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; + } +} diff --git a/kernel/arch/x86_64/trampoline.S b/kernel/arch/x86_64/trampoline.S new file mode 100644 index 0000000..ff648a0 --- /dev/null +++ b/kernel/arch/x86_64/trampoline.S @@ -0,0 +1,107 @@ +#include +#include + +.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: diff --git a/kernel/arch/x86_64/usercopy.c b/kernel/arch/x86_64/usercopy.c new file mode 100644 index 0000000..c8ac98e --- /dev/null +++ b/kernel/arch/x86_64/usercopy.c @@ -0,0 +1,128 @@ +#include +#include + +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" + ); +} diff --git a/kernel/boot/cmdline.c b/kernel/boot/cmdline.c new file mode 100644 index 0000000..675ba26 --- /dev/null +++ b/kernel/boot/cmdline.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +// 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; + } +} diff --git a/kernel/boot/limine/entry.c b/kernel/boot/limine/entry.c new file mode 100644 index 0000000..a098407 --- /dev/null +++ b/kernel/boot/limine/entry.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/kernel/boot/limine/limine.h b/kernel/boot/limine/limine.h new file mode 100644 index 0000000..2441a14 --- /dev/null +++ b/kernel/boot/limine/limine.h @@ -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 + +/* 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 diff --git a/kernel/boot/limine/meson.build b/kernel/boot/limine/meson.build new file mode 100644 index 0000000..527e9ba --- /dev/null +++ b/kernel/boot/limine/meson.build @@ -0,0 +1 @@ +kernel_sources += files('entry.c') diff --git a/kernel/boot/main.c b/kernel/boot/main.c new file mode 100644 index 0000000..c5fe9d1 --- /dev/null +++ b/kernel/boot/main.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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"); +} diff --git a/kernel/boot/meson.build b/kernel/boot/meson.build new file mode 100644 index 0000000..5276801 --- /dev/null +++ b/kernel/boot/meson.build @@ -0,0 +1,6 @@ +kernel_sources += files( + 'cmdline.c', + 'main.c', +) + +subdir('limine') diff --git a/kernel/core/clock.c b/kernel/core/clock.c new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/kernel/core/clock.c @@ -0,0 +1 @@ + diff --git a/kernel/core/console.c b/kernel/core/console.c new file mode 100644 index 0000000..fdbb306 --- /dev/null +++ b/kernel/core/console.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +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); + } +} diff --git a/kernel/core/elf.c b/kernel/core/elf.c new file mode 100644 index 0000000..75d9be2 --- /dev/null +++ b/kernel/core/elf.c @@ -0,0 +1 @@ +#include diff --git a/kernel/core/irq.c b/kernel/core/irq.c new file mode 100644 index 0000000..34bd843 --- /dev/null +++ b/kernel/core/irq.c @@ -0,0 +1,16 @@ +#include +#include +#include + +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); + } +} diff --git a/kernel/core/meson.build b/kernel/core/meson.build new file mode 100644 index 0000000..e2cb440 --- /dev/null +++ b/kernel/core/meson.build @@ -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', +) diff --git a/kernel/core/panic.c b/kernel/core/panic.c new file mode 100644 index 0000000..9bbfbd5 --- /dev/null +++ b/kernel/core/panic.c @@ -0,0 +1,7 @@ +#include + +[[noreturn]] +void panic() { + // TODO + while (1) {} +} diff --git a/kernel/core/percpu.c b/kernel/core/percpu.c new file mode 100644 index 0000000..249b0b4 --- /dev/null +++ b/kernel/core/percpu.c @@ -0,0 +1,14 @@ +#include +#include + +[[__used, __section(".percpu")]] +struct percpu percpu_bsp = { + .self = &percpu_bsp, + .id = 0, + .online = false, +}; + +struct percpu* percpu_new() { + // TODO + return nullptr; +} diff --git a/kernel/core/print.c b/kernel/core/print.c new file mode 100644 index 0000000..7b495cd --- /dev/null +++ b/kernel/core/print.c @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/kernel/core/sched.c b/kernel/core/sched.c new file mode 100644 index 0000000..c8752d8 --- /dev/null +++ b/kernel/core/sched.c @@ -0,0 +1,6 @@ +#include +#include + +void sched_init() { + kprintf("Scheduler initialized\n"); +} diff --git a/kernel/core/spin.c b/kernel/core/spin.c new file mode 100644 index 0000000..8af43d5 --- /dev/null +++ b/kernel/core/spin.c @@ -0,0 +1,16 @@ +#include +#include + +// 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); +} diff --git a/kernel/include/kernel/assert.h b/kernel/include/kernel/assert.h new file mode 100644 index 0000000..025ec9a --- /dev/null +++ b/kernel/include/kernel/assert.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +#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(); \ + } \ + }) diff --git a/kernel/include/kernel/cmdline.h b/kernel/include/kernel/cmdline.h new file mode 100644 index 0000000..57fc594 --- /dev/null +++ b/kernel/include/kernel/cmdline.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#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); diff --git a/kernel/include/kernel/common.h b/kernel/include/kernel/common.h new file mode 100644 index 0000000..f92997e --- /dev/null +++ b/kernel/include/kernel/common.h @@ -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)) diff --git a/kernel/include/kernel/compiler.h b/kernel/include/kernel/compiler.h new file mode 100644 index 0000000..3b658d9 --- /dev/null +++ b/kernel/include/kernel/compiler.h @@ -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() diff --git a/kernel/include/kernel/console.h b/kernel/include/kernel/console.h new file mode 100644 index 0000000..647f95d --- /dev/null +++ b/kernel/include/kernel/console.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +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) diff --git a/kernel/include/kernel/elf.h b/kernel/include/kernel/elf.h new file mode 100644 index 0000000..259524e --- /dev/null +++ b/kernel/include/kernel/elf.h @@ -0,0 +1,227 @@ +#pragma once + +#include +#include + +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; +}; diff --git a/kernel/include/kernel/handle.h b/kernel/include/kernel/handle.h new file mode 100644 index 0000000..fe074b4 --- /dev/null +++ b/kernel/include/kernel/handle.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/kernel/include/kernel/init.h b/kernel/include/kernel/init.h new file mode 100644 index 0000000..2c88226 --- /dev/null +++ b/kernel/include/kernel/init.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include + +#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(); diff --git a/kernel/include/kernel/irq.h b/kernel/include/kernel/irq.h new file mode 100644 index 0000000..e675799 --- /dev/null +++ b/kernel/include/kernel/irq.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include + +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(); +} diff --git a/kernel/include/kernel/list.h b/kernel/include/kernel/list.h new file mode 100644 index 0000000..b0e8329 --- /dev/null +++ b/kernel/include/kernel/list.h @@ -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)) diff --git a/kernel/include/kernel/mem.h b/kernel/include/kernel/mem.h new file mode 100644 index 0000000..fc912ae --- /dev/null +++ b/kernel/include/kernel/mem.h @@ -0,0 +1,180 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/kernel/include/kernel/mutex.h b/kernel/include/kernel/mutex.h new file mode 100644 index 0000000..0dca22a --- /dev/null +++ b/kernel/include/kernel/mutex.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +struct mutex { + struct task* owner; + bool flag; +}; + +void mutex_lock(struct mutex* mutex); +void mutex_unlock(struct mutex* mutex); diff --git a/kernel/include/kernel/panic.h b/kernel/include/kernel/panic.h new file mode 100644 index 0000000..d596c9a --- /dev/null +++ b/kernel/include/kernel/panic.h @@ -0,0 +1,5 @@ +#pragma once + +// Stop all execution upon panic. +[[noreturn]] +void panic(); diff --git a/kernel/include/kernel/percpu.h b/kernel/include/kernel/percpu.h new file mode 100644 index 0000000..103b7a3 --- /dev/null +++ b/kernel/include/kernel/percpu.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include + +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(); diff --git a/kernel/include/kernel/print.h b/kernel/include/kernel/print.h new file mode 100644 index 0000000..c0d77f9 --- /dev/null +++ b/kernel/include/kernel/print.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +[[__format(printf, 1, 2)]] +void kprintf(const char* message, ...); diff --git a/kernel/include/kernel/sched.h b/kernel/include/kernel/sched.h new file mode 100644 index 0000000..1810845 --- /dev/null +++ b/kernel/include/kernel/sched.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include + +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(); diff --git a/kernel/include/kernel/spin.h b/kernel/include/kernel/spin.h new file mode 100644 index 0000000..41d3ef5 --- /dev/null +++ b/kernel/include/kernel/spin.h @@ -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); diff --git a/kernel/include/kernel/syscalls.h b/kernel/include/kernel/syscalls.h new file mode 100644 index 0000000..116f55a --- /dev/null +++ b/kernel/include/kernel/syscalls.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include +#include + +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); diff --git a/kernel/include/kernel/types.h b/kernel/include/kernel/types.h new file mode 100644 index 0000000..0a87117 --- /dev/null +++ b/kernel/include/kernel/types.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +ASSERT_TYPE(phys_t); +ASSERT_TYPE(virt_t); +ASSERT_TYPE(reg_t); diff --git a/kernel/include/kernel/uio.h b/kernel/include/kernel/uio.h new file mode 100644 index 0000000..e1561cc --- /dev/null +++ b/kernel/include/kernel/uio.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +struct iovec_iter {}; diff --git a/kernel/include/kernel/usercopy.h b/kernel/include/kernel/usercopy.h new file mode 100644 index 0000000..dfed923 --- /dev/null +++ b/kernel/include/kernel/usercopy.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + +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); +} diff --git a/kernel/include/kernel/vector.h b/kernel/include/kernel/vector.h new file mode 100644 index 0000000..52c1ed6 --- /dev/null +++ b/kernel/include/kernel/vector.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include + +#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) diff --git a/kernel/include/stdarg.h b/kernel/include/stdarg.h new file mode 100644 index 0000000..e1e7566 --- /dev/null +++ b/kernel/include/stdarg.h @@ -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 diff --git a/kernel/include/stdatomic.h b/kernel/include/stdatomic.h new file mode 100644 index 0000000..3dd754f --- /dev/null +++ b/kernel/include/stdatomic.h @@ -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 diff --git a/kernel/include/stddef.h b/kernel/include/stddef.h new file mode 100644 index 0000000..3ca0f43 --- /dev/null +++ b/kernel/include/stddef.h @@ -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 diff --git a/kernel/include/stdint.h b/kernel/include/stdint.h new file mode 100644 index 0000000..996433c --- /dev/null +++ b/kernel/include/stdint.h @@ -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 diff --git a/kernel/include/string.h b/kernel/include/string.h new file mode 100644 index 0000000..835963e --- /dev/null +++ b/kernel/include/string.h @@ -0,0 +1,17 @@ +#ifndef ZINNIA_STRING_H +#define ZINNIA_STRING_H + +#include + +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 diff --git a/kernel/kernel.ld b/kernel/kernel.ld new file mode 100644 index 0000000..a93d76d --- /dev/null +++ b/kernel/kernel.ld @@ -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) + } +} diff --git a/kernel/klibc/meson.build b/kernel/klibc/meson.build new file mode 100644 index 0000000..b857478 --- /dev/null +++ b/kernel/klibc/meson.build @@ -0,0 +1,3 @@ +kernel_sources += files( + 'string.c', +) diff --git a/kernel/klibc/string.c b/kernel/klibc/string.c new file mode 100644 index 0000000..99a4d28 --- /dev/null +++ b/kernel/klibc/string.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include + +[[__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++; + } +} diff --git a/kernel/mem/init.c b/kernel/mem/init.c new file mode 100644 index 0000000..86eb7b3 --- /dev/null +++ b/kernel/mem/init.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include + +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"); +} diff --git a/kernel/mem/meson.build b/kernel/mem/meson.build new file mode 100644 index 0000000..9cb0901 --- /dev/null +++ b/kernel/mem/meson.build @@ -0,0 +1,7 @@ +kernel_sources += files( + 'init.c', + 'pmm.c', + 'pt.c', + 'slab.c', + 'vmm.c', +) diff --git a/kernel/mem/pmm.c b/kernel/mem/pmm.c new file mode 100644 index 0000000..dfdf971 --- /dev/null +++ b/kernel/mem/pmm.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/kernel/mem/pt.c b/kernel/mem/pt.c new file mode 100644 index 0000000..37fdbbc --- /dev/null +++ b/kernel/mem/pt.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +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 = ¤t_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 = ¤t_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; +} diff --git a/kernel/mem/slab.c b/kernel/mem/slab.c new file mode 100644 index 0000000..286c063 --- /dev/null +++ b/kernel/mem/slab.c @@ -0,0 +1 @@ +#include diff --git a/kernel/mem/vmm.c b/kernel/mem/vmm.c new file mode 100644 index 0000000..e69de29 diff --git a/kernel/meson.build b/kernel/meson.build new file mode 100644 index 0000000..9cfec71 --- /dev/null +++ b/kernel/meson.build @@ -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', +) diff --git a/kernel/syscalls/channel.c b/kernel/syscalls/channel.c new file mode 100644 index 0000000..4ca6cab --- /dev/null +++ b/kernel/syscalls/channel.c @@ -0,0 +1,21 @@ +#include +#include +#include + +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; +} diff --git a/kernel/syscalls/dispatch.c b/kernel/syscalls/dispatch.c new file mode 100644 index 0000000..f549812 --- /dev/null +++ b/kernel/syscalls/dispatch.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include +#include + +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; +} diff --git a/kernel/syscalls/handle.c b/kernel/syscalls/handle.c new file mode 100644 index 0000000..7af9008 --- /dev/null +++ b/kernel/syscalls/handle.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +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; +} diff --git a/kernel/syscalls/log.c b/kernel/syscalls/log.c new file mode 100644 index 0000000..e69de29 diff --git a/kernel/syscalls/meson.build b/kernel/syscalls/meson.build new file mode 100644 index 0000000..0e9119f --- /dev/null +++ b/kernel/syscalls/meson.build @@ -0,0 +1,6 @@ +kernel_sources += files( + 'channel.c', + 'dispatch.c', + 'handle.c', + 'log.c', +) diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 0000000..b8269af --- /dev/null +++ b/lib/meson.build @@ -0,0 +1 @@ +subdir('zinnia') diff --git a/lib/zinnia/channel.c b/lib/zinnia/channel.c new file mode 100644 index 0000000..67935e4 --- /dev/null +++ b/lib/zinnia/channel.c @@ -0,0 +1,28 @@ +#include +#include +#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); +} diff --git a/lib/zinnia/handle.c b/lib/zinnia/handle.c new file mode 100644 index 0000000..68baa67 --- /dev/null +++ b/lib/zinnia/handle.c @@ -0,0 +1,15 @@ +#include +#include +#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); +} diff --git a/lib/zinnia/log.c b/lib/zinnia/log.c new file mode 100644 index 0000000..cc679f0 --- /dev/null +++ b/lib/zinnia/log.c @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include "syscall_stubs.h" + +void zn_log(const char* message) { + syscall2(ZN_SYSCALL_LOG, (arg_t)message, (arg_t)strlen(message)); +} diff --git a/lib/zinnia/meson.build b/lib/zinnia/meson.build new file mode 100644 index 0000000..d3ba2ec --- /dev/null +++ b/lib/zinnia/meson.build @@ -0,0 +1,9 @@ +libzinnia = both_libraries( + 'zinnia', + 'channel.c', + 'handle.c', + 'log.c', + 'status.c', + include_directories: common_includes, + install: true, +) diff --git a/lib/zinnia/status.c b/lib/zinnia/status.c new file mode 100644 index 0000000..b559fb3 --- /dev/null +++ b/lib/zinnia/status.c @@ -0,0 +1,30 @@ +#include + +const char* zn_status_to_string(zn_status_t err) { + switch (err) { + case ZN_OK: + return "No error (OK)"; + case ZN_ERR_INTERNAL: + return "An internal error occured (INTERNAL)"; + case ZN_ERR_BAD_SYSCALL: + return "Syscall number is not a recognized syscall (BAD_SYSCALL)"; + case ZN_ERR_UNSUPPORTED: + return "This operation is not supported (UNSUPPORTED)"; + case ZN_ERR_NO_MEMORY: + return "System does not have enough free memory for this operation (NO_MEMORY)"; + case ZN_ERR_NO_HANDLES: + return "Process can not own any more handles (NO_HANDLES)"; + case ZN_ERR_BAD_ARG: + return "One or more of the provided arguments is not valid (BAD_ARG)"; + case ZN_ERR_BAD_RANGE: + return "Argument is outside of the range for valid values (BAD_RANGE)"; + case ZN_ERR_BAD_OBJECT: + return "Object handle does not name a valid object (BAD_OBJECT)"; + case ZN_ERR_BAD_PERMS: + return "Object has insufficient permissions for this operation (BAD_PERMS)"; + case ZN_ERR_BAD_BUFFER: + return "Buffer is not large enough or doesn't point to a valid memory region (BAD_BUFFER)"; + } + + return "Unknown error code"; +} diff --git a/lib/zinnia/syscall_stubs.h b/lib/zinnia/syscall_stubs.h new file mode 100644 index 0000000..add30dc --- /dev/null +++ b/lib/zinnia/syscall_stubs.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include + +#ifdef __x86_64__ +#define ASM_REG_NUM "rax" +#define ASM_REG_RET "rax" +#define ASM_REG_A0 "rdi" +#define ASM_REG_A1 "rsi" +#define ASM_REG_A2 "rdx" +#define ASM_REG_A3 "r9" +#define ASM_REG_A4 "r8" +#define ASM_REG_A5 "r10" +#define ASM_SYSCALL "syscall" +#define ASM_CLOBBER "rcx", "r11" +typedef uint64_t arg_t; +#elif defined(__aarch64__) +#define ASM_REG_NUM "x8" +#define ASM_REG_RET "x0" +#define ASM_REG_A0 "x0" +#define ASM_REG_A1 "x1" +#define ASM_REG_A2 "x2" +#define ASM_REG_A3 "x3" +#define ASM_REG_A4 "x4" +#define ASM_REG_A5 "x5" +#define ASM_SYSCALL "svc 0" +#define ASM_CLOBBER +typedef uint64_t arg_t; +#elif defined(__riscv) && (__riscv_xlen == 64) +#define ASM_REG_NUM "a7" +#define ASM_REG_RET "a0" +#define ASM_REG_A0 "a0" +#define ASM_REG_A1 "a1" +#define ASM_REG_A2 "a2" +#define ASM_REG_A3 "a3" +#define ASM_REG_A4 "a4" +#define ASM_REG_A5 "a5" +#define ASM_SYSCALL "ecall" +#define ASM_CLOBBER +typedef uint64_t arg_t; +#elif defined(__loongarch64) +#define ASM_REG_NUM "a7" +#define ASM_REG_RET "a0" +#define ASM_REG_A0 "a0" +#define ASM_REG_A1 "a1" +#define ASM_REG_A2 "a2" +#define ASM_REG_A3 "a3" +#define ASM_REG_A4 "a4" +#define ASM_REG_A5 "a5" +#define ASM_SYSCALL "syscall 0" +#define ASM_CLOBBER +typedef uint64_t arg_t; +#else +#error "Unsupported architecture!" +#endif + +static inline zn_status_t syscall0(arg_t num) { + register arg_t rnum asm(ASM_REG_NUM) = num; + register arg_t value asm(ASM_REG_RET); + asm volatile(ASM_SYSCALL : "=r"(value) : "r"(rnum) : "memory", ASM_CLOBBER); + + return value; +} + +static inline zn_status_t syscall1(arg_t num, arg_t a0) { + register arg_t rnum asm(ASM_REG_NUM) = num; + register arg_t value asm(ASM_REG_RET); + register arg_t r0 asm(ASM_REG_A0) = a0; + asm volatile(ASM_SYSCALL : "=r"(value) : "r"(rnum), "r"(r0) : "memory", ASM_CLOBBER); + + return value; +} + +static inline zn_status_t syscall2(arg_t num, arg_t a0, arg_t a1) { + register arg_t rnum asm(ASM_REG_NUM) = num; + register arg_t value asm(ASM_REG_RET); + register arg_t r0 asm(ASM_REG_A0) = a0; + register arg_t r1 asm(ASM_REG_A1) = a1; + asm volatile(ASM_SYSCALL : "=r"(value) : "r"(rnum), "r"(r0), "r"(r1) : "memory", ASM_CLOBBER); + + return value; +} + +static inline zn_status_t syscall3(arg_t num, arg_t a0, arg_t a1, arg_t a2) { + register arg_t rnum asm(ASM_REG_NUM) = num; + register arg_t value asm(ASM_REG_RET); + register arg_t r0 asm(ASM_REG_A0) = a0; + register arg_t r1 asm(ASM_REG_A1) = a1; + register arg_t r2 asm(ASM_REG_A2) = a2; + asm volatile(ASM_SYSCALL : "=r"(value) : "r"(rnum), "r"(r0), "r"(r1), "r"(r2) : "memory", ASM_CLOBBER); + + return value; +} + +static inline zn_status_t syscall4(arg_t num, arg_t a0, arg_t a1, arg_t a2, arg_t a3) { + register arg_t rnum asm(ASM_REG_NUM) = num; + register arg_t value asm(ASM_REG_RET); + register arg_t r0 asm(ASM_REG_A0) = a0; + register arg_t r1 asm(ASM_REG_A1) = a1; + register arg_t r2 asm(ASM_REG_A2) = a2; + register arg_t r3 asm(ASM_REG_A3) = a3; + asm volatile(ASM_SYSCALL : "=r"(value) : "r"(rnum), "r"(r0), "r"(r1), "r"(r2), "r"(r3) : "memory", ASM_CLOBBER); + + return value; +} + +static inline zn_status_t syscall5(arg_t num, arg_t a0, arg_t a1, arg_t a2, arg_t a3, arg_t a4) { + register arg_t rnum asm(ASM_REG_NUM) = num; + register arg_t value asm(ASM_REG_RET); + register arg_t r0 asm(ASM_REG_A0) = a0; + register arg_t r1 asm(ASM_REG_A1) = a1; + register arg_t r2 asm(ASM_REG_A2) = a2; + register arg_t r3 asm(ASM_REG_A3) = a3; + register arg_t r4 asm(ASM_REG_A4) = a4; + asm volatile(ASM_SYSCALL + : "=r"(value) + : "r"(rnum), "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4) + : "memory", ASM_CLOBBER); + + return value; +} + +static inline zn_status_t syscall6(arg_t num, arg_t a0, arg_t a1, arg_t a2, arg_t a3, arg_t a4, arg_t a5) { + register arg_t rnum asm(ASM_REG_NUM) = num; + register arg_t value asm(ASM_REG_RET); + register arg_t r0 asm(ASM_REG_A0) = a0; + register arg_t r1 asm(ASM_REG_A1) = a1; + register arg_t r2 asm(ASM_REG_A2) = a2; + register arg_t r3 asm(ASM_REG_A3) = a3; + register arg_t r4 asm(ASM_REG_A4) = a4; + register arg_t r5 asm(ASM_REG_A5) = a5; + asm volatile(ASM_SYSCALL + : "=r"(value) + : "r"(rnum), "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5) + : "memory", ASM_CLOBBER); + + return value; +} diff --git a/menix.svg b/menix.svg new file mode 100644 index 0000000..45fc0ad --- /dev/null +++ b/menix.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..021ceeb --- /dev/null +++ b/meson.build @@ -0,0 +1,40 @@ +project( + 'zinnia', + 'c', + license: 'MIT', + license_files: ['LICENSE'], + version: '0.1.0', + meson_version: '>=1.1.0', + default_options: [ + 'c_std=gnu23', + 'werror=true', + 'warning_level=2', + ], +) + +add_project_arguments( + '-Wno-unused', + '-Wno-unused-parameter', + language: 'c', +) + +add_project_link_arguments( + '-znoexecstack', + '-Wl,--gc-sections', + '-Wl,--build-id=none', + language: 'c', +) + +common_includes = include_directories('include') + +# Config file +common_cfg = configuration_data() +common_cfg.set_quoted('ZINNIA_VERSION', meson.project_version()) +common_cfg.set_quoted('ZINNIA_ARCH', target_machine.cpu_family()) +c_compiler = meson.get_compiler('c') +common_cfg.set_quoted('ZINNIA_COMPILER_ID', c_compiler.get_id() + ' ' + c_compiler.version()) +common_cfg.set_quoted('ZINNIA_LINKER_ID', c_compiler.get_linker_id()) + +subdir('kernel') +subdir('lib') +subdir('servers') diff --git a/meson.options b/meson.options new file mode 100644 index 0000000..9c63029 --- /dev/null +++ b/meson.options @@ -0,0 +1,6 @@ +option( + 'kernel_only', + type: 'boolean', + value: true, + description: 'Build only the kernel', +) diff --git a/servers/init/main.c b/servers/init/main.c new file mode 100644 index 0000000..f5880dd --- /dev/null +++ b/servers/init/main.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +int main() { + zn_log("Hello, init world!\n"); + + // Create the root channel so other processes can find each other. + zn_status_t e; + zn_handle_t end0, end1; + zn_log("init: Creating root channel\n"); + if ((e = zn_channel_create(0, &end0, &end1))) { + zn_log(zn_status_to_string(e)); + zn_log(zn_status_to_string(e)); + return e; + } + + return ZN_OK; +} diff --git a/servers/init/meson.build b/servers/init/meson.build new file mode 100644 index 0000000..543903c --- /dev/null +++ b/servers/init/meson.build @@ -0,0 +1,10 @@ +executable( + 'init', + 'main.c', + # The init process is linked statically because the dynamic loader can't be + # read before vfsd is up. + c_args: '-static', + include_directories: common_includes, + link_with: libzinnia, + install: true, +) diff --git a/servers/meson.build b/servers/meson.build new file mode 100644 index 0000000..c6b9913 --- /dev/null +++ b/servers/meson.build @@ -0,0 +1,2 @@ +subdir('init') +subdir('posixd') diff --git a/servers/posixd/include/uapi/dirent.h b/servers/posixd/include/uapi/dirent.h new file mode 100644 index 0000000..ba1212e --- /dev/null +++ b/servers/posixd/include/uapi/dirent.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 +#define DT_WHT 14 + +struct dirent { + ino_t d_ino; + off_t d_off; + uint16_t d_reclen; + uint8_t d_type; + char d_name[256]; +}; diff --git a/servers/posixd/include/uapi/fcntl.h b/servers/posixd/include/uapi/fcntl.h new file mode 100644 index 0000000..cd82bce --- /dev/null +++ b/servers/posixd/include/uapi/fcntl.h @@ -0,0 +1,87 @@ +#pragma once + +#include + +#define O_RDONLY (1 << 0) +#define O_WRONLY (1 << 1) +#define O_CREAT (1 << 6) +#define O_EXCL (1 << 7) +#define O_NOCTTY (1 << 8) +#define O_TRUNC (1 << 9) +#define O_APPEND (1 << 10) +#define O_NONBLOCK (1 << 11) +#define O_DSYNC (1 << 12) +#define O_ASYNC (1 << 13) +#define O_DIRECT (1 << 14) +#define O_LARGEFILE (1 << 15) +#define O_DIRECTORY (1 << 16) +#define O_NOFOLLOW (1 << 17) +#define O_NOATIME (1 << 18) +#define O_CLOEXEC (1 << 19) +#define O_PATH (1 << 21) +#define O_TMPFILE (1 << 22) +#define O_SYNC (O_DIRECTORY | O_TMPFILE) +#define O_RSYNC O_SYNC +#define O_EXEC O_PATH +#define O_SEARCH O_PATH +#define O_RDWR (O_RDONLY | O_WRONLY) +#define O_ACCMODE (O_RDWR | O_PATH) + +#define F_DUPFD 0 +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 +#define F_GETLK 5 +#define F_SETLK 6 +#define F_SETLK64 F_SETLK +#define F_SETLKW 7 +#define F_SETLKW64 F_SETLKW +#define F_SETOWN 8 +#define F_GETOWN 9 +#define F_SETSIG 10 +#define F_GETSIG 11 +#define F_SETOWN_EX 15 +#define F_GETOWN_EX 16 +#define F_GETOWNER_UIDS 17 +#define F_SETLEASE 1024 +#define F_GETLEASE 1025 +#define F_NOTIFY 1026 +#define F_DUPFD_CLOEXEC 1030 +#define F_SETPIPE_SZ 1031 +#define F_GETPIPE_SZ 1032 +#define F_ADD_SEALS 1033 +#define F_GET_SEALS 1034 +#define F_SEAL_SEAL (1 << 0) +#define F_SEAL_SHRINK (1 << 1) +#define F_SEAL_GROW (1 << 2) +#define F_SEAL_WRITE (1 << 3) +#define F_OFD_GETLK 36 +#define F_OFD_SETLK 37 +#define F_OFD_SETLKW 38 +#define F_RDLCK 0 +#define F_WRLCK 1 +#define F_UNLCK 2 + +#define FD_CLOEXEC 1 + +#define AT_FDCWD (-100) +#define AT_SYMLINK_NOFOLLOW (1 << 8) +#define AT_REMOVEDIR (1 << 9) +#define AT_SYMLINK_FOLLOW (1 << 10) +#define AT_EACCESS (1 << 11) +#define AT_EMPTY_PATH (1 << 12) + +struct f_owner_ex { + int type; + pid_t pid; +}; + +#define F_OWNER_TID 0 + +#define POSIX_FADV_NORMAL 0 +#define POSIX_FADV_RANDOM 1 +#define POSIX_FADV_SEQUENTIAL 2 +#define POSIX_FADV_WILLNEED 3 +#define POSIX_FADV_DONTNEED 4 +#define POSIX_FADV_NOREUSE 5 diff --git a/servers/posixd/include/uapi/time.h b/servers/posixd/include/uapi/time.h new file mode 100644 index 0000000..fa7bfe0 --- /dev/null +++ b/servers/posixd/include/uapi/time.h @@ -0,0 +1,14 @@ +#pragma once + +typedef long time_t; +typedef long suseconds_t; + +struct timespec { + time_t tv_sec; + long tv_nsec; +}; + +struct timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; diff --git a/servers/posixd/include/uapi/types.h b/servers/posixd/include/uapi/types.h new file mode 100644 index 0000000..751abcd --- /dev/null +++ b/servers/posixd/include/uapi/types.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +typedef long off_t; +typedef long ssize_t; +typedef size_t off64_t; +typedef size_t blksize_t; +typedef size_t blkcnt_t; +typedef size_t clockid_t; +typedef size_t dev_t; +typedef size_t gid_t; +typedef size_t ino_t; +typedef uint32_t mode_t; +typedef size_t nlink_t; +typedef size_t pid_t; +typedef size_t rlim_t; +typedef size_t uid_t; +typedef size_t fsblkcnt_t; +typedef size_t fsfilcnt_t; +typedef size_t pthread_attr_t; diff --git a/servers/posixd/include/uapi/uio.h b/servers/posixd/include/uapi/uio.h new file mode 100644 index 0000000..f379c03 --- /dev/null +++ b/servers/posixd/include/uapi/uio.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +struct iovec { + void* base; + size_t len; +}; diff --git a/servers/posixd/include/uapi/uname.h b/servers/posixd/include/uapi/uname.h new file mode 100644 index 0000000..df4dfea --- /dev/null +++ b/servers/posixd/include/uapi/uname.h @@ -0,0 +1,10 @@ +#pragma once + +struct utsname { + char sysname[65]; + char nodename[65]; + char release[65]; + char version[65]; + char machine[65]; + char domainname[65]; +}; diff --git a/servers/posixd/include/vfs.h b/servers/posixd/include/vfs.h new file mode 100644 index 0000000..8bff49b --- /dev/null +++ b/servers/posixd/include/vfs.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +struct file; +struct inode; +struct path; + +// struct file_ops { +// zn_status_t (*open)(struct file* self, uint32_t flags); +// zn_status_t (*close)(struct file* self); +// zn_status_t (*read)(struct file* self, struct iovec_iter* iter, ssize_t* out_read); +// zn_status_t (*write)(struct file* self, struct iovec_iter* iter, ssize_t* out_written); +// zn_status_t (*devctl)(struct file* self, uint32_t dcmd, void* data, size_t num, int* out_info); +// zn_status_t (*poll)(struct file* self, int16_t mask, int16_t* out_mask); // TODO: This is not correct. +// zn_status_t (*mmap)( +// struct file* self, +// zn_handle_t* space, +// void* addr, +// size_t len, +// int prot, +// int flags, +// off_t offset +// ); +// }; + +struct file { + const struct file_ops* ops; + struct inode* inode; + uint32_t mode; + uint32_t flags; +}; + +struct file* file_alloc(); +struct file* file_from_fd(int fd); + +struct inode_ops { + union { + struct { + zn_status_t (*lookup)(struct inode* inode, struct path* path); + } dir; + struct { + zn_status_t (*truncate)(struct inode* inode); + } reg; + }; +}; + +struct inode_attr { + struct timespec atime, mtime, ctime; + uid_t uid; + gid_t gid; +}; + +struct inode { + const struct inode_ops* ops; + struct inode_attr attr; +}; + +struct entry { + const char* name; + struct inode* inode; + uint8_t state; +}; + +struct path { + struct entry* entry; + struct mount* mount; +}; + +struct mount { + uint32_t flags; + struct entry root; + struct path mount_point; +}; + +struct file_system_ops {}; + +struct file_system { + const char* name; + struct file_system_ops ops; +}; diff --git a/servers/posixd/main.c b/servers/posixd/main.c new file mode 100644 index 0000000..81edc78 --- /dev/null +++ b/servers/posixd/main.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +int main() { + zn_log("Hello, posixd world!\n"); + + zn_status_t e; + + // Open a connection to the init server. + zn_handle_t* handles; + void* data; + if ((e = zn_channel_open(ZN_INIT_CHANNEL, 32, 4096, &handles, &data))) + return e; + + while (1) {} +} diff --git a/servers/posixd/meson.build b/servers/posixd/meson.build new file mode 100644 index 0000000..8710b85 --- /dev/null +++ b/servers/posixd/meson.build @@ -0,0 +1,8 @@ +executable( + 'posixd', + 'main.c', + c_args: '-static', + include_directories: [common_includes, 'include'], + link_with: libzinnia, + install: true, +)