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,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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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__ */
|
||||
@@ -0,0 +1,4 @@
|
||||
# src/platform/linux/
|
||||
|
||||
- `time.c` — POSIX timing backend (`clock_gettime`) for the hosted Linux
|
||||
build; implements `include/platform_time.h`.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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 */
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user