Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization: - Move StarForth v3 engine to v3/ (src/, include/, Makefile) - Move kernel to kernel/ (src/, include/, linker/, Makefile) - Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md) - Move FABRIC-0..4.md to docs/fabric/ - Move ONTOLOGY.md and ROADMAP.md to docs/ Board infrastructure: - Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/ - Each board has board.mk (ISA, CPU flags, boot recipe) and README.md - Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET - make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board - ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet) - scripts/mkdiskimage.sh builds disk images for all boards Docs pipeline: - docs/book/ with LaTeX master (main.tex) and Makefile - pandoc converts Markdown to LaTeX at build time - Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks) - make docs builds single PDF (754 pages, 0 missing characters) - make docs TARGET=<board> adds board appendix - build/docs/<book|board>/meta.tex stamps git commit into PDF Bug fixes: - 42 include paths that only worked by accident now use correct relative paths - clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build) - Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved - Doxyfile, .clang-tidy, README.md, Kconfig paths updated Verified: - Hosted v3 build passes 1012 tests, 0 failures - SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE) - Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes - make clean TARGET=<board> removes only that board and its ISA objects - make all builds all boards, hosted v3, and docs in one run Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This 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.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This 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.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* elf_loader.c - ELF64 kernel loader for StarKernel
|
||||
*
|
||||
* Loads the StarKernel ELF binary from ESP, parses segments,
|
||||
* applies relocations, and jumps to entry point.
|
||||
*/
|
||||
|
||||
#include "uefi.h"
|
||||
#include "elf64.h"
|
||||
#include "arch.h"
|
||||
|
||||
/**
|
||||
* @brief Fill a memory region with a byte value (freestanding memset).
|
||||
*
|
||||
* Writes @p n copies of the low byte of @p c to the memory starting at
|
||||
* @p s. Used by @c elf_load_segments() to zero-fill BSS regions
|
||||
* (@c p_memsz > @c p_filesz) without a libc dependency. The byte loop
|
||||
* is correct for all sizes; a future optimisation could use SIMD or
|
||||
* word-width stores for large BSS regions.
|
||||
*
|
||||
* @param s Pointer to the start of the destination region.
|
||||
* @param c Value to fill (only the low 8 bits are used).
|
||||
* @param n Number of bytes to write.
|
||||
* @return @p s (matches standard @c memset signature).
|
||||
*/
|
||||
static void *elf_memset(void *s, int c, uint64_t n)
|
||||
{
|
||||
unsigned char *p = (unsigned char *)s;
|
||||
while (n--) *p++ = (unsigned char)c;
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy a non-overlapping memory region (freestanding memcpy).
|
||||
*
|
||||
* Copies @p n bytes from @p src to @p dest using a byte-at-a-time loop.
|
||||
* Behaviour is undefined if the source and destination ranges overlap.
|
||||
* Used by @c elf_load_segments() to copy each PT_LOAD segment's
|
||||
* @c p_filesz bytes from the ELF image in memory to its load address.
|
||||
*
|
||||
* @param dest Destination buffer; must not overlap with @p src.
|
||||
* @param src Source buffer.
|
||||
* @param n Number of bytes to copy.
|
||||
* @return @p dest (matches standard @c memcpy signature).
|
||||
*/
|
||||
static void *elf_memcpy(void *dest, const void *src, uint64_t n)
|
||||
{
|
||||
unsigned char *d = (unsigned char *)dest;
|
||||
const unsigned char *s = (const unsigned char *)src;
|
||||
while (n--) *d++ = *s++;
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate an ELF64 header for the current target architecture.
|
||||
*
|
||||
* Checks the following fields in the ELF identification block and header:
|
||||
* - Magic bytes @c e_ident[0..3]: must be @c 0x7F 'E' 'L' 'F'.
|
||||
* - Class @c e_ident[EI_CLASS]: must be @c ELFCLASS64 (64-bit).
|
||||
* - Data encoding @c e_ident[EI_DATA]: must be @c ELFDATA2LSB
|
||||
* (little-endian, the only byte order used by all three supported ISAs).
|
||||
* - Version @c e_ident[EI_VERSION]: must be @c EV_CURRENT (1).
|
||||
* - Type @c e_type: must be @c ET_EXEC (statically linked) or
|
||||
* @c ET_DYN (position-independent executable).
|
||||
* - Machine @c e_machine: must match the build target —
|
||||
* @c EM_X86_64 for @c ARCH_AMD64,
|
||||
* @c EM_AARCH64 for @c ARCH_AARCH64,
|
||||
* @c EM_RISCV for @c ARCH_RISCV64.
|
||||
*
|
||||
* @param ehdr Pointer to the ELF64 header at the start of the image.
|
||||
* @return 1 if the header is valid for this architecture, 0 otherwise.
|
||||
*/
|
||||
static int elf_validate_header(const Elf64_Ehdr *ehdr)
|
||||
{
|
||||
/* Check magic number */
|
||||
if (ehdr->e_ident[EI_MAG0] != ELFMAG0 ||
|
||||
ehdr->e_ident[EI_MAG1] != ELFMAG1 ||
|
||||
ehdr->e_ident[EI_MAG2] != ELFMAG2 ||
|
||||
ehdr->e_ident[EI_MAG3] != ELFMAG3)
|
||||
{
|
||||
return 0; /* Not an ELF file */
|
||||
}
|
||||
|
||||
/* Check class (64-bit) */
|
||||
if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check data encoding (little-endian) */
|
||||
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check version */
|
||||
if (ehdr->e_ident[EI_VERSION] != EV_CURRENT) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check type (ET_EXEC or ET_DYN) */
|
||||
if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check machine type matches architecture */
|
||||
#if defined(ARCH_AMD64)
|
||||
if (ehdr->e_machine != EM_X86_64) {
|
||||
return 0;
|
||||
}
|
||||
#elif defined(ARCH_AARCH64)
|
||||
if (ehdr->e_machine != EM_AARCH64) {
|
||||
return 0;
|
||||
}
|
||||
#elif defined(ARCH_RISCV64)
|
||||
if (ehdr->e_machine != EM_RISCV) {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#error "Unknown architecture"
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Map all PT_LOAD segments from an ELF64 image into memory.
|
||||
*
|
||||
* Iterates the program header table (@c e_phnum entries starting at
|
||||
* offset @c e_phoff) and processes every entry with @c p_type == @c PT_LOAD:
|
||||
*
|
||||
* 1. Computes the destination address as @p load_base + @c p_vaddr.
|
||||
* 2. Copies @c p_filesz bytes from @p elf_data + @c p_offset using
|
||||
* @c elf_memcpy().
|
||||
* 3. If @c p_memsz > @c p_filesz, zero-fills the remaining
|
||||
* (@c p_memsz - @c p_filesz) bytes with @c elf_memset() to
|
||||
* initialise the BSS region within the segment.
|
||||
*
|
||||
* The function does not check for address-range overlaps or that
|
||||
* destination memory is physically available; the UEFI loader is
|
||||
* responsible for allocating pages before calling @c elf_load_kernel().
|
||||
*
|
||||
* @param elf_data Pointer to the start of the ELF64 file in memory.
|
||||
* @param load_base Base address at which to load the ELF image
|
||||
* (0 for @c ET_EXEC, 0x400000 for @c ET_DYN).
|
||||
* @return 1 always (errors are silently ignored at this milestone; a
|
||||
* future revision should return 0 on a failed memory access).
|
||||
*/
|
||||
static int elf_load_segments(const uint8_t *elf_data, Elf64_Addr load_base)
|
||||
{
|
||||
const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *)elf_data;
|
||||
const Elf64_Phdr *phdr = (const Elf64_Phdr *)(elf_data + ehdr->e_phoff);
|
||||
|
||||
/* Iterate through program headers */
|
||||
for (uint16_t i = 0; i < ehdr->e_phnum; i++) {
|
||||
if (phdr[i].p_type == PT_LOAD) {
|
||||
/* Calculate load address */
|
||||
uint8_t *dest = (uint8_t *)(load_base + phdr[i].p_vaddr);
|
||||
const uint8_t *src = elf_data + phdr[i].p_offset;
|
||||
|
||||
/* Copy file contents to memory */
|
||||
elf_memcpy(dest, src, phdr[i].p_filesz);
|
||||
|
||||
/* Zero out BSS (p_memsz > p_filesz) */
|
||||
if (phdr[i].p_memsz > phdr[i].p_filesz) {
|
||||
elf_memset(dest + phdr[i].p_filesz, 0,
|
||||
phdr[i].p_memsz - phdr[i].p_filesz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply all RELA relocation sections from an ELF64 image.
|
||||
*
|
||||
* Scans the section header table for sections of type @c SHT_RELA. For
|
||||
* each such section, resolves the associated symbol table (via
|
||||
* @c sh_link) and applies every relocation entry to the loaded image.
|
||||
*
|
||||
* Supported relocation types are ISA-specific and compile-time selected:
|
||||
*
|
||||
* - **amd64 (@c ARCH_AMD64)**:
|
||||
* - @c R_X86_64_NONE — no-op.
|
||||
* - @c R_X86_64_RELATIVE — absolute address = @c load_base + @c r_addend.
|
||||
* - @c R_X86_64_64 — @c load_base + symbol value + @c r_addend (64-bit).
|
||||
* - @c R_X86_64_32 / @c R_X86_64_32S — same, truncated to 32 bits.
|
||||
* - @c R_X86_64_PC32 / @c R_X86_64_PLT32 — @c symbol + @c r_addend - P
|
||||
* (PC-relative, 32 bits), where P is the relocation's own runtime
|
||||
* address; PLT32 has no real PLT indirection in this static, non-PIE
|
||||
* link, so it resolves the same as PC32.
|
||||
*
|
||||
* - **aarch64 (@c ARCH_AARCH64)**:
|
||||
* - @c R_AARCH64_NONE — no-op.
|
||||
* - @c R_AARCH64_RELATIVE — @c load_base + @c r_addend.
|
||||
* - @c R_AARCH64_ABS64 — @c load_base + symbol + @c r_addend.
|
||||
*
|
||||
* - **riscv64 (@c ARCH_RISCV64)**:
|
||||
* - @c R_RISCV_NONE — no-op.
|
||||
* - @c R_RISCV_RELATIVE — @c load_base + @c r_addend.
|
||||
* - @c R_RISCV_64 — @c load_base + symbol + @c r_addend.
|
||||
*
|
||||
* Returns 0 (failure) if a symbol index in a relocation entry is out
|
||||
* of bounds, if the symbol table entry size is zero, or if an unknown
|
||||
* relocation type is encountered. Returns 1 on success.
|
||||
*
|
||||
* @param elf_data Pointer to the ELF64 file image.
|
||||
* @param load_base Load base address applied during @c elf_load_segments().
|
||||
* @return 1 on success, 0 on any relocation error.
|
||||
*/
|
||||
static int elf_apply_relocations(const uint8_t *elf_data, Elf64_Addr load_base)
|
||||
{
|
||||
const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *)elf_data;
|
||||
const Elf64_Shdr *shdr = (const Elf64_Shdr *)(elf_data + ehdr->e_shoff);
|
||||
|
||||
for (uint16_t i = 0; i < ehdr->e_shnum; i++) {
|
||||
const Elf64_Shdr *rela_sh = &shdr[i];
|
||||
if (rela_sh->sh_type != SHT_RELA) {
|
||||
continue;
|
||||
}
|
||||
if (rela_sh->sh_link >= ehdr->e_shnum) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Elf64_Shdr *symtab_sh = &shdr[rela_sh->sh_link];
|
||||
if (symtab_sh->sh_entsize == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Elf64_Rela *rela = (const Elf64_Rela *)(elf_data + rela_sh->sh_offset);
|
||||
uint64_t num_rela = rela_sh->sh_size / sizeof(Elf64_Rela);
|
||||
const Elf64_Sym *symtab = (const Elf64_Sym *)(elf_data + symtab_sh->sh_offset);
|
||||
uint64_t sym_count = symtab_sh->sh_size / symtab_sh->sh_entsize;
|
||||
|
||||
for (uint64_t j = 0; j < num_rela; j++) {
|
||||
uint32_t type = ELF64_R_TYPE(rela[j].r_info);
|
||||
uint32_t sym_index = ELF64_R_SYM(rela[j].r_info);
|
||||
uint64_t reloc_addr = load_base + rela[j].r_offset;
|
||||
uint64_t sym_value = 0;
|
||||
if (sym_index < sym_count) {
|
||||
sym_value = symtab[sym_index].st_value;
|
||||
} else if (sym_index != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(ARCH_AMD64)
|
||||
switch (type) {
|
||||
case R_X86_64_NONE:
|
||||
break;
|
||||
case R_X86_64_RELATIVE: {
|
||||
uint64_t *target = (uint64_t *)reloc_addr;
|
||||
*target = load_base + rela[j].r_addend;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_64: {
|
||||
uint64_t *target = (uint64_t *)reloc_addr;
|
||||
*target = load_base + sym_value + rela[j].r_addend;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S: {
|
||||
uint32_t *target32 = (uint32_t *)reloc_addr;
|
||||
uint64_t value = load_base + sym_value + rela[j].r_addend;
|
||||
*target32 = (uint32_t)value;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PLT32: {
|
||||
/* value = S + A - P, where P is the address of the relocation
|
||||
* itself (reloc_addr already includes load_base). PLT32 is
|
||||
* treated identically to PC32: this is a static, non-PIE
|
||||
* link with every symbol resolved in-image, so there is no
|
||||
* real PLT indirection to apply. */
|
||||
uint32_t *target32 = (uint32_t *)reloc_addr;
|
||||
int64_t value = (int64_t)(load_base + sym_value + rela[j].r_addend) -
|
||||
(int64_t)reloc_addr;
|
||||
*target32 = (uint32_t)value;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
#elif defined(ARCH_AARCH64)
|
||||
switch (type) {
|
||||
case R_AARCH64_NONE:
|
||||
break;
|
||||
case R_AARCH64_RELATIVE: {
|
||||
uint64_t *target = (uint64_t *)reloc_addr;
|
||||
*target = load_base + rela[j].r_addend;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ABS64: {
|
||||
uint64_t *target = (uint64_t *)reloc_addr;
|
||||
*target = load_base + sym_value + rela[j].r_addend;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
#elif defined(ARCH_RISCV64)
|
||||
switch (type) {
|
||||
case R_RISCV_NONE:
|
||||
break;
|
||||
case R_RISCV_RELATIVE: {
|
||||
uint64_t *target = (uint64_t *)reloc_addr;
|
||||
*target = load_base + rela[j].r_addend;
|
||||
break;
|
||||
}
|
||||
case R_RISCV_64: {
|
||||
uint64_t *target = (uint64_t *)reloc_addr;
|
||||
*target = load_base + sym_value + rela[j].r_addend;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load, relocate, and return the entry point of a StarKernel ELF64 binary.
|
||||
*
|
||||
* Top-level ELF loader called by @c efi_main() (in the split-build path)
|
||||
* after @c ExitBootServices(). Performs three operations in sequence:
|
||||
*
|
||||
* 1. **Header validation** via @c elf_validate_header() — checks magic,
|
||||
* class, data encoding, version, type, and machine-specific architecture.
|
||||
* Returns 0 immediately on failure.
|
||||
*
|
||||
* 2. **Segment loading** via @c elf_load_segments() — maps all @c PT_LOAD
|
||||
* segments to their target addresses, initialises BSS to zero.
|
||||
* The @p load_base is:
|
||||
* - 0 for @c ET_EXEC (absolute-address kernel; typical for StarKernel).
|
||||
* - 0x400000 (4 MB) for @c ET_DYN (position-independent kernel).
|
||||
*
|
||||
* 3. **Relocation processing** via @c elf_apply_relocations() — applies
|
||||
* @c SHT_RELA sections to patch in the correct runtime addresses.
|
||||
* Returns 0 on any relocation error.
|
||||
*
|
||||
* On success, stores @c load_base + @c e_entry in @c *entry_out — the
|
||||
* virtual address of the kernel's C entry function @c kernel_main() — and
|
||||
* returns 1. The caller performs an indirect call to this address.
|
||||
*
|
||||
* @c elf_size is currently unused (reserved for future bounds-checking of
|
||||
* program-header and section-header offsets against the file boundary).
|
||||
*
|
||||
* @param elf_data Pointer to the ELF64 file image in UEFI loader memory;
|
||||
* must remain valid until after the call returns.
|
||||
* @param elf_size Size of the ELF image in bytes (currently unchecked).
|
||||
* @param entry_out Output: receives the kernel entry point address on success.
|
||||
* @return 1 on success, 0 if validation, segment loading, or relocation fails.
|
||||
*/
|
||||
int elf_load_kernel(const uint8_t *elf_data, uint64_t elf_size,
|
||||
Elf64_Addr *entry_out)
|
||||
{
|
||||
(void)elf_size; /* Reserved for future bounds checking */
|
||||
const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *)elf_data;
|
||||
|
||||
/* Validate ELF header */
|
||||
if (!elf_validate_header(ehdr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Determine load base address */
|
||||
Elf64_Addr load_base = 0;
|
||||
if (ehdr->e_type == ET_DYN) {
|
||||
/* Position-independent: use a fixed base for now */
|
||||
load_base = 0x400000; /* 4MB */
|
||||
} else if (ehdr->e_type == ET_EXEC) {
|
||||
/* Absolute addresses: load at 0 (segments have absolute vaddr) */
|
||||
load_base = 0;
|
||||
}
|
||||
|
||||
/* Load segments */
|
||||
if (!elf_load_segments(elf_data, load_base)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Apply relocations */
|
||||
if (!elf_apply_relocations(elf_data, load_base)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculate entry point */
|
||||
*entry_out = load_base + ehdr->e_entry;
|
||||
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user