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:
rajames
2026-10-01 15:40:09 -04:00
co-authored by Junie
parent 3c709c115b
commit a8b70e88d3
630 changed files with 2646 additions and 2036 deletions
+20
View File
@@ -0,0 +1,20 @@
# src/platform/
Platform abstraction layer for the hosted VM build.
- `alloc_host.c` — hosted (`malloc`-backed) allocator implementation of
`include/platform_alloc.h`.
- `alloc_kernel.c` — a kernel-side allocator implementation of the same
interface. Note: `src/starkernel/vm/alloc_kernel.c` is the copy actually
referenced by `Makefile.starkernel`'s kernel build; this one under
`src/platform/` is not wired into any build target — check before relying
on it.
- `platform_init.c` — platform-specific initialization dispatch.
- `threading.c` — threading primitives backing the heartbeat background
thread (`HEARTBEAT_THREAD_ENABLED=1`).
Subdirectories:
- `linux/time.c` — POSIX (`clock_gettime`) timing backend.
- `l4re/time.c` — HISTORICAL: L4Re/Fiasco.OC timing backend. L4Re was a
supported platform target through mid-2026; it has been removed as an
active target and this file is no longer wired into any build.
+164
View File
@@ -0,0 +1,164 @@
/*
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.
*/
/*
* platform/alloc_host.c - Host (Linux/POSIX) memory allocator
*
* Simple wrapper around standard malloc/free with statistics tracking.
*/
#include "../../include/platform_alloc.h"
#include <stdlib.h>
#include <string.h>
/* Statistics tracking */
static sf_alloc_stats_t g_stats = {0};
/**
* @brief Initialise the host allocator statistics.
*
* Resets the @c g_stats struct to zero. Safe to call multiple times;
* subsequent calls simply reset the counters. No heap memory is allocated
* by this function — the underlying allocator is the system @c malloc.
*
* @return 0 always (cannot fail on the hosted path)
*/
int sf_alloc_init(void)
{
memset(&g_stats, 0, sizeof(g_stats));
return 0;
}
/**
* @brief Allocate @c size bytes from the system heap.
*
* Delegates to @c malloc(). On success, increments @c g_stats.used_bytes
* and @c g_stats.alloc_count, and updates @c g_stats.peak_bytes if the
* new used total exceeds the recorded peak.
*
* @param size Number of bytes to allocate; 0 returns NULL
* @return Pointer to allocated memory, or NULL on allocation failure or zero size
*/
void* sf_malloc(size_t size)
{
if (size == 0) return NULL;
void* ptr = malloc(size);
if (ptr)
{
g_stats.used_bytes += size;
g_stats.alloc_count++;
if (g_stats.used_bytes > g_stats.peak_bytes)
{
g_stats.peak_bytes = g_stats.used_bytes;
}
}
return ptr;
}
/**
* @brief Allocate a zero-initialised array of @c count elements of @c size bytes each.
*
* Delegates to @c calloc(). Updates statistics the same way as @c sf_malloc():
* adds @c count*size to @c used_bytes, increments @c alloc_count, and updates
* @c peak_bytes.
*
* @param count Number of elements; 0 returns NULL
* @param size Size of each element in bytes; 0 returns NULL
* @return Pointer to zero-filled memory, or NULL on allocation failure or zero arguments
*/
void* sf_calloc(size_t count, size_t size)
{
if (count == 0 || size == 0) return NULL;
void* ptr = calloc(count, size);
if (ptr)
{
size_t total = count * size;
g_stats.used_bytes += total;
g_stats.alloc_count++;
if (g_stats.used_bytes > g_stats.peak_bytes)
{
g_stats.peak_bytes = g_stats.used_bytes;
}
}
return ptr;
}
/**
* @brief Resize a previously allocated block to @c new_size bytes.
*
* Delegates to @c realloc(). On success, increments @c g_stats.alloc_count.
* The exact byte delta is not tracked because the original allocation size is
* not stored alongside the pointer — @c used_bytes is therefore approximate
* after any @c sf_realloc() call. For accurate accounting, wrap allocations
* with size-tracking headers.
*
* @param ptr Pointer to an existing allocation, or NULL (behaves like @c sf_malloc)
* @param new_size Desired new size in bytes; 0 may free the block (platform-defined)
* @return Pointer to resized memory, or NULL on failure (original block unchanged)
*/
void* sf_realloc(void* ptr, size_t new_size)
{
void* new_ptr = realloc(ptr, new_size);
if (new_ptr)
{
/* Note: Can't track exact delta without size tracking */
g_stats.alloc_count++;
}
return new_ptr;
}
/**
* @brief Release a previously allocated block back to the system heap.
*
* Delegates to @c free(). Increments @c g_stats.free_count on non-NULL @c ptr.
* The freed byte count is not subtracted from @c used_bytes because the
* original size is not tracked — see @c sf_realloc() note.
*
* @param ptr Pointer to free; NULL is a safe no-op
*/
void sf_free(void* ptr)
{
if (!ptr) return;
/* Note: We can't track exact bytes freed without size tracking.
* For accurate stats, would need to wrap allocations with headers.
* For now, just count frees. */
g_stats.free_count++;
free(ptr);
}
/**
* @brief Copy the current allocator statistics into @c stats.
*
* Fills @c *stats with a snapshot of @c g_stats. Note that @c used_bytes
* overstates the live total when blocks have been freed or reallocated,
* because freed sizes are not tracked. @c free_count is accurate.
*
* @param stats Output buffer to populate; no-op if NULL
*/
void sf_alloc_get_stats(sf_alloc_stats_t* stats)
{
if (!stats) return;
*stats = g_stats;
}
+225
View File
@@ -0,0 +1,225 @@
/*
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.
*/
/*
* platform/alloc_kernel.c - Kernel (bare-metal) memory allocator
*
* Static arena with bump allocation. Free is a no-op.
* This is appropriate for kernel use where:
* - VM is long-lived (no restart)
* - Allocations happen at init time
* - Runtime allocations are rare
*
* Arena size: 4MB by default (configurable via SF_ARENA_SIZE)
*/
#include "../../include/platform_alloc.h"
#include <stdint.h>
#ifndef SF_ARENA_SIZE
#define SF_ARENA_SIZE (4 * 1024 * 1024) /* 4MB default */
#endif
/* Alignment for all allocations (8 bytes for 64-bit safety) */
#define SF_ALIGN 8
#define SF_ALIGN_UP(x) (((x) + (SF_ALIGN - 1)) & ~(SF_ALIGN - 1))
/* Static arena */
static uint8_t g_arena[SF_ARENA_SIZE] __attribute__((aligned(SF_ALIGN)));
static size_t g_arena_offset = 0;
static int g_initialized = 0;
/* Statistics */
static sf_alloc_stats_t g_stats = {0};
/**
* @brief Initialise the kernel bump allocator.
*
* Resets @c g_arena_offset to zero and clears all @c g_stats fields.
* Sets @c g_initialized so that @c sf_malloc() skips the lazy-init guard.
* Safe to call at any point; all prior allocations become invalid after a
* reset, so this must only be called before any VM allocation takes place.
*
* @return 0 always (cannot fail)
*/
int sf_alloc_init(void)
{
g_arena_offset = 0;
g_initialized = 1;
g_stats.total_bytes = SF_ARENA_SIZE;
g_stats.used_bytes = 0;
g_stats.peak_bytes = 0;
g_stats.alloc_count = 0;
g_stats.free_count = 0;
return 0;
}
/*
* @brief Allocate @c size bytes from the static kernel arena.
*
* Advances @c g_arena_offset by @c SF_ALIGN_UP(size). Returns a pointer
* directly into @c g_arena; the caller must not @c free() the result.
* Returns NULL (cast 0) when the arena is exhausted or @c size is zero.
* Lazy-calls @c sf_alloc_init() if the arena has not yet been initialised.
*
* @param size Number of bytes to allocate; 0 returns NULL
* @return Pointer into the static arena, or NULL on exhaustion or zero size
*/
void* sf_malloc(size_t size)
{
if (!g_initialized) sf_alloc_init();
if (size == 0) return (void*)0;
size_t aligned_size = SF_ALIGN_UP(size);
size_t new_offset = g_arena_offset + aligned_size;
if (new_offset > SF_ARENA_SIZE)
{
/* Out of memory */
return (void*)0;
}
void* ptr = &g_arena[g_arena_offset];
g_arena_offset = new_offset;
g_stats.used_bytes = g_arena_offset;
g_stats.alloc_count++;
if (g_stats.used_bytes > g_stats.peak_bytes)
{
g_stats.peak_bytes = g_stats.used_bytes;
}
return ptr;
}
/*
* @brief Allocate a zero-initialised block of @c count * @c size bytes from the arena.
*
* Delegates to @c sf_malloc() then manually zeroes the result with a byte loop
* (no @c memset dependency — avoids linking the C runtime in freestanding builds).
* Guards against @c count*size overflow by verifying @c total/size == count.
*
* @param count Number of elements; 0 returns NULL
* @param size Size of each element in bytes; 0 returns NULL
* @return Pointer to zero-filled arena memory, or NULL on overflow/exhaustion/zero args
*/
void* sf_calloc(size_t count, size_t size)
{
if (count == 0 || size == 0) return (void*)0;
size_t total = count * size;
/* Check for overflow */
if (size != 0 && total / size != count)
{
return (void*)0;
}
void* ptr = sf_malloc(total);
if (ptr)
{
/* Zero-initialize */
uint8_t* p = (uint8_t*)ptr;
for (size_t i = 0; i < total; i++)
{
p[i] = 0;
}
}
return ptr;
}
/*
* @brief Resize an allocation — bump-allocator approximation.
*
* Because the arena does not store block sizes, the old block cannot be
* reclaimed. This function allocates a fresh block of @c new_size bytes and
* returns it; the old block is orphaned in the arena. The caller is responsible
* for copying any content from @c ptr to the returned pointer before discarding
* @c ptr. If @c ptr is NULL, behaves identically to @c sf_malloc(new_size).
*
* @param ptr Existing allocation (orphaned after this call); NULL is safe
* @param new_size Desired size in bytes; 0 returns NULL
* @return Pointer to a new arena block of at least @c new_size bytes, or NULL
*/
void* sf_realloc(void* ptr, size_t new_size)
{
/*
* Bump allocator limitation: we don't track allocation sizes.
* For kernel use, realloc is rarely needed.
*
* Strategy: allocate new block, caller must copy if needed.
* This wastes memory but works for rare realloc cases.
*
* If ptr is NULL, this is equivalent to malloc.
*/
if (!ptr) return sf_malloc(new_size);
if (new_size == 0) return (void*)0;
/* Allocate new block - old block is orphaned (bump allocator limitation) */
return sf_malloc(new_size);
}
/*
* @brief Release a block — no-op for the bump allocator.
*
* The kernel arena has no reclaim mechanism; memory is held until the
* next @c sf_alloc_init() call (i.e. the next power cycle). Increments
* @c g_stats.free_count so the caller can observe that @c sf_free() was
* invoked, but no bytes are returned to the arena.
*
* @param ptr Block to "free"; NULL is a safe no-op
*/
void sf_free(void* ptr)
{
/* Bump allocator: free is a no-op.
*
* This is acceptable because:
* 1. VM allocations happen at init time
* 2. VM runs until power-off
* 3. No fragmentation issues in practice
*
* If needed, could implement a simple free list here.
*/
if (ptr)
{
g_stats.free_count++;
}
(void)ptr;
}
/*
* @brief Copy the current arena statistics into @c stats.
*
* Fills @c *stats with a snapshot of @c g_stats. @c total_bytes is fixed at
* @c SF_ARENA_SIZE; @c used_bytes equals the current arena offset (exact,
* since no reclaim occurs); @c free_count tracks @c sf_free() call count
* even though no memory is actually released.
*
* @param stats Output buffer to populate; no-op if NULL
*/
void sf_alloc_get_stats(sf_alloc_stats_t* stats)
{
if (!stats) return;
*stats = g_stats;
}
+6
View File
@@ -0,0 +1,6 @@
# src/platform/l4re/
- `time.c` — HISTORICAL: L4Re/Fiasco.OC timing backend implementing
`include/platform_time.h`. L4Re was a supported platform target through
mid-2026; it has been removed as an active target, and this file is no
longer wired into any build (hosted or kernel).
+300
View File
@@ -0,0 +1,300 @@
/*
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.
*/
/*
* StarForth Platform Time - L4Re/StarshipOS Backend
*
* HISTORICAL: L4Re/Fiasco.OC was a supported platform target through
* mid-2026 (`make L4RE=1`). Removed as an active target -- no Makefile
* defines __l4__ anymore, so the entire body of this file is unreachable
* dead code. Retained for reference; see Makefile's "Platform support"
* section for the commented-out restoration path if L4Re support is ever
* revived.
*
* Implements platform time abstraction using L4Re RTC server and KIP clock.
*
* License: See LICENSE file
*/
#include "../../../include/platform_time.h"
#ifdef __l4__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* L4Re headers */
#include <l4/sys/kip.h>
#include <l4/re/env.h>
#include <l4/rtc/rtc>
/* L4Re RTC capability (obtained at init) */
static L4::Cap<L4rtc::Rtc> rtc_cap = L4::Cap<L4rtc::Rtc>::Invalid;
static l4_uint64_t rtc_offset = 0; /* nanoseconds from 1970 to boot */
static int rtc_available = 0;
/* L4Re implementation */
/**
* @brief Return the current monotonic time in nanoseconds (L4Re build).
*
* Reads the KIP (Kernel Info Page) clock via @c l4_kip_clock_ns(). The KIP
* clock is a monotonic counter driven by the L4Re microkernel and is the
* standard high-resolution time source on L4Re. Returns 0 if the KIP pointer
* is unavailable (should never occur in a healthy L4Re environment).
*
* @return Nanoseconds since the L4Re epoch (system boot), or 0 on KIP error
*/
static sf_time_ns_t l4re_get_monotonic_ns(void) {
/* Use L4Re KIP clock: monotonic time since boot */
l4_kernel_info_t *kip = l4re_kip();
if (!kip) {
return 0; /* Fallback if KIP unavailable */
}
return l4_kip_clock_ns(kip);
}
/**
* @brief Return the current wall-clock time in nanoseconds since the Unix epoch (L4Re build).
*
* Combines the RTC offset (nanoseconds from epoch to system boot, obtained
* from the L4Re RTC server during @c sf_time_init_l4re()) with the current
* KIP clock value to produce an absolute timestamp. Falls back to monotonic
* time alone when @c rtc_available is 0, and to @c rtc_offset alone when
* the KIP pointer is unavailable.
*
* @return Nanoseconds since 1970-01-01 00:00:00 UTC, or approximate value on fallback
*/
static sf_time_ns_t l4re_get_realtime_ns(void) {
/* Use RTC server offset + KIP clock for wall-clock time */
if (!rtc_available) {
/* No RTC: return monotonic time (better than nothing) */
return l4re_get_monotonic_ns();
}
/* Real-time = RTC offset (ns since epoch to boot) + KIP clock (ns since boot) */
l4_kernel_info_t *kip = l4re_kip();
if (!kip) {
return rtc_offset; /* Fallback to RTC offset only */
}
return rtc_offset + l4_kip_clock_ns(kip);
}
/**
* @brief Set the system real-time clock via the L4Re RTC server (L4Re build).
*
* Computes the new RTC timer offset as @c ns_since_epoch minus the current
* KIP clock value, then calls @c rtc_cap->set_timer_offset() over IPC.
* On success, updates the cached @c rtc_offset. Returns -1 if the RTC
* capability is invalid, the KIP pointer is unavailable, or the IPC call
* fails.
*
* @param ns_since_epoch Desired wall-clock time in nanoseconds since Unix epoch
* @return 0 on success, -1 on any failure (no RTC capability, KIP error, IPC error)
*/
static int l4re_set_realtime_ns(sf_time_ns_t ns_since_epoch) {
if (!rtc_available || !rtc_cap.is_valid()) {
return -1; /* No RTC capability */
}
/* Calculate new offset: desired epoch time - current KIP clock */
l4_kernel_info_t *kip = l4re_kip();
if (!kip) {
return -1;
}
l4_uint64_t kip_ns = l4_kip_clock_ns(kip);
l4_uint64_t new_offset = ns_since_epoch - kip_ns;
/* Set RTC offset via IPC */
long ret = rtc_cap->set_timer_offset(new_offset);
if (ret < 0) {
return -1;
}
/* Update cached offset */
rtc_offset = new_offset;
return 0;
}
/**
* @brief Format a nanosecond-precision wall-clock time as a human-readable string (L4Re build).
*
* Converts @c ns_since_epoch to a @c time_t, calls @c localtime() and
* @c strftime() using the L4Re libc. Unlike the Linux backend, this variant
* uses a full date+time format (@c "%Y-%m-%d %H:%M:%S" or @c "%Y-%m-%d %I:%M:%S %p")
* rather than time-only. On @c localtime() failure, writes the raw second count as
* a decimal fallback and returns -1.
*
* @param ns_since_epoch Wall-clock time in nanoseconds since Unix epoch
* @param buf Output buffer (at least @c SF_TIME_STAMP_SIZE bytes); must not be NULL
* @param format_24h Non-zero for 24-hour format, zero for 12-hour with AM/PM
* @return 0 on success, -1 if @c buf is NULL or time formatting fails
*/
static int l4re_format_timestamp(sf_time_ns_t ns_since_epoch, char *buf, int format_24h) {
if (!buf) {
return -1;
}
/* Convert nanoseconds to seconds for libc time functions */
time_t seconds = (time_t)(ns_since_epoch / 1000000000ULL);
/* Use L4Re libc localtime + strftime */
struct tm *tm_info = localtime(&seconds);
if (!tm_info) {
/* Fallback: show raw seconds */
snprintf(buf, SF_TIME_STAMP_SIZE, "%llu", (unsigned long long) seconds);
return -1;
}
/* Format timestamp based on 24h preference */
const char *format = format_24h ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d %I:%M:%S %p";
size_t ret = strftime(buf, SF_TIME_STAMP_SIZE, format, tm_info);
return (ret > 0) ? 0 : -1;
}
/**
* @brief Report whether an RTC is available (L4Re build).
*
* Returns the @c rtc_available flag set during @c sf_time_init_l4re().
* A value of 0 means the RTC IPC capability was absent or its offset query
* failed; wall-clock functions will return monotonic time as a fallback.
*
* @return 1 if the L4Re RTC server is reachable and its offset was obtained, 0 otherwise
*/
static int l4re_has_rtc(void) {
return rtc_available;
}
/* L4Re backend vtable */
const sf_time_backend_t sf_time_backend_l4re = {
.get_monotonic_ns = l4re_get_monotonic_ns,
.get_realtime_ns = l4re_get_realtime_ns,
.set_realtime_ns = l4re_set_realtime_ns,
.format_timestamp = l4re_format_timestamp,
.has_rtc = l4re_has_rtc,
};
/**
* @brief Initialise the L4Re time backend — acquire RTC capability and fetch epoch offset.
*
* Called once from @c sf_time_init() on L4Re builds. Attempts to obtain the
* @c "rtc" capability from the L4Re environment, then queries the RTC server
* for its timer offset (nanoseconds from the Unix epoch to the system boot
* time). On success sets @c rtc_available = 1 and caches @c rtc_offset.
* On any failure (missing capability, IPC error) sets @c rtc_available = 0
* and @c rtc_offset = 0, leaving the backend in monotonic-only mode.
* Diagnostic messages are printed to @c stderr throughout.
*
* @note Defined only when @c __l4__ is set; compiles to a no-op stub otherwise.
*/
void sf_time_init_l4re(void) {
fprintf(stderr, "[L4Re Time] Initializing L4Re time backend...\n");
/* Try to get RTC capability from L4Re environment */
rtc_cap = L4Re::Env::env()->get_cap<L4rtc::Rtc>("rtc");
if (!rtc_cap.is_valid()) {
fprintf(stderr, "[L4Re Time] WARNING: RTC capability not available\n");
fprintf(stderr, "[L4Re Time] Falling back to monotonic-only mode\n");
fprintf(stderr, "[L4Re Time] Real-time will use KIP clock (time since boot)\n");
rtc_available = 0;
rtc_offset = 0;
return;
}
/* RTC capability acquired successfully */
fprintf(stderr, "[L4Re Time] RTC capability acquired\n");
/* Query the RTC timer offset (nanoseconds from epoch to boot) */
long ret = rtc_cap->get_timer_offset(&rtc_offset);
if (ret < 0) {
fprintf(stderr, "[L4Re Time] ERROR: Failed to get RTC timer offset (ret=%ld)\n", ret);
fprintf(stderr, "[L4Re Time] RTC may not be initialized or running\n");
fprintf(stderr, "[L4Re Time] Setting offset to 0 (will show time since boot as epoch)\n");
rtc_available = 0;
rtc_offset = 0;
return;
}
/* Success! */
rtc_available = 1;
fprintf(stderr, "[L4Re Time] RTC offset retrieved: %llu ns since epoch\n",
(unsigned long long) rtc_offset);
/* Convert offset to a readable date for verification */
time_t boot_time_sec = (time_t)(rtc_offset / 1000000000ULL);
struct tm *boot_tm = gmtime(&boot_time_sec);
if (boot_tm) {
char boot_str[64];
strftime(boot_str, sizeof(boot_str), "%Y-%m-%d %H:%M:%S UTC", boot_tm);
fprintf(stderr, "[L4Re Time] System boot time: %s\n", boot_str);
}
/* Display current time */
l4_kernel_info_t *kip = l4re_kip();
if (kip) {
l4_uint64_t now_ns = rtc_offset + l4_kip_clock_ns(kip);
time_t now_sec = (time_t)(now_ns / 1000000000ULL);
struct tm *now_tm = gmtime(&now_sec);
if (now_tm) {
char now_str[64];
strftime(now_str, sizeof(now_str), "%Y-%m-%d %H:%M:%S UTC", now_tm);
fprintf(stderr, "[L4Re Time] Current time: %s\n", now_str);
}
}
fprintf(stderr, "[L4Re Time] Initialization complete\n");
}
#else
/* Dummy implementation when not building for L4Re */
const sf_time_backend_t sf_time_backend_l4re = {0};
void sf_time_init_l4re(void) {
}
#endif /* __l4__ */
+4
View File
@@ -0,0 +1,4 @@
# src/platform/linux/
- `time.c` — POSIX timing backend (`clock_gettime`) for the hosted Linux
build; implements `include/platform_time.h`.
+49
View File
@@ -0,0 +1,49 @@
/*
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 Platform IO - Linux/POSIX Backend
*
* Implements platform_io.h's terminal-readiness check using select() on
* fd 0, the standard POSIX non-blocking-stdin-peek idiom. Zero timeout:
* a single poll, never blocks.
*
* License: See LICENSE file
*/
#include "../../../include/platform_io.h"
#include <sys/select.h>
#include <unistd.h>
int sf_terminal_ready(void) {
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
int r = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
return (r > 0 && FD_ISSET(STDIN_FILENO, &readfds)) ? 1 : 0;
}
+172
View File
@@ -0,0 +1,172 @@
/*
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.
*/
/*
* StarForth Platform Time - Linux/POSIX Backend
*
* Implements platform time abstraction using POSIX clock_gettime().
*
* License: See LICENSE file
*/
#define _POSIX_C_SOURCE 199309L
#include "../../../include/platform_time.h"
#include <stdio.h>
#include <time.h>
#include <string.h>
/* POSIX implementation */
/**
* @brief Return the current monotonic time in nanoseconds.
*
* Reads @c CLOCK_MONOTONIC via @c clock_gettime(). The clock advances
* continuously and is unaffected by wall-clock adjustments — suitable for
* measuring elapsed durations such as execution latency and decay intervals.
* Returns 0 on @c clock_gettime() failure (should not occur on any modern
* POSIX kernel).
*
* @return Nanoseconds since an arbitrary but fixed epoch, or 0 on error
*/
static sf_time_ns_t posix_get_monotonic_ns(void) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return 0;
return (sf_time_ns_t) ts.tv_sec * 1000000000ULL + (sf_time_ns_t) ts.tv_nsec;
}
/**
* @brief Return the current wall-clock time in nanoseconds since the Unix epoch.
*
* Reads @c CLOCK_REALTIME via @c clock_gettime(). Subject to NTP or @c settimeofday()
* adjustments — use @c posix_get_monotonic_ns() for interval timing.
* Returns 0 on @c clock_gettime() failure.
*
* @return Nanoseconds since 1970-01-01 00:00:00 UTC, or 0 on error
*/
static sf_time_ns_t posix_get_realtime_ns(void) {
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
return 0;
return (sf_time_ns_t) ts.tv_sec * 1000000000ULL + (sf_time_ns_t) ts.tv_nsec;
}
/**
* @brief Set the system real-time clock to @c ns_since_epoch.
*
* Converts @c ns_since_epoch to a @c struct timespec and calls
* @c clock_settime(CLOCK_REALTIME). Requires @c CAP_SYS_TIME or equivalent
* privilege; fails with @c EPERM if the caller lacks permission.
*
* @param ns_since_epoch Desired new wall-clock time in nanoseconds since Unix epoch
* @return 0 on success, non-zero errno-encoded error on failure
*/
static int posix_set_realtime_ns(sf_time_ns_t ns_since_epoch) {
struct timespec ts;
ts.tv_sec = (time_t)(ns_since_epoch / 1000000000ULL);
ts.tv_nsec = (long) (ns_since_epoch % 1000000000ULL);
return clock_settime(CLOCK_REALTIME, &ts);
}
/**
* @brief Format a nanosecond-precision wall-clock time as a human-readable string.
*
* Converts @c ns_since_epoch to local time, formats hours/minutes/seconds via
* @c strftime(), then appends a three-digit millisecond suffix. The result is
* written into @c buf (must be at least @c SF_TIME_STAMP_SIZE bytes). On
* @c localtime() or @c strftime() failure, writes @c "??:??:??.???" into @c buf
* and returns -1.
*
* @param ns_since_epoch Wall-clock time in nanoseconds since Unix epoch
* @param buf Output buffer (at least @c SF_TIME_STAMP_SIZE bytes); must not be NULL
* @param format_24h Non-zero for 24-hour format (@c %H:%M:%S), zero for 12-hour (@c %I:%M:%S %p)
* @return 0 on success, -1 if @c buf is NULL or time formatting fails
*/
static int posix_format_timestamp(sf_time_ns_t ns_since_epoch, char *buf, int format_24h) {
if (!buf)
return -1;
time_t seconds = (time_t)(ns_since_epoch / 1000000000ULL);
unsigned int ms = (unsigned int)((ns_since_epoch % 1000000000ULL) / 1000000ULL);
struct tm *tm_info = localtime(&seconds);
if (!tm_info) {
strncpy(buf, "??:??:??.???", SF_TIME_STAMP_SIZE - 1);
buf[SF_TIME_STAMP_SIZE - 1] = '\0';
return -1;
}
char hms[16];
const char *format = format_24h ? "%H:%M:%S" : "%I:%M:%S %p";
if (strftime(hms, sizeof(hms), format, tm_info) == 0) {
strncpy(buf, "??:??:??.???", SF_TIME_STAMP_SIZE - 1);
buf[SF_TIME_STAMP_SIZE - 1] = '\0';
return -1;
}
snprintf(buf, SF_TIME_STAMP_SIZE, "%s.%03u", hms, ms);
return 0;
}
/**
* @brief Probe whether a real-time clock is available.
*
* Attempts a @c clock_gettime(CLOCK_REALTIME) probe. POSIX systems invariably
* support @c CLOCK_REALTIME so this will return 1 in practice; the check
* mirrors the bare-metal backend's interface where RTC presence is genuinely
* uncertain.
*
* @return 1 if @c CLOCK_REALTIME is readable, 0 otherwise
*/
static int posix_has_rtc(void) {
/* POSIX systems typically have working CLOCK_REALTIME */
struct timespec ts;
return (clock_gettime(CLOCK_REALTIME, &ts) == 0) ? 1 : 0;
}
/* POSIX backend vtable */
const sf_time_backend_t sf_time_backend_posix = {
.get_monotonic_ns = posix_get_monotonic_ns,
.get_realtime_ns = posix_get_realtime_ns,
.set_realtime_ns = posix_set_realtime_ns,
.format_timestamp = posix_format_timestamp,
.has_rtc = posix_has_rtc,
};
+85
View File
@@ -0,0 +1,85 @@
/*
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.
*/
/*
* StarForth Platform Initialization
*
* Selects and initializes the appropriate platform backend based on build configuration.
*
* License: See LICENSE file
*/
#include "../../include/platform_time.h"
/* Global backend pointer (starts as NULL) */
const sf_time_backend_t *sf_time_backend = NULL;
/* HISTORICAL: L4Re-specific init (defined in src/platform/l4re/time.c,
* retained but no longer wired into any build). L4Re support has been
* removed as an active target.
* #ifdef __l4__
* extern void sf_time_init_l4re(void);
* #endif
*/
/**
* @brief Initialize platform time subsystem
*
* Selects the POSIX backend (clock_gettime()). Call this once at program
* startup before using any time functions.
*/
void sf_time_init(void) {
if (sf_time_backend != NULL)
return; /* Already initialized */
/* HISTORICAL: an #ifdef __l4__ branch here called sf_time_init_l4re()
* and selected sf_time_backend_l4re. L4Re support has been removed as
* an active target.
* #ifdef __l4__
* sf_time_init_l4re();
* sf_time_backend = &sf_time_backend_l4re;
* #else
*/
/* POSIX build */
sf_time_backend = &sf_time_backend_posix;
/* #endif -- matches the commented-out #ifdef __l4__ above */
}
+202
View File
@@ -0,0 +1,202 @@
/*
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.
*/
/*
* platform/threading.c - Minimal mutex abstraction bridging POSIX and
* embedded (spinlock-based) targets
*/
#include "../../include/platform_lock.h"
/* HISTORICAL: these two conditions also referenced L4RE_TARGET
* (`&& !defined(L4RE_TARGET)` / `|| defined(L4RE_TARGET)`) -- L4Re had no
* pthreads either, so it shared the spinlock path with STARFORTH_MINIMAL.
* L4Re support has been removed as an active target. */
#if !defined(STARFORTH_MINIMAL)
#include <pthread.h>
#endif
#if defined(STARFORTH_MINIMAL)
/* Spinlock-based mutex for minimal/embedded builds */
/**
* @brief Initialise a spinlock mutex (minimal build).
*
* Sets @c mutex->state to 0 (unlocked). Safe to call on a mutex that has
* already been destroyed. On embedded targets where @c pthread is unavailable,
* this replaces @c pthread_mutex_init().
*
* @param mutex Mutex to initialise; returns -1 if NULL
* @return 0 on success, -1 if @c mutex is NULL
*/
int sf_mutex_init(sf_mutex_t *mutex)
{
if (!mutex) return -1;
mutex->state = 0;
return 0;
}
/**
* @brief Destroy a spinlock mutex (minimal build).
*
* Resets @c mutex->state to 0. No resources are released because the
* spinlock uses only the single @c state field. Safe to call on an
* already-destroyed mutex.
*
* @param mutex Mutex to destroy; no-op if NULL
*/
void sf_mutex_destroy(sf_mutex_t *mutex)
{
if (!mutex) return;
mutex->state = 0;
}
/**
* @brief Acquire a spinlock mutex (minimal build).
*
* Busy-waits using @c __sync_lock_test_and_set() until the lock is obtained.
* Issues an architecture-appropriate yield hint (@c PAUSE on x86,
* @c YIELD on ARM/AArch64, @c NOP on RISC-V) inside the spin loop to
* reduce power consumption and SMT contention.
*
* @param mutex Mutex to lock; no-op if NULL
*/
void sf_mutex_lock(sf_mutex_t *mutex)
{
if (!mutex) return;
while (__sync_lock_test_and_set(&mutex->state, 1))
{
/* CPU yield to reduce power consumption and improve performance */
#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)
/* x86/x86_64: PAUSE instruction reduces pipeline flush penalty */
__asm__ __volatile__("pause" ::: "memory");
#elif defined(__aarch64__) || defined(__arm__)
/* ARM/AArch64: YIELD hint for SMT threads */
__asm__ __volatile__("yield" ::: "memory");
#elif defined(__riscv)
/* RISC-V: NOP as yield hint */
__asm__ __volatile__("nop" ::: "memory");
#else
/* Fallback: compiler barrier prevents optimization */
__asm__ __volatile__("" ::: "memory");
#endif
}
}
/**
* @brief Release a spinlock mutex (minimal build).
*
* Clears the lock state atomically using @c __sync_lock_release(), which
* implies a full memory barrier on all supported architectures. Threads
* blocked in @c sf_mutex_lock() will observe the release on their next
* test-and-set iteration.
*
* @param mutex Mutex to unlock; no-op if NULL
*/
void sf_mutex_unlock(sf_mutex_t *mutex)
{
if (!mutex) return;
__sync_lock_release(&mutex->state);
}
#else /* POSIX */
/**
* @brief Initialise a POSIX mutex (hosted build).
*
* Delegates to @c pthread_mutex_init() with default attributes.
*
* @param mutex Mutex to initialise; returns -1 if NULL
* @return 0 on success, -1 if @c mutex is NULL, or a positive @c errno value
* propagated from @c pthread_mutex_init()
*/
int sf_mutex_init(sf_mutex_t *mutex)
{
if (!mutex) return -1;
return pthread_mutex_init(&mutex->handle, NULL);
}
/**
* @brief Destroy a POSIX mutex (hosted build).
*
* Delegates to @c pthread_mutex_destroy(). The mutex must not be locked
* when destroyed. Return value is discarded; callers that need the error
* code should call @c pthread_mutex_destroy() directly.
*
* @param mutex Mutex to destroy; no-op if NULL
*/
void sf_mutex_destroy(sf_mutex_t *mutex)
{
if (!mutex) return;
pthread_mutex_destroy(&mutex->handle);
}
/**
* @brief Acquire a POSIX mutex (hosted build).
*
* Delegates to @c pthread_mutex_lock(). Blocks until the mutex is available.
* Return value is discarded; a locking error will manifest as a deadlock or
* data race, not a silent failure.
*
* @param mutex Mutex to lock; no-op if NULL
*/
void sf_mutex_lock(sf_mutex_t *mutex)
{
if (!mutex) return;
pthread_mutex_lock(&mutex->handle);
}
/**
* @brief Release a POSIX mutex (hosted build).
*
* Delegates to @c pthread_mutex_unlock(). Must be called by the same thread
* that acquired the lock. Return value is discarded.
*
* @param mutex Mutex to unlock; no-op if NULL
*/
void sf_mutex_unlock(sf_mutex_t *mutex)
{
if (!mutex) return;
pthread_mutex_unlock(&mutex->handle);
}
#endif