Working kernel written on C and userspace-ready #1

Merged
sonya merged 61 commits from dev into main 2026-05-03 09:13:20 +00:00
13 changed files with 46 additions and 507 deletions
Showing only changes of commit bfa84040b8 - Show all commits
+1
View File
@@ -1,4 +1,5 @@
.build
build
.vscode
.DS_Store
compile_commands.json
+30 -169
View File
@@ -1,182 +1,43 @@
cmake_minimum_required(VERSION 3.20)
project(ksOSKernel LANGUAGES ASM C)
set(KERNEL_MODULE_NAME "Kernel")
# Where to emit compile_commands.json for SourceKit-LSP. Empty: Kernel/
# (ancestor of SWIFT_SOURCES). Override: -DKERNEL_COMPILE_COMMANDS_DIR=/path
# or env KERNEL_COMPILE_COMMANDS_DIR.
set(KERNEL_COMPILE_COMMANDS_DIR "" CACHE PATH
"Directory for Swift compile_commands.json (SourceKit-LSP)")
file(GLOB_RECURSE KERNEL_SOURCES CMAKE_CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/Source/KernelMain.c
${CMAKE_CURRENT_SOURCE_DIR}/Source/Arch/entry.S
${CMAKE_CURRENT_SOURCE_DIR}/Source/**/*.c
)
# --- Locate Swift toolchain with Embedded Swift stdlib ---
# Priority: cmake var > env var > auto-detect
if(NOT SWIFT_TOOLCHAIN AND DEFINED ENV{SWIFT_TOOLCHAIN})
set(SWIFT_TOOLCHAIN "$ENV{SWIFT_TOOLCHAIN}")
endif()
add_executable(Kernel ${KERNEL_SOURCES})
target_include_directories(Kernel PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Include/
${CMAKE_CURRENT_SOURCE_DIR}/../Common
)
if(SWIFT_TOOLCHAIN)
set(SWIFTC "${SWIFT_TOOLCHAIN}/usr/bin/swiftc")
set(SWIFT_RESOURCE_DIR "${SWIFT_TOOLCHAIN}/usr/lib/swift")
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
# Scan for a swift.org toolchain that ships the embedded stdlib.
# APPLE is false here (target is Generic), so we check the HOST OS.
file(GLOB _tc_candidates
"$ENV{HOME}/Library/Developer/Toolchains/*.xctoolchain"
"/Library/Developer/Toolchains/*.xctoolchain"
)
foreach(_tc ${_tc_candidates})
if(EXISTS "${_tc}/usr/lib/swift/embedded")
set(SWIFTC "${_tc}/usr/bin/swiftc")
set(SWIFT_RESOURCE_DIR "${_tc}/usr/lib/swift")
break()
endif()
endforeach()
else()
# Linux: find swiftc in PATH
find_program(_SWIFTC_EXE swiftc)
if(_SWIFTC_EXE)
get_filename_component(_SWIFTC_BIN "${_SWIFTC_EXE}" DIRECTORY)
get_filename_component(_SWIFTC_USR "${_SWIFTC_BIN}" DIRECTORY)
target_compile_options(Kernel PRIVATE
$<$<COMPILE_LANGUAGE:C>:
-ffreestanding
-fno-stack-protector
-fno-builtin
-Wall -Wextra
-g
-mgeneral-regs-only
>
)
if(EXISTS "${_SWIFTC_USR}/lib/swift/embedded")
set(SWIFTC "${_SWIFTC_EXE}")
set(SWIFT_RESOURCE_DIR "${_SWIFTC_USR}/lib/swift")
elseif(EXISTS "${_SWIFTC_USR}/lib/swift/lib/swift/embedded")
set(SWIFTC "${_SWIFTC_EXE}")
set(SWIFT_RESOURCE_DIR "${_SWIFTC_USR}/lib/swift/lib/swift")
endif()
endif()
endif()
if(NOT SWIFTC OR NOT EXISTS "${SWIFT_RESOURCE_DIR}/embedded")
message(FATAL_ERROR
"Swift toolchain with Embedded Swift not found.\n"
"Install a swift.org toolchain and pass -DSWIFT_TOOLCHAIN=<root> "
"or set the SWIFT_TOOLCHAIN env var.")
endif()
message(STATUS "Swift: ${SWIFTC}")
message(STATUS "Swift resource dir: ${SWIFT_RESOURCE_DIR}")
# Hint for SourceKit: VSCode must use this toolchain (same as compile_commands),
# not Xcode's default; otherwise: "Loading the standard library failed" with Embedded.
get_filename_component(_SWIFT_IDE_PATH "${SWIFTC}" DIRECTORY)
set(_ide_hint
"# If SourceKit reports \"Loading the standard library failed\", set the Swift
# extension's swift.path to the directory on the line below and restart
# \"Swift: Restart SourceKit LSP\". (Embedded Swift — must match this toolchain, not Xcode.)
# Regenerated on each CMake configure.
")
string(APPEND _ide_hint "${_SWIFT_IDE_PATH}\n")
file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/../ide-swift-toolchain.txt" "${_ide_hint}")
# --- Build ---
add_compile_options(-ffreestanding -nostdlib -O0 -g)
set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld)
add_link_options(
-fuse-ld=lld
target_link_options(Kernel PRIVATE
-nostdlib
-static
-Wl,-T,${LINKER_SCRIPT}
-no-pie
-T "${CMAKE_CURRENT_SOURCE_DIR}/linker.ld"
-z max-page-size=0x1000
)
set(SWIFT_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Source/Kernel.swift
${CMAKE_CURRENT_SOURCE_DIR}/Source/Arch/DTB.swift
${CMAKE_CURRENT_SOURCE_DIR}/Source/IO/UART.swift
${CMAKE_CURRENT_SOURCE_DIR}/Source/Arch/ExceptionContext.swift
${CMAKE_CURRENT_SOURCE_DIR}/Source/OS/panic.swift # idfk why but sourcekit-lsp dont see Panic.swift but see panic.swift perfectly
)
set(SWIFT_OBJ ${CMAKE_CURRENT_BINARY_DIR}/kernel_swift.o)
add_custom_command(
OUTPUT ${SWIFT_OBJ}
COMMAND ${SWIFTC}
-target aarch64-none-none-elf
-enable-experimental-feature Embedded
-parse-as-library
-wmo
-O
-Xcc -fno-stack-protector
-Xcc -I${CMAKE_CURRENT_SOURCE_DIR}/../Common
-import-bridging-header ${CMAKE_CURRENT_SOURCE_DIR}/Source/Support/BridgingHeader.h
-resource-dir ${SWIFT_RESOURCE_DIR}
-c ${SWIFT_SOURCES}
-o ${SWIFT_OBJ}
COMMAND ${LLVM_OBJCOPY} --remove-section=.swift_modhash ${SWIFT_OBJ}
DEPENDS ${SWIFT_SOURCES}
COMMENT "Compiling Swift kernel"
set_target_properties(Kernel PROPERTIES
OUTPUT_NAME "Kernel.elf"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
)
set_source_files_properties(${SWIFT_OBJ} PROPERTIES
EXTERNAL_OBJECT TRUE
GENERATED TRUE
)
add_executable(kernel.elf Source/Arch/entry.S Source/Support/stubs.c Source/Arch/vectors.S ${SWIFT_OBJ})
add_custom_command(TARGET kernel.elf POST_BUILD
COMMAND ${LLVM_OBJCOPY} -O binary kernel.elf kernel.bin
)
# --- SourceKit-LSP: generate compile_commands.json for Swift (Dynamic) ---
set(_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/Source/Support/BridgingHeader.h")
set(SWIFT_ARGS
"\"${SWIFTC}\""
"\"-target\"" "\"aarch64-none-none-elf\""
"\"-enable-experimental-feature\"" "\"Embedded\""
"\"-module-name\"" "\"${KERNEL_MODULE_NAME}\""
"\"-parse-as-library\""
"\"-wmo\""
"\"-import-bridging-header\"" "\"${_BRIDGING_HEADER}\""
"\"-Xcc\"" "\"-I${CMAKE_CURRENT_SOURCE_DIR}/../Common\""
"\"-Xcc\"" "\"-fno-stack-protector\""
"\"-resource-dir\"" "\"${SWIFT_RESOURCE_DIR}\""
)
foreach(_src IN LISTS SWIFT_SOURCES)
list(APPEND SWIFT_ARGS "\"${_src}\"")
endforeach()
string(JOIN ", " SWIFT_ARGS_JSON ${SWIFT_ARGS})
set(COMPDB_ENTRIES "")
list(LENGTH SWIFT_SOURCES _src_count)
math(EXPR _last_idx "${_src_count} - 1")
set(_idx 0)
foreach(_src IN LISTS SWIFT_SOURCES)
set(_entry " {\n \"file\": \"${_src}\",\n \"directory\": \"${CMAKE_CURRENT_BINARY_DIR}\",\n \"arguments\": [${SWIFT_ARGS_JSON}]\n }")
if(_idx LESS _last_idx)
string(APPEND _entry ",\n")
else()
string(APPEND _entry "\n")
endif()
string(APPEND COMPDB_ENTRIES "${_entry}")
math(EXPR _idx "${_idx} + 1")
endforeach()
# Precedence: CMake -D > env > Kernel/ (ancestor of all SWIFT_SOURCES).
if(NOT KERNEL_COMPILE_COMMANDS_DIR STREQUAL "")
set(COMPDB_OUTPUT_DIR "${KERNEL_COMPILE_COMMANDS_DIR}")
elseif(DEFINED ENV{KERNEL_COMPILE_COMMANDS_DIR} AND NOT "$ENV{KERNEL_COMPILE_COMMANDS_DIR}" STREQUAL "")
set(COMPDB_OUTPUT_DIR "$ENV{KERNEL_COMPILE_COMMANDS_DIR}")
else()
set(COMPDB_OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
endif()
# Same JSON twice: (1) optional Kernel/ or KERNEL_COMPILE_COMMANDS_DIR, (2) repo
# root. SourceKit/Cursor/VSCode Swift extension resolve compile_commands at the
# workspace root first; a DB only under Kernel/ often yields no language service
# and broken cross-file resolution (WMO) even though the build works.
set(KERNEL_COMPDB_CONTENT "[\n${COMPDB_ENTRIES}]\n")
file(GENERATE OUTPUT "${COMPDB_OUTPUT_DIR}/compile_commands.json"
CONTENT "${KERNEL_COMPDB_CONTENT}"
)
set(_REPO_COMPDB "${CMAKE_CURRENT_SOURCE_DIR}/../compile_commands.json")
file(GENERATE OUTPUT "${_REPO_COMPDB}"
CONTENT "${KERNEL_COMPDB_CONTENT}"
add_custom_command(TARGET Kernel POST_BUILD
COMMAND ${LLVM_OBJCOPY} -O binary ${CMAKE_BINARY_DIR}/Kernel.elf ${CMAKE_BINARY_DIR}/Kernel.bin
COMMENT "Generating ksOSKernel.bin from Kernel.elf"
)
-74
View File
@@ -1,74 +0,0 @@
@frozen
public struct ExceptionContext {
public var x0: UInt64
public var x1: UInt64
public var x2: UInt64
public var x3: UInt64
public var x4: UInt64
public var x5: UInt64
public var x6: UInt64
public var x7: UInt64
public var x8: UInt64
public var x9: UInt64
public var x10: UInt64
public var x11: UInt64
public var x12: UInt64
public var x13: UInt64
public var x14: UInt64
public var x15: UInt64
public var x16: UInt64
public var x17: UInt64
public var x18: UInt64
public var x19: UInt64
public var x20: UInt64
public var x21: UInt64
public var x22: UInt64
public var x23: UInt64
public var x24: UInt64
public var x25: UInt64
public var x26: UInt64
public var x27: UInt64
public var x28: UInt64
public var x29: UInt64 // fp
public var x30: UInt64 // lr
public var elr_el1: UInt64 // pc
public var spsr_el1: UInt64 // cpu status
public var esr_el1: UInt64 // error reason
}
@frozen
public enum ExceptionType: UInt64 {
// curr el with sp0 (EL1t)
// usually dont happen cuz we switch to sp_el1, but just in case
case syncEL1t = 0
case irqEL1t = 1
case fiqEL1t = 2
case seErrorEL1t = 3
// curr el with sp1 (EL1h)
// exception in kernel space
case syncEL1h = 4
case irqEL1h = 5
case fiqEL1h = 6
case seErrorEL1h = 7
// lower EL 64-bit from userspace
case syncEL0_64 = 8
case irqEL0_64 = 9
case fiqEL0_64 = 10
case seErrorEL0_64 = 11
// lower EL 32-bit from userspace
case syncEL0_32 = 12
case irqEL0_32 = 13
case fiqEL0_32 = 14
case seErrorEL0_32 = 15
}
@_cdecl("swift_trap_handler")
public func trapHandler(context: UnsafeMutableRawPointer, rawType: UInt64) {
let context = context.assumingMemoryBound(to: ExceptionContext.self)
let type = ExceptionType(rawValue: rawType)!
panic(context: context.pointee)
}
-83
View File
@@ -1,83 +0,0 @@
struct FDTHeader {
let magic: UInt32 // 0xd00dfeed
let totalsize: UInt32
let off_dt_struct: UInt32
let off_dt_strings: UInt32
let off_mem_rsvmap: UInt32
let version: UInt32
let last_comp_version: UInt32
let boot_cpuid_phys: UInt32
let size_dt_strings: UInt32
let size_dt_struct: UInt32
}
struct FDTProperty {
let len: UInt32
let nameoff: UInt32
}
enum FDTToken: UInt32 {
case beginNode = 0x1
case endNode = 0x2
case prop = 0x3
case nop = 0x4
case end = 0x9
}
extension UInt32 {
var fromBe: UInt32 {
return self.byteSwapped
}
}
func alignUp(_ value: Int, to: Int) -> Int {
return (value + to - 1) & ~(to - 1)
}
let kFDTHeaderMagic = 0xd00dfeed
func parseDTB(at pointer: UnsafeRawPointer) {
let header = pointer.bindMemory(to: FDTHeader.self, capacity: 1).pointee
guard header.magic.byteSwapped == kFDTHeaderMagic else {
return
}
let structOffset = Int(header.off_dt_struct.byteSwapped)
let stringOffset = Int(header.off_dt_strings.byteSwapped)
var structBase = pointer.advanced(by: structOffset)
let stringsBase = pointer.advanced(by: stringOffset)
var finished = false
while !finished {
let rawToken = structBase.load(as: UInt32.self).byteSwapped
structBase = structBase.advanced(by: 4)
if let token = FDTToken(rawValue: rawToken) {
switch token {
case .beginNode:
let namePointer = structBase.assumingMemoryBound(to: UInt8.self)
let name = String(cString: namePointer)
let nameLength = name.utf8.count + 1
structBase = structBase.advanced(by: alignUp(nameLength, to: 4))
case .prop:
let propertyHeader = structBase.load(as: FDTProperty.self)
let dataLength = Int(propertyHeader.len.byteSwapped)
let nameOffset = Int(propertyHeader.nameoff.byteSwapped)
let namePointer = stringsBase.advanced(by: nameOffset).assumingMemoryBound(to: UInt8.self)
structBase = structBase.advanced(by: 8) // skip header
structBase = structBase.advanced(by: alignUp(dataLength, to: 4)) // skip data for now
// TODO: finish it :D
case .endNode:
continue
case .nop:
continue
case .end:
finished = true
}
}
}
}
+2 -4
View File
@@ -1,7 +1,5 @@
.section .text.boot, "ax"
.global _start
_start:
bl kmain
.hang:
b .hang
bl KernelMain
b .
-83
View File
@@ -1,83 +0,0 @@
.macro ventry type
.align 7
sub sp, sp, #272 // save 272 bytes of stack
stp x0, x1, [sp, #0] // move stack
mov x1, #\type // move type to x1
b common_trap_entry
.endm
.section .text.vectors
.align 11
.global vectors
vector_table:
// EL1t (curr EL with SP0)
ventry 0 // Sync
ventry 1 // IRQ
ventry 2 // FIQ
ventry 3 // SError
// EL1h (curr EL with SP1)
ventry 4 // Sync
ventry 5 // IRQ
ventry 6 // FIQ
ventry 7 // SError
// EL0 (lower EL 64-bit from userspace)
ventry 8 // Sync
ventry 9 // IRQ
ventry 10 // FIQ
ventry 11 // SError
// EL0 (lower EL 32-bit from userspace)
ventry 12; ventry 13; ventry 14; ventry 15
common_trap_entry:
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
mrs x21, elr_el1
mrs x22, spsr_el1
mrs x23, esr_el1
stp x30, x21, [sp, #16 * 15]
stp x22, x23, [sp, #16 * 16]
mov x0, sp
bl swift_trap_handler
ldp x22, x23, [sp, #16 * 16]
msr spsr_el1, x22
ldp x30, x21, [sp, #16 * 15]
msr elr_el1, x21
ldp x28, x29, [sp, #16 * 14]
ldp x26, x27, [sp, #16 * 13]
ldp x24, x25, [sp, #16 * 12]
ldp x22, x23, [sp, #16 * 11]
ldp x20, x21, [sp, #16 * 10]
ldp x18, x19, [sp, #16 * 9]
ldp x16, x17, [sp, #16 * 8]
ldp x14, x15, [sp, #16 * 7]
ldp x12, x13, [sp, #16 * 6]
ldp x10, x11, [sp, #16 * 5]
ldp x8, x9, [sp, #16 * 4]
ldp x6, x7, [sp, #16 * 3]
ldp x4, x5, [sp, #16 * 2]
ldp x2, x3, [sp, #16 * 1]
ldp x0, x1, [sp, #0]
add sp, sp, #272
eret
-20
View File
@@ -1,20 +0,0 @@
let kUARTBaseAddress: UInt = 0x09000000 // TODO: Make it dynamic by parsion DTB
@_cdecl("putchar")
@discardableResult
public func _serialPutchar(_ char: Int32) -> Int32 {
let uartFR: UInt = kUARTBaseAddress + 0x18// TXFF -- TRansmit FIFO Full for PL011 is 5 bit of FR reg (0x18)
while (mmio_read32(uartFR) & (1 << 5)) != 0 {
// i love pizza btw
}
mmio_write32(UInt(kUARTBaseAddress), UInt32(char))
return char
}
public func kprint(_ message: StaticString) {
for i in 0..<message.utf8CodeUnitCount {
_serialPutchar(Int32(message.utf8Start[i]))
}
}
+3
View File
@@ -0,0 +1,3 @@
void KernelMain(void) {
}
-6
View File
@@ -1,6 +0,0 @@
// idfk why but sourcekit-lsp dont see Panic.swift but see panic.swift perfectly
public func panic(context: ExceptionContext) {
kprint("kernel panic stub meow")
_wfi()
}
-14
View File
@@ -1,14 +0,0 @@
#include "bootinfo.h"
#include <stdint.h>
static inline void mmio_write32(uintptr_t addr, uint32_t val) {
*(volatile uint32_t *)addr = val;
}
static inline uint32_t mmio_read32(uintptr_t addr) {
return *(volatile uint32_t *)addr;
}
static inline void _wfi(void) {
__asm__ volatile("wfi");
}
-42
View File
@@ -1,42 +0,0 @@
#include <stddef.h>
#include <stdint.h>
void* memmove(void* dest, const void* src, unsigned long n) {
unsigned char* d = (unsigned char*)dest;
const unsigned char* s = (const unsigned char*)src;
if (d < s) {
while (n--) *d++ = *s++;
} else {
d += n; s += n;
while (n--) *--d = *--s;
}
return dest;
}
void* memcpy(void* dest, const void* src, unsigned long n) {
return memmove(dest, src, n);
}
void* memset(void* dest, int c, unsigned long n) {
unsigned char* d = (unsigned char*)dest;
while (n--) *d++ = (unsigned char)c;
return dest;
}
// Stack protection
long __stack_chk_guard = (long)0xDEADBEEFCAFEBABEULL;
void __stack_chk_fail(void) { while (1); }
// Swift runtime allocator stubs — should never be called in embedded mode
int posix_memalign(void** ptr, unsigned long align, unsigned long size) {
(void)ptr; (void)align; (void)size;
while (1);
}
void free(void* ptr) { (void)ptr; }
// Swift stdlib uses arc4random_buf for Hasher seed — stub with zeroes in bare-metal
void arc4random_buf(void* buf, unsigned long nbytes) {
unsigned char* b = (unsigned char*)buf;
while (nbytes--) *b++ = 0;
}
-12
View File
@@ -1,12 +0,0 @@
@inline(never)
func getDenominator() -> Int {
return 0
}
@_cdecl("kmain")
public func kernelMain(_ bootInfo: UnsafeMutablePointer<Bootinfo>) {
kprint("Test nya")
let a: Int = 10
let b = getDenominator()
let c = a / b
}
+10
View File
@@ -26,3 +26,13 @@ set(LLVM_OBJCOPY "${LLVM_BIN}/llvm-objcopy")
set(TARGET_TRIPLE aarch64-none-elf)
set(CMAKE_C_COMPILER_TARGET ${TARGET_TRIPLE})
set(CMAKE_ASM_COMPILER_TARGET ${TARGET_TRIPLE})
if(APPLE)
find_program(TERMOS_LD_LLD NAMES ld.lld HINTS /usr/local/bin /opt/homebrew/bin REQUIRED)
find_program(LLVM_OBJCOPY NAMES llvm-objcopy objcopy
HINTS /usr/local/opt/llvm/bin /opt/homebrew/opt/llvm/bin /usr/local/bin /opt/homebrew/bin
REQUIRED)
set(CMAKE_C_LINK_FLAGS "")
set(CMAKE_C_LINK_EXECUTABLE
"${TERMOS_LD_LLD} <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
endif()