Files
LithosAnanake/kernel/src/log_region.c
T
rajamesandJunie a8b70e88d3 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>
2026-10-01 15:40:09 -04:00

179 lines
7.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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.
*/
/**
* log_region.c - Growable per-VM log-persistence ring on Artemis's disk.
* See starkernel/log_region.h for the format/interface design.
*
* Deliberately never calls log_message() (or anything documented to call
* it) -- this runs inside Artemis's own dictionary context during message
* delivery (LOG-APPEND, dispatched by a sender's MSG-SEND/MSG-TICK); a log
* call here would append to Artemis's own buffer and eventually flush back
* through this same path.
*/
#include "starkernel/log_region.h"
#include <stddef.h>
#include <string.h>
#include "block_subsystem.h" /* blk_meta_zone_read/write, compute_crc64 */
static uint64_t log_region_ctrl_compute_crc(const log_region_ctrl_t *ctrl) {
size_t crc_span = offsetof(log_region_ctrl_t, hdr_crc);
return compute_crc64((const uint8_t *)ctrl, crc_span);
}
/* Reads the control header; returns 0 and *out populated only if magic,
* version, and CRC all check out. Any other result (blank, foreign,
* corrupt, or a read failure) is treated identically by the caller: "not
* yet initialized," matching genesis_marker_read()'s own discipline. */
static int log_region_ctrl_read(log_region_ctrl_t *out) {
if (blk_meta_zone_read(LOG_REGION_DEVBLOCK_FROM_TOP_BASE, (uint8_t *)out) != 0) return -1;
if (LOG_REGION_GET_MAGIC(out->magic) != (uint32_t)(LOG_REGION_MAGIC & 0xFFFFFFFFull)) return -1;
if (LOG_REGION_GET_VERSION(out->magic) != LOG_REGION_VERSION_0) return -1;
uint64_t want_crc = log_region_ctrl_compute_crc(out);
if (want_crc != out->hdr_crc) return -1;
return 0;
}
static int log_region_ctrl_write(log_region_ctrl_t *ctrl) {
ctrl->hdr_crc = log_region_ctrl_compute_crc(ctrl);
return blk_meta_zone_write(LOG_REGION_DEVBLOCK_FROM_TOP_BASE, (const uint8_t *)ctrl) == 0 ? 0 : -1;
}
static void log_region_ctrl_init_fresh(log_region_ctrl_t *ctrl) {
memset(ctrl, 0, sizeof(*ctrl));
ctrl->magic = LOG_REGION_PACK(LOG_REGION_VERSION_0);
ctrl->devblocks = LOG_REGION_INITIAL_DEVBLOCKS;
ctrl->head_slot = 0;
ctrl->tail_slot = 0;
ctrl->record_count = 0;
}
/* Reads/writes the devblock holding slot_index (LOG_SLOTS_PER_DEVBLOCK
* slots per devblock), one devblock_from_top past the control header for
* every LOG_SLOTS_PER_DEVBLOCK slots. */
static uint32_t log_region_slot_devblock_from_top(uint32_t slot_index) {
return LOG_REGION_DEVBLOCK_FROM_TOP_BASE + 1u + (slot_index / LOG_SLOTS_PER_DEVBLOCK);
}
static int log_region_read_slot(uint32_t slot_index, log_slot_t *out) {
uint8_t devblock_buf[4096];
uint32_t dft = log_region_slot_devblock_from_top(slot_index);
uint32_t slot_in_devblock = slot_index % LOG_SLOTS_PER_DEVBLOCK;
if (blk_meta_zone_read(dft, devblock_buf) != 0) return -1;
memcpy(out, devblock_buf + (size_t)slot_in_devblock * LOG_SLOT_SIZE, sizeof(*out));
return 0;
}
static int log_region_write_slot(uint32_t slot_index, const log_slot_t *slot) {
uint8_t devblock_buf[4096];
uint32_t dft = log_region_slot_devblock_from_top(slot_index);
uint32_t slot_in_devblock = slot_index % LOG_SLOTS_PER_DEVBLOCK;
/* Read-modify-write: blk_meta_zone_*() only operates at whole-devblock
* granularity, and a devblock holds LOG_SLOTS_PER_DEVBLOCK slots, so a
* single-slot write must preserve its siblings. A read failure on a
* never-yet-written devblock (blank fence content) is not fatal here --
* proceed with a zeroed buffer, matching zuse_eligibility.c's own
* "blank fence content is indistinguishable from absent, treat as
* absent" discipline. */
if (blk_meta_zone_read(dft, devblock_buf) != 0) {
memset(devblock_buf, 0, sizeof(devblock_buf));
}
memcpy(devblock_buf + (size_t)slot_in_devblock * LOG_SLOT_SIZE, slot, sizeof(*slot));
return blk_meta_zone_write(dft, devblock_buf) == 0 ? 0 : -1;
}
int log_region_append(uint8_t level, uint64_t timestamp,
const char *source, uint32_t source_len,
const char *msg, uint32_t msg_len) {
log_region_ctrl_t ctrl;
if (log_region_ctrl_read(&ctrl) != 0) {
log_region_ctrl_init_fresh(&ctrl);
}
uint32_t total_slots = ctrl.devblocks * LOG_SLOTS_PER_DEVBLOCK;
if (ctrl.record_count >= total_slots) {
if (ctrl.devblocks < LOG_REGION_MAX_DEVBLOCKS) {
/* Grow: new slots appear at indices [old_total_slots ..
* new_total_slots-1], nothing already written moves -- safe
* to do at any point in the ring's lifecycle regardless of
* wrap state, since head/tail are slot INDICES modulo
* total_slots, and the modulus is simply larger from here on. */
ctrl.devblocks += LOG_REGION_GROWTH_INCREMENT;
if (ctrl.devblocks > LOG_REGION_MAX_DEVBLOCKS) ctrl.devblocks = LOG_REGION_MAX_DEVBLOCKS;
total_slots = ctrl.devblocks * LOG_SLOTS_PER_DEVBLOCK;
}
if (ctrl.record_count >= total_slots) {
/* Still full (growth ceiling already reached) -- evict oldest,
* unless doing so would discard higher-priority history for a
* lower-priority newcomer (FABRIC-3.md §XXXII.4, 2026-09-15:
* plain FIFO eviction here was flagged as level-blind -- a
* flood of INFO/DEBUG noise could push out real ERROR/WARN
* records). Read the oldest slot's own level before evicting:
* if it's ERROR/WARN and the incoming record is not, protect
* it -- drop the incoming record instead of evicting real
* history. Any other combination (oldest is already low
* priority, or the incoming record is itself ERROR/WARN)
* evicts exactly as before. A read failure on the oldest slot
* (should not happen -- it was written by this same function)
* falls back to the prior unconditional-eviction behavior
* rather than wedging the ring. */
log_slot_t oldest;
int oldest_protected = 0;
if (log_region_read_slot(ctrl.head_slot, &oldest) == 0) {
oldest_protected = (oldest.level <= LOG_REGION_PROTECTED_MAX_LEVEL);
}
int incoming_protected = (level <= LOG_REGION_PROTECTED_MAX_LEVEL);
if (oldest_protected && !incoming_protected) {
return 1; /* dropped by design, not a failure -- see header */
}
ctrl.head_slot = (ctrl.head_slot + 1u) % total_slots;
ctrl.record_count--;
}
}
log_slot_t slot;
memset(&slot, 0, sizeof(slot));
slot.timestamp = timestamp;
slot.level = level;
uint32_t src_n = source_len;
if (src_n > LOG_SLOT_SOURCE_MAX) src_n = LOG_SLOT_SOURCE_MAX;
memcpy(slot.source, source, src_n);
uint32_t msg_n = msg_len;
if (msg_n > LOG_SLOT_MSG_MAX) msg_n = LOG_SLOT_MSG_MAX;
memcpy(slot.msg, msg, msg_n);
slot.msg_len = (uint16_t)msg_n;
if (log_region_write_slot(ctrl.tail_slot, &slot) != 0) return -1;
ctrl.tail_slot = (ctrl.tail_slot + 1u) % total_slots;
if (ctrl.record_count < total_slots) ctrl.record_count++;
return log_region_ctrl_write(&ctrl);
}