This commit is contained in:
2026-07-15 22:09:13 +02:00
commit cf33867e68
152 changed files with 9663 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
---
AlignAfterOpenBracket: BlockIndent
BinPackArguments: false
BinPackParameters: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowAllArgumentsOnNextLine: false
BreakAfterReturnType: Automatic
PenaltyReturnTypeOnItsOwnLine: 1000
AlignArrayOfStructures: None
IndentWidth: 4
InsertNewlineAtEOF: true
IntegerLiteralSeparator:
HexMinDigits: 8
IndentCaseBlocks: false
IndentCaseLabels: false
IndentPPDirectives: None
IndentGotoLabels: false
TabWidth: 4
Standard: Latest
Language: Cpp
LineEnding: LF
AlignOperands: DontAlign
BitFieldColonSpacing: None
ColumnLimit: 120
MaxEmptyLinesToKeep: 1
PointerAlignment: Left
QualifierAlignment: Leave
SpaceAfterLogicalNot: false
SpaceAfterCStyleCast: false
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortBlocksOnASingleLine: Empty
AlwaysBreakBeforeMultilineStrings: true
BreakAfterAttributes: Always
BreakConstructorInitializers: BeforeComma
BreakBeforeBraces: Attach
AllowShortLoopsOnASingleLine: false
SpacesInSquareBrackets: false
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignCompound: true
AlignFunctionPointers: false
AlignEscapedNewlines: DontAlign
BreakBeforeInlineASMColon: OnlyMultiline
AccessModifierOffset: -4
SpacesInParens: Never
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
IncludeBlocks: Merge
IncludeCategories:
- Regex: "^<zinnia/.*>"
Priority: 1
- Regex: "^<common/.*>"
Priority: 2
- Regex: "^<kernel/.*>"
Priority: 3
- Regex: "^<vdso/.*>"
Priority: 4
- Regex: '^<.*\.h>'
Priority: 5
- Regex: '^".*\.h"'
Priority: 6
SortIncludes: CaseInsensitive
+2
View File
@@ -0,0 +1,2 @@
Style:
AngledHeaders: [ "zinnia/.*", " kernel/.*" ]
+3
View File
@@ -0,0 +1,3 @@
[submodule "subprojects/uacpi"]
path = subprojects/uacpi
url = https://github.com/uACPI/uacpi
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Marvin Friedrich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
# Zinnia
![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 Issues or Pull Requests](https://img.shields.io/github/issues/zinnia-os/zinnia?style=flat)
Zinnia is a lightweight Unix-like microkernel targeting 64-bit devices.
## Building Zinnia
To build the kernel and the servers you need `meson` and a C23-compatible
GNU-like compiler targeting Zinnia.
Currently supported are GCC and Clang toolchains.
To configure, run:
```sh
meson setup -Dbuild_kernel=true -Dbuild_servers=true $build_dir
```
And to build:
```sh
meson compile -C $build_dir
```
+47
View File
@@ -0,0 +1,47 @@
#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
#define __alias(x) gnu::alias(x)
#else
#error "Unsupported compiler!"
#endif
// Intrinsics
#define __unreachable() __builtin_unreachable()
+371
View File
@@ -0,0 +1,371 @@
#pragma once
#include <stdint.h>
#ifdef __x86_64__
#define ELF_ARCH_CLASS ELFCLASS64
#define ELF_ARCH_DATA ELFDATA2LSB
#define ELF_ARCH_MACHINE EM_X86_64
#define ELF_R_SYM ELF64_R_SYM
#define ELF_R_TYPE ELF64_R_TYPE
#define ELF_ST_BIND ELF64_ST_BIND
#define ELF_ST_TYPE ELF64_ST_TYPE
#define elf_ehdr elf64_ehdr
#define elf_phdr elf64_phdr
#define elf_dyn elf64_dyn
#define elf_addr elf64_addr
#define elf_off elf64_off
#define elf_nhdr elf64_nhdr
#define elf_auxv elf64_auxv
#define elf_sym elf64_sym
#define elf_rela elf64_rela
#define elf_rel elf64_rel
enum {
R_NONE = 0,
R_COPY = 5,
R_GLOB_DAT = 6,
R_JUMP_SLOT = 7,
R_RELATIVE = 8,
R_IRELATIVE = 37,
};
#endif
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,
AT_HWCAP = 16,
AT_SECURE = 23,
AT_RANDOM = 25,
AT_EXECFN = 31,
AT_SYSINFO_EHDR = 33,
AT_INIT_HANDLE = 0x1000,
};
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;
};
struct elf_hash {
uint32_t n_buckets;
uint32_t n_chain;
};
struct elf32_sym {
elf32_off_t st_name;
elf32_addr_t st_value;
elf32_off_t st_size;
uint8_t st_info;
uint8_t st_other;
uint16_t st_shndx;
};
struct elf64_sym {
uint32_t st_name;
uint8_t st_info;
uint8_t st_other;
uint16_t st_shndx;
uint64_t st_value;
uint64_t st_size;
};
static uint8_t ELF64_ST_BIND(uint8_t info) {
return info >> 4;
}
static inline uint8_t ELF64_ST_TYPE(uint8_t info) {
return info & 0x0F;
}
static inline uint8_t ELF64_ST_INFO(uint8_t bind, uint8_t type) {
return (bind << 4) | type;
}
typedef struct {
uint16_t si_boundto;
uint16_t si_flags;
} elf64_syminfo;
static inline uint8_t ELF32_ST_BIND(uint8_t info) {
return info >> 4;
}
static inline uint8_t ELF32_ST_TYPE(uint8_t info) {
return info & 0xF;
}
static inline uint8_t ELF32_ST_INFO(uint8_t bind, uint8_t type) {
return (bind << 4) | (type & 0xF);
}
enum {
STB_GLOBAL = 1,
STB_WEAK = 2,
STB_GNU_UNIQUE = 10,
STB_LOPROC = 13,
STB_HIPROC = 15
};
enum {
STT_NOTYPE = 0,
STT_OBJECT = 1,
STT_FUNC = 2,
STT_TLS = 6,
STT_GNU_IFUNC = 10,
STT_LOPROC = 13,
STT_HIPROC = 15
};
struct elf32_rela {
elf32_addr_t r_offset;
uint32_t r_info;
int32_t r_addend;
};
struct elf64_rela {
elf64_addr_t r_offset;
uint64_t r_info;
int64_t r_addend;
};
struct elf32_rel {
elf32_addr_t r_offset;
uint32_t r_info;
};
struct elf64_rel {
elf64_addr_t r_offset;
uint64_t r_info;
};
static inline uint32_t ELF32_R_SYM(uint32_t info) {
return info >> 8;
}
static inline uint32_t ELF32_R_TYPE(uint32_t info) {
return info & 0xF;
}
static inline uint32_t ELF64_R_SYM(uint64_t info) {
return info >> 32;
}
static inline uint32_t ELF64_R_TYPE(uint64_t info) {
return info & 0xFFFFFFFF;
}
+40
View File
@@ -0,0 +1,40 @@
#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))
#if defined(__has_builtin)
#if __has_builtin(__builtin_types_compatible_p)
// Get pointer to parent object given pointer to a struct or union member.
#define CONTAINER_OF(ptr, type, member) \
({ \
_Static_assert( \
__builtin_types_compatible_p(__typeof__(*(ptr)), __typeof__(((type*)0)->member)), \
"Incompatible types for CONTAINER_OF" \
); \
(type*)((size_t)ptr - offsetof(type, member)); \
})
#endif
#endif
#ifndef CONTAINER_OF
// Get pointer to parent object given pointer to a struct or union member.
#define CONTAINER_OF(ptr, type, member) (type*)((size_t)ptr - offsetof(type, member))
#endif
#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))
+29
View File
@@ -0,0 +1,29 @@
#ifndef ZINNIA_ARCHCTL_H
#define ZINNIA_ARCHCTL_H
#include <zinnia/status.h>
#include <zinnia/syscall_stubs.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
// Does nothing.
ZN_ARCHCTL_NONE = 0,
// On x86_64, sets the FSBASE MSR to the value.
ZN_ARCHCTL_SET_FSBASE = 1,
// On x86_64, sets the GSBASE MSR to the value.
ZN_ARCHCTL_SET_GSBASE = 2,
} zn_archctl_t;
// Performs an architecture-dependent operation identified by `op`.
static inline zn_status_t zn_archctl(zn_archctl_t op, void* value) {
return zn_syscall2(op, value, ZN_SYSCALL_ARCHCTL);
}
#ifdef __cplusplus
}
#endif
#endif
+64
View File
@@ -0,0 +1,64 @@
#ifndef ZINNIA_CHANNEL_H
#define ZINNIA_CHANNEL_H
#include <zinnia/handle.h>
#include <zinnia/status.h>
#include <zinnia/syscall_stubs.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
enum zn_channel_flags {
ZN_CHANNEL_NONE = 0,
};
// Creates a new channel.
static inline zn_status_t zn_channel_create(
enum zn_channel_flags flags,
zn_handle_t* endpoint0,
zn_handle_t* endpoint1
) {
return zn_syscall3(flags, endpoint0, endpoint1, ZN_SYSCALL_CHANNEL_CREATE);
}
// Notifies the peer that there is new data available.
// The kernel only copies the first `num_handles` handles and `num_bytes` bytes.
static inline zn_status_t zn_channel_write(
zn_handle_t channel,
const zn_handle_t* handles,
const void* bytes,
size_t num_handles,
size_t num_bytes
) {
return zn_syscall5(channel, handles, bytes, num_handles, num_bytes, ZN_SYSCALL_CHANNEL_WRITE);
}
// Waits for a new message to appear in the channel.
static inline zn_status_t zn_channel_read(
zn_handle_t channel,
zn_handle_t* handles,
void* bytes,
size_t num_handles,
size_t num_bytes,
size_t* read_handles,
size_t* read_bytes
) {
return zn_syscall7(
channel,
handles,
bytes,
num_handles,
num_bytes,
read_handles,
read_bytes,
ZN_SYSCALL_CHANNEL_READ
);
}
#ifdef __cplusplus
}
#endif
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef ZINNIA_FUTEX_H
#define ZINNIA_FUTEX_H
#include <zinnia/status.h>
#include <zinnia/syscall_stubs.h>
#ifdef __cplusplus
extern "C" {
#endif
// Blocks the calling thread if *addr == expected.
// Returns immediately if the value at addr has changed.
static inline zn_status_t zn_futex_wait(int* addr, int expected) {
return zn_syscall2(addr, expected, ZN_SYSCALL_FUTEX_WAIT);
}
// Wakes up to `count` threads waiting on the given futex address.
static inline zn_status_t zn_futex_wake(int* addr, int count) {
return zn_syscall2(addr, count, ZN_SYSCALL_FUTEX_WAKE);
}
#ifdef __cplusplus
}
#endif
#endif
+45
View File
@@ -0,0 +1,45 @@
#ifndef ZINNIA_HANDLE_H
#define ZINNIA_HANDLE_H
#include <zinnia/rights.h>
#include <zinnia/status.h>
#include <zinnia/syscall_stubs.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// A generic object handle.
typedef int32_t zn_handle_t;
#define ZN_HANDLE_INVALID ((zn_handle_t)(0))
#define ZN_HANDLE_THIS_NS ((zn_handle_t)(-1))
#define ZN_HANDLE_THIS_VAS ((zn_handle_t)(-2))
#define ZN_HANDLE_THIS_THREAD ((zn_handle_t)(-3))
#define ZN_HANDLE_ZERO_VMO ((zn_handle_t)(-4))
// Checks an object handle for validity.
static inline zn_status_t zn_handle_validate(zn_handle_t handle) {
return zn_syscall1(handle, ZN_SYSCALL_HANDLE_VALIDATE);
}
// Drops 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.
static inline zn_status_t zn_handle_drop(zn_handle_t handle) {
return zn_syscall1(handle, ZN_SYSCALL_HANDLE_DROP);
}
// Creates a new handle pointing to the same object as pointed to
// by the original handle. The new handle may obtain new rights.
static inline zn_status_t zn_handle_clone(zn_handle_t handle, zn_rights_t cloned_rights, zn_handle_t* cloned) {
return zn_syscall3(handle, cloned_rights, cloned, ZN_SYSCALL_HANDLE_CLONE);
}
#ifdef __cplusplus
}
#endif
#endif
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <zinnia/handle.h>
#include <stddef.h>
#include <stdint.h>
#define IPC_GET_STRING(data, str, buf) \
do { \
memcpy(buf, (data) + (str)->offset, (str)->len); \
} while (0)
#define IPC_SET_STRING(cursor, data, field, string) \
do { \
const size_t _len = strlen(string); \
(field)->offset = cursor; \
(field)->len = _len; \
memcpy(data + cursor, string, _len); \
cursor += _len; \
} while (0)
enum ipc_msg_id {
IPC_MSG_FOO,
IPC_MSG_BAR,
IPC_MSG_BAZ,
};
struct ipc_array {
uint16_t offset;
uint16_t len;
};
struct ipc_option {
uint16_t offset;
};
struct ipc_init_register_rq {
enum ipc_msg_id id;
struct ipc_array name;
char data[];
};
struct ipc_kernel_msg {
enum ipc_msg_id id;
struct ipc_array name;
char data[];
};
+64
View File
@@ -0,0 +1,64 @@
#ifndef ZINNIA_MEM_H
#define ZINNIA_MEM_H
#include <zinnia/handle.h>
#include <zinnia/status.h>
#include <zinnia/syscall_stubs.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// Virtual memory flags.
enum zn_vm_flags {
// Mapping is readable.
ZN_VM_MAP_READ = 1 << 0,
// Mapping is writable.
ZN_VM_MAP_WRITE = 1 << 1,
// Mapping is executable.
ZN_VM_MAP_EXEC = 1 << 2,
// Shared memory (not cloned on calls to fork).
ZN_VM_MAP_SHARED = 1 << 3,
// Map at a fixed address, replacing whatever is in the way.
ZN_VM_MAP_FIXED = 1 << 8,
// Map at a fixed address, failing if anything is in the way.
ZB_VM_MAP_FIXED_NOREPLACE = 1 << 9,
// Map the entire range immediately.
ZN_VM_MAP_POPULATE = 1 << 10,
};
// Creates a new virtual memory object.
static inline zn_status_t zn_vmo_create(size_t length, zn_handle_t* out) {
return zn_syscall2(length, out, ZN_SYSCALL_VMO_CREATE);
}
// Creates a new virtual memory object which points to a contiguous phyiscal memory region.
static inline zn_status_t zn_vmo_create_phys(uintptr_t phys_addr, size_t length, zn_handle_t* out) {
return zn_syscall3(phys_addr, length, out, ZN_SYSCALL_VMO_CREATE_PHYS);
}
// Maps `bytes` of `vmo` at `vmo_offset` to `addr` in `vas`.
static inline zn_status_t zn_vmo_map(
zn_handle_t vmo,
zn_handle_t vas,
uintptr_t vmo_offset,
uintptr_t* addr,
size_t bytes,
enum zn_vm_flags flags
) {
return zn_syscall6(vmo, vas, vmo_offset, addr, bytes, flags, ZN_SYSCALL_VMO_MAP);
}
// Creates a new virtual address space.
static inline zn_status_t zn_vas_create(zn_handle_t* out) {
return zn_syscall1(out, ZN_SYSCALL_VAS_CREATE);
}
#ifdef __cplusplus
}
#endif
#endif
+24
View File
@@ -0,0 +1,24 @@
#ifndef ZINNIA_NAMESPACE_H
#define ZINNIA_NAMESPACE_H
#include <zinnia/handle.h>
#include <zinnia/status.h>
#ifdef __cplusplus
extern "C" {
#endif
// Creates a new namespace.
static inline zn_status_t zn_ns_create(zn_handle_t* out) {
return ZN_ERR_UNSUPPORTED;
}
static inline zn_status_t zn_ns_move(zn_handle_t handle, zn_handle_t target_namespace) {
return ZN_ERR_UNSUPPORTED;
}
#ifdef __cplusplus
}
#endif
#endif
+30
View File
@@ -0,0 +1,30 @@
#ifndef ZINNIA_RANDOM_H
#define ZINNIA_RANDOM_H
#include <zinnia/status.h>
#include <zinnia/syscall_stubs.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
ZN_RANDOM_MAX_ENTROPY = 0x1000
};
// Stores random bytes in a buffer.
static inline zn_status_t zn_random_get(void* addr, size_t len) {
return zn_syscall2(addr, len, ZN_SYSCALL_RANDOM_GET);
}
// Adds entropy to the RNG.
static inline zn_status_t zn_random_entropy(const void* addr, size_t len) {
return zn_syscall2(addr, len, ZN_SYSCALL_RANDOM_ENTROPY);
}
#ifdef __cplusplus
}
#endif
#endif
+35
View File
@@ -0,0 +1,35 @@
#ifndef ZINNIA_RIGHTS_H
#define ZINNIA_RIGHTS_H
#ifdef __cplusplus
extern "C" {
#endif
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 universe.
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_ALL = ~0u,
} zn_rights_t;
#ifdef __cplusplus
}
#endif
#endif
+72
View File
@@ -0,0 +1,72 @@
#ifndef ZINNIA_STATUS_H
#define ZINNIA_STATUS_H
#ifdef __cplusplus
extern "C" {
#endif
// Status values that may be returned by syscalls or other calls.
// A status value of 0 means no error occured.
// A negative value indicates a common error, usually from the kernel.
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 or implemented yet.
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_INVALID = -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_HANDLE = -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,
// The resource already exists.
ZN_ERR_ALREADY_EXISTS = -11,
} zn_status_t;
static inline 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 or implemented yet (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_INVALID:
return "One or more of the provided arguments is not valid (INVALID)";
case ZN_ERR_BAD_RANGE:
return "Argument is outside of the range for valid values (BAD_RANGE)";
case ZN_ERR_BAD_HANDLE:
return "Object handle does not name a valid object (BAD_HANDLE)";
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)";
case ZN_ERR_ALREADY_EXISTS:
return "The resource already exists (ALREADY_EXISTS)";
}
return "Unknown error code";
}
#ifdef __cplusplus
}
#endif
#endif
+36
View File
@@ -0,0 +1,36 @@
#ifndef ZINNIA_SYSCALL_NUMBERS_H
#define ZINNIA_SYSCALL_NUMBERS_H
typedef enum {
ZN_SYSCALL_LOG,
ZN_SYSCALL_ARCHCTL,
ZN_SYSCALL_PAGE_SIZE,
ZN_SYSCALL_TIME,
ZN_SYSCALL_HANDLE_VALIDATE,
ZN_SYSCALL_HANDLE_DROP,
ZN_SYSCALL_HANDLE_CLONE,
ZN_SYSCALL_VAS_CREATE,
ZN_SYSCALL_VMO_CREATE,
ZN_SYSCALL_VMO_CREATE_PHYS,
ZN_SYSCALL_VMO_MAP,
ZN_SYSCALL_CHANNEL_CREATE,
ZN_SYSCALL_CHANNEL_WAIT,
ZN_SYSCALL_CHANNEL_READ,
ZN_SYSCALL_CHANNEL_WRITE,
ZN_SYSCALL_THREAD_CREATE,
ZN_SYSCALL_THREAD_START,
ZN_SYSCALL_THREAD_EXIT,
ZN_SYSCALL_RANDOM_GET,
ZN_SYSCALL_RANDOM_ENTROPY,
ZN_SYSCALL_FUTEX_WAIT,
ZN_SYSCALL_FUTEX_WAKE,
} zn_syscall_t;
#endif
+252
View File
@@ -0,0 +1,252 @@
#ifndef ZINNIA_SYSCALL_STUBS_H
#define ZINNIA_SYSCALL_STUBS_H
#include <zinnia/status.h>
#include <zinnia/syscall_numbers.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __x86_64__
typedef uint64_t zn_arg_t;
#elif defined(__aarch64__)
typedef uint64_t zn_arg_t;
#elif defined(__riscv) && (__riscv_xlen == 64)
typedef uint64_t zn_arg_t;
#elif defined(__loongarch64)
typedef uint64_t zn_arg_t;
#else
#error "Unsupported architecture!"
#endif
#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_REG_A6 "r12"
#define ASM_REG_A7 "r13"
#define ASM_SYSCALL "syscall"
#define ASM_CLOBBER "rcx", "r11"
#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_REG_A6 "x6"
#define ASM_REG_A7 "x7"
#define ASM_SYSCALL "svc 0"
#define ASM_CLOBBER
#elif defined(__riscv) && (__riscv_xlen == 64)
#define ASM_REG_NUM "t3"
#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_REG_A5 "a6"
#define ASM_REG_A5 "a7"
#define ASM_SYSCALL "ecall"
#define ASM_CLOBBER
#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"
// TODO: Define ABI
#define ASM_REG_A6 TODO
#define ASM_REG_A7 TODO
#define ASM_SYSCALL "syscall 0"
#define ASM_CLOBBER
#else
#error "Unsupported architecture!"
#endif
static inline zn_status_t zn_syscall0(zn_syscall_t num) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
asm volatile(ASM_SYSCALL : "=r"(value) : "r"(rnum) : "memory", ASM_CLOBBER);
return value;
}
static inline zn_status_t zn_syscall1(zn_arg_t a0, zn_syscall_t num) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
register zn_arg_t r0 asm(ASM_REG_A0) = a0;
asm volatile(ASM_SYSCALL : "=r"(value) : "r"(rnum), "r"(r0) : "memory", ASM_CLOBBER);
return value;
}
#define zn_syscall1(a0, num) zn_syscall1((zn_arg_t)a0, num)
static inline zn_status_t zn_syscall2(zn_arg_t a0, zn_arg_t a1, zn_syscall_t num) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
register zn_arg_t r0 asm(ASM_REG_A0) = a0;
register zn_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;
}
#define zn_syscall2(a0, a1, num) zn_syscall2((zn_arg_t)a0, (zn_arg_t)a1, num)
static inline zn_status_t zn_syscall3(zn_arg_t a0, zn_arg_t a1, zn_arg_t a2, zn_syscall_t num) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
register zn_arg_t r0 asm(ASM_REG_A0) = a0;
register zn_arg_t r1 asm(ASM_REG_A1) = a1;
register zn_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;
}
#define zn_syscall3(a0, a1, a2, num) zn_syscall3((zn_arg_t)a0, (zn_arg_t)a1, (zn_arg_t)a2, num)
static inline zn_status_t zn_syscall4(zn_arg_t a0, zn_arg_t a1, zn_arg_t a2, zn_arg_t a3, zn_syscall_t num) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
register zn_arg_t r0 asm(ASM_REG_A0) = a0;
register zn_arg_t r1 asm(ASM_REG_A1) = a1;
register zn_arg_t r2 asm(ASM_REG_A2) = a2;
register zn_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;
}
#define zn_syscall4(a0, a1, a2, a3, num) zn_syscall4((zn_arg_t)a0, (zn_arg_t)a1, (zn_arg_t)a2, (zn_arg_t)a3, num)
static inline zn_status_t zn_syscall5(
zn_arg_t a0,
zn_arg_t a1,
zn_arg_t a2,
zn_arg_t a3,
zn_arg_t a4,
zn_syscall_t num
) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
register zn_arg_t r0 asm(ASM_REG_A0) = a0;
register zn_arg_t r1 asm(ASM_REG_A1) = a1;
register zn_arg_t r2 asm(ASM_REG_A2) = a2;
register zn_arg_t r3 asm(ASM_REG_A3) = a3;
register zn_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;
}
#define zn_syscall5(a0, a1, a2, a3, a4, num) \
zn_syscall5((zn_arg_t)a0, (zn_arg_t)a1, (zn_arg_t)a2, (zn_arg_t)a3, (zn_arg_t)a4, num)
static inline zn_status_t zn_syscall6(
zn_arg_t a0,
zn_arg_t a1,
zn_arg_t a2,
zn_arg_t a3,
zn_arg_t a4,
zn_arg_t a5,
zn_syscall_t num
) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
register zn_arg_t r0 asm(ASM_REG_A0) = a0;
register zn_arg_t r1 asm(ASM_REG_A1) = a1;
register zn_arg_t r2 asm(ASM_REG_A2) = a2;
register zn_arg_t r3 asm(ASM_REG_A3) = a3;
register zn_arg_t r4 asm(ASM_REG_A4) = a4;
register zn_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;
}
#define zn_syscall6(a0, a1, a2, a3, a4, a5, num) \
zn_syscall6((zn_arg_t)a0, (zn_arg_t)a1, (zn_arg_t)a2, (zn_arg_t)a3, (zn_arg_t)a4, (zn_arg_t)a5, num)
static inline zn_status_t zn_syscall7(
zn_arg_t a0,
zn_arg_t a1,
zn_arg_t a2,
zn_arg_t a3,
zn_arg_t a4,
zn_arg_t a5,
zn_arg_t a6,
zn_syscall_t num
) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
register zn_arg_t r0 asm(ASM_REG_A0) = a0;
register zn_arg_t r1 asm(ASM_REG_A1) = a1;
register zn_arg_t r2 asm(ASM_REG_A2) = a2;
register zn_arg_t r3 asm(ASM_REG_A3) = a3;
register zn_arg_t r4 asm(ASM_REG_A4) = a4;
register zn_arg_t r5 asm(ASM_REG_A5) = a5;
register zn_arg_t r6 asm(ASM_REG_A6) = a6;
asm volatile(ASM_SYSCALL
: "=r"(value)
: "r"(rnum), "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5), "r"(r6)
: "memory", ASM_CLOBBER);
return value;
}
#define zn_syscall7(a0, a1, a2, a3, a4, a5, a6, num) \
zn_syscall7((zn_arg_t)a0, (zn_arg_t)a1, (zn_arg_t)a2, (zn_arg_t)a3, (zn_arg_t)a4, (zn_arg_t)a5, (zn_arg_t)a6, num)
static inline zn_status_t zn_syscall8(
zn_arg_t a0,
zn_arg_t a1,
zn_arg_t a2,
zn_arg_t a3,
zn_arg_t a4,
zn_arg_t a5,
zn_arg_t a6,
zn_arg_t a7,
zn_syscall_t num
) {
register zn_syscall_t rnum asm(ASM_REG_NUM) = num;
register zn_status_t value asm(ASM_REG_RET);
register zn_arg_t r0 asm(ASM_REG_A0) = a0;
register zn_arg_t r1 asm(ASM_REG_A1) = a1;
register zn_arg_t r2 asm(ASM_REG_A2) = a2;
register zn_arg_t r3 asm(ASM_REG_A3) = a3;
register zn_arg_t r4 asm(ASM_REG_A4) = a4;
register zn_arg_t r5 asm(ASM_REG_A5) = a5;
register zn_arg_t r6 asm(ASM_REG_A6) = a6;
register zn_arg_t r7 asm(ASM_REG_A7) = a7;
asm volatile(ASM_SYSCALL
: "=r"(value)
: "r"(rnum), "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5), "r"(r6), "r"(r7)
: "memory", ASM_CLOBBER);
return value;
}
#define zn_syscall8(a0, a1, a2, a3, a4, a5, a6, a7, num) \
zn_syscall8( \
(zn_arg_t)a0, \
(zn_arg_t)a1, \
(zn_arg_t)a2, \
(zn_arg_t)a3, \
(zn_arg_t)a4, \
(zn_arg_t)a5, \
(zn_arg_t)a6, \
(zn_arg_t)a7, \
num \
)
#ifdef __cplusplus
}
#endif
#endif
+37
View File
@@ -0,0 +1,37 @@
#ifndef ZINNIA_SYSTEM_H
#define ZINNIA_SYSTEM_H
#include <zinnia/status.h>
#include <zinnia/syscall_stubs.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
static inline void zn_log(const char* message, size_t len) {
zn_syscall2(message, len, ZN_SYSCALL_LOG);
}
// Returns the page size of the system.
static inline size_t zn_page_size() {
size_t size = 0;
zn_syscall1(&size, ZN_SYSCALL_PAGE_SIZE);
return size;
}
static inline zn_status_t zn_powerctl() {
return ZN_ERR_UNSUPPORTED;
}
static inline uint64_t zn_time() {
uint64_t time = 0;
zn_syscall1(&time, ZN_SYSCALL_TIME);
return time;
}
#ifdef __cplusplus
}
#endif
#endif
+43
View File
@@ -0,0 +1,43 @@
#ifndef ZINNIA_THREAD_H
#define ZINNIA_THREAD_H
#include <zinnia/handle.h>
#include <zinnia/status.h>
#include <zinnia/syscall_stubs.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum zn_thread_flags {
ZN_THREAD_NONE = 0,
};
// Creates a new thread in the given namespace.
static inline zn_status_t zn_thread_create(
zn_handle_t ns,
const char* name,
size_t name_len,
enum zn_thread_flags flags,
zn_handle_t* out
) {
return zn_syscall5(ns, name, name_len, flags, out, ZN_SYSCALL_THREAD_CREATE);
}
// Starts executing a thread.
static inline zn_status_t zn_thread_start(zn_handle_t thread, uintptr_t ip, uintptr_t sp, uintptr_t arg) {
return zn_syscall4(thread, ip, sp, arg, ZN_SYSCALL_THREAD_START);
}
// Stops execution of the calling thread.
static inline void zn_thread_exit(void) {
zn_syscall0(ZN_SYSCALL_THREAD_EXIT);
}
#ifdef __cplusplus
}
#endif
#endif
+103
View File
@@ -0,0 +1,103 @@
#include <zinnia/mem.h>
#include <kernel/assert.h>
#include <kernel/clock.h>
#include <kernel/mmio.h>
#include <kernel/percpu.h>
#include <stdint.h>
#include <x86_64/apic.h>
#include <x86_64/asm.h>
#include <x86_64/defs.h>
static uint64_t lapic_read_reg(struct local_apic* lapic, uint32_t reg) {
if (lapic->xapic_regs) {
if (reg == APIC_REG_ICR) {
uint64_t lo, hi;
lo = lapic->xapic_regs[APIC_REG_ICR / sizeof(uint32_t)];
hi = lapic->xapic_regs[APIC_REG_ICR_HI / sizeof(uint32_t)];
return (hi << 32) | lo;
} else {
return lapic->xapic_regs[reg / sizeof(uint32_t)];
}
} else {
return asm_rdmsr(0x800 + (reg >> 4));
}
}
static void lapic_write_reg(struct local_apic* lapic, uint32_t reg, uint64_t value) {
if (lapic->xapic_regs) {
if (reg == APIC_REG_ICR) {
lapic->xapic_regs[APIC_REG_ICR_HI / sizeof(uint32_t)] = value >> 32;
lapic->xapic_regs[APIC_REG_ICR / sizeof(uint32_t)] = value & 0xFFFF'FFFF;
} else {
lapic->xapic_regs[reg / sizeof(uint32_t)] = value & 0xFFFF'FFFF;
}
} else {
asm_wrmsr(0x800 + (reg >> 4), value);
}
}
void lapic_init(struct local_apic* lapic) {
uint64_t apic_msr = asm_rdmsr(0x1B);
apic_msr |= 1 << 11; // Enable APIC.
struct cpuid cpuid = asm_cpuid(1, 0);
if (cpuid.ecx & CPUID_1C_X2APIC)
apic_msr |= 1 << 10;
else {
lapic->xapic_regs = mmio_new(apic_msr & 0xFFFF'F000, 0x1000);
ASSERT(lapic->xapic_regs, "Failed to allocate virtual memory!\n");
}
asm_wrmsr(0x1B, apic_msr);
// Reset the TPR.
lapic_write_reg(lapic, APIC_REG_TPR, 0);
// Enable APIC bit in the SIVR.
lapic_write_reg(lapic, APIC_REG_SIVR, lapic_read_reg(lapic, APIC_REG_SIVR) | 0x100);
if (lapic->xapic_regs) {
lapic_write_reg(lapic, APIC_REG_DFR, 0xF000'0000);
// Logical destination = LAPIC ID.
lapic_write_reg(lapic, APIC_REG_LDR, lapic_read_reg(lapic, APIC_REG_ID));
}
// TODO: Parse MADT and setup NMI sources.
// Tell the APIC timer to divide by 16.
lapic_write_reg(lapic, APIC_REG_DCR, 3);
// Set the timer to the highest value possible.
lapic_write_reg(lapic, APIC_REG_ICR_TIMER, 0xFFFF'FFFF);
// Sleep for 10 milliseconds.
clock_spin_ns(10'000'000);
lapic->ticks_per_10ms = 0xFFFF'FFFF - lapic_read_reg(lapic, APIC_REG_CCR);
lapic_write_reg(lapic, APIC_REG_LVT_TR, IDT_IPI_RESCHED | 0x20000);
lapic_write_reg(lapic, APIC_REG_ICR_TIMER, lapic->ticks_per_10ms);
}
void lapic_eoi(struct local_apic* lapic) {
lapic_write_reg(lapic, APIC_REG_EOI, 0);
}
#define PIC1_COMMAND_PORT 0x20
#define PIC1_DATA_PORT 0x21
#define PIC2_COMMAND_PORT 0xA0
#define PIC2_DATA_PORT 0xA1
void pic_disable() {
// Note: We initialize the PIC properly, but completely disable it and use the APIC in favor of it.
// Remap IRQs so they start at 0x20 since interrupts 0x00..0x1F are used by CPU exceptions.
asm_outb(PIC1_COMMAND_PORT, 0x11); // ICW1: Begin initialization and set cascade mode.
asm_outb(PIC1_DATA_PORT, 0x20); // ICW2: Set where interrupts should be mapped to (0x20-0x27).
asm_outb(PIC1_DATA_PORT, 0x04); // ICW3: Connect IRQ2 (0x04) to the slave PIC.
asm_outb(PIC1_DATA_PORT, 0x01); // ICW4: Set the PIC to operate in 8086/88 mode.
asm_outb(PIC1_DATA_PORT, 0xFF); // Mask all interrupts.
// Same for the slave PIC.
asm_outb(PIC2_COMMAND_PORT, 0x11); // ICW1: Begin initialization.
asm_outb(PIC2_DATA_PORT, 0x28); // ICW2: Set where interrupts should be mapped to (0x28-0x2F).
asm_outb(PIC2_DATA_PORT, 0x02); // ICW3: Connect to master PIC at IRQ2.
asm_outb(PIC2_DATA_PORT, 0x01); // ICW4: Set the PIC to operate in 8086/88 mode.
asm_outb(PIC2_DATA_PORT, 0xFF); // Mask all interrupts.
}
+77
View File
@@ -0,0 +1,77 @@
#include <kernel/percpu.h>
#include <kernel/syscalls.h>
#include <stddef.h>
#include <string.h>
#include <x86_64/asm.h>
#include <x86_64/defs.h>
#include <x86_64/gdt.h>
#include <x86_64/tss.h>
static const struct gdt main_gdt = {
.null = {.val = 0},
.kernel_code32 = {.val = 0x00cf9b000000ffff},
.kernel_data32 = {.val = 0x00cf93000000ffff},
.kernel_code64 = {.val = 0x00a09b0000000000},
.kernel_data64 = {.val = 0x0000930000000000},
.user_code32 = {.val = 0x00cffb000000ffff},
.user_data = {.val = 0x0000f30000000000},
.user_code64 = {.val = 0x00a0fb0000000000},
.tss = {
.limit0 = sizeof(struct tss) - 1,
.access = 0x89, // Present | DPL=0 | System | 64-bit TSS
.limit1_flags = ((sizeof(struct tss) - 1) >> 16) & 0x0F,
.reserved = 0,
},
};
void gdt_load() {
struct gdt* gdt = &percpu_get()->arch.gdt;
memcpy(gdt, &main_gdt, sizeof(main_gdt));
struct tss* tss = &percpu_get()->arch.tss;
tss->iopb = sizeof(struct tss);
uintptr_t tss_addr = (uintptr_t)tss;
gdt->tss.base0 = tss_addr;
gdt->tss.base1 = tss_addr >> 16;
gdt->tss.base2 = tss_addr >> 24;
gdt->tss.base3 = tss_addr >> 32;
struct gdtr gdtr = {
.limit = sizeof(struct gdt) - 1,
.base = gdt,
};
asm volatile("lgdt %0" ::"m"(gdtr));
// Save the contents of MSR_GS_BASE, as they get cleared by a write to `gs`.
uintptr_t gs = asm_rdmsr(MSR_GS_BASE);
// Flush and reload the segment registers.
asm volatile(
"push %0\n"
"lea rax, [rip + 1f]\n"
"push rax\n"
"retfq\n"
"1:\n"
"mov ax, %1\n"
"mov ds, ax\n"
"mov es, ax\n"
"mov fs, ax\n"
"mov gs, ax\n"
"mov ss, ax\n"
:
: "i"(offsetof(struct gdt, kernel_code64)), "i"(offsetof(struct gdt, kernel_data64))
: "rax", "memory"
);
asm_wrmsr(MSR_GS_BASE, gs);
asm volatile(
"mov ax, %c0\n"
"ltr ax"
:
: "i"(offsetof(struct gdt, tss))
: "rax", "memory"
);
}
+69
View File
@@ -0,0 +1,69 @@
#include <common/utils.h>
#include <kernel/assert.h>
#include <kernel/clock.h>
#include <kernel/cmdline.h>
#include <kernel/mmio.h>
#include <kernel/print.h>
#include <uacpi/acpi.h>
#include <uacpi/status.h>
#include <uacpi/tables.h>
#include <uacpi/uacpi.h>
static bool use_hpet = true;
static void hpet_option(bool use) {
use_hpet = use;
}
CMDLINE_OPTION("hpet", hpet_option);
struct hpet {
struct clock clock;
volatile uint64_t* regs;
uint32_t period;
};
#define HPET_CAP (0x0 / sizeof(uint64_t))
#define HPET_CFG (0x10 / sizeof(uint64_t))
#define HPET_COUNTER (0xF0 / sizeof(uint64_t))
static void hpet_reset(struct clock* c) {
struct hpet* hpet = CONTAINER_OF(c, struct hpet, clock);
hpet->regs[HPET_COUNTER] = 0;
}
static uint64_t hpet_get_elapsed_ns(struct clock* c) {
struct hpet* hpet = CONTAINER_OF(c, struct hpet, clock);
uint64_t counter = hpet->regs[HPET_COUNTER];
return (counter * (uint64_t)hpet->period / 1'000'000);
}
static struct hpet hpet_clock = {
.clock = {
.name = "hpet",
.priority = 128,
.reset = hpet_reset,
.get_elapsed_ns = hpet_get_elapsed_ns,
},
};
static char table_buf[0x1000];
void hpet_init() {
if (!use_hpet)
return;
uacpi_setup_early_table_access(table_buf, sizeof(table_buf));
uacpi_table table = {0};
ASSERT(!uacpi_table_find_by_signature("HPET", &table), "HPET table not found!\n");
struct acpi_hpet* hpet = table.ptr;
hpet_clock.regs = mmio_new(hpet->address.address, 0x1000);
uacpi_table_unref(&table);
kprintf("HPET regs at %p\n", hpet_clock.regs);
hpet_clock.regs[HPET_CFG] = hpet_clock.regs[HPET_CFG] | 1;
uint64_t caps = hpet_clock.regs[HPET_CAP];
hpet_clock.period = (caps >> 32) & 0xFFFF'FFFF;
ASSERT(clock_switch(&hpet_clock.clock), "Unable to switch to HPET!\n");
}
+209
View File
@@ -0,0 +1,209 @@
#include <common/compiler.h>
#include <kernel/assert.h>
#include <kernel/irq.h>
#include <kernel/panic.h>
#include <kernel/percpu.h>
#include <kernel/print.h>
#include <kernel/sched.h>
#include <bits/sched.h>
#include <stddef.h>
#include <stdint.h>
#include <x86_64/apic.h>
#include <x86_64/defs.h>
#include <x86_64/gdt.h>
struct [[__packed]] idt_entry {
uint16_t base0;
uint16_t selector;
uint8_t ist;
uint8_t attrib;
uint16_t base1;
uint32_t base2;
uint32_t reserved;
};
static_assert(sizeof(struct idt_entry) == 16);
struct [[__packed]] idtr {
uint16_t limit;
struct idt_entry* base;
};
static_assert(sizeof(struct idtr) == 10);
void interrupt_handler(struct arch_context* ctx) {
struct percpu* cpu = percpu_get();
bool old = irq_set_interrupted(true);
uint64_t isr = ctx->isr;
switch (isr) {
case IDT_PF:
uint64_t cr2;
asm volatile("mov %0, cr2" : "=r"(cr2));
panic("Page fault at %#lx, accessed: %#lx\n", ctx->rip, cr2);
case IDT_IPI_RESCHED:
arch_sched_preempt_disable();
if (arch_sched_preempt_enable()) {
lapic_eoi(&cpu->arch.lapic);
sched_reschedule(&cpu->sched);
}
break;
case 0x20 ...(0x20 + ARRAY_SIZE(cpu->arch.irq_lines)):
struct irq_line* line = cpu->arch.irq_lines[isr - 0x20];
ASSERT(line, "Unhandled interrupt on ISR %lu\n", isr);
// TODO: irq_raise(line);
break;
default:
panic("Unhandled exception on ISR %lu\n", isr);
}
irq_set_interrupted(old);
}
#define CS_OFFSET (sizeof(struct arch_context) - sizeof(uint64_t) - offsetof(struct arch_context, cs))
[[__naked]]
void interrupt_return() {
asm volatile(
"pop r15\n"
"pop r14\n"
"pop r13\n"
"pop r12\n"
"pop r11\n"
"pop r10\n"
"pop r9\n"
"pop r8\n"
"pop rsi\n"
"pop rdi\n"
"pop rbp\n"
"pop rdx\n"
"pop rcx\n"
"pop rbx\n"
"pop rax\n"
// Change GS back if we came from user mode.
"cmp word ptr [rsp + %c0], %c1\n"
"je 2f\n"
"swapgs\n"
"2:\n"
// Skip .error and .isr fields.
"add rsp, 0x10\n"
"iretq\n"
:
: "i"(CS_OFFSET), "i"(offsetof(struct gdt, kernel_code64))
);
}
[[__naked]]
void interrupt_stub_internal() {
asm volatile(
// Load the kernel GS base if we're coming from user space.
"cmp word ptr [rsp + %c0], %c1\n"
"je 2f\n"
"swapgs\n"
"2:\n"
"push rax\n"
"push rbx\n"
"push rcx\n"
"push rdx\n"
"push rbp\n"
"push rdi\n"
"push rsi\n"
"push r8\n"
"push r9\n"
"push r10\n"
"push r11\n"
"push r12\n"
"push r13\n"
"push r14\n"
"push r15\n"
"cld\n"
// Zero out the base pointer since we can't trust it.
"xor rbp, rbp\n"
// Load the frame as first argument.
"mov rdi, rsp\n"
"call %c2\n"
"jmp %c3\n"
:
: "i"(CS_OFFSET), "i"(offsetof(struct gdt, kernel_code64)), "i"(interrupt_handler), "i"(interrupt_return)
);
}
// clang-format off
#define REPEAT_256(M) \
M(0) M(1) M(2) M(3) M(4) M(5) M(6) M(7) \
M(8) M(9) M(10) M(11) M(12) M(13) M(14) M(15) \
M(16) M(17) M(18) M(19) M(20) M(21) M(22) M(23) \
M(24) M(25) M(26) M(27) M(28) M(29) M(30) M(31) \
M(32) M(33) M(34) M(35) M(36) M(37) M(38) M(39) \
M(40) M(41) M(42) M(43) M(44) M(45) M(46) M(47) \
M(48) M(49) M(50) M(51) M(52) M(53) M(54) M(55) \
M(56) M(57) M(58) M(59) M(60) M(61) M(62) M(63) \
M(64) M(65) M(66) M(67) M(68) M(69) M(70) M(71) \
M(72) M(73) M(74) M(75) M(76) M(77) M(78) M(79) \
M(80) M(81) M(82) M(83) M(84) M(85) M(86) M(87) \
M(88) M(89) M(90) M(91) M(92) M(93) M(94) M(95) \
M(96) M(97) M(98) M(99) M(100) M(101) M(102) M(103) \
M(104) M(105) M(106) M(107) M(108) M(109) M(110) M(111) \
M(112) M(113) M(114) M(115) M(116) M(117) M(118) M(119) \
M(120) M(121) M(122) M(123) M(124) M(125) M(126) M(127) \
M(128) M(129) M(130) M(131) M(132) M(133) M(134) M(135) \
M(136) M(137) M(138) M(139) M(140) M(141) M(142) M(143) \
M(144) M(145) M(146) M(147) M(148) M(149) M(150) M(151) \
M(152) M(153) M(154) M(155) M(156) M(157) M(158) M(159) \
M(160) M(161) M(162) M(163) M(164) M(165) M(166) M(167) \
M(168) M(169) M(170) M(171) M(172) M(173) M(174) M(175) \
M(176) M(177) M(178) M(179) M(180) M(181) M(182) M(183) \
M(184) M(185) M(186) M(187) M(188) M(189) M(190) M(191) \
M(192) M(193) M(194) M(195) M(196) M(197) M(198) M(199) \
M(200) M(201) M(202) M(203) M(204) M(205) M(206) M(207) \
M(208) M(209) M(210) M(211) M(212) M(213) M(214) M(215) \
M(216) M(217) M(218) M(219) M(220) M(221) M(222) M(223) \
M(224) M(225) M(226) M(227) M(228) M(229) M(230) M(231) \
M(232) M(233) M(234) M(235) M(236) M(237) M(238) M(239) \
M(240) M(241) M(242) M(243) M(244) M(245) M(246) M(247) \
M(248) M(249) M(250) M(251) M(252) M(253) M(254) M(255)
// clang-format on
#define INTERRUPT_STUB(n) \
[[__naked]] \
static void interrupt_stub_##n() { \
asm volatile( \
".if (%c0 == 8 || (%c0 >= 10 && %c0 <= 14) || %c0 == 17 || %c0 == 21 || %c0 == 29 || %c0 == 30)\n" \
".else\n" \
"push 0\n" \
".endif\n" \
"push %c0\n" \
"jmp %c1\n" \
: \
: "i"(n), "i"(interrupt_stub_internal) \
); \
}
#define INTERRUPT_SETUP(n) \
do { \
uint64_t addr = (uint64_t)interrupt_stub_##n; \
idt[n].base0 = addr & 0xFFFF; \
idt[n].base1 = (addr >> 16) & 0xFFFF; \
idt[n].base2 = (addr >> 32) & 0xFFFF'FFFF; \
idt[n].ist = 0; \
idt[n].attrib = (1 << 7) | 0xE; \
idt[n].selector = offsetof(struct gdt, kernel_code64); \
idt[n].reserved = 0; \
} while (0);
REPEAT_256(INTERRUPT_STUB)
static struct idt_entry idt[256] = {};
void idt_init() {
REPEAT_256(INTERRUPT_SETUP)
}
void idt_load() {
struct idtr idtr = {
.limit = sizeof(idt) - 1,
.base = idt,
};
asm volatile("lidt %0" ::"m"(idtr));
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <stdint.h>
#include <x86_64/defs.h>
static inline bool arch_irq_get_state() {
uint64_t flags;
asm volatile("pushf; pop %0" : "=r"(flags)::"memory");
return flags & RFLAGS_IF;
}
static inline void arch_irq_set_state(bool state) {
if (state)
asm volatile("sti");
else
asm volatile("cli");
}
static inline void arch_irq_wait() {
asm volatile("hlt");
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <kernel/types.h>
#include <stddef.h>
#include <stdint.h>
static inline size_t arch_mem_page_size() {
return 1 << 12;
}
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 uintptr_t arch_mem_hhdm_addr() {
return 0xFFFF'8000'0000'0000;
}
static inline uintptr_t arch_vm_pfndb_addr() {
return 0xFFFF'A000'0000'0000;
}
static inline uintptr_t arch_mem_mapping_addr() {
return 0xFFFF'C000'0000'0000;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
typedef uint64_t pte_t;
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <kernel/irq.h>
#include <stddef.h>
#include <x86_64/apic.h>
#include <x86_64/gdt.h>
#include <x86_64/tss.h>
static inline struct percpu* arch_percpu_get() {
struct percpu* result;
asm volatile("mov %0, gs:0" : "=r"(result)::"memory");
return result;
}
struct arch_percpu {
struct gdt gdt;
struct tss tss;
struct local_apic lapic;
struct irq_line* irq_lines[128];
size_t fpu_size;
void (*fpu_save)(void*);
void (*fpu_restore)(const void*);
bool can_smap;
};
void arch_percpu_bsp_init();
void arch_percpu_init();
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <bits/types.h>
#include <stdint.h>
struct arch_task_context {
uint64_t rsp;
uint8_t* fpu_region;
uint16_t ds, es, fs, gs;
uint64_t fs_base, gs_base;
};
struct arch_context {
uint64_t r15;
uint64_t r14;
uint64_t r13;
uint64_t r12;
uint64_t r11;
uint64_t r10;
uint64_t r9;
uint64_t r8;
uint64_t rsi;
uint64_t rdi;
uint64_t rbp;
uint64_t rdx;
uint64_t rcx;
uint64_t rbx;
uint64_t rax;
uint64_t isr; // Pushed onto the stack by the interrupt handler stubs.
uint64_t error; // Pushed onto the stack by the CPU if the interrupt has an error code.
uint64_t rip; // The rest is pushed onto the stack by the CPU during an interrupt.
uint64_t cs;
uint64_t rflags;
uint64_t rsp;
uint64_t ss;
};
#define ARCH_CTX_NUM rax
#define ARCH_CTX_A0 rdi
#define ARCH_CTX_A1 rsi
#define ARCH_CTX_A2 rdx
#define ARCH_CTX_A3 r9
#define ARCH_CTX_A4 r8
#define ARCH_CTX_A5 r10
#define ARCH_CTX_A6 r12
#define ARCH_CTX_A7 r13
#define ARCH_CTX_RET0 rax
#define ARCH_CTX_RET1 rdx
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <stdint.h>
typedef uint64_t phys_t;
typedef uint64_t reg_t;
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include <stdint.h>
struct local_apic {
uint32_t id;
uint32_t ticks_per_10ms;
volatile uint32_t* xapic_regs;
};
enum apic_delivery_mode {
APIC_DELIVERY_FIXED = 0b000,
APIC_DELIVERY_LOWESTPRIO = 0b001,
APIC_DELIVERY_SMI = 0b010,
APIC_DELIVERY_NMI = 0b100,
APIC_DELIVERY_INIT = 0b101,
APIC_DELIVERY_STARTUP = 0b110,
};
enum apic_dest_mode {
APIC_DEST_PHYSICAL = 0,
APIC_DEST_LOGICAL = 1,
};
enum apic_delivery_status {
APIC_DELIVERY_STATUS_IDLE = 0,
APIC_DELIVERY_STATUS_PENDING = 1,
};
enum apic_level {
APIC_LEVEL_DEASSERT = 0,
APIC_LEVEL_ASSERT = 1,
};
enum apic_trigger_mode {
APIC_TRIGGER_EDGE = 0,
APIC_TRIGGER_LEVEL = 1,
};
enum apic_regs {
APIC_REG_ID = 0x20,
APIC_REG_TPR = 0x80,
APIC_REG_EOI = 0xB0,
APIC_REG_LDR = 0xD0,
APIC_REG_DFR = 0xE0,
APIC_REG_SIVR = 0xF0,
APIC_REG_ESR = 0x280,
APIC_REG_ICR = 0x300,
APIC_REG_ICR_HI = 0x310,
APIC_REG_LVT_TR = 0x320,
APIC_REG_ICR_TIMER = 0x380,
APIC_REG_CCR = 0x390,
APIC_REG_DCR = 0x3E0,
};
void lapic_init(struct local_apic* lapic);
void lapic_eoi(struct local_apic* lapic);
void pic_disable();
+135
View File
@@ -0,0 +1,135 @@
#pragma once
#include <stdint.h>
struct cpuid {
uint32_t eax, ebx, ecx, edx;
};
static inline struct cpuid asm_cpuid(uint32_t leaf, uint32_t subleaf) {
struct cpuid out = {0};
asm volatile("cpuid" : "=a"(out.eax), "=b"(out.ebx), "=c"(out.ecx), "=d"(out.edx) : "0"(leaf), "2"(subleaf));
return out;
}
static inline uint64_t asm_rdmsr(uint32_t msr) {
uint32_t eax;
uint32_t edx;
asm volatile("rdmsr" : "=a"(eax), "=d"(edx) : "c"(msr) : "memory");
return ((uint64_t)edx << 32) | eax;
}
static inline uint64_t asm_rdtsc() {
uint32_t eax;
uint32_t edx;
asm volatile("rdtsc" : "=a"(eax), "=d"(edx) : : "memory");
return ((uint64_t)edx << 32) | eax;
}
// Writes a 64-bit value to a given MSR.
static inline void asm_wrmsr(uint32_t msr, uint64_t val) {
uint32_t eax = (uint32_t)val;
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]" ::"r"(mem) : "memory");
}
// Restores the FPU state from a 512-byte region of memory using FXRSTOR.
// Pointer must be 16-byte aligned.
static inline void asm_fxrstor(const void* mem) {
asm volatile("fxrstor [%0]" ::"r"(mem) : "memory");
}
// Saves the FPU state to a region of memory using XSAVE.
// Pointer must be 16-byte aligned.
static inline void asm_xsave(void* mem) {
asm volatile("xsave [%0]" ::"r"(mem) : "memory");
}
// Restores the FPU state from a region of memory using XRSTOR.
// Pointer must be 16-byte aligned.
static inline void asm_xrstor(const void* mem) {
asm volatile("xrstor [%0]" ::"r"(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));
}
static inline uint16_t asm_read_ds() {
uint16_t value = 0;
asm volatile("mov %0, ds" : "=r"(value));
return value;
}
static inline uint16_t asm_read_es() {
uint16_t value = 0;
asm volatile("mov %0, es" : "=r"(value));
return value;
}
static inline uint16_t asm_read_fs() {
uint16_t value = 0;
asm volatile("mov %0, fs" : "=r"(value));
return value;
}
static inline uint16_t asm_read_gs() {
uint16_t value = 0;
asm volatile("mov %0, gs" : "=r"(value));
return value;
}
static inline void asm_write_ds(uint16_t value) {
asm volatile("mov ds, %0" ::"r"(value));
}
static inline void asm_write_es(uint16_t value) {
asm volatile("mov es, %0" ::"r"(value));
}
static inline void asm_write_fs(uint16_t value) {
asm volatile("mov fs, %0" ::"r"(value));
}
static inline void asm_write_gs(uint16_t value) {
asm volatile("mov gs, %0" ::"r"(value));
}
+201
View File
@@ -0,0 +1,201 @@
#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 XCR0_X87 (1 << 0)
#define XCR0_SSE (1 << 1)
#define XCR0_AVX (1 << 2)
#define XCR0_AXV512_OPMASK (1 << 5)
#define XCR0_AVX512_ZMM_HI256 (1 << 6)
#define XCR0_AVX512_HI16_ZMM (1 << 7)
#define CR0_PE (1 << 0)
#define CR0_MP (1 << 1)
#define CR0_EM (1 << 2)
#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)
#define IDT_PF (0xE)
#define IDT_IPI_RESCHED (0xFF)
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include <common/compiler.h>
#include <stdint.h>
union gdt_desc {
uint64_t val;
struct {
uint16_t limit0;
uint16_t base0;
uint8_t base1;
uint8_t access;
uint8_t limit1_flags;
uint8_t base2;
};
};
static_assert(sizeof(union gdt_desc) == 0x8);
union gdt_long_desc {
uint64_t val[2];
struct {
uint16_t limit0;
uint16_t base0;
uint8_t base1;
uint8_t access;
uint8_t limit1_flags;
uint8_t base2;
uint32_t base3;
uint32_t reserved;
};
};
static_assert(sizeof(union gdt_long_desc) == 0x10);
struct [[__packed]] gdt {
union gdt_desc null;
union gdt_desc kernel_code32;
union gdt_desc kernel_data32;
union gdt_desc kernel_code64;
union gdt_desc kernel_data64;
union gdt_desc user_code32;
union gdt_desc user_data;
union gdt_desc user_code64;
union gdt_long_desc tss;
};
struct [[__packed]] gdtr {
uint16_t limit;
struct gdt* base;
};
struct percpu;
// Initializes a GDT on the local CPU.
void gdt_load();
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void hpet_init();
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <kernel/init.h>
[[__init]]
void idt_init();
// Loads the IDT on this CPU.
void idt_load();
void interrupt_return();
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#define SMP_GDTR_OFFSET 80
#define SMP_FARJMP_OFFSET 90
#define SMP_TEMP_STACK_OFFSET 98
#define SMP_TEMP_CR3_OFFSET 102
#define SMP_ENTRY_OFFSET 106
#define SMP_HHDM_OFFSET 114
#define SMP_KERNEL32_DS 16
#define SMP_KERNEL64_CS 24
#define SMP_KERNEL64_DS 32
#define SMP_INFO_SIZE 127
#ifndef __ASSEMBLER__
#include <common/compiler.h>
#include <common/utils.h>
#include <stddef.h>
#include <stdint.h>
#include <x86_64/gdt.h>
struct [[__packed]] smp_info {
struct gdt gdt;
struct gdtr gdtr;
uint32_t farjmp_offset;
uint32_t farjmp_segment;
uint32_t temp_stack;
uint32_t temp_cr3;
uint64_t entry;
uint64_t hhdm_offset;
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
@@ -0,0 +1,3 @@
#pragma once
void arch_syscall_stub();
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void tsc_init();
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <common/compiler.h>
#include <stdint.h>
struct [[__packed]] 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;
};
static_assert(sizeof(struct tss) == 0x68);
+22
View File
@@ -0,0 +1,22 @@
kernel_c_args += [
'-mgeneral-regs-only',
'-mno-red-zone',
'-mcmodel=kernel',
]
kernel_sources += files(
'apic.c',
'gdt.c',
'hpet.c',
'idt.c',
'percpu.c',
'pmap.c',
'sched.c',
'serial.c',
'smp.c',
'sys.c',
'syscall.c',
'trampoline.S',
'tsc.c',
'usercopy.c',
)
+116
View File
@@ -0,0 +1,116 @@
#include <kernel/percpu.h>
#include <kernel/print.h>
#include <stddef.h>
#include <x86_64/apic.h>
#include <x86_64/asm.h>
#include <x86_64/defs.h>
#include <x86_64/gdt.h>
#include <x86_64/hpet.h>
#include <x86_64/idt.h>
#include <x86_64/syscall.h>
#include <x86_64/tsc.h>
void arch_percpu_bsp_init() {
asm_wrmsr(MSR_GS_BASE, (uint64_t)&percpu_bsp);
asm_wrmsr(MSR_FS_BASE, 0);
asm_wrmsr(MSR_KERNEL_GS_BASE, 0);
pic_disable();
gdt_load();
idt_init();
idt_load();
}
union ia32_star {
uint64_t value;
struct {
uint32_t rsvd;
uint16_t kernel_cs_ss;
uint16_t user_cs_ss;
};
};
static void setup_cpu(struct percpu* cpu) {
asm_wrmsr(MSR_GS_BASE, (uint64_t)cpu);
asm_wrmsr(MSR_FS_BASE, 0);
asm_wrmsr(MSR_KERNEL_GS_BASE, 0);
gdt_load();
idt_load();
// Syscall extension.
asm_wrmsr(MSR_EFER, asm_rdmsr(MSR_EFER) | MSR_EFER_SCE);
union ia32_star star = {0};
star.kernel_cs_ss = offsetof(struct gdt, kernel_code64);
star.user_cs_ss = offsetof(struct gdt, user_code32);
asm_wrmsr(MSR_STAR, star.value);
asm_wrmsr(MSR_LSTAR, (uint64_t)arch_syscall_stub);
asm_wrmsr(MSR_SFMASK, RFLAGS_AC | RFLAGS_DF | RFLAGS_IF);
uint64_t cr0, cr4;
asm volatile("mov %0, cr0" : "=r"(cr0));
asm volatile("mov %0, cr4" : "=r"(cr4));
struct cpuid cpuid1 = asm_cpuid(1, 0);
struct cpuid cpuid7 = asm_cpuid(7, 0);
struct cpuid cpuid13 = asm_cpuid(13, 0);
// Enable SSE.
cr0 &= ~CR0_EM;
cr0 |= CR0_MP;
cr4 |= CR4_OSFXSR | CR4_OSXMMEXCPT;
if (cpuid1.ecx & CPUID_1C_XSAVE) {
cr4 |= CR4_OSXSAVE;
asm volatile("mov cr4, %0" ::"r"(cr4));
uint64_t xcr0 = XCR0_X87 | XCR0_SSE;
// AVX
if (cpuid1.ecx & CPUID_1C_AVX)
xcr0 |= XCR0_AVX;
// AVX-512
if (cpuid7.ebx & CPUID_7B_AVX512F)
xcr0 |= XCR0_AXV512_OPMASK | XCR0_AVX512_ZMM_HI256 | XCR0_AVX512_HI16_ZMM;
asm_wrxcr(0, xcr0);
cpu->arch.fpu_size = cpuid13.ecx;
cpu->arch.fpu_save = asm_xsave;
cpu->arch.fpu_restore = asm_xrstor;
} else {
cpu->arch.fpu_size = 512;
cpu->arch.fpu_save = asm_fxsave;
cpu->arch.fpu_restore = asm_fxrstor;
}
if (cpuid7.ecx & CPUID_7C_UMIP)
cr4 |= CR4_UMIP;
if (cpuid7.ebx & CPUID_7B_SMEP)
cr4 |= CR4_SMEP;
if (cpuid7.ebx & CPUID_7B_SMAP) {
cr4 |= CR4_SMAP;
cpu->arch.can_smap = true;
}
if (cpuid7.ebx & CPUID_7B_FSGSBASE)
cr4 |= CR4_FSGSBASE;
asm volatile("mov %0, cr0" : "=r"(cr0));
asm volatile("mov %0, cr4" : "=r"(cr4));
lapic_init(&cpu->arch.lapic);
cpu->online = true;
}
void arch_percpu_init() {
hpet_init();
tsc_init();
// Init the BSP.
setup_cpu(&percpu_bsp);
// Init any APs.
}
+56
View File
@@ -0,0 +1,56 @@
#include <kernel/pmap.h>
#include <kernel/types.h>
enum : pte_t {
ARCH_FLAG_PRESENT = 1 << 0,
ARCH_FLAG_READ_WRITE = 1 << 1,
ARCH_FLAG_USER_MODE = 1 << 2,
ARCH_FLAG_WRITE_THROUGH = 1 << 3,
ARCH_FLAG_CACHE_DISABLE = 1 << 4,
ARCH_FLAG_ACCESSED = 1 << 5,
ARCH_FLAG_DIRTY = 1 << 6,
ARCH_FLAG_SIZE = 1 << 7,
ARCH_FLAG_GLOBAL = 1 << 8,
ARCH_FLAG_AVAILABLE = 1 << 9,
ARCH_FLAG_ATTRIBUTE_TABLE = 1 << 10,
ARCH_FLAG_EXECUTE_DISABLE = 1LU << 63,
ARCH_PTE_ADDR_MASK = 0x000F'FFFF'FFFF'F000,
};
void pte_clear(pte_t* pte) {
*pte = 0;
}
pte_t pte_build(phys_t addr, enum pte_flags flags, enum cache_mode cache) {
pte_t result = ((pte_t)addr & ARCH_PTE_ADDR_MASK) | ARCH_FLAG_PRESENT;
if (flags & PTE_USER)
result |= ARCH_FLAG_USER_MODE;
if (flags & PTE_DIR)
result |= ARCH_FLAG_READ_WRITE;
else {
if (flags & PTE_WRITE)
result |= ARCH_FLAG_READ_WRITE;
if (!(flags & PTE_EXEC))
result |= ARCH_FLAG_EXECUTE_DISABLE;
}
return result;
}
bool pte_is_present(pte_t* pte) {
return *pte & ARCH_FLAG_PRESENT;
}
bool pte_is_dir(pte_t* pte) {
return *pte & ARCH_FLAG_SIZE;
}
phys_t pte_address(pte_t* pte) {
return (phys_t)(*pte & ARCH_PTE_ADDR_MASK);
}
void pmap_set(struct pmap* pt) {
asm volatile("mov cr3, %0" ::"r"(pt->root) : "memory");
}
+206
View File
@@ -0,0 +1,206 @@
#include <zinnia/status.h>
#include <common/compiler.h>
#include <kernel/alloc.h>
#include <kernel/percpu.h>
#include <kernel/sched.h>
#include <kernel/virt.h>
#include <bits/sched.h>
#include <stddef.h>
#include <stdint.h>
#include <x86_64/asm.h>
#include <x86_64/defs.h>
#include <x86_64/gdt.h>
#include <x86_64/idt.h>
// The task frame consists of registers that the C ABI marks as callee-saved.
// If we don't save them, these registers are lost during a context switch.
// The order of these fields is important.
struct task_frame {
uint64_t rbx, rbp, r12, r13, r14, r15, rip;
};
[[__naked]]
static void perform_switch(uint64_t* old_rsp, uint64_t new_rsp) {
asm volatile(
"sub rsp, 0x30\n" // Make room for all regs (except RIP).
"mov [rsp + %c0], rbx\n"
"mov [rsp + %c1], rbp\n"
"mov [rsp + %c2], r12\n"
"mov [rsp + %c3], r13\n"
"mov [rsp + %c4], r14\n"
"mov [rsp + %c5], r15\n"
"mov [rdi], rsp\n" // rdi = old_rsp
"mov rsp, rsi\n" // rsi = new_rsp
"mov rbx, [rsp + %c0]\n"
"mov rbp, [rsp + %c1]\n"
"mov r12, [rsp + %c2]\n"
"mov r13, [rsp + %c3]\n"
"mov r14, [rsp + %c4]\n"
"mov r15, [rsp + %c5]\n"
"add rsp, 0x30\n"
"call %c6\n"
"ret" // This will conveniently move us to the RIP we put at this stack entry.
:
: "i"(offsetof(struct task_frame, rbx)),
"i"(offsetof(struct task_frame, rbp)),
"i"(offsetof(struct task_frame, r12)),
"i"(offsetof(struct task_frame, r13)),
"i"(offsetof(struct task_frame, r14)),
"i"(offsetof(struct task_frame, r15)),
"i"(irq_unlock)
: "memory"
);
}
void arch_sched_switch(struct task* from, struct task* to) {
struct arch_percpu* cpu = &percpu_get()->arch;
struct arch_task_context* from_ctx = &from->context;
struct arch_task_context* to_ctx = &to->context;
cpu->tss.rsp0 = to->kernel_stack + KERNEL_STACK_SIZE;
if (from->user_stack) {
cpu->fpu_save(from_ctx->fpu_region);
from_ctx->ds = asm_read_ds();
from_ctx->es = asm_read_es();
from_ctx->fs = asm_read_fs();
from_ctx->gs = asm_read_gs();
from_ctx->fs_base = asm_rdmsr(MSR_FS_BASE);
from_ctx->gs_base = asm_rdmsr(MSR_KERNEL_GS_BASE);
}
if (to->user_stack) {
cpu->fpu_restore(to_ctx->fpu_region);
asm_write_ds(to_ctx->ds);
asm_write_es(to_ctx->es);
asm_write_fs(to_ctx->fs);
// If we have to change the GS segment we need to reload the MSR, otherwise we lose its value.
if (to_ctx->gs != asm_read_gs()) {
uint64_t percpu = asm_rdmsr(MSR_GS_BASE);
asm_write_gs(to_ctx->gs);
asm_wrmsr(MSR_GS_BASE, percpu);
}
asm_wrmsr(MSR_FS_BASE, to_ctx->fs_base);
asm_wrmsr(MSR_KERNEL_GS_BASE, to_ctx->gs_base);
}
perform_switch(&from_ctx->rsp, to_ctx->rsp);
}
[[__naked]]
void task_entry_thunk() {
asm volatile(
"mov rdi, rbx\n"
"mov rsi, r12\n"
"jmp %c0"
:
: "i"(task_entry)
: "memory"
);
}
zn_status_t arch_task_init(
struct arch_task_context* context,
void* entry,
uintptr_t arg0,
uintptr_t stack_start,
bool is_user
) {
struct arch_percpu* cpu = &percpu_get()->arch;
// Prepare a dummy stack with an entry point function to return to.
struct task_frame* frame = (struct task_frame*)stack_start - 1;
frame->rbx = (uint64_t)entry;
frame->r12 = arg0;
frame->rip = (uint64_t)task_entry_thunk;
context->rsp = (uint64_t)frame;
if (is_user) {
// Allocate FPU space.
const size_t fpu_size = cpu->fpu_size;
void* fpu_base = vm_alloc(fpu_size);
if (!fpu_base)
return ZN_ERR_NO_MEMORY;
for (size_t i = 0; i < fpu_size; i += arch_mem_page_size()) {
phys_t page;
zn_status_t s = mem_phys_alloc(1, 0, &page);
if (s) {
vm_free(fpu_base, fpu_size);
return s;
}
pmap_map(&kernel_vas.pmap, (uintptr_t)fpu_base, page, PTE_WRITE | PTE_READ, CACHE_WRITE_BACK);
}
context->fpu_region = fpu_base;
context->ds = asm_read_ds();
context->es = asm_read_es();
context->fs = asm_read_fs();
context->gs = asm_read_gs();
context->fs_base = asm_rdmsr(MSR_FS_BASE);
context->gs_base = asm_rdmsr(MSR_KERNEL_GS_BASE);
}
return ZN_OK;
}
[[__naked]]
void arch_sched_preempt_disable() {
asm volatile(
"inc qword ptr gs:%c0\n"
"ret"
:
: "i"(offsetof(struct percpu, sched.preempt_level))
);
}
[[__naked]]
bool arch_sched_preempt_enable() {
asm volatile(
"dec qword ptr gs:%c0\n"
"setz al\n"
"movzx eax, al\n"
"ret\n"
:
: "i"(offsetof(struct percpu, sched.preempt_level))
: "cc"
);
}
[[__naked]]
void arch_sched_jump_to_context(struct arch_context* context) {
asm volatile(
"mov rsp, rdi\n"
"jmp %c0\n" ::"i"(interrupt_return)
);
}
void arch_sched_jump_to_user(uintptr_t ip, uintptr_t sp, uintptr_t arg) {
struct arch_context context = {
.rip = ip,
.rsp = sp,
.rdi = arg,
.rflags = 0x202,
.cs = offsetof(struct gdt, user_code64) | CPL_USER,
.ss = offsetof(struct gdt, user_data) | CPL_USER,
};
uintptr_t percpu = asm_rdmsr(MSR_GS_BASE);
const uint16_t zero = 0;
asm volatile(
"mov ds, %0\n"
"mov es, %0\n"
"mov fs, %0\n"
"mov gs, %0\n"
:
: "r"(zero)
);
asm_wrmsr(MSR_FS_BASE, 0);
asm_wrmsr(MSR_GS_BASE, percpu);
asm_wrmsr(MSR_KERNEL_GS_BASE, 0);
arch_sched_jump_to_context(&context);
}
+47
View File
@@ -0,0 +1,47 @@
#include <kernel/console.h>
#include <stdint.h>
#include <x86_64/asm.h>
constexpr uint16_t COM1_BASE = 0x3F8;
static bool is_ready() {
return asm_inb(COM1_BASE + 5) & 0x20;
}
static void serial_write(struct console* con, const char* buf, size_t count) {
for (size_t i = 0; i < count; i++) {
while (!is_ready())
asm volatile("pause");
asm_outb(COM1_BASE, buf[i]);
}
}
bool serial_setup(struct console* con) {
asm_outb(COM1_BASE + 1, 0x00); // Disable interrupts
asm_outb(COM1_BASE + 3, 0x80); // Enable DLAB (set baud rate divisor)
asm_outb(COM1_BASE, 0x01); // Set divisor low byte (115200 baud if 1)
asm_outb(COM1_BASE + 1, 0x00); // Set divisor high byte
asm_outb(COM1_BASE + 3, 0x03); // 8 bits, no parity, one stop bit (8n1)
asm_outb(COM1_BASE + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
asm_outb(COM1_BASE + 4, 0x0B); // IRQs enabled, RTS/DSR set
asm_outb(COM1_BASE + 4, 0x1E); // Set to loopback mode.
asm_outb(COM1_BASE, 0xAE); // Send a test byte.
// If we don't get the same value back, this serial port doesn't work.
if (asm_inb(COM1_BASE) != 0xAE) {
return false;
}
asm_outb(COM1_BASE + 4, 0x0F); // Disable loopback mode.
return true;
}
static struct console serial_con = {
.name = "com1",
.write = serial_write,
.init = serial_setup,
};
DEFINE_CONSOLE(&serial_con);
+17
View File
@@ -0,0 +1,17 @@
#include <common/compiler.h>
#include <kernel/types.h>
#include <x86_64/smp.h>
extern uint8_t smp_trampoline_start[];
extern uint8_t smp_trampoline_end[];
[[noreturn]]
static void ap_entry(phys_t info) {
// TODO
while (1) {
asm volatile("hlt");
}
}
void x86_64_smp_init(uint32_t id) {}
+31
View File
@@ -0,0 +1,31 @@
#include <zinnia/archctl.h>
#include <zinnia/status.h>
#include <common/compiler.h>
#include <kernel/init.h>
#include <kernel/percpu.h>
#include <x86_64/apic.h>
#include <x86_64/asm.h>
#include <x86_64/defs.h>
#include <x86_64/gdt.h>
[[__init, __naked, __used]]
void _start() {
asm volatile(
"lea rsp, [rip + %0]\n"
"jmp %1"
:
: "i"(__ld_stack_top), "r"(kernel_entry)
);
}
void arch_panic() {}
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_INVALID;
}
}
+68
View File
@@ -0,0 +1,68 @@
#include <common/compiler.h>
#include <kernel/percpu.h>
#include <kernel/syscalls.h>
#include <bits/sched.h>
#include <stddef.h>
#include <x86_64/defs.h>
[[__naked]]
void arch_syscall_stub() {
asm volatile(
"swapgs\n"
"mov gs:%c0, rsp\n"
"mov rsp, gs:%c1\n"
"cld\n"
// We're pretending to be an interrupt, so fill the bottom fields of `Context`.
"push %c3\n" // SS and CS are not changed during SYSCALL. Use `gdt::user_data | CPL_USER`.
"push gs:%c0\n"
"push r11\n" // RFLAGS is moved into r11 by the CPU.
"push %c2\n" // Same as SS. Use `gdt::user_code64 | CPL_USER`
"push rcx\n" // RIP is moved into rcx by the CPU.
"push 0x00\n" // arch_context::error field
"push 0x00\n" // arch_context::isr field
"push rax\n"
"push rbx\n"
"push rcx\n"
"push rdx\n"
"push rbp\n"
"push rdi\n"
"push rsi\n"
"push r8\n"
"push r9\n"
"push r10\n"
"push r11\n"
"push r12\n"
"push r13\n"
"push r14\n"
"push r15\n"
"xor rbp, rbp\n"
"mov rdi, rsp\n" // Put the trap frame struct as first argument.
"call %c4\n" // Call syscall handler
"cli\n"
"pop r15\n"
"pop r14\n"
"pop r13\n"
"pop r12\n"
"pop r11\n"
"pop r10\n"
"pop r9\n"
"pop r8\n"
"pop rsi\n"
"pop rdi\n"
"pop rbp\n"
"pop rdx\n"
"pop rcx\n"
"pop rbx\n"
"pop rax\n"
"add rsp, 0x10\n" // Skip .error and .isr fields (2 * sizeof(uint64_t))
"mov rsp, gs:%c0\n" // Load user stack from `percpu::user_stack`.
"swapgs\n"
"sysretq\n" // Return to user mode.
:
: "i"(offsetof(struct percpu, user_stack)),
"i"(offsetof(struct percpu, kernel_stack)),
"i"(offsetof(struct gdt, user_code64) | CPL_USER),
"i"(offsetof(struct gdt, user_data) | CPL_USER),
"i"(syscall_dispatch)
);
}
+107
View File
@@ -0,0 +1,107 @@
#include <x86_64/smp.h>
#include <x86_64/defs.h>
.intel_syntax noprefix
.global smp_trampoline_start
.global smp_trampoline_end
.section .rodata
.code16
smp_trampoline_start:
cli
cld
jmp 1f
smp_trampoline_data:
.skip SMP_INFO_SIZE
.set data_offset, (smp_trampoline_data - smp_trampoline_start)
.set gdtr_offset, (data_offset + SMP_GDTR_OFFSET)
.set farjmp_offset, (data_offset + SMP_FARJMP_OFFSET)
.set temp_stack_offset, (data_offset + SMP_TEMP_STACK_OFFSET)
.set temp_cr3_offset, (data_offset + SMP_TEMP_CR3_OFFSET)
.set entry_offset, (data_offset + SMP_ENTRY_OFFSET)
.set hhdm_offset, (data_offset + SMP_HHDM_OFFSET)
1:
mov bx, cs
shl ebx, 4
.set idtr_offset, (invalid_idtr - smp_trampoline_start)
lidtd cs:idtr_offset
lgdtd cs:gdtr_offset
.set mode32_offset, (mode32 - smp_trampoline_start)
lea eax, [ebx + mode32_offset]
mov dword ptr cs:farjmp_offset, eax
mov eax, CR0_PE | CR0_ET
mov cr0, eax
jmp fword ptr cs:farjmp_offset
.code32
mode32:
mov ax, SMP_KERNEL32_DS
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
xor eax, eax
lldt ax
mov esp, dword ptr [ebx + temp_stack_offset]
xor eax, eax
mov cr4, eax
or eax, CR4_PAE
mov cr4, eax
mov ecx, MSR_EFER
mov eax, MSR_EFER_LME
xor edx, edx
wrmsr
mov eax, dword ptr [ebx + temp_cr3_offset]
mov cr3, eax
mov eax, cr0
or eax, CR0_PG
mov cr0, eax
.set mode64_offset, (mode64 - smp_trampoline_start)
lea eax, [ebx + mode64_offset]
push SMP_KERNEL64_CS
push eax
retf
.code64
mode64:
mov ax, SMP_KERNEL64_DS
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov ecx, MSR_EFER
rdmsr
or eax, MSR_EFER_NXE
wrmsr
xor ebp, ebp
lea rdi, [rbx + data_offset]
mov rax, [rbx + entry_offset]
add rsp, [rbx + hhdm_offset]
jmp rax
invalid_idtr:
.word 0
.quad 0
smp_trampoline_end:
+54
View File
@@ -0,0 +1,54 @@
#include <kernel/clock.h>
#include <kernel/cmdline.h>
#include <kernel/print.h>
#include <x86_64/asm.h>
#include <x86_64/defs.h>
static bool use_tsc = true;
void tsc_init() {
if (!use_tsc)
return;
if (!(asm_cpuid(1, 0).edx & CPUID_1D_TSC) || !(asm_cpuid(0x8000'0007, 0).edx & (1 << 8))) {
kprintf("No invariant TSC detected!\n");
return;
}
// Check for the TSC info leaf.
bool has_tsc_leaf = false;
struct cpuid tsc_leaf = {0};
if (asm_cpuid(0x8000'0000, 0).eax >= 0x15) {
tsc_leaf = asm_cpuid(0x15, 0);
}
uint64_t freq = 0;
if (clock_available()) {
// Calibrate using existing clock.
uint64_t t1 = asm_rdtsc();
clock_spin_ns(10'000'000);
uint64_t t2 = asm_rdtsc();
freq = (t2 - t1) * 100;
} else if (has_tsc_leaf) {
// If we have no timer (yet), the only way we can calibrate the TSC is if CPUID gives us the frequency.
// On a normal system, this should usually never be called and is a last resort
// since at this point we have at least the HPET timer.
if (tsc_leaf.ecx != 0 && tsc_leaf.ebx != 0 && tsc_leaf.eax != 0) {
kprintf("Calibrating TSC using CPUID 0x15");
freq = tsc_leaf.ecx * tsc_leaf.ebx / tsc_leaf.eax;
} else {
kprintf("Unable to calibrate TSC using CPUID frequency information");
return;
}
} else {
kprintf("Unable to calibrate the TSC without a clock or static frequency info!\n");
return;
}
kprintf("Timer frequency is %lu MHz\n", freq / 1'000'000);
}
static void tsc_option(bool opt) {
use_tsc = opt;
}
CMDLINE_OPTION("tsc", tsc_option);
+137
View File
@@ -0,0 +1,137 @@
#include <common/compiler.h>
#include <kernel/usercopy.h>
extern void x86_64_read_start();
extern void x86_64_read_end();
extern void x86_64_read_fault();
static const struct usercopy_region read_region = {
.start_ip = x86_64_read_start,
.end_ip = x86_64_read_end,
.fault_ip = x86_64_read_fault,
};
bool arch_usercopy_read(void* dst, const __user void* src, size_t len, struct usercopy_region** region) {
asm volatile(
"stac\n"
// 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"
"clac\n"
"ret\n"
".global x86_64_read_fault\n"
"x86_64_read_fault:\n"
"xor rax, rax\n"
"mov [rdx], rax\n"
"clac\n"
"ret\n"
:
: "i"(&read_region)
: "memory"
);
return false;
}
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,
};
bool arch_usercopy_write(__user void* dst, const void* src, size_t len, struct usercopy_region** region) {
asm volatile(
"stac\n"
// 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"
"clac\n"
"ret\n"
".global x86_64_write_fault\n"
"x86_64_write_fault:\n"
"xor rax, rax\n"
"mov [rdx], rax\n"
"clac\n"
"ret\n"
:
: "i"(&read_region)
: "memory"
);
return false;
}
extern void x86_64_strlen_start();
extern void x86_64_strlen_end();
extern void x86_64_strlen_fault();
static const struct usercopy_region strlen_region = {
.start_ip = x86_64_strlen_start,
.end_ip = x86_64_strlen_end,
.fault_ip = x86_64_strlen_fault,
};
[[__naked]]
bool arch_usercopy_strlen(const __user char* str, size_t max, size_t* len, struct usercopy_region** region) {
asm volatile(
"stac\n"
"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"
"clac\n"
"ret\n"
".global x86_64_strlen_fault\n"
"x86_64_strlen_fault:\n"
"xor rax, rax\n"
"mov [rcx], rax\n"
"clac\n"
"ret\n"
:
: "i"(&strlen_region)
: "memory"
);
}
+102
View File
@@ -0,0 +1,102 @@
#include <kernel/cmdline.h>
#include <kernel/init.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
// Defined in linker script.
extern const struct cmdline_option* __ld_cmdline_start[];
extern const struct cmdline_option* __ld_cmdline_end[];
static char cmdline_buf[0x1000];
[[__init]]
void cmdline_parse(const char* cmdline) {
const size_t len = strnlen(cmdline, sizeof(cmdline_buf) - 1);
size_t idx = 0;
memcpy(cmdline_buf, cmdline, len);
while (1) {
const struct cmdline_option** opt = __ld_cmdline_start;
char* name = nullptr;
char* value = nullptr;
// Skip all leading spaces.
while (idx < len && cmdline_buf[idx] == ' ')
idx++;
if (idx >= len)
break;
size_t name_idx = idx;
name = cmdline_buf + name_idx;
// Find the next equal sign or space.
while (idx < len && cmdline_buf[idx] != '=' && cmdline_buf[idx] != ' ')
idx++;
if (idx > len)
break;
// Check if the option has a value (=foo).
char seperator = cmdline_buf[idx];
cmdline_buf[idx++] = 0;
if (seperator == '=') {
// Check if we need to escape the value.
char check;
if (cmdline_buf[idx] == '"') {
check = '"';
cmdline_buf[idx++] = 0;
} else {
check = ' ';
}
value = cmdline_buf + idx;
// Skip the value.
while (idx < len && cmdline_buf[idx] != check)
idx++;
if (idx > len)
break;
cmdline_buf[idx++] = 0;
}
// Find the corresponding option.
while (opt < __ld_cmdline_end) {
const struct cmdline_option* current = *opt;
if (!strcmp(current->name, name)) {
switch (current->option_type) {
case CMDLINE_STRING:
current->func_str(value);
break;
case CMDLINE_BOOL:
if (!strcmp(value, "on") || !strcmp(value, "yes") || !strcmp(value, "true") || !strcmp(value, "1"))
current->func_bool(true);
else
current->func_bool(false);
break;
case CMDLINE_INTPTR: {
int base = 10;
if (!strncmp(value, "0x", 2)) {
base = 0x10;
value += 2;
}
current->func_intptr(atol(value, base));
break;
}
case CMDLINE_UINTPTR: {
int base = 10;
if (!strncmp(value, "0x", 2)) {
base = 0x10;
value += 2;
}
current->func_uintptr(atolu(value, base));
break;
}
}
break;
}
opt++;
}
if (idx >= len)
break;
}
}
+94
View File
@@ -0,0 +1,94 @@
#include <common/utils.h>
#include <kernel/alloc.h>
#include <kernel/assert.h>
#include <kernel/cmdline.h>
#include <kernel/init.h>
#include <kernel/print.h>
#include <string.h>
#include "limine.h"
[[__initdata_sorted("limine.0")]]
static volatile uint64_t limine_start[] = LIMINE_REQUESTS_START_MARKER;
[[__initdata_sorted("limine.1")]]
static volatile uint64_t limine_revision[] = LIMINE_BASE_REVISION(4);
[[__initdata_sorted("limine.2")]]
static volatile uint64_t limine_end[] = LIMINE_REQUESTS_END_MARKER;
#define LIMINE_REQUEST(request, tag, rev) \
[[__initdata_sorted("limine.1")]] \
static volatile struct limine_##request request = { \
.id = tag, \
.revision = rev, \
.response = nullptr, \
}
LIMINE_REQUEST(memmap_request, LIMINE_MEMMAP_REQUEST_ID, 0);
LIMINE_REQUEST(hhdm_request, LIMINE_HHDM_REQUEST_ID, 0);
LIMINE_REQUEST(executable_address_request, LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID, 0);
LIMINE_REQUEST(executable_file_request, LIMINE_EXECUTABLE_FILE_REQUEST_ID, 0);
LIMINE_REQUEST(framebuffer_request, LIMINE_FRAMEBUFFER_REQUEST_ID, 1);
LIMINE_REQUEST(module_request, LIMINE_MODULE_REQUEST_ID, 0);
LIMINE_REQUEST(dtb_request, LIMINE_DTB_REQUEST_ID, 0);
LIMINE_REQUEST(rsdp_request, LIMINE_RSDP_REQUEST_ID, 0);
[[__initdata]]
static struct phys_mem mem[128];
[[__initdata]]
static struct boot_file files[8];
[[__init]]
void kernel_entry() {
kernel_early_init();
struct boot_info info;
struct limine_memmap_response* mm_res = memmap_request.response;
struct limine_module_response* module_res = module_request.response;
struct limine_executable_address_response* exec_res = executable_address_request.response;
struct limine_executable_file_response* exec_file_res = executable_file_request.response;
info.cmdline = exec_file_res->executable_file->string;
info.num_mem_maps = MIN(ARRAY_SIZE(mem), mm_res->entry_count);
for (size_t i = 0; i < info.num_mem_maps; i++) {
mem[i].address = mm_res->entries[i]->base;
mem[i].length = mm_res->entries[i]->length;
switch (mm_res->entries[i]->type) {
case LIMINE_MEMMAP_USABLE:
mem[i].usage = PHYS_USABLE;
break;
case LIMINE_MEMMAP_ACPI_RECLAIMABLE:
case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE:
mem[i].usage = PHYS_RECLAIMABLE;
break;
case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES:
mem[i].usage = PHYS_STATIC;
break;
default:
mem[i].usage = PHYS_RESERVED;
break;
}
}
if (module_res) {
info.files = files;
info.num_files = MIN(ARRAY_SIZE(files), module_res->module_count);
for (size_t i = 0; i < info.num_files; i++) {
files[i].data = (phys_t)module_res->modules[i]->address - hhdm_request.response->offset;
files[i].length = module_res->modules[i]->size;
strncpy(files[i].path, module_res->modules[i]->path, sizeof(files[i].path));
}
}
info.mem_map = mem;
info.phys_base = (phys_t)exec_res->physical_base;
info.virt_base = (uintptr_t)exec_res->virtual_base;
info.hhdm_base = (uintptr_t)hhdm_request.response->offset;
info.rsdp = (phys_t)rsdp_request.response->address - info.hhdm_base;
kernel_main(&info);
}
phys_t rsdp_addr;
+576
View File
@@ -0,0 +1,576 @@
/* BSD Zero Clause License */
/* Copyright (C) 2022-2025 Mintsuki and contributors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef LIMINE_H
#define LIMINE_H 1
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* Misc */
#ifdef LIMINE_NO_POINTERS
#define LIMINE_PTR(TYPE) uint64_t
#else
#define LIMINE_PTR(TYPE) TYPE
#endif
#define LIMINE_REQUESTS_START_MARKER {0xf6b8f4b39de7d1ae, 0xfab91a6940fcb9cf, 0x785c6ed015d3e316, 0x181e920a7852b9d9}
#define LIMINE_REQUESTS_END_MARKER {0xadc0e0531bb10d03, 0x9572709f31764c62}
#define LIMINE_BASE_REVISION(N) {0xf9562b2d5c95a6c8, 0x6a7b384944536bdc, (N)}
#define LIMINE_BASE_REVISION_SUPPORTED(VAR) ((VAR)[2] == 0)
#define LIMINE_LOADED_BASE_REVISION_VALID(VAR) ((VAR)[1] != 0x6a7b384944536bdc)
#define LIMINE_LOADED_BASE_REVISION(VAR) ((VAR)[1])
#define LIMINE_COMMON_MAGIC 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b
struct limine_uuid {
uint32_t a;
uint16_t b;
uint16_t c;
uint8_t d[8];
};
#define LIMINE_MEDIA_TYPE_GENERIC 0
#define LIMINE_MEDIA_TYPE_OPTICAL 1
#define LIMINE_MEDIA_TYPE_TFTP 2
struct limine_file {
uint64_t revision;
LIMINE_PTR(void*) address;
uint64_t size;
LIMINE_PTR(char*) path;
LIMINE_PTR(char*) string;
uint32_t media_type;
uint32_t unused;
uint32_t tftp_ip;
uint32_t tftp_port;
uint32_t partition_index;
uint32_t mbr_disk_id;
struct limine_uuid gpt_disk_uuid;
struct limine_uuid gpt_part_uuid;
struct limine_uuid part_uuid;
};
/* Boot info */
#define LIMINE_BOOTLOADER_INFO_REQUEST_ID {LIMINE_COMMON_MAGIC, 0xf55038d8e2a1202f, 0x279426fcf5f59740}
struct limine_bootloader_info_response {
uint64_t revision;
LIMINE_PTR(char*) name;
LIMINE_PTR(char*) version;
};
struct limine_bootloader_info_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_bootloader_info_response*) response;
};
/* Executable command line */
#define LIMINE_EXECUTABLE_CMDLINE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x4b161536e598651e, 0xb390ad4a2f1f303a}
struct limine_executable_cmdline_response {
uint64_t revision;
LIMINE_PTR(char*) cmdline;
};
struct limine_executable_cmdline_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_executable_cmdline_response*) response;
};
/* Firmware type */
#define LIMINE_FIRMWARE_TYPE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x8c2f75d90bef28a8, 0x7045a4688eac00c3}
#define LIMINE_FIRMWARE_TYPE_X86BIOS 0
#define LIMINE_FIRMWARE_TYPE_EFI32 1
#define LIMINE_FIRMWARE_TYPE_EFI64 2
#define LIMINE_FIRMWARE_TYPE_SBI 3
struct limine_firmware_type_response {
uint64_t revision;
uint64_t firmware_type;
};
struct limine_firmware_type_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_firmware_type_response*) response;
};
/* Stack size */
#define LIMINE_STACK_SIZE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x224ef0460a8e8926, 0xe1cb0fc25f46ea3d}
struct limine_stack_size_response {
uint64_t revision;
};
struct limine_stack_size_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_stack_size_response*) response;
uint64_t stack_size;
};
/* HHDM */
#define LIMINE_HHDM_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x48dcf1cb8ad2b852, 0x63984e959a98244b}
struct limine_hhdm_response {
uint64_t revision;
uint64_t offset;
};
struct limine_hhdm_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_hhdm_response*) response;
};
/* Framebuffer */
#define LIMINE_FRAMEBUFFER_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x9d5827dcd881dd75, 0xa3148604f6fab11b}
#define LIMINE_FRAMEBUFFER_RGB 1
struct limine_video_mode {
uint64_t pitch;
uint64_t width;
uint64_t height;
uint16_t bpp;
uint8_t memory_model;
uint8_t red_mask_size;
uint8_t red_mask_shift;
uint8_t green_mask_size;
uint8_t green_mask_shift;
uint8_t blue_mask_size;
uint8_t blue_mask_shift;
};
struct limine_framebuffer {
LIMINE_PTR(void*) address;
uint64_t width;
uint64_t height;
uint64_t pitch;
uint16_t bpp;
uint8_t memory_model;
uint8_t red_mask_size;
uint8_t red_mask_shift;
uint8_t green_mask_size;
uint8_t green_mask_shift;
uint8_t blue_mask_size;
uint8_t blue_mask_shift;
uint8_t unused[7];
uint64_t edid_size;
LIMINE_PTR(void*) edid;
/* Response revision 1 */
uint64_t mode_count;
LIMINE_PTR(struct limine_video_mode**) modes;
};
struct limine_framebuffer_response {
uint64_t revision;
uint64_t framebuffer_count;
LIMINE_PTR(struct limine_framebuffer**) framebuffers;
};
struct limine_framebuffer_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_framebuffer_response*) response;
};
/* Paging mode */
#define LIMINE_PAGING_MODE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x95c1a0edab0944cb, 0xa4e5cb3842f7488a}
#if defined(__x86_64__) || defined(__i386__)
#define LIMINE_PAGING_MODE_X86_64_4LVL 0
#define LIMINE_PAGING_MODE_X86_64_5LVL 1
#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_X86_64_4LVL
#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_X86_64_4LVL
#elif defined(__aarch64__)
#define LIMINE_PAGING_MODE_AARCH64_4LVL 0
#define LIMINE_PAGING_MODE_AARCH64_5LVL 1
#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_AARCH64_4LVL
#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_AARCH64_4LVL
#elif defined(__riscv) && (__riscv_xlen == 64)
#define LIMINE_PAGING_MODE_RISCV_SV39 0
#define LIMINE_PAGING_MODE_RISCV_SV48 1
#define LIMINE_PAGING_MODE_RISCV_SV57 2
#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_RISCV_SV39
#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_RISCV_SV48
#elif defined(__loongarch__) && (__loongarch_grlen == 64)
#define LIMINE_PAGING_MODE_LOONGARCH64_4LVL 0
#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_LOONGARCH64_4LVL
#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_LOONGARCH64_4LVL
#else
#error Unknown architecture
#endif
struct limine_paging_mode_response {
uint64_t revision;
uint64_t mode;
};
struct limine_paging_mode_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_paging_mode_response*) response;
uint64_t mode;
uint64_t max_mode;
uint64_t min_mode;
};
/* MP */
#define LIMINE_MP_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x95a67b819a1b857e, 0xa0b61b723b6a73e0}
struct limine_mp_info;
typedef void (*limine_goto_address)(struct limine_mp_info*);
#if defined(__x86_64__) || defined(__i386__)
#define LIMINE_MP_X2APIC (1 << 0)
struct limine_mp_info {
uint32_t processor_id;
uint32_t lapic_id;
uint64_t reserved;
LIMINE_PTR(limine_goto_address) goto_address;
uint64_t extra_argument;
};
struct limine_mp_response {
uint64_t revision;
uint32_t flags;
uint32_t bsp_lapic_id;
uint64_t cpu_count;
LIMINE_PTR(struct limine_mp_info**) cpus;
};
#elif defined(__aarch64__)
struct limine_mp_info {
uint32_t processor_id;
uint32_t reserved1;
uint64_t mpidr;
uint64_t reserved;
LIMINE_PTR(limine_goto_address) goto_address;
uint64_t extra_argument;
};
struct limine_mp_response {
uint64_t revision;
uint64_t flags;
uint64_t bsp_mpidr;
uint64_t cpu_count;
LIMINE_PTR(struct limine_mp_info**) cpus;
};
#elif defined(__riscv) && (__riscv_xlen == 64)
struct limine_mp_info {
uint64_t processor_id;
uint64_t hartid;
uint64_t reserved;
LIMINE_PTR(limine_goto_address) goto_address;
uint64_t extra_argument;
};
struct limine_mp_response {
uint64_t revision;
uint64_t flags;
uint64_t bsp_hartid;
uint64_t cpu_count;
LIMINE_PTR(struct limine_mp_info**) cpus;
};
#elif defined(__loongarch__) && (__loongarch_grlen == 64)
struct limine_mp_info {
uint64_t reserved;
};
struct limine_mp_response {
uint64_t cpu_count;
LIMINE_PTR(struct limine_mp_info**) cpus;
};
#else
#error Unknown architecture
#endif
struct limine_mp_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_mp_response*) response;
uint64_t flags;
};
/* Memory map */
#define LIMINE_MEMMAP_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x67cf3d9d378a806f, 0xe304acdfc50c3c62}
#define LIMINE_MEMMAP_USABLE 0
#define LIMINE_MEMMAP_RESERVED 1
#define LIMINE_MEMMAP_ACPI_RECLAIMABLE 2
#define LIMINE_MEMMAP_ACPI_NVS 3
#define LIMINE_MEMMAP_BAD_MEMORY 4
#define LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE 5
#define LIMINE_MEMMAP_EXECUTABLE_AND_MODULES 6
#define LIMINE_MEMMAP_FRAMEBUFFER 7
#define LIMINE_MEMMAP_ACPI_TABLES 8
struct limine_memmap_entry {
uint64_t base;
uint64_t length;
uint64_t type;
};
struct limine_memmap_response {
uint64_t revision;
uint64_t entry_count;
LIMINE_PTR(struct limine_memmap_entry**) entries;
};
struct limine_memmap_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_memmap_response*) response;
};
/* Entry point */
#define LIMINE_ENTRY_POINT_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a}
typedef void (*limine_entry_point)(void);
struct limine_entry_point_response {
uint64_t revision;
};
struct limine_entry_point_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_entry_point_response*) response;
LIMINE_PTR(limine_entry_point) entry;
};
/* Executable File */
#define LIMINE_EXECUTABLE_FILE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69}
struct limine_executable_file_response {
uint64_t revision;
LIMINE_PTR(struct limine_file*) executable_file;
};
struct limine_executable_file_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_executable_file_response*) response;
};
/* Module */
#define LIMINE_MODULE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x3e7e279702be32af, 0xca1c4f3bd1280cee}
#define LIMINE_INTERNAL_MODULE_REQUIRED (1 << 0)
#define LIMINE_INTERNAL_MODULE_COMPRESSED (1 << 1)
struct limine_internal_module {
LIMINE_PTR(const char*) path;
LIMINE_PTR(const char*) string;
uint64_t flags;
};
struct limine_module_response {
uint64_t revision;
uint64_t module_count;
LIMINE_PTR(struct limine_file**) modules;
};
struct limine_module_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_module_response*) response;
/* Request revision 1 */
uint64_t internal_module_count;
LIMINE_PTR(struct limine_internal_module**) internal_modules;
};
/* RSDP */
#define LIMINE_RSDP_REQUEST_ID {LIMINE_COMMON_MAGIC, 0xc5e77b6b397e7b43, 0x27637845accdcf3c}
struct limine_rsdp_response {
uint64_t revision;
LIMINE_PTR(void*) address;
};
struct limine_rsdp_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_rsdp_response*) response;
};
/* SMBIOS */
#define LIMINE_SMBIOS_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x9e9046f11e095391, 0xaa4a520fefbde5ee}
struct limine_smbios_response {
uint64_t revision;
uint64_t entry_32;
uint64_t entry_64;
};
struct limine_smbios_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_smbios_response*) response;
};
/* EFI system table */
#define LIMINE_EFI_SYSTEM_TABLE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x5ceba5163eaaf6d6, 0x0a6981610cf65fcc}
struct limine_efi_system_table_response {
uint64_t revision;
uint64_t address;
};
struct limine_efi_system_table_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_efi_system_table_response*) response;
};
/* EFI memory map */
#define LIMINE_EFI_MEMMAP_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x7df62a431d6872d5, 0xa4fcdfb3e57306c8}
struct limine_efi_memmap_response {
uint64_t revision;
LIMINE_PTR(void*) memmap;
uint64_t memmap_size;
uint64_t desc_size;
uint64_t desc_version;
};
struct limine_efi_memmap_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_efi_memmap_response*) response;
};
/* Date at boot */
#define LIMINE_DATE_AT_BOOT_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x502746e184c088aa, 0xfbc5ec83e6327893}
struct limine_date_at_boot_response {
uint64_t revision;
int64_t timestamp;
};
struct limine_date_at_boot_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_date_at_boot_response*) response;
};
/* Executable address */
#define LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x71ba76863cc55f63, 0xb2644a48c516a487}
struct limine_executable_address_response {
uint64_t revision;
uint64_t physical_base;
uint64_t virtual_base;
};
struct limine_executable_address_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_executable_address_response*) response;
};
/* Device Tree Blob */
#define LIMINE_DTB_REQUEST_ID {LIMINE_COMMON_MAGIC, 0xb40ddb48fb54bac7, 0x545081493f81ffb7}
struct limine_dtb_response {
uint64_t revision;
LIMINE_PTR(void*) dtb_ptr;
};
struct limine_dtb_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_dtb_response*) response;
};
/* RISC-V Boot Hart ID */
#define LIMINE_RISCV_BSP_HARTID_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x1369359f025525f9, 0x2ff2a56178391bb6}
struct limine_riscv_bsp_hartid_response {
uint64_t revision;
uint64_t bsp_hartid;
};
struct limine_riscv_bsp_hartid_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_riscv_bsp_hartid_response*) response;
};
/* Bootloader Performance */
#define LIMINE_BOOTLOADER_PERFORMANCE_REQUEST_ID {LIMINE_COMMON_MAGIC, 0x6b50ad9bf36d13ad, 0xdc4c7e88fc759e17}
struct limine_bootloader_performance_response {
uint64_t revision;
uint64_t reset_usec;
uint64_t init_usec;
uint64_t exec_usec;
};
struct limine_bootloader_performance_request {
uint64_t id[4];
uint64_t revision;
LIMINE_PTR(struct limine_bootloader_performance_response*) response;
};
#ifdef __cplusplus
}
#endif
#endif
+1
View File
@@ -0,0 +1 @@
kernel_sources += files('entry.c')
+151
View File
@@ -0,0 +1,151 @@
#include <zinnia/ipc/init.h>
#include <common/compiler.h>
#include <kernel/alloc.h>
#include <kernel/assert.h>
#include <kernel/channel.h>
#include <kernel/clock.h>
#include <kernel/cmdline.h>
#include <kernel/console.h>
#include <kernel/elf.h>
#include <kernel/futex.h>
#include <kernel/init.h>
#include <kernel/irq.h>
#include <kernel/namespace.h>
#include <kernel/percpu.h>
#include <kernel/print.h>
#include <kernel/sched.h>
#include <kernel/vas.h>
#include <kernel/vmo.h>
#include <config.h>
#include <stdint.h>
static zn_status_t load_server(
struct boot_file* file,
const char** argv,
const char** envp,
struct namespace_desc* init_handle,
struct task** out
) {
struct vas* space;
zn_status_t status = vma_new(&space);
if (status != ZN_OK)
return status;
struct paged_vmo* file_vmo;
status = vmo_new_phys(&file_vmo);
if (status != ZN_OK)
return status;
vmo_write(&file_vmo->object, 0, HHDM_PTR(file->data), file->length, nullptr);
struct namespace* ns;
status = namespace_new(&ns);
if (status != ZN_OK)
return status;
struct exec_info info = {
.file_obj = &file_vmo->object,
.ns = ns,
.space = space,
.argv = argv,
.envp = envp,
.init_handle = init_handle,
};
kprintf("Loading executable \"%s\"\n", file->path);
return elf_load(&info, out);
}
static const char zinnia_banner[] =
"Zinnia " ZINNIA_VERSION " (" ZINNIA_ARCH ", " ZINNIA_COMPILER_ID ", " ZINNIA_LINKER_ID ")";
static void kernel_main_task(uintptr_t arg0) {
struct boot_info* info = (struct boot_info*)arg0;
kprintf("%s\n", zinnia_banner); // Say hello!
ASSERT(info->num_files >= 1, "No init executable provided\n");
struct channel* init_ep;
struct channel* other_ep;
ASSERT(channel_create(&init_ep, &other_ep) == ZN_OK, "Failed to create init channel\n");
struct vas* init_space;
ASSERT(vma_new(&init_space) == ZN_OK, "");
struct boot_file* file_data = &info->files[0];
struct paged_vmo* init_file;
ASSERT(vmo_new_phys(&init_file) == ZN_OK, "");
vmo_write(&init_file->object, 0, HHDM_PTR(file_data->data), file_data->length, nullptr);
struct namespace* ns;
ASSERT(namespace_new(&ns) == ZN_OK, "");
struct namespace_desc init_ch_desc = {
.type = NAMESPACE_DESC_CHANNEL,
.rights = ZN_RIGHTS_ALL,
.value.channel = init_ep,
};
// Load init.
const char* init_argv[] = {"init", nullptr};
const char* init_envp[] = {nullptr};
struct task* init_task;
zn_status_t status = load_server(&info->files[0], init_argv, init_envp, &init_ch_desc, &init_task);
ASSERT(status == ZN_OK, "Failed to load init process (%i)\n", status);
sched_add_task(&percpu_get()->sched, init_task);
// Load other daemons.
for (size_t i = 1; i < info->num_files; i++) {
struct namespace_desc ch_desc = {
.type = NAMESPACE_DESC_CHANNEL,
.rights = ZN_RIGHTS_ALL,
.value.channel = other_ep,
};
const char* argv[] = {info->files[i].path, nullptr};
const char* envp[] = {nullptr};
struct task* task;
status = load_server(&info->files[i], argv, envp, &ch_desc, &task);
ASSERT(status == ZN_OK, "Failed to load server \"%s\": (%i)\n", info->files[i].path, status);
sched_add_task(&percpu_get()->sched, task);
}
struct ipc_kernel_msg* msg = mem_alloc(0x40, 0);
msg->id = IPC_MSG_BAR;
memcpy(msg->data, "This message is from the kernel!", 33);
channel_write(other_ep, msg, 0x40, nullptr, 0);
while (true) {
arch_irq_wait();
}
}
[[noreturn]]
void kernel_main(struct boot_info* info) {
cmdline_parse(info->cmdline);
console_init();
mem_init(info->mem_map, info->num_mem_maps, info->virt_base, info->phys_base, info->hhdm_base);
rsdp_addr = info->rsdp; // TODO
percpu_init();
sched_init(&percpu_get()->sched);
futex_init();
struct task* main_task;
task_create("main", &kernel_vas, nullptr, kernel_main_task, (uintptr_t)info, &main_task);
sched_add_task(&percpu_get()->sched, main_task);
irq_unlock();
sched_yield(&percpu_get()->sched);
__unreachable();
}
[[__init]]
void kernel_early_init() {
percpu_bsp_init();
percpu_get()->online = true;
irq_lock();
}
+6
View File
@@ -0,0 +1,6 @@
kernel_sources += files(
'cmdline.c',
'main.c',
)
subdir('limine')
+112
View File
@@ -0,0 +1,112 @@
#include <zinnia/status.h>
#include <kernel/alloc.h>
#include <kernel/channel.h>
#include <kernel/namespace.h>
#include <kernel/percpu.h>
#include <kernel/sched.h>
#include <kernel/tailq.h>
#include <kernel/wait.h>
#include <stdatomic.h>
zn_status_t channel_create(struct channel** ep0, struct channel** ep1) {
struct channel* a = mem_alloc(sizeof(struct channel), 0);
if (!a)
return ZN_ERR_NO_MEMORY;
struct channel* b = mem_alloc(sizeof(struct channel), 0);
if (!b) {
mem_free(a);
return ZN_ERR_NO_MEMORY;
}
a->peer = b;
a->lock = (struct spinlock){0};
TAILQ_INIT(&a->messages);
wait_queue_init(&a->waiters);
b->peer = a;
b->lock = (struct spinlock){0};
TAILQ_INIT(&b->messages);
wait_queue_init(&b->waiters);
*ep0 = a;
*ep1 = b;
return ZN_OK;
}
void channel_msg_free(struct channel_msg* msg) {
mem_free(msg->data);
mem_free(msg->descs);
mem_free(msg);
}
zn_status_t channel_write(
struct channel* ch,
void* data,
size_t num_bytes,
struct namespace_desc* descs,
size_t num_handles
) {
// Check that the peer is still alive.
struct channel* peer = atomic_load_explicit(&ch->peer, memory_order_acquire);
if (!peer)
return ZN_ERR_BAD_HANDLE;
// Allocate the message.
struct channel_msg* msg = mem_alloc(sizeof(struct channel_msg), 0);
if (!msg)
return ZN_ERR_NO_MEMORY;
msg->data = data;
msg->num_bytes = num_bytes;
msg->descs = descs;
msg->num_handles = num_handles;
// Enqueue on the peer's message queue and wake a waiter.
spin_lock(&peer->lock);
TAILQ_INSERT_TAIL(&peer->messages, msg, next);
spin_unlock(&peer->lock);
struct sched_percpu* sched = &percpu_get()->sched;
sched_wake(sched, &peer->waiters);
return ZN_OK;
}
zn_status_t channel_read(struct channel* ch, struct channel_msg** msg_out) {
spin_lock(&ch->lock);
struct channel_msg* msg = TAILQ_FIRST(&ch->messages);
if (msg != nullptr) {
TAILQ_REMOVE(&ch->messages, msg, next);
spin_unlock(&ch->lock);
*msg_out = msg;
return ZN_OK;
}
// No message and peer is gone — nothing will ever arrive.
if (!ch->peer) {
spin_unlock(&ch->lock);
return ZN_ERR_BAD_HANDLE;
}
spin_unlock(&ch->lock);
return ZN_ERR_INVALID;
}
zn_status_t channel_poll(struct channel* ch) {
spin_lock(&ch->lock);
if (!TAILQ_EMPTY(&ch->messages)) {
spin_unlock(&ch->lock);
return ZN_OK;
}
if (!ch->peer) {
spin_unlock(&ch->lock);
return ZN_ERR_BAD_HANDLE;
}
spin_unlock(&ch->lock);
return ZN_ERR_INVALID;
}
+54
View File
@@ -0,0 +1,54 @@
#include <common/compiler.h>
#include <kernel/clock.h>
#include <kernel/print.h>
#include <kernel/spin.h>
#include <stdatomic.h>
#include <stdint.h>
static struct clock* active_clock = nullptr;
static uint64_t counter_base = 0;
static struct spinlock lock = {};
bool clock_available() {
return active_clock != nullptr;
}
uint64_t clock_get_elapsed_ns() {
struct clock* active = atomic_load(&active_clock);
if (__unlikely(!active))
return 0;
return atomic_load(&counter_base) + active->get_elapsed_ns(active);
}
bool clock_switch(struct clock* clock) {
spin_lock(&lock);
if (active_clock) {
if (clock->priority <= active_clock->priority) {
spin_unlock(&lock);
return false;
}
}
kprintf("Switching to clock source \"%s\"\n", clock->name);
uint64_t elapsed = clock_get_elapsed_ns();
counter_base += elapsed;
active_clock = clock;
spin_unlock(&lock);
return true;
}
void clock_spin_ns(uint64_t ns) {
if (active_clock == nullptr) {
kprintf("Unable to sleep for %lu ns, no clock available!\n", ns);
return;
}
uint64_t target = clock_get_elapsed_ns() + ns;
while (clock_get_elapsed_ns() < target) {
// TODO: Maybe pause?
}
}
+44
View File
@@ -0,0 +1,44 @@
#include <kernel/cmdline.h>
#include <kernel/console.h>
#include <kernel/print.h>
#include <kernel/syscalls.h>
#include <string.h>
extern struct console* const __ld_console_start[];
extern struct console* const __ld_console_end[];
struct console* active = nullptr;
void console_write(const char* buf, size_t len) {
if (__likely(active))
active->write(active, buf, len);
}
static void console_setup(const char* value) {
if (!value)
return;
// Find a suitable boot console.
struct console* const* cur = __ld_console_start;
while (cur < __ld_console_end) {
if (!strcmp(value, (*cur)->name)) {
active = *cur;
}
cur++;
}
}
CMDLINE_OPTION("console", console_setup);
void console_init() {
// If we have no console set and at least one set, use that as a default.
if (!active && &__ld_console_start[0] != &__ld_console_end[0])
active = __ld_console_start[0];
if (active) {
if (active->init(active) == false)
active = nullptr;
else
kprintf("Logging on console \"%s\"\n", active->name);
}
}
+201
View File
@@ -0,0 +1,201 @@
#include <zinnia/mem.h>
#include <zinnia/status.h>
#include <common/elf.h>
#include <common/utils.h>
#include <kernel/alloc.h>
#include <kernel/assert.h>
#include <kernel/elf.h>
#include <kernel/percpu.h>
#include <kernel/pmap.h>
#include <kernel/print.h>
#include <kernel/sched.h>
#include <kernel/vas.h>
#include <kernel/vdso.h>
#include <kernel/vmo.h>
#include <string.h>
zn_status_t elf_load(struct exec_info* info, struct task** out) {
zn_status_t status;
size_t read;
struct elf_ehdr ehdr = {};
vmo_read(info->file_obj, 0, &ehdr, sizeof(ehdr), &read);
// Check if the file is an ELF.
if (memcmp(ehdr.e_ident, ELF_MAG, sizeof(ELF_MAG)) != 0)
return ZN_ERR_INVALID;
if (ehdr.e_ident[EI_VERSION] != EV_CURRENT)
return ZN_ERR_INVALID;
if (ehdr.e_ident[EI_CLASS] != ELF_ARCH_CLASS)
return ZN_ERR_INVALID;
if (ehdr.e_ident[EI_DATA] != ELF_ARCH_DATA)
return ZN_ERR_INVALID;
if (ehdr.e_machine != ELF_ARCH_MACHINE)
return ZN_ERR_INVALID;
if (ehdr.e_type != ET_EXEC)
return ZN_ERR_INVALID;
const size_t page_size = arch_mem_page_size();
for (size_t i = 0; i < ehdr.e_phnum; i++) {
struct elf_phdr phdr = {};
vmo_read(info->file_obj, (ehdr.e_phoff + (i * ehdr.e_phentsize)), &phdr, sizeof(phdr), &read);
if (phdr.p_type == PT_LOAD) {
enum zn_vm_flags prot = 0;
if (phdr.p_flags & PF_R)
prot |= ZN_VM_MAP_READ;
if (phdr.p_flags & PF_W)
prot |= ZN_VM_MAP_WRITE;
if (phdr.p_flags & PF_X)
prot |= ZN_VM_MAP_EXEC;
ASSERT(phdr.p_offset % phdr.p_align == phdr.p_vaddr % phdr.p_align, "");
const uintptr_t misalign = phdr.p_vaddr & (page_size - 1);
const uintptr_t map_address = phdr.p_vaddr - misalign;
const size_t backed_map_size = (phdr.p_filesz + misalign + page_size - 1) & ~(page_size - 1);
const size_t total_map_size = (phdr.p_memsz + misalign + page_size - 1) & ~(page_size - 1);
// Copy the file data into its own mapping.
struct paged_vmo* phdr_obj;
status = vmo_new_phys(&phdr_obj);
if (status)
return status;
status = vmo_copy(&phdr_obj->object, phdr.p_offset, info->file_obj, phdr.p_offset, phdr.p_filesz, nullptr);
if (status)
return status;
// We map more than we copied so the rest is filled with zeroed pages.
status = vma_map_vmo(info->space, &phdr_obj->object, phdr.p_vaddr, phdr.p_memsz, prot, phdr.p_offset);
if (status)
return status;
}
}
const uintptr_t highest = ((uintptr_t)1 << (mem_high_shift() - 1)) - page_size;
const size_t stack_size = 2 * 1024 * 1024; // 2MiB
const uintptr_t stack_start = highest - stack_size;
// Allocate stack.
struct paged_vmo* stack;
status = vmo_new_phys(&stack);
if (status)
return status;
// Fill in stack info.
uintptr_t stack_off = stack_size;
// TODO: Instead of allocating, calculate it on the fly.
size_t num_envp = 0;
for (const char** ptr = info->envp; *ptr != nullptr; ptr++) {
num_envp++;
}
size_t num_argv = 0;
for (const char** ptr = info->argv; *ptr != nullptr; ptr++) {
num_argv++;
}
uintptr_t* envp_offsets = mem_alloc(num_envp * sizeof(uintptr_t), 0);
uintptr_t* argv_offsets = mem_alloc(num_argv * sizeof(uintptr_t), 0);
for (size_t env = 0; env < num_envp; env++) {
const char nul = 0;
stack_off -= 1;
vmo_write(&stack->object, stack_off, &nul, sizeof(nul), nullptr);
const size_t len = strlen(info->envp[env]);
stack_off -= len;
vmo_write(&stack->object, stack_off, info->envp[env], len, nullptr);
envp_offsets[env] = stack_start + stack_off;
}
for (size_t arg = 0; arg < num_argv; arg++) {
const char nul = 0;
stack_off -= 1;
vmo_write(&stack->object, stack_off, &nul, sizeof(nul), nullptr);
const size_t len = strlen(info->argv[arg]);
stack_off -= len;
vmo_write(&stack->object, stack_off, info->argv[arg], len, nullptr);
argv_offsets[arg] = stack_start + stack_off;
}
stack_off = ALIGN_DOWN(stack_off, 16);
// Align the stack if argc + argv + envp does not add up to 16 byte alignment.
if ((1 + num_argv + num_envp) & 1) {
stack_off -= sizeof(uintptr_t);
uintptr_t zero = 0;
vmo_write(&stack->object, stack_off, &zero, sizeof(zero), nullptr);
}
// Add the init handle to the process namespace.
zn_handle_t init_handle = ZN_HANDLE_INVALID;
if (info->init_handle) {
status = namespace_add_desc(info->ns, *info->init_handle, &init_handle);
if (status)
return status;
}
#define WRITE_AUXV(auxv, value) \
do { \
stack_off -= sizeof(uintptr_t); \
uintptr_t auxv_val = value; \
vmo_write(&stack->object, stack_off, &auxv_val, sizeof(uintptr_t), nullptr); \
stack_off -= sizeof(uintptr_t); \
auxv_val = auxv; \
vmo_write(&stack->object, stack_off, &auxv_val, sizeof(uintptr_t), nullptr); \
} while (0)
// Write auxiliary values.
WRITE_AUXV(AT_NULL, 0);
WRITE_AUXV(AT_SECURE, 0);
WRITE_AUXV(AT_PHNUM, ehdr.e_phnum);
WRITE_AUXV(AT_PHENT, ehdr.e_phentsize);
WRITE_AUXV(AT_ENTRY, ehdr.e_entry);
WRITE_AUXV(AT_INIT_HANDLE, init_handle);
// envp pointers.
stack_off -= sizeof(uintptr_t);
const uintptr_t zero = 0;
vmo_write(&stack->object, stack_off, &zero, sizeof(zero), nullptr);
for (size_t env = 0; env < num_envp; env++) {
stack_off -= sizeof(uintptr_t);
vmo_write(&stack->object, stack_off, &envp_offsets[env], sizeof(envp_offsets[env]), nullptr);
}
// argv pointers.
stack_off -= sizeof(uintptr_t);
vmo_write(&stack->object, stack_off, &zero, sizeof(zero), nullptr);
for (size_t arg = 0; arg < num_argv; arg++) {
stack_off -= sizeof(uintptr_t);
vmo_write(&stack->object, stack_off, &argv_offsets[arg], sizeof(argv_offsets[arg]), nullptr);
}
mem_free(envp_offsets);
mem_free(argv_offsets);
// argc
stack_off -= sizeof(uintptr_t);
uintptr_t argc = num_argv;
vmo_write(&stack->object, stack_off, &argc, sizeof(argc), nullptr);
status = vma_map_vmo(info->space, &stack->object, stack_start, stack_size, ZN_VM_MAP_READ | ZN_VM_MAP_WRITE, 0);
if (status)
return status;
struct task_start_info* start_info = mem_alloc(sizeof(struct task_start_info), 0);
start_info->ip = ehdr.e_entry;
start_info->sp = highest - stack_size + stack_off;
start_info->arg = 0;
struct task* result;
status = task_create(info->argv[0], info->space, info->ns, sched_to_user, (uintptr_t)start_info, &result);
if (status)
return status;
*out = result;
return ZN_OK;
}
+120
View File
@@ -0,0 +1,120 @@
#include <zinnia/status.h>
#include <kernel/futex.h>
#include <kernel/percpu.h>
#include <kernel/print.h>
#include <kernel/sched.h>
#include <kernel/spin.h>
#include <kernel/usercopy.h>
struct futex_bucket {
struct wait_queue wq;
uintptr_t key; // Virtual address used as the futex key.
bool in_use;
};
#define FUTEX_BUCKET_COUNT 64
static struct futex_bucket futex_table[FUTEX_BUCKET_COUNT];
static struct spinlock futex_table_lock;
void futex_init(void) {
futex_table_lock = (struct spinlock){0};
for (size_t i = 0; i < FUTEX_BUCKET_COUNT; i++) {
wait_queue_init(&futex_table[i].wq);
futex_table[i].key = 0;
futex_table[i].in_use = false;
}
}
static size_t futex_hash(uintptr_t addr) {
// Simple hash: mix the address bits and mod by bucket count.
addr = (addr >> 2) ^ (addr >> 12);
return addr % FUTEX_BUCKET_COUNT;
}
static struct futex_bucket* futex_bucket_get(uintptr_t addr) {
size_t idx = futex_hash(addr);
// Linear probing to find a matching or free bucket.
for (size_t i = 0; i < FUTEX_BUCKET_COUNT; i++) {
size_t slot = (idx + i) % FUTEX_BUCKET_COUNT;
struct futex_bucket* b = &futex_table[slot];
if (b->in_use && b->key == addr)
return b;
if (!b->in_use) {
b->key = addr;
b->in_use = true;
return b;
}
}
return nullptr;
}
zn_status_t futex_wait(__user int* addr, int expected) {
uintptr_t key = (uintptr_t)addr;
spin_lock(&futex_table_lock);
// Read the current value at the userspace address.
int current;
if (!usercopy_read(&current, addr, sizeof(*addr))) {
spin_unlock(&futex_table_lock);
return ZN_ERR_BAD_BUFFER;
}
// If the value has already changed, don't block.
if (current != expected) {
spin_unlock(&futex_table_lock);
return ZN_OK;
}
struct futex_bucket* bucket = futex_bucket_get(key);
if (!bucket) {
spin_unlock(&futex_table_lock);
return ZN_ERR_NO_MEMORY;
}
spin_unlock(&futex_table_lock);
// Block on the bucket's wait queue.
struct sched_percpu* sched = &percpu_get()->sched;
sched_block(sched, &bucket->wq);
return ZN_OK;
}
zn_status_t futex_wake(__user int* addr, int count) {
uintptr_t key = (uintptr_t)addr;
spin_lock(&futex_table_lock);
size_t idx = futex_hash(key);
struct futex_bucket* bucket = nullptr;
for (size_t i = 0; i < FUTEX_BUCKET_COUNT; i++) {
size_t slot = (idx + i) % FUTEX_BUCKET_COUNT;
struct futex_bucket* b = &futex_table[slot];
if (b->in_use && b->key == key) {
bucket = b;
break;
}
if (!b->in_use)
break;
}
if (!bucket) {
spin_unlock(&futex_table_lock);
return ZN_OK; // No waiters, nothing to do.
}
spin_unlock(&futex_table_lock);
struct sched_percpu* sched = &percpu_get()->sched;
if (count == INT32_MAX) {
sched_wake_all(sched, &bucket->wq);
} else {
for (int i = 0; i < count; i++) {
sched_wake(sched, &bucket->wq);
}
}
return ZN_OK;
}
+28
View File
@@ -0,0 +1,28 @@
#include <kernel/irq.h>
#include <kernel/percpu.h>
#include <stdatomic.h>
void irq_lock() {
struct irq_percpu* cpu = &percpu_get()->irq;
if (atomic_load_explicit(&cpu->in_interrupt, memory_order_acquire))
return;
arch_irq_set_state(false);
atomic_fetch_add_explicit(&percpu_get()->irq.level, 1, memory_order_acq_rel);
}
void irq_unlock() {
struct irq_percpu* cpu = &percpu_get()->irq;
if (atomic_load_explicit(&cpu->in_interrupt, memory_order_acquire))
return;
uint32_t old_level = atomic_fetch_sub_explicit(&cpu->level, 1, memory_order_acq_rel);
// If it was 1, the new IRQ level is now 0.
if (old_level == 1) {
arch_irq_set_state(true);
}
}
bool irq_set_interrupted(bool is_interrupted) {
return atomic_exchange_explicit(&percpu_get()->irq.in_interrupt, is_interrupted, memory_order_release);
}
+15
View File
@@ -0,0 +1,15 @@
kernel_sources += files(
'channel.c',
'clock.c',
'futex.c',
'console.c',
'elf.c',
'irq.c',
'namespace.c',
'panic.c',
'percpu.c',
'print.c',
'sched.c',
'spin.c',
'uacpi_glue.c',
)
+115
View File
@@ -0,0 +1,115 @@
#include <zinnia/handle.h>
#include <zinnia/rights.h>
#include <zinnia/status.h>
#include <kernel/alloc.h>
#include <kernel/namespace.h>
#include <stdatomic.h>
zn_status_t namespace_new(struct namespace** out) {
if (!out)
return ZN_ERR_INTERNAL;
struct namespace* namespace = mem_alloc(sizeof(struct namespace), 0);
if (!namespace)
return ZN_ERR_NO_MEMORY;
namespace->mutex = (struct mutex){0};
namespace->next_handle = 1;
HASHMAP_INIT(&namespace->descriptors);
*out = namespace;
return ZN_OK;
}
zn_status_t namespace_add_desc(struct namespace* namespace, struct namespace_desc desc, zn_handle_t* out) {
if (!namespace)
return ZN_ERR_INTERNAL;
// Skip over handles that are already in use (e.g. hard-coded handles).
zn_handle_t handle;
do {
handle = atomic_fetch_add_explicit(&namespace->next_handle, 1, memory_order_acq_rel);
if (handle <= ZN_HANDLE_INVALID)
continue;
} while (HASHMAP_GET(&namespace->descriptors, handle, hashmap_hash_default, hashmap_eq_default) != nullptr);
zn_status_t status =
HASHMAP_INSERT(&namespace->descriptors, handle, desc, hashmap_hash_default, hashmap_eq_default);
if (status != ZN_OK)
return status;
*out = handle;
return ZN_OK;
}
zn_status_t namespace_add_desc_at(struct namespace* namespace, struct namespace_desc desc, zn_handle_t handle) {
if (!namespace)
return ZN_ERR_INTERNAL;
if (handle <= ZN_HANDLE_INVALID)
return ZN_ERR_BAD_HANDLE;
// Fail if a descriptor already exists at this handle.
if (HASHMAP_GET(&namespace->descriptors, handle, hashmap_hash_default, hashmap_eq_default) != nullptr)
return ZN_ERR_ALREADY_EXISTS;
return HASHMAP_INSERT(&namespace->descriptors, handle, desc, hashmap_hash_default, hashmap_eq_default);
}
zn_status_t namespace_get(struct namespace* namespace, zn_handle_t handle, struct namespace_desc* out) {
if (!namespace)
return ZN_ERR_INTERNAL;
if (!out)
return ZN_ERR_INTERNAL;
if (handle <= ZN_HANDLE_INVALID)
return ZN_ERR_BAD_HANDLE;
struct namespace_desc* desc =
HASHMAP_GET(&namespace->descriptors, handle, hashmap_hash_default, hashmap_eq_default);
if (!desc)
return ZN_ERR_BAD_HANDLE;
*out = *desc;
return ZN_OK;
}
zn_status_t namespace_del_desc(struct namespace* namespace, zn_handle_t handle) {
if (!namespace)
return ZN_ERR_INTERNAL;
if (handle <= ZN_HANDLE_INVALID)
return ZN_ERR_BAD_HANDLE;
if (!HASHMAP_REMOVE(&namespace->descriptors, handle, hashmap_hash_default, hashmap_eq_default))
return ZN_ERR_BAD_HANDLE;
return ZN_OK;
}
zn_status_t namespace_clone_desc(
struct namespace* ns,
struct namespace_desc* desc,
zn_rights_t cloned_rights,
zn_handle_t* out
) {
// The source handle must have the clone right.
if (!(desc->rights & ZN_RIGHT_CLONE))
return ZN_ERR_BAD_PERMS;
// Determine the rights for the cloned handle.
zn_rights_t new_rights;
if (cloned_rights & ZN_RIGHTS_IDENTICAL) {
new_rights = desc->rights;
} else {
// The requested rights must be a subset of the original rights.
if (cloned_rights & ~desc->rights)
return ZN_ERR_BAD_PERMS;
new_rights = cloned_rights;
}
// Create the cloned descriptor.
struct namespace_desc new_desc = *desc;
new_desc.rights = new_rights;
return namespace_add_desc(ns, new_desc, out);
}
+32
View File
@@ -0,0 +1,32 @@
#include <kernel/irq.h>
#include <kernel/panic.h>
#include <kernel/percpu.h>
#include <kernel/print.h>
#include <stdarg.h>
[[noreturn]]
void panic(const char* msg, ...) {
// TODO: Stop all other CPUs with IPI.
arch_irq_set_state(false);
struct percpu* cpu = percpu_get();
struct task* current = cpu->sched.current;
kprintf(
"----[ Kernel panic ]----\n"
"In task \"%s\" (TID %zu) on CPU %zu!\n",
current ? current->name : nullptr,
current ? current->id : 0,
cpu->id
);
va_list args;
va_start(args, msg);
kvprintf(msg, args);
va_end(args);
kprintf("----[ End of panic ]----\n");
while (1) {
arch_irq_wait();
}
}
+14
View File
@@ -0,0 +1,14 @@
#include <common/compiler.h>
#include <kernel/percpu.h>
[[__used, __section(".percpu")]]
struct percpu percpu_bsp = {
.self = &percpu_bsp,
.id = 0,
.online = false,
};
struct percpu* percpu_new() {
// TODO
return nullptr;
}
+399
View File
@@ -0,0 +1,399 @@
#include <kernel/clock.h>
#include <kernel/console.h>
#include <kernel/panic.h>
#include <kernel/print.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
static void reverse(char* s) {
int i, j;
char c;
for (i = 0, j = strlen(s) - 1; i < j; i++, j--) {
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
static int char_to_digit(char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'z')
return c - 'a' + 10;
if (c >= 'A' && c <= 'Z')
return c - 'A' + 10;
return -1;
}
unsigned long atolu(const char* str, int base) {
unsigned long result = 0;
if (base == 16 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
str += 2;
}
while (*str) {
int digit = char_to_digit(*str++);
if (digit < 0 || digit >= base)
break;
result = result * base + digit;
}
return result;
}
long atol(const char* str, int base) {
int neg = 0;
if (*str == '-') {
neg = 1;
str++;
}
unsigned long val = atolu(str, base);
return neg ? -(long)val : (long)val;
}
char* lutoa(uint64_t value, char* str, int base) {
if (base < 2 || base > 36)
return nullptr;
size_t i = 0;
do {
uint64_t digit = value % base;
str[i++] = (digit < 10) ? '0' + digit : 'a' + (digit - 10);
value /= base;
} while (value);
str[i] = '\0';
reverse(str);
return str;
}
char* ltoa(int64_t value, char* str, int base) {
if (base < 2 || base > 36)
return nullptr;
uint64_t magnitude;
int negative = 0;
if (value < 0) {
negative = 1;
magnitude = (uint64_t)(-(value + 1)) + 1;
// avoids INT64_MIN overflow
} else {
magnitude = (uint64_t)value;
}
size_t i = 0;
do {
uint64_t digit = magnitude % base;
str[i++] = (digit < 10) ? '0' + digit : 'a' + (digit - 10);
magnitude /= base;
} while (magnitude);
if (negative)
str[i++] = '-';
str[i] = '\0';
reverse(str);
return str;
}
typedef void (*log_fn_t)(void* ctx, const char* buf, size_t len);
static void do_print(log_fn_t callback, void* ctx, const char* restrict fmt, va_list args) {
// Amount of bytes written.
int32_t written = 0;
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_precision)
len = strnlen(str, precision);
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);
do_print(printf_console, nullptr, message, args);
va_end(args);
}
void kvprintf(const char* message, va_list args) {
do_print(printf_console, nullptr, message, args);
}
+185
View File
@@ -0,0 +1,185 @@
#include <zinnia/status.h>
#include <kernel/alloc.h>
#include <kernel/assert.h>
#include <kernel/irq.h>
#include <kernel/list.h>
#include <kernel/percpu.h>
#include <kernel/print.h>
#include <kernel/sched.h>
#include <kernel/spin.h>
#include <kernel/tailq.h>
#include <bits/irq.h>
#include <stdatomic.h>
#include <stdint.h>
#include <string.h>
static void idle_func(uintptr_t) {
while (1) {
arch_irq_set_state(true);
arch_irq_wait();
}
}
void sched_to_user(uintptr_t info_ptr) {
struct task_start_info* info = (struct task_start_info*)info_ptr;
uintptr_t ip = info->ip;
uintptr_t sp = info->sp;
uintptr_t arg = info->arg;
mem_free(info);
arch_sched_jump_to_user(ip, sp, arg);
}
void sched_to_user_context(uintptr_t ctx) {
arch_sched_jump_to_context((struct arch_context*)ctx);
}
void sched_init(struct sched_percpu* sched) {
struct task* idle_task;
ASSERT(
task_create("idle", &kernel_vas, nullptr, idle_func, 0, &idle_task) == ZN_OK,
"Unable to create idle task!\n"
);
sched->idle_task = idle_task;
sched->current = idle_task;
TAILQ_INIT(&sched->run_queue);
kprintf("Scheduler initialized\n");
}
void sched_add_task(struct sched_percpu* sched, struct task* task) {
ASSERT(task != nullptr, "Tried to add NULL to the run queue\n");
TAILQ_INSERT_TAIL(&sched->run_queue, task, next);
}
static struct task* next_task(struct sched_percpu* sched) {
struct task* t = TAILQ_FIRST(&sched->run_queue);
if (t == nullptr)
return sched->idle_task;
TAILQ_REMOVE(&sched->run_queue, t, next);
return t;
}
static void do_reschedule(struct sched_percpu* sched) {
struct task* from = atomic_load_explicit(&sched->current, memory_order_acquire);
struct task* to = next_task(sched);
if (from == to) {
irq_unlock();
return;
}
atomic_store_explicit(&sched->current, to, memory_order_relaxed);
pmap_set(&to->space->pmap);
struct percpu* cpu = percpu_get();
from->kernel_stack = cpu->kernel_stack;
from->user_stack = cpu->user_stack;
cpu->kernel_stack = to->kernel_stack;
cpu->user_stack = to->user_stack;
arch_sched_switch(from, to);
}
void sched_reschedule(struct sched_percpu* sched) {
irq_lock();
if (sched->current->state != TASK_STATE_DEAD)
sched_add_task(sched, sched->current);
do_reschedule(sched);
}
void sched_yield(struct sched_percpu* sched) {
irq_lock();
do_reschedule(sched);
}
zn_status_t task_create(
const char* name,
struct vas* space,
struct namespace* ns,
task_fn_t entry,
uintptr_t arg0,
struct task** out
) {
if (!out)
return ZN_ERR_INVALID;
struct task* new_task = mem_alloc(sizeof(struct task), 0);
if (new_task == nullptr)
return ZN_ERR_NO_MEMORY;
static size_t last_tid = 0;
new_task->id = atomic_fetch_add(&last_tid, 1);
new_task->space = space;
new_task->namespace = ns;
strncpy(new_task->name, name, sizeof(new_task->name));
// Allocate a kernel stack.
new_task->kernel_stack = (uintptr_t)mem_alloc(KERNEL_STACK_SIZE, 0);
if (!new_task->kernel_stack)
return ZN_ERR_NO_MEMORY;
new_task->user_stack = 0;
// Initialize the arch-dependent parts.
zn_status_t s = arch_task_init(&new_task->context, entry, arg0, new_task->kernel_stack + KERNEL_STACK_SIZE, true);
if (s != ZN_OK)
return s;
*out = new_task;
return ZN_OK;
}
void task_entry(task_fn_t entry, uintptr_t arg0) {
entry(arg0);
struct sched_percpu* sched = &percpu_get()->sched;
sched->current->state = TASK_STATE_DEAD;
sched_yield(sched);
panic("unreachable");
}
void wait_queue_init(struct wait_queue* wq) {
TAILQ_INIT(&wq->waiters);
wq->lock = (struct spinlock){0};
}
void sched_block(struct sched_percpu* sched, struct wait_queue* wq) {
irq_lock();
spin_lock(&wq->lock);
sched->current->state = TASK_STATE_BLOCKED;
TAILQ_INSERT_TAIL(&wq->waiters, sched->current, next);
spin_unlock(&wq->lock);
do_reschedule(sched);
}
void sched_wake(struct sched_percpu* sched, struct wait_queue* wq) {
spin_lock(&wq->lock);
struct task* t = TAILQ_FIRST(&wq->waiters);
if (t != nullptr) {
TAILQ_REMOVE(&wq->waiters, t, next);
t->state = TASK_STATE_READY;
sched_add_task(sched, t);
}
spin_unlock(&wq->lock);
}
void sched_wake_all(struct sched_percpu* sched, struct wait_queue* wq) {
spin_lock(&wq->lock);
struct task* t;
while ((t = TAILQ_FIRST(&wq->waiters)) != nullptr) {
TAILQ_REMOVE(&wq->waiters, t, next);
t->state = TASK_STATE_READY;
sched_add_task(sched, t);
}
spin_unlock(&wq->lock);
}
+16
View File
@@ -0,0 +1,16 @@
#include <kernel/spin.h>
#include <stdatomic.h>
// TODO: Ticketing system
void spin_lock(struct spinlock* spin) {
while (atomic_exchange_explicit(&spin->locked, true, memory_order_acquire)) {
asm volatile("pause");
}
atomic_store_explicit(&spin->locked, true, memory_order_release);
}
void spin_unlock(struct spinlock* spin) {
atomic_store_explicit(&spin->locked, false, memory_order_release);
}
+34
View File
@@ -0,0 +1,34 @@
#include <common/utils.h>
#include <kernel/init.h>
#include <kernel/mmio.h>
#include <kernel/print.h>
#include <stdarg.h>
#include <uacpi/kernel_api.h>
#include <uacpi/status.h>
#include <uacpi/types.h>
uacpi_status uacpi_kernel_get_rsdp(uacpi_phys_addr* out_rsdp_address) {
*out_rsdp_address = (uacpi_phys_addr)rsdp_addr;
return UACPI_STATUS_OK;
}
void* uacpi_kernel_map(uacpi_phys_addr addr, uacpi_size len) {
return mmio_new(addr, len);
}
void uacpi_kernel_unmap(void* addr, uacpi_size len) {
mmio_free(addr, len);
}
void uacpi_kernel_log(uacpi_log_level lvl, const uacpi_char* msg, ...) {
va_list args;
va_start(args, msg);
kvprintf(msg, args);
va_end(args);
}
void uacpi_kernel_vlog(uacpi_log_level lvl, const uacpi_char* msg, uacpi_va_list args) {
kvprintf(msg, args);
}
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include <zinnia/status.h>
#include <bits/mem.h>
#include <stddef.h>
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_STATIC,
PHYS_RECLAIMABLE,
};
struct phys_mem {
phys_t address;
size_t length;
enum phys_mem_usage usage;
};
// Base address of the HHDM.
extern uintptr_t mem_hhdm_base;
#define HHDM_PTR(paddr) (void*)(mem_hhdm_base + (paddr))
// 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.
void* mem_alloc(size_t length, enum alloc_flags flags);
// Frees an allocation created by `mem_alloc`.
// Passing `nullptr` is a no-op.
void mem_free(void* mem);
zn_status_t mem_phys_alloc(size_t num_pages, enum alloc_flags flags, phys_t* out);
zn_status_t mem_phys_free(phys_t start, size_t num_pages);
void mem_init(struct phys_mem* map, size_t length, uintptr_t kernel_virt, phys_t kernel_phys, uintptr_t tmp_hhdm);
void slab_init();
void mem_phys_bootstrap(struct phys_mem* mem);
void mem_phys_init(struct phys_mem* map, size_t length);
uintptr_t arch_mem_hhdm_addr();
uintptr_t arch_vm_pfndb_addr();
uintptr_t arch_mem_mapping_addr();
size_t arch_mem_page_bits();
size_t arch_mem_page_size();
size_t arch_mem_level_bits();
size_t arch_mem_num_levels();
static inline size_t mem_high_shift() {
return arch_mem_level_bits() * arch_mem_num_levels() + arch_mem_page_bits();
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <common/compiler.h>
#include <kernel/panic.h>
#define ASSERT(expr, msg, ...) \
({ \
if (__unlikely(!(expr))) { \
panic( \
"In function \"%s\" (%s:%u):\n" \
"Assertion \"%s\" failed!\n" msg, \
__FUNCTION__, \
__FILE__, \
__LINE__, \
#expr, \
##__VA_ARGS__ \
); \
} \
})
#ifndef NDEBUG
#define DEBUG_ASSERT(...) ASSERT(__VA_ARGS__)
#else
#define DEBUG_ASSERT(...) ((void)0)
#endif
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include <zinnia/status.h>
#include <kernel/spin.h>
#include <kernel/tailq.h>
#include <kernel/wait.h>
#include <stddef.h>
struct namespace_desc;
struct channel_msg {
void* data;
size_t num_bytes;
struct namespace_desc* descs;
size_t num_handles;
TAILQ_LINK(struct channel_msg) next;
};
struct channel {
struct channel* peer;
struct spinlock lock;
TAILQ_HEAD(struct channel_msg) messages;
struct wait_queue waiters;
};
// Creates a linked pair of channel endpoints.
zn_status_t channel_create(struct channel** ep0, struct channel** ep1);
// Writes a message to the peer channel.
// On success, ownership of `data` and `descs` transfers to the message.
// On failure, the caller retains ownership and must free them.
zn_status_t channel_write(
struct channel* ch,
void* data,
size_t num_bytes,
struct namespace_desc* descs,
size_t num_handles
);
// Non-blocking dequeue of the next message.
zn_status_t channel_read(struct channel* ch, struct channel_msg** msg);
// Non-blocking check for message availability.
zn_status_t channel_poll(struct channel* ch);
// Frees a channel message and its associated data and descriptors.
void channel_msg_free(struct channel_msg* msg);
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
struct clock {
const char* name;
uint8_t priority;
void (*reset)(struct clock* c);
uint64_t (*get_elapsed_ns)(struct clock* c);
};
// Attempts to set a new clock. Returns true if the clock was changed.
bool clock_switch(struct clock* clock);
// Returns true, if a clock is available.
bool clock_available();
// Gets the current timestamp in nanoseconds since the last reset.
uint64_t clock_get_elapsed_ns();
// Spin for `ns` nanoseconds.
void clock_spin_ns(uint64_t ns);
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <common/compiler.h>
#include <common/utils.h>
#include <stdint.h>
#define _CMDLINE_OPTION(x, opt_name, opt_func) \
static const struct cmdline_option x = { \
.name = opt_name, \
.option_type = _Generic( \
(opt_func), \
void (*)(const char*): CMDLINE_STRING, \
void (*)(bool): CMDLINE_BOOL, \
void (*)(intptr_t): CMDLINE_INTPTR, \
void (*)(uintptr_t): CMDLINE_UINTPTR \
), \
.func = (void*)(opt_func), \
}; \
[[__used, __section(".cmdline")]] \
static const struct cmdline_option* CONCAT(x, _ptr) = &x;
#define CMDLINE_OPTION(opt_name, opt_func) _CMDLINE_OPTION(UNIQUE_IDENT(cmdline_option), opt_name, opt_func)
enum cmdline_type {
CMDLINE_STRING,
CMDLINE_BOOL,
CMDLINE_INTPTR,
CMDLINE_UINTPTR,
};
struct cmdline_option {
// The name of this option.
const char* name;
// Gets called if this option is present on the command line.
union {
void (*func_str)(const char* value);
void (*func_bool)(bool value);
void (*func_intptr)(intptr_t value);
void (*func_uintptr)(uintptr_t value);
void* func;
};
enum cmdline_type option_type;
};
// Parses the command line and invokes all options.
void cmdline_parse(const char* cmdline);
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <common/compiler.h>
#include <common/utils.h>
#include <stddef.h>
struct console {
const char* name;
void* private;
// Initializes the console.
bool (*init)(struct console* con);
// Writes to the console.
void (*write)(struct console* con, const char* buf, size_t count);
};
// Initializes a console for logging.
void console_init();
// Writes a message to a console.
void console_write(const char* buf, size_t len);
// Defines a new early console which can be used to log early boot messages.
// An early console must not make any heap allocations.
#define DEFINE_CONSOLE(con) \
[[__used, __section(".console")]] \
static struct console* UNIQUE_IDENT(console) = (con)
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <zinnia/status.h>
#include <kernel/namespace.h>
#include <kernel/sched.h>
#include <kernel/vas.h>
#include <kernel/vmo.h>
struct exec_info {
struct vas* space;
struct vmo* file_obj;
struct namespace* ns;
const char** argv;
const char** envp;
struct namespace_desc* init_handle;
};
zn_status_t elf_load(struct exec_info* info, struct task** out);
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include <zinnia/status.h>
#include <common/compiler.h>
#include <kernel/wait.h>
// Initializes the global futex table.
void futex_init(void);
// Blocks the calling task if *addr == expected.
zn_status_t futex_wait(__user int* addr, int expected);
// Wakes up to `count` tasks waiting on the given address.
// Returns the number of tasks woken.
zn_status_t futex_wake(__user int* addr, int count);
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include <zinnia/handle.h>
+223
View File
@@ -0,0 +1,223 @@
#pragma once
#include <zinnia/status.h>
#include <kernel/alloc.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
// FNV-1a hash over raw bytes.
static inline uint64_t hashmap_hash_default(const void* key, size_t len) {
const uint8_t* data = (const uint8_t*)key;
uint64_t hash = 0xcbf29ce484222325;
for (size_t i = 0; i < len; i++) {
hash ^= data[i];
hash *= 0x100000001b3;
}
return hash;
}
// Byte-wise equality.
static inline bool hashmap_eq_default(const void* a, const void* b, size_t len) {
return memcmp(a, b, len) == 0;
}
// Ensure a hash is never zero (zero is the empty sentinel).
#define _HASHMAP_FIXHASH(h) ((h) == 0 ? (uint64_t)1 : (h))
#define HASHMAP(K, V) \
struct { \
K* keys; \
V* values; \
uint64_t* hashes; \
size_t capacity; \
size_t count; \
}
#define HASHMAP_INIT(map) \
do { \
(map)->keys = nullptr; \
(map)->values = nullptr; \
(map)->hashes = nullptr; \
(map)->capacity = 0; \
(map)->count = 0; \
} while (0)
#define HASHMAP_DESTROY(map) \
do { \
mem_free((map)->keys); \
mem_free((map)->values); \
mem_free((map)->hashes); \
(map)->keys = nullptr; \
(map)->values = nullptr; \
(map)->hashes = nullptr; \
(map)->capacity = 0; \
(map)->count = 0; \
} while (0)
#define HASHMAP_COUNT(map) ((map)->count)
// Internal: probe distance from ideal position.
#define _HASHMAP_PROBE_DIST(hash, pos, cap) (((pos) - ((hash) & ((cap) - 1))) & ((cap) - 1))
// Internal: grow and rehash.
#define _HASHMAP_GROW(map, hash_fn, eq_fn) \
do { \
size_t _old_cap = (map)->capacity; \
size_t _new_cap = _old_cap == 0 ? 16 : _old_cap * 2; \
typeof((map)->keys) _old_keys = (map)->keys; \
typeof((map)->values) _old_values = (map)->values; \
uint64_t* _old_hashes = (map)->hashes; \
(map)->keys = mem_alloc(_new_cap * sizeof(*(map)->keys), ALLOC_NOZERO); \
(map)->values = mem_alloc(_new_cap * sizeof(*(map)->values), ALLOC_NOZERO); \
(map)->hashes = mem_alloc(_new_cap * sizeof(uint64_t), 0); \
(map)->capacity = _new_cap; \
(map)->count = 0; \
for (size_t _i = 0; _i < _old_cap; _i++) { \
if (_old_hashes[_i] != 0) { \
_HASHMAP_INSERT_INNER(map, _old_keys[_i], _old_values[_i], _old_hashes[_i]); \
} \
} \
mem_free(_old_keys); \
mem_free(_old_values); \
mem_free(_old_hashes); \
} while (0)
// Internal: insert with a precomputed hash. Used by grow and the public INSERT macro.
#define _HASHMAP_INSERT_INNER(map, k, v, h) \
do { \
size_t _cap = (map)->capacity; \
size_t _pos = (h) & (_cap - 1); \
typeof(*(map)->keys) _ik = (k); \
typeof(*(map)->values) _iv = (v); \
uint64_t _ih = (h); \
for (;;) { \
if ((map)->hashes[_pos] == 0) { \
(map)->keys[_pos] = _ik; \
(map)->values[_pos] = _iv; \
(map)->hashes[_pos] = _ih; \
(map)->count++; \
break; \
} \
size_t _existing_dist = _HASHMAP_PROBE_DIST((map)->hashes[_pos], _pos, _cap); \
size_t _insert_dist = _HASHMAP_PROBE_DIST(_ih, _pos, _cap); \
if (_insert_dist > _existing_dist) { \
typeof(*(map)->keys) _tk = (map)->keys[_pos]; \
typeof(*(map)->values) _tv = (map)->values[_pos]; \
uint64_t _th = (map)->hashes[_pos]; \
(map)->keys[_pos] = _ik; \
(map)->values[_pos] = _iv; \
(map)->hashes[_pos] = _ih; \
_ik = _tk; \
_iv = _tv; \
_ih = _th; \
} \
_pos = (_pos + 1) & (_cap - 1); \
} \
} while (0)
// Insert or update a key-value pair. Returns ZN_OK or ZN_ERR_NO_MEMORY.
#define HASHMAP_INSERT(map, key, val, hash_fn, eq_fn) \
({ \
zn_status_t _status = ZN_OK; \
typeof(*(map)->keys) _k = (key); \
uint64_t _h = _HASHMAP_FIXHASH((hash_fn)(&_k, sizeof(_k))); \
/* Check for existing key first. */ \
bool _found = false; \
if ((map)->capacity > 0) { \
size_t _cap = (map)->capacity; \
size_t _pos = _h & (_cap - 1); \
for (size_t _d = 0; _d < _cap; _d++) { \
if ((map)->hashes[_pos] == 0) \
break; \
if (_HASHMAP_PROBE_DIST((map)->hashes[_pos], _pos, _cap) < _d) \
break; \
if ((map)->hashes[_pos] == _h && (eq_fn)(&(map)->keys[_pos], &_k, sizeof(_k))) { \
(map)->values[_pos] = (val); \
_found = true; \
break; \
} \
_pos = (_pos + 1) & (_cap - 1); \
} \
} \
if (!_found) { \
if ((map)->count * 4 >= (map)->capacity * 3 || (map)->capacity == 0) { \
_HASHMAP_GROW(map, hash_fn, eq_fn); \
if ((map)->keys == nullptr || (map)->values == nullptr || (map)->hashes == nullptr) { \
_status = ZN_ERR_NO_MEMORY; \
} \
} \
if (_status == ZN_OK) { \
_HASHMAP_INSERT_INNER(map, _k, (val), _h); \
} \
} \
_status; \
})
// Look up a key. Returns a pointer to the value, or nullptr if not found.
#define HASHMAP_GET(map, key, hash_fn, eq_fn) \
({ \
typeof(*(map)->values)* _result = nullptr; \
if ((map)->capacity > 0) { \
typeof(*(map)->keys) _k = (key); \
uint64_t _h = _HASHMAP_FIXHASH((hash_fn)(&_k, sizeof(_k))); \
size_t _cap = (map)->capacity; \
size_t _pos = _h & (_cap - 1); \
for (size_t _d = 0; _d < _cap; _d++) { \
if ((map)->hashes[_pos] == 0) \
break; \
if (_HASHMAP_PROBE_DIST((map)->hashes[_pos], _pos, _cap) < _d) \
break; \
if ((map)->hashes[_pos] == _h && (eq_fn)(&(map)->keys[_pos], &_k, sizeof(_k))) { \
_result = &(map)->values[_pos]; \
break; \
} \
_pos = (_pos + 1) & (_cap - 1); \
} \
} \
_result; \
})
// Remove a key. Returns true if the key was found and removed.
// Uses backward-shift deletion to avoid tombstones.
#define HASHMAP_REMOVE(map, key, hash_fn, eq_fn) \
({ \
bool _removed = false; \
if ((map)->capacity > 0) { \
typeof(*(map)->keys) _k = (key); \
uint64_t _h = _HASHMAP_FIXHASH((hash_fn)(&_k, sizeof(_k))); \
size_t _cap = (map)->capacity; \
size_t _pos = _h & (_cap - 1); \
for (size_t _d = 0; _d < _cap; _d++) { \
if ((map)->hashes[_pos] == 0) \
break; \
if (_HASHMAP_PROBE_DIST((map)->hashes[_pos], _pos, _cap) < _d) \
break; \
if ((map)->hashes[_pos] == _h && (eq_fn)(&(map)->keys[_pos], &_k, sizeof(_k))) { \
(map)->hashes[_pos] = 0; \
(map)->count--; \
/* Backward-shift: move subsequent displaced entries back. */ \
size_t _empty = _pos; \
size_t _next = (_pos + 1) & (_cap - 1); \
while ((map)->hashes[_next] != 0 && _HASHMAP_PROBE_DIST((map)->hashes[_next], _next, _cap) > 0) { \
(map)->keys[_empty] = (map)->keys[_next]; \
(map)->values[_empty] = (map)->values[_next]; \
(map)->hashes[_empty] = (map)->hashes[_next]; \
(map)->hashes[_next] = 0; \
_empty = _next; \
_next = (_next + 1) & (_cap - 1); \
} \
_removed = true; \
break; \
} \
_pos = (_pos + 1) & (_cap - 1); \
} \
} \
_removed; \
})
// Iterate over all occupied entries. `idx` is a size_t loop variable.
// Access keys and values via (map)->keys[idx] and (map)->values[idx].
#define HASHMAP_FOREACH(map, idx) \
for (size_t idx = 0; idx < (map)->capacity; idx++) \
if ((map)->hashes[idx] != 0)
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <common/compiler.h>
#include <common/utils.h>
#include <kernel/types.h>
#include <stddef.h>
#include <stdint.h>
#define __init __used, __section(".init.text"), __cold
#define __initdata __used, __section(".init.data")
#define __initdata_sorted(name) __used, __section(".init.data." name)
struct boot_file {
phys_t data;
size_t length;
char path[128];
};
struct boot_info {
char* cmdline;
struct phys_mem* mem_map;
size_t num_mem_maps;
phys_t phys_base;
uintptr_t virt_base;
uintptr_t hhdm_base;
struct boot_file* files;
size_t num_files;
phys_t rsdp;
};
extern uint8_t __ld_stack_top[];
extern uint8_t __ld_stack_bottom[];
extern phys_t rsdp_addr;
// 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);
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include <common/compiler.h>
#include <kernel/list.h>
#include <bits/irq.h>
#include <stdint.h>
enum irq_status : uint8_t {
IRQ_STATUS_HANDLED,
IRQ_STATUS_IGNORED,
};
enum irq_polarity : uint8_t {
IRQ_POLARITY_LOW,
IRQ_POLARITY_HIGH,
};
enum irq_trigger_mode : uint8_t {
IRQ_TRIGGER_EDGE,
IRQ_TRIGGER_LEVEL,
};
struct irq_handler {
enum irq_status (*handler)(void* self);
SLIST_LINK(struct irq_handler*) next;
};
struct irq_line {
int (*set_config)(enum irq_trigger_mode mode, enum irq_polarity polarity);
void (*mask)(struct irq_line* self);
void (*unmask)(struct irq_line* self);
void (*eoi)(struct irq_line* self);
SLIST_HEAD(struct irq_handler*) handlers;
bool is_busy;
};
void irq_line_attach(struct irq_line* self, struct irq_handler* handler);
struct irq_percpu {
uint32_t level;
bool in_interrupt;
};
void irq_lock();
void irq_unlock();
bool irq_set_interrupted(bool is_interrupted);
void arch_irq_set_state(bool state);
bool arch_irq_get_state();
void arch_irq_wait();
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#define SLIST_HEAD(type) \
struct { \
type sl_head; \
}
#define SLIST_LINK(type) \
struct { \
type sl_link; \
}
#define SLIST_EMPTY(head) ((head)->sl_head == NULL)
#define SLIST_FIRST(head) ((head)->sl_head)
#define SLIST_NEXT(elm, field) ((elm)->field.sl_link)
#define SLIST_INSERT_END(slistelm, elm, field) \
do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) \
do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_FOREACH(var, head, field) for ((var) = SLIST_FIRST((head)); (var); (var) = SLIST_NEXT((var), field))
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <kernel/types.h>
#include <stddef.h>
void* mmio_new(phys_t addr, size_t length);
void mmio_free(void* ptr, size_t length);
+11
View File
@@ -0,0 +1,11 @@
#pragma once
struct task;
struct mutex {
struct task* owner;
bool flag;
};
void mutex_lock(struct mutex* mutex);
void mutex_unlock(struct mutex* mutex);
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <zinnia/handle.h>
#include <kernel/channel.h>
#include <kernel/hashmap.h>
#include <kernel/mutex.h>
#include <kernel/spin.h>
#include <kernel/vas.h>
#include <kernel/vmo.h>
enum namespace_desc_type {
NAMESPACE_DESC_NS,
NAMESPACE_DESC_TASK,
NAMESPACE_DESC_VAS,
NAMESPACE_DESC_VMO,
NAMESPACE_DESC_CHANNEL,
};
struct namespace_desc {
enum namespace_desc_type type;
zn_rights_t rights;
union {
struct namespace* namespace;
struct task* task;
struct channel* channel;
struct vas* vas;
struct vmo* vmo;
} value;
};
// Translates between handles and objects.
struct namespace {
zn_handle_t next_handle;
struct mutex mutex;
HASHMAP(zn_handle_t, struct namespace_desc) descriptors;
};
// Creates a new namespace with no contents.
zn_status_t namespace_new(struct namespace** out);
// Adds a descriptor to this namespace with an auto-allocated handle.
zn_status_t namespace_add_desc(struct namespace* namespace, struct namespace_desc desc, zn_handle_t* out);
// Adds a descriptor to this namespace at a specific handle value.
zn_status_t namespace_add_desc_at(struct namespace* namespace, struct namespace_desc desc, zn_handle_t handle);
// Gets the descriptor referenced by the given handle.
zn_status_t namespace_get(struct namespace* namespace, zn_handle_t handle, struct namespace_desc* out);
// Deletes a descriptor from this namespace.
zn_status_t namespace_del_desc(struct namespace* namespace, zn_handle_t handle);
// Clones a descriptor with optionally restricted rights.
// If cloned_rights has ZN_RIGHTS_IDENTICAL set, the clone keeps the original rights.
// Otherwise, cloned_rights must be a subset of the original descriptor's rights.
zn_status_t namespace_clone_desc(
struct namespace* ns,
struct namespace_desc* desc,
zn_rights_t cloned_rights,
zn_handle_t* out
);
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <zinnia/status.h>
#include <stdint.h>
struct pager {
zn_status_t (*try_get_page)(struct pager* pager, uintptr_t offset);
};
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <common/compiler.h>
// Stop all execution upon panic.
[[noreturn, __format(printf, 1, 2)]]
void panic(const char* msg, ...);
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <common/compiler.h>
#include <kernel/irq.h>
#include <kernel/sched.h>
#include <kernel/usercopy.h>
#include <bits/percpu.h>
#include <stddef.h>
ASSERT_TYPE(struct arch_percpu);
// CPU-relative information.
struct percpu {
struct percpu* self; // A pointer to this structure.
size_t id; // The virtual ID of this CPU.
bool online; // Whether this CPU is initialized and active.
uintptr_t kernel_stack; // The kernel mode stack.
uintptr_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;
// Allocates a block of memory for a new CPU.
struct percpu* percpu_new();
// Gets the per-CPU data on the current CPU.
static inline struct percpu* percpu_get() {
return arch_percpu_get();
}
// Initializes the bootstrap processor.
static inline void percpu_bsp_init() {
arch_percpu_bsp_init();
}
// Initializes all processors.
static inline void percpu_init() {
arch_percpu_init();
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <zinnia/status.h>
#include <common/utils.h>
#include <kernel/alloc.h>
#include <kernel/spin.h>
#include <bits/mmu.h>
ASSERT_TYPE(pte_t);
enum pte_flags {
PTE_READ = 1 << 0, // Can read from this page.
PTE_WRITE = 1 << 1, // Can write to this page.
PTE_EXEC = 1 << 2, // Can execute code on this page.
PTE_USER = 1 << 3, // Can be accessed by the user.
PTE_DIR = 1 << 4, // Is a non-leaf page.
};
enum cache_mode {
CACHE_NONE,
CACHE_WRITE_COMBINE,
CACHE_WRITE_THROUGH,
CACHE_WRITE_BACK,
CACHE_MMIO,
};
struct pmap {
phys_t root;
struct spinlock lock;
bool is_user;
};
zn_status_t pmap_new_kernel(struct pmap* pt, enum alloc_flags flags);
zn_status_t pmap_new_user(struct pmap* pt, enum alloc_flags flags);
zn_status_t pmap_map(struct pmap* pt, uintptr_t vaddr, phys_t paddr, enum pte_flags flags, enum cache_mode cache);
zn_status_t pmap_protect(struct pmap* pt, uintptr_t vaddr, enum pte_flags flags);
zn_status_t pmap_unmap(struct pmap* pt, uintptr_t vaddr);
void pmap_set(struct pmap* pt);
void pte_clear(pte_t* pte);
pte_t pte_build(phys_t addr, enum pte_flags flags, enum cache_mode cache);
bool pte_is_present(pte_t* pte);
bool pte_is_dir(pte_t* pte);
phys_t pte_address(pte_t* pte);
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <common/compiler.h>
#include <stdarg.h>
[[__format(printf, 1, 2)]]
void kprintf(const char* message, ...);
void kvprintf(const char* message, va_list args);
+104
View File
@@ -0,0 +1,104 @@
#pragma once
#include <zinnia/status.h>
#include <kernel/alloc.h>
#include <kernel/init.h>
#include <kernel/namespace.h>
#include <kernel/tailq.h>
#include <kernel/types.h>
#include <kernel/wait.h>
#include <bits/sched.h>
#include <stddef.h>
#include <stdint.h>
ASSERT_TYPE(struct arch_context);
ASSERT_TYPE(struct arch_task_context);
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.
TASK_STATE_DEAD, // Task is killed.
};
#define TASK_NAME_MAX 128
#define KERNEL_STACK_SIZE 0x4000
// A task is the smallest unit of the scheduler.
struct task {
size_t id;
char name[TASK_NAME_MAX];
struct vas* space;
struct namespace* namespace;
enum task_state state;
struct arch_task_context context;
struct usercopy_region* usercopy_region;
uintptr_t kernel_stack;
uintptr_t user_stack;
size_t time_slice;
int8_t priority;
TAILQ_LINK(struct task) next;
};
// Per-CPU data for scheduling.
struct sched_percpu {
struct task* current;
struct task* idle_task;
size_t preempt_level;
TAILQ_HEAD(struct task) run_queue;
};
// Blocks the current task on the given wait queue.
// The caller must NOT hold any spinlocks. Returns after the task is woken.
void sched_block(struct sched_percpu* sched, struct wait_queue* wq);
// Wakes the first task blocked on the given wait queue, if any.
void sched_wake(struct sched_percpu* sched, struct wait_queue* wq);
// Wakes all tasks blocked on the given wait queue.
void sched_wake_all(struct sched_percpu* sched, struct wait_queue* wq);
typedef void (*task_fn_t)(uintptr_t arg0);
zn_status_t task_create(
const char* name,
struct vas* space,
struct namespace* ns,
task_fn_t entry,
uintptr_t arg0,
struct task** out
);
[[noreturn]]
void task_entry(task_fn_t entry, uintptr_t arg0);
void sched_init(struct sched_percpu* sched);
void sched_reschedule(struct sched_percpu* sched);
void sched_yield(struct sched_percpu* sched);
void sched_add_task(struct sched_percpu* sched, struct task* task);
[[noreturn]]
void sched_to_user(uintptr_t ctx);
[[noreturn]]
void sched_to_user_context(uintptr_t ctx);
// Info block for sched_to_user. Heap-allocated by the caller, freed by sched_to_user_arg.
struct task_start_info {
uintptr_t ip;
uintptr_t sp;
uintptr_t arg;
};
void arch_sched_preempt_disable();
bool arch_sched_preempt_enable();
void arch_sched_switch(struct task* from, struct task* to);
zn_status_t arch_task_init(
struct arch_task_context* context,
void* entry,
uintptr_t arg0,
uintptr_t stack_start,
bool is_user
);
[[noreturn]]
void arch_sched_jump_to_context(struct arch_context* context);
[[noreturn]]
void arch_sched_jump_to_user(uintptr_t ip, uintptr_t sp, uintptr_t arg);
+13
View File
@@ -0,0 +1,13 @@
#pragma once
// Busy-waits in a loop until the lock is freed.
// Does not put the CPU to sleep.
struct spinlock {
bool locked;
};
// Attempts to lock a spinlock. If it's already locked, waits until it's freed.
void spin_lock(struct spinlock* spin);
// Unlocks a spinlock.
void spin_unlock(struct spinlock* spin);
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <kernel/sched.h>
typedef zn_status_t (*syscall_fn_t)(struct arch_context* ctx);
void syscall_dispatch(struct arch_context* ctx);
zn_status_t syscall_log(struct arch_context* ctx);
zn_status_t syscall_archctl(struct arch_context* ctx);
zn_status_t syscall_page_size(struct arch_context* ctx);
zn_status_t syscall_time(struct arch_context* ctx);
zn_status_t syscall_handle_validate(struct arch_context* ctx);
zn_status_t syscall_handle_drop(struct arch_context* ctx);
zn_status_t syscall_handle_clone(struct arch_context* ctx);
zn_status_t syscall_vas_create(struct arch_context* ctx);
zn_status_t syscall_vmo_create(struct arch_context* ctx);
zn_status_t syscall_vmo_create_phys(struct arch_context* ctx);
zn_status_t syscall_vmo_map(struct arch_context* ctx);
zn_status_t syscall_channel_create(struct arch_context* ctx);
zn_status_t syscall_channel_wait(struct arch_context* ctx);
zn_status_t syscall_channel_read(struct arch_context* ctx);
zn_status_t syscall_channel_write(struct arch_context* ctx);
zn_status_t syscall_thread_create(struct arch_context* ctx);
zn_status_t syscall_thread_start(struct arch_context* ctx);
zn_status_t syscall_thread_exit(struct arch_context* ctx);
zn_status_t syscall_random_get(struct arch_context* ctx);
zn_status_t syscall_random_entropy(struct arch_context* ctx);
zn_status_t syscall_futex_wait(struct arch_context* ctx);
zn_status_t syscall_futex_wake(struct arch_context* ctx);
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#define TAILQ_HEAD(type) \
struct { \
type* tqh_first; \
type** tqh_last; \
}
#define TAILQ_LINK(type) \
struct { \
type* tqe_next; \
type** tqe_prev; \
}
#define TAILQ_INIT(head) \
do { \
(head)->tqh_first = nullptr; \
(head)->tqh_last = &(head)->tqh_first; \
} while (0)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == nullptr)
#define TAILQ_INSERT_TAIL(head, elm, field) \
do { \
(elm)->field.tqe_next = nullptr; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &(elm)->field.tqe_next; \
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) \
do { \
if (((elm)->field.tqe_next = (head)->tqh_first) != nullptr) \
(head)->tqh_first->field.tqe_prev = &(elm)->field.tqe_next; \
else \
(head)->tqh_last = &(elm)->field.tqe_next; \
(head)->tqh_first = (elm); \
(elm)->field.tqe_prev = &(head)->tqh_first; \
} while (0)
#define TAILQ_REMOVE(head, elm, field) \
do { \
if (((elm)->field.tqe_next) != nullptr) \
(elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev; \
else \
(head)->tqh_last = (elm)->field.tqe_prev; \
*(elm)->field.tqe_prev = (elm)->field.tqe_next; \
} while (0)
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <common/utils.h>
#include <bits/types.h>
ASSERT_TYPE(phys_t);
ASSERT_TYPE(uintptr_t);
ASSERT_TYPE(reg_t);
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <common/compiler.h>
#include <kernel/percpu.h>
#include <stddef.h>
struct usercopy_region {
void (*start_ip)();
void (*end_ip)();
void (*fault_ip)();
};
// Copies a block of data from user to kernel memory.
bool usercopy_read(void* dst, const __user void* src, size_t len);
// Copies a block of data from kernel to user memory.
bool usercopy_write(__user void* dst, const void* src, size_t len);
// Performs a strlen() on a user string.
bool usercopy_strlen(const __user char* str, size_t max, size_t* len);
bool arch_usercopy_read(void* dst, const __user void* src, size_t len, struct usercopy_region** region);
bool arch_usercopy_write(__user void* dst, const void* src, size_t len, struct usercopy_region** region);
bool arch_usercopy_strlen(const __user char* str, size_t max, size_t* len, struct usercopy_region** region);

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