Initial commit
Signed-off-by: Robert Allan James <robert.allan.james@gmail.com>
This commit is contained in:
+1
-1
@@ -211,7 +211,7 @@ Output: `build/<arch>/kernel/starkernel_loader.efi` + `build/<arch>/kernel/stark
|
|||||||
Two independently tracked version strings flow into the generated `include/version.h`:
|
Two independently tracked version strings flow into the generated `include/version.h`:
|
||||||
`VERSION` (`Makefile.starkernel` — the embedded StarForth engine version, currently `3.1.0`;
|
`VERSION` (`Makefile.starkernel` — the embedded StarForth engine version, currently `3.1.0`;
|
||||||
note this does **not** auto-sync with the standalone StarForth repo's own version) and
|
note this does **not** auto-sync with the standalone StarForth repo's own version) and
|
||||||
`LITHOS_VERSION` (`Makefile.starkernel` — the kernel version, currently `2.0.0`).
|
`LITHOS_VERSION` (`Makefile.starkernel` — the kernel version, currently `2.0.1`).
|
||||||
|
|
||||||
### Build configuration (Kconfig — real, wired, not vestigial)
|
### Build configuration (Kconfig — real, wired, not vestigial)
|
||||||
|
|
||||||
|
|||||||
+54
-4
@@ -76,9 +76,11 @@ endif
|
|||||||
# v1.0.x — serial-only production (released)
|
# v1.0.x — serial-only production (released)
|
||||||
# v1.5.x — framebuffer VT100 terminal/console milestone (released)
|
# v1.5.x — framebuffer VT100 terminal/console milestone (released)
|
||||||
# v2.0.0 — QEMU release (even major = LTS): three-arch QEMU story complete
|
# v2.0.0 — QEMU release (even major = LTS): three-arch QEMU story complete
|
||||||
|
# v2.0.1 — SER5 hardware-track line: RDRAND backend + generic thumbdrive image goal
|
||||||
|
# v2.2.0 — amd64 bare-metal (Beelink SER5) — see ROADMAP "Board-by-board rollout"
|
||||||
# v2.5.0 — hardware bare-metal release: real per-arch RNG + real-board boot
|
# v2.5.0 — hardware bare-metal release: real per-arch RNG + real-board boot
|
||||||
VERSION ?= 3.1.0
|
VERSION ?= 3.1.0
|
||||||
LITHOS_VERSION ?= 2.0.0
|
LITHOS_VERSION ?= 2.0.1
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# BUILD PATHS
|
# BUILD PATHS
|
||||||
@@ -556,7 +558,7 @@ KERNEL_OBJS := \
|
|||||||
|
|
||||||
.PHONY: all clean clean-kernel kernel kernel-all
|
.PHONY: all clean clean-kernel kernel kernel-all
|
||||||
.PHONY: qemu qemu-esp qemu-gdb
|
.PHONY: qemu qemu-esp qemu-gdb
|
||||||
.PHONY: thumbdrive
|
.PHONY: thumbdrive iso-usb
|
||||||
.PHONY: info help
|
.PHONY: info help
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -1022,16 +1024,23 @@ thumbdrive: all
|
|||||||
riscv64) BOOTNAME="BOOTRISCV64.EFI" ;; \
|
riscv64) BOOTNAME="BOOTRISCV64.EFI" ;; \
|
||||||
*) echo "Error: no thumbdrive EFI boot name for ARCH=$(ARCH)"; exit 1 ;; \
|
*) echo "Error: no thumbdrive EFI boot name for ARCH=$(ARCH)"; exit 1 ;; \
|
||||||
esac; \
|
esac; \
|
||||||
|
# Geometry: 128 MiB disk (262144 sectors), GPT partition 1 spans sectors \
|
||||||
|
# 2048..262110 (the GPT last-usable sector for this disk size), i.e. \
|
||||||
|
# 260063 sectors. The FAT32 ESP image MUST match the partition size \
|
||||||
|
# exactly; a larger ESP overruns the disk (GPT grows corrupt) or \
|
||||||
|
# spills past the partition end, both of which made earlier builds \
|
||||||
|
# unbootable on hardware. \
|
||||||
DISK=$(BUILD_DIR)/starkernel-thumbdrive.img; \
|
DISK=$(BUILD_DIR)/starkernel-thumbdrive.img; \
|
||||||
|
ESP_SECTORS=260063; \
|
||||||
rm -f $$DISK $(BUILD_DIR)/thumbdrive-esp.img; \
|
rm -f $$DISK $(BUILD_DIR)/thumbdrive-esp.img; \
|
||||||
dd if=/dev/zero of=$(BUILD_DIR)/thumbdrive-esp.img bs=512 count=131072 2>/dev/null; \
|
dd if=/dev/zero of=$(BUILD_DIR)/thumbdrive-esp.img bs=512 count=$$ESP_SECTORS 2>/dev/null; \
|
||||||
mkfs.fat -F 32 -n "STARKERNEL" $(BUILD_DIR)/thumbdrive-esp.img >/dev/null 2>&1; \
|
mkfs.fat -F 32 -n "STARKERNEL" $(BUILD_DIR)/thumbdrive-esp.img >/dev/null 2>&1; \
|
||||||
mmd -i $(BUILD_DIR)/thumbdrive-esp.img ::/EFI; \
|
mmd -i $(BUILD_DIR)/thumbdrive-esp.img ::/EFI; \
|
||||||
mmd -i $(BUILD_DIR)/thumbdrive-esp.img ::/EFI/BOOT; \
|
mmd -i $(BUILD_DIR)/thumbdrive-esp.img ::/EFI/BOOT; \
|
||||||
mcopy -i $(BUILD_DIR)/thumbdrive-esp.img $(LOADER_EFI) ::/EFI/BOOT/$$BOOTNAME; \
|
mcopy -i $(BUILD_DIR)/thumbdrive-esp.img $(LOADER_EFI) ::/EFI/BOOT/$$BOOTNAME; \
|
||||||
printf 'FS0:\\EFI\\BOOT\\%s\r\n' "$$BOOTNAME" > $(BUILD_DIR)/thumbdrive-startup.nsh; \
|
printf 'FS0:\\EFI\\BOOT\\%s\r\n' "$$BOOTNAME" > $(BUILD_DIR)/thumbdrive-startup.nsh; \
|
||||||
mcopy -i $(BUILD_DIR)/thumbdrive-esp.img $(BUILD_DIR)/thumbdrive-startup.nsh ::/startup.nsh; \
|
mcopy -i $(BUILD_DIR)/thumbdrive-esp.img $(BUILD_DIR)/thumbdrive-startup.nsh ::/startup.nsh; \
|
||||||
dd if=/dev/zero of=$$DISK bs=1M count=64 2>/dev/null; \
|
dd if=/dev/zero of=$$DISK bs=1M count=128 2>/dev/null; \
|
||||||
sgdisk -n 1:2048:0 -t 1:ef00 -c 1:"EFI System" $$DISK >/dev/null 2>&1; \
|
sgdisk -n 1:2048:0 -t 1:ef00 -c 1:"EFI System" $$DISK >/dev/null 2>&1; \
|
||||||
dd if=$(BUILD_DIR)/thumbdrive-esp.img of=$$DISK bs=512 seek=2048 conv=notrunc 2>/dev/null; \
|
dd if=$(BUILD_DIR)/thumbdrive-esp.img of=$$DISK bs=512 seek=2048 conv=notrunc 2>/dev/null; \
|
||||||
rm -f $(BUILD_DIR)/thumbdrive-esp.img $(BUILD_DIR)/thumbdrive-startup.nsh; \
|
rm -f $(BUILD_DIR)/thumbdrive-esp.img $(BUILD_DIR)/thumbdrive-startup.nsh; \
|
||||||
@@ -1039,6 +1048,46 @@ thumbdrive: all
|
|||||||
echo "Write to a USB stick with: dd if=$$DISK of=/dev/sdX bs=4M status=progress"
|
echo "Write to a USB stick with: dd if=$$DISK of=/dev/sdX bs=4M status=progress"
|
||||||
|
|
||||||
|
|
||||||
|
# iso-usb — build a PURE UEFI isohybrid ISO for the Iso Image Writer workflow
|
||||||
|
# (GNOME Disks "Restore Disk Image...", or dd). Writes a single .iso to a USB
|
||||||
|
# stick as a raw image; the ISO carries a clean GPT with an EFI System
|
||||||
|
# Partition holding EFI/BOOT/BOOT<ARCH>.EFI, so real UEFI firmware (e.g.
|
||||||
|
# Beelink SER5) scans the ESP and boots it. No GRUB, no isolinux, no MBR boot
|
||||||
|
# code — the protective MBR template is zeroed (partition metadata only).
|
||||||
|
# Also still boots under QEMU via -cdrom. This is the novice path: one ISO,
|
||||||
|
# picked with a GUI, written to the stick.
|
||||||
|
iso-usb: all
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
@if ! which xorriso >/dev/null 2>&1; then echo "Error: xorriso not found. Install xorriso (apt-get install xorriso)."; exit 1; fi
|
||||||
|
@if ! which mformat >/dev/null 2>&1 || ! which mmd >/dev/null 2>&1 || ! which mcopy >/dev/null 2>&1; then echo "Error: mtools not found. Install mtools (apt-get install mtools)."; exit 1; fi
|
||||||
|
@case "$(ARCH)" in \
|
||||||
|
amd64) BOOTNAME="BOOTX64.EFI" ;; \
|
||||||
|
aarch64) BOOTNAME="BOOTAA64.EFI" ;; \
|
||||||
|
*) echo "Error: iso-usb EFI boot name only defined for amd64/aarch64 yet"; exit 1 ;; \
|
||||||
|
esac; \
|
||||||
|
ISODIR=$(BUILD_DIR)/iso-usb; \
|
||||||
|
rm -rf $$ISODIR; mkdir -p $$ISODIR; \
|
||||||
|
dd if=/dev/zero of=$$ISODIR/efi.img bs=512 count=8192 2>/dev/null; \
|
||||||
|
mformat -i $$ISODIR/efi.img :: >/dev/null 2>&1; \
|
||||||
|
mmd -i $$ISODIR/efi.img ::/EFI; mmd -i $$ISODIR/efi.img ::/EFI/BOOT; \
|
||||||
|
mcopy -i $$ISODIR/efi.img $(LOADER_EFI) ::/EFI/BOOT/$$BOOTNAME; \
|
||||||
|
printf 'FS0:\\EFI\\BOOT\\%s\r\n' "$$BOOTNAME" > $$ISODIR/startup.nsh; \
|
||||||
|
mcopy -i $$ISODIR/efi.img $$ISODIR/startup.nsh ::/startup.nsh; \
|
||||||
|
head -c 432 /dev/zero > $$ISODIR/protmbr.bin; \
|
||||||
|
ISO=$(BUILD_DIR)/starkernel-iso-usb.iso; \
|
||||||
|
rm -f $$ISO; \
|
||||||
|
xorriso -as mkisofs \
|
||||||
|
-V STARKERNEL -r -J \
|
||||||
|
-isohybrid-mbr $$ISODIR/protmbr.bin \
|
||||||
|
-eltorito-alt-boot -e efi.img -no-emul-boot \
|
||||||
|
-isohybrid-gpt-basdat \
|
||||||
|
-o $$ISO $$ISODIR >/dev/null 2>&1; \
|
||||||
|
rm -rf $$ISODIR; \
|
||||||
|
echo "Iso Image Writer ISO (pure UEFI): $$ISO"; \
|
||||||
|
echo " In GNOME Disks, pick the ISO with 'Restore Disk Image...' and select your USB stick."; \
|
||||||
|
echo " Or: dd if=$$ISO of=/dev/sdX bs=4M status=progress"
|
||||||
|
|
||||||
|
|
||||||
# qemu-esp — interactive dev boot from FAT directory (no disk image rebuild,
|
# qemu-esp — interactive dev boot from FAT directory (no disk image rebuild,
|
||||||
# no auto-kill/timeout/DOE-injection logic — stays up until you quit it
|
# no auto-kill/timeout/DOE-injection logic — stays up until you quit it
|
||||||
# yourself with Ctrl-A X or by closing the window).
|
# yourself with Ctrl-A X or by closing the window).
|
||||||
@@ -1172,6 +1221,7 @@ help:
|
|||||||
@echo " qemu-esp — quick boot from FAT directory (amd64, aarch64)"
|
@echo " qemu-esp — quick boot from FAT directory (amd64, aarch64)"
|
||||||
@echo " qemu-gdb — boot with GDB stub on :1234"
|
@echo " qemu-gdb — boot with GDB stub on :1234"
|
||||||
@echo " thumbdrive — build generic GPT/FAT32 UEFI disk image (write to a USB stick with dd; real-hardware/SER5 path)"
|
@echo " thumbdrive — build generic GPT/FAT32 UEFI disk image (write to a USB stick with dd; real-hardware/SER5 path)"
|
||||||
|
@echo " iso-usb — build UEFI isohybrid ISO for Iso Image Writer / GNOME Disks 'Restore Disk Image...' (novice path; real-hardware/SER5)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "QEMU display:"
|
@echo "QEMU display:"
|
||||||
@echo " QEMU_DISPLAY=gtk — framebuffer window backend for 'qemu' goal (default: gtk)"
|
@echo " QEMU_DISPLAY=gtk — framebuffer window backend for 'qemu' goal (default: gtk)"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# LithosAnanke v2.0.0
|
# LithosAnanke v2.0.1
|
||||||
|
|
||||||
**UEFI-bootable FORTH microkernel.** Boots from firmware, initialises memory and interrupts, then runs the StarForth VM as its sole userspace runtime. No libc. No OS. Just stone and necessity.
|
**UEFI-bootable FORTH microkernel.** Boots from firmware, initialises memory and interrupts, then runs the StarForth VM as its sole userspace runtime. No libc. No OS. Just stone and necessity.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-08-29T14:22:56Z -->
|
|
||||||
|
<!-- Generated by mkcapsule --manifest 2026-08-30T08:47:40Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -128,6 +128,24 @@ void console_puts(const char *s);
|
|||||||
*/
|
*/
|
||||||
void console_println(const char *s);
|
void console_println(const char *s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* console_ensure_line_start - Make sure the next console_putc() starts at
|
||||||
|
* the beginning of a fresh output line, emitting a newline if output is
|
||||||
|
* currently mid-line (e.g. dangling behind a re-anchored prompt).
|
||||||
|
* No-op if already at line start. Mirrors console_putc()'s dual
|
||||||
|
* serial+framebuffer behavior (a bare '\n' reaches both).
|
||||||
|
*/
|
||||||
|
void console_ensure_line_start(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* console_tx_count - Monotonic count of console_putc() calls delivered to
|
||||||
|
* either output (serial and/or framebuffer). In use by the REPL to detect
|
||||||
|
* that an idle bottom half (heartbeat, USB attach/detach) wrote to the
|
||||||
|
* console while the top-level prompt was showing, so it can re-anchor the
|
||||||
|
* prompt afterward. Never decreases.
|
||||||
|
*/
|
||||||
|
uint64_t console_tx_count(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a single character from serial console (non-blocking)
|
* Read a single character from serial console (non-blocking)
|
||||||
* Returns -1 if no character available
|
* Returns -1 if no character available
|
||||||
|
|||||||
@@ -120,12 +120,20 @@ int sk_console_key_available(void);
|
|||||||
* uses internally, so a mid-word EXPECT behaves identically to typing at
|
* uses internally, so a mid-word EXPECT behaves identically to typing at
|
||||||
* "ok>" itself.
|
* "ok>" itself.
|
||||||
*
|
*
|
||||||
* @param buf Destination buffer
|
* @param buf Destination buffer
|
||||||
* @param size Buffer capacity, including the NUL terminator
|
* @param size Buffer capacity, including the NUL terminator
|
||||||
* @param active_vm VM whose idle dispatch runs while waiting
|
* @param active_vm VM whose idle dispatch runs while waiting
|
||||||
|
* @param reanchor_prompt Nonzero to re-print the "ok> " prompt whenever
|
||||||
|
* an idle bottom half (heartbeat, USB attach/detach) writes
|
||||||
|
* to the console while this readline blocks at a bare,
|
||||||
|
* untyped prompt -- keeps the top-level prompt as the last
|
||||||
|
* thing shown once the chatter dies down. Callers whose
|
||||||
|
* prompt line is their own (shim.c's fgets(), i.e.
|
||||||
|
* QUERY/EXPECT/ACCEPT) pass 0 so "ok> " never gets stamped
|
||||||
|
* onto their mid-word input context.
|
||||||
* @return number of characters placed in buf, not counting the NUL
|
* @return number of characters placed in buf, not counting the NUL
|
||||||
*/
|
*/
|
||||||
int sk_console_readline(char *buf, int size, VM *active_vm);
|
int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,18 @@ extern void kernel_main(BootInfo *boot_info);
|
|||||||
|
|
||||||
#if defined(ARCH_AMD64)
|
#if defined(ARCH_AMD64)
|
||||||
#define COM1_BASE 0x3F8
|
#define COM1_BASE 0x3F8
|
||||||
|
|
||||||
|
/* Set by raw_serial_init() only after a 16550 presence probe succeeds.
|
||||||
|
* When 0, raw_serial_putc() drops bytes immediately so boot can never
|
||||||
|
* wedge on a board whose 0x3F8 decode has no UART behind it (real
|
||||||
|
* mini-PCs like the Beelink SER5 typically expose no legacy COM port,
|
||||||
|
* whereas QEMU always emulates one — this is why this hang only bites
|
||||||
|
* on hardware). */
|
||||||
|
static int raw_serial_ready = 0;
|
||||||
|
|
||||||
|
/* Bounded THRE poll bound: provides the same "drop, don't hang" safety
|
||||||
|
* even if the probe above passes on a phantom decode. */
|
||||||
|
#define RAW_SERIAL_THRE_MAX_SPIN 100000u
|
||||||
/**
|
/**
|
||||||
* @brief Write a byte to an x86 I/O port via @c OUT (early-boot raw path).
|
* @brief Write a byte to an x86 I/O port via @c OUT (early-boot raw path).
|
||||||
*
|
*
|
||||||
@@ -137,6 +149,29 @@ static void raw_serial_init(void)
|
|||||||
raw_outb(COM1_BASE + 2, 0xC7);
|
raw_outb(COM1_BASE + 2, 0xC7);
|
||||||
/* RTS/DSR set */
|
/* RTS/DSR set */
|
||||||
raw_outb(COM1_BASE + 4, 0x0B);
|
raw_outb(COM1_BASE + 4, 0x0B);
|
||||||
|
|
||||||
|
/* 16550 presence probe: the scratch register (COM1+7) reads back what
|
||||||
|
* was written only when a real UART owns this decode. Unclaimed ports
|
||||||
|
* (or no I/O bridge routing) return garbage/0xFF, so the probe fails
|
||||||
|
* and serial is disabled — otherwise raw_serial_putc()'s THRE poll
|
||||||
|
* could spin forever on a board with no COM port. */
|
||||||
|
raw_outb(COM1_BASE + 7, 0x5A);
|
||||||
|
raw_serial_ready = (raw_inb(COM1_BASE + 7) == 0x5A) ? 1 : 0;
|
||||||
|
|
||||||
|
if (raw_serial_ready)
|
||||||
|
{
|
||||||
|
/* Sanity-check that THRE can actually be observed; if not, the
|
||||||
|
* decode is a phantom — disable serial rather than risk a spin. */
|
||||||
|
unsigned int spin = 0;
|
||||||
|
while ((raw_inb(COM1_BASE + 5) & 0x20) == 0 && spin < RAW_SERIAL_THRE_MAX_SPIN)
|
||||||
|
{
|
||||||
|
++spin;
|
||||||
|
}
|
||||||
|
if (spin >= RAW_SERIAL_THRE_MAX_SPIN)
|
||||||
|
{
|
||||||
|
raw_serial_ready = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,7 +189,20 @@ static void raw_serial_init(void)
|
|||||||
*/
|
*/
|
||||||
static void raw_serial_putc(char c)
|
static void raw_serial_putc(char c)
|
||||||
{
|
{
|
||||||
while ((raw_inb(COM1_BASE + 5) & 0x20) == 0) { }
|
/* No UART behind 0x3F8 (or probe failed): drop the byte, never block. */
|
||||||
|
if (!raw_serial_ready) return;
|
||||||
|
|
||||||
|
unsigned int spin = 0;
|
||||||
|
while ((raw_inb(COM1_BASE + 5) & 0x20) == 0)
|
||||||
|
{
|
||||||
|
/* Bounded: on hardware whose probe passed but THRE never asserts
|
||||||
|
* (phantom decode), drop the character instead of hanging boot. */
|
||||||
|
if (++spin >= RAW_SERIAL_THRE_MAX_SPIN)
|
||||||
|
{
|
||||||
|
raw_serial_ready = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
raw_outb(COM1_BASE + 0, (uint8_t)c);
|
raw_outb(COM1_BASE + 0, (uint8_t)c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ static int serial_transmit_empty(void) {
|
|||||||
static char g_active_vm_name_buf[CONSOLE_VM_NAME_BUF];
|
static char g_active_vm_name_buf[CONSOLE_VM_NAME_BUF];
|
||||||
static const char *g_active_vm_name = (void *)0;
|
static const char *g_active_vm_name = (void *)0;
|
||||||
static int g_line_start = 1;
|
static int g_line_start = 1;
|
||||||
|
static uint64_t g_console_tx_count; /* monotonic console_putc() counter (BSS) */
|
||||||
|
|
||||||
void console_set_vm_name(const char *name) {
|
void console_set_vm_name(const char *name) {
|
||||||
/* Empty string treated the same as NULL: console_save_vm_name()
|
/* Empty string treated the same as NULL: console_save_vm_name()
|
||||||
@@ -258,8 +259,23 @@ static void emit_prefix(void) {
|
|||||||
* Emits "[VMName] " at the start of each new line when a VM name is set.
|
* Emits "[VMName] " at the start of each new line when a VM name is set.
|
||||||
* Also mirrors output to the framebuffer VT100 terminal when available.
|
* Also mirrors output to the framebuffer VT100 terminal when available.
|
||||||
* Serial output is ALWAYS active regardless of framebuffer state.
|
* Serial output is ALWAYS active regardless of framebuffer state.
|
||||||
|
*
|
||||||
|
* console_putc_inner(): the same body minus the tx-counter increment.
|
||||||
|
* console_ensure_line_start() uses the inner form so its format-only
|
||||||
|
* newline does not read as "real output" to the REPL's prompt re-anchor
|
||||||
|
* (repl.c) -- a silent idle beat that merely closes a dangling prompt line
|
||||||
|
* must not be mistaken for chatter and trigger a prompt repaint. The
|
||||||
|
* counter counts characters the caller actually intended to emit.
|
||||||
*/
|
*/
|
||||||
|
static void console_putc_inner(char c);
|
||||||
|
|
||||||
void console_putc(char c) {
|
void console_putc(char c) {
|
||||||
|
g_console_tx_count++;
|
||||||
|
console_putc_inner(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void console_putc_inner(char c)
|
||||||
|
{
|
||||||
/* --- serial UART path (always on) --- */
|
/* --- serial UART path (always on) --- */
|
||||||
if (g_active_vm_name && g_line_start && c != '\n') {
|
if (g_active_vm_name && g_line_start && c != '\n') {
|
||||||
emit_prefix();
|
emit_prefix();
|
||||||
@@ -288,6 +304,19 @@ void console_puts(const char *s) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t console_tx_count(void)
|
||||||
|
{
|
||||||
|
return g_console_tx_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void console_ensure_line_start(void)
|
||||||
|
{
|
||||||
|
if (!g_line_start)
|
||||||
|
{
|
||||||
|
console_putc_inner('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a string with newline to serial console
|
* Write a string with newline to serial console
|
||||||
*/
|
*/
|
||||||
|
|||||||
+51
-3
@@ -141,6 +141,16 @@ static int g_idle_pump_active; /* zero-initialized (BSS) */
|
|||||||
|
|
||||||
static void sk_repl_idle(VM *active_vm)
|
static void sk_repl_idle(VM *active_vm)
|
||||||
{
|
{
|
||||||
|
/* Close any dangling output line before this bottom half emits its own
|
||||||
|
* chatter (xhci attach/detach progress, block-subsystem notices). If we
|
||||||
|
* are mid-prompt-line -- the REPL started the attach while sitting at
|
||||||
|
* "ok> " -- a bare console_println() would otherwise glue its text onto
|
||||||
|
* the prompt and inherit no {VMName} prefix (g_line_start is 0). A
|
||||||
|
* fresh line first keeps every idle line prefix-tagged and readable,
|
||||||
|
* matching what an interactive typing session expects. No-op when the
|
||||||
|
* console is already at a line boundary. */
|
||||||
|
console_ensure_line_start();
|
||||||
|
|
||||||
/* Artemis Milestone 2d: xHCI Event Ring servicing. This is exactly the
|
/* Artemis Milestone 2d: xHCI Event Ring servicing. This is exactly the
|
||||||
* "interrupt-driven, coarse cadence, cheap early-exit" trigger Section
|
* "interrupt-driven, coarse cadence, cheap early-exit" trigger Section
|
||||||
* U item 6 asked for -- xhci_poll_events() is a no-op read (loop
|
* U item 6 asked for -- xhci_poll_events() is a no-op read (loop
|
||||||
@@ -498,11 +508,22 @@ int sk_console_key_available(void)
|
|||||||
* for the REPL's own top-level prompt, since it's the same underlying
|
* for the REPL's own top-level prompt, since it's the same underlying
|
||||||
* console. Any g_console_pending_key left over from a ?TERMINAL peek is
|
* console. Any g_console_pending_key left over from a ?TERMINAL peek is
|
||||||
* consumed first so a line read never drops a byte ?TERMINAL already saw.
|
* consumed first so a line read never drops a byte ?TERMINAL already saw.
|
||||||
|
* @param reanchor_prompt nonzero from the REPL's own prompt sites (which
|
||||||
|
* print SK_PROMPT_TEXT immediately before): re-print the prompt whenever an
|
||||||
|
* idle bottom half wrote to the console while this call blocked at the bare
|
||||||
|
* prompt (see the re-anchor block in the idle branch). shim.c's fgets()
|
||||||
|
* passes 0 -- its prompt context is caller-owned.
|
||||||
*===========================================================================*/
|
*===========================================================================*/
|
||||||
|
|
||||||
int sk_console_readline(char *buf, int size, VM *active_vm)
|
int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt)
|
||||||
{
|
{
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
/* TX counter value right after the caller printed its prompt. Any
|
||||||
|
* console output that lands while this readline blocks (heartbeat
|
||||||
|
* status, sk_repl_idle()'s USB attach/detach chatter) pushes the
|
||||||
|
* counter past this mark and away from a bare prompt; when that
|
||||||
|
* happens, re-anchor the prompt (below). */
|
||||||
|
uint64_t prompt_tx_mark = console_tx_count();
|
||||||
|
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
console_fb_draw_cursor(); /* show the cursor at the bare prompt, before any input */
|
console_fb_draw_cursor(); /* show the cursor at the bare prompt, before any input */
|
||||||
@@ -531,6 +552,33 @@ int sk_console_readline(char *buf, int size, VM *active_vm)
|
|||||||
g_last_beat_tick = now;
|
g_last_beat_tick = now;
|
||||||
sk_repl_idle(active_vm);
|
sk_repl_idle(active_vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Re-anchor the prompt (FABRIC.md 4.4a unified prompt: print
|
||||||
|
* only "ok> " here -- console_putc() auto-prefixes the current
|
||||||
|
* [VMName] on a fresh line). When an idle bottom half above
|
||||||
|
* pushed output past prompt_tx_mark, the console cursor is now
|
||||||
|
* below/after new lines and the "ok> " the caller printed has
|
||||||
|
* been scrolled or buried -- once the flood passes, the screen
|
||||||
|
* and serial log would end on a stale line with no prompt
|
||||||
|
* (FABRIC-3.md: the bare prompt must be the last thing shown
|
||||||
|
* while the REPL sits idle). Reprinting it restores that
|
||||||
|
* invariant. Skipped while a line is being edited (n > 0) so
|
||||||
|
* partial echo stays attached to its own prompt; shim.c's
|
||||||
|
* fgets() (QUERY/EXPECT/ACCEPT) calls in with reanchor_prompt
|
||||||
|
* == 0 for the same reason -- its prompt line is caller-owned
|
||||||
|
* text, not the REPL's. Each silent beat leaves the mark
|
||||||
|
* unchanged, so the final state after the chatter dies down is
|
||||||
|
* a fresh prompt on the last visible line, cursor on it.
|
||||||
|
*/
|
||||||
|
if (reanchor_prompt && n == 0 &&
|
||||||
|
console_tx_count() != prompt_tx_mark)
|
||||||
|
{
|
||||||
|
console_puts(SK_PROMPT_TEXT);
|
||||||
|
console_fb_draw_cursor();
|
||||||
|
prompt_tx_mark = console_tx_count();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Do NOT use hlt here: QEMU single-threaded TCG can't process
|
* Do NOT use hlt here: QEMU single-threaded TCG can't process
|
||||||
* its APIC timer callbacks while the guest CPU is halted (the
|
* its APIC timer callbacks while the guest CPU is halted (the
|
||||||
@@ -720,7 +768,7 @@ int sk_repl_step(VM *vm)
|
|||||||
console_puts(SK_PROMPT_TEXT);
|
console_puts(SK_PROMPT_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
sk_console_readline(input, sizeof(input), vm);
|
sk_console_readline(input, sizeof(input), vm, 1);
|
||||||
|
|
||||||
if (input[0] == '\0') {
|
if (input[0] == '\0') {
|
||||||
console_puts(" ok\n");
|
console_puts(" ok\n");
|
||||||
@@ -763,7 +811,7 @@ void sk_repl_run(VM *vm)
|
|||||||
* step()'s matching comment above. */
|
* step()'s matching comment above. */
|
||||||
console_puts(SK_PROMPT_TEXT);
|
console_puts(SK_PROMPT_TEXT);
|
||||||
|
|
||||||
sk_console_readline(input, sizeof(input), active);
|
sk_console_readline(input, sizeof(input), active, 1);
|
||||||
|
|
||||||
if (input[0] == '\0') {
|
if (input[0] == '\0') {
|
||||||
console_puts(" ok\n");
|
console_puts(" ok\n");
|
||||||
|
|||||||
@@ -1154,7 +1154,7 @@ static VM *shim_console_vm(void) {
|
|||||||
char *fgets(char *s, int size, FILE *stream) {
|
char *fgets(char *s, int size, FILE *stream) {
|
||||||
(void)stream;
|
(void)stream;
|
||||||
if (!s || size <= 0) return NULL;
|
if (!s || size <= 0) return NULL;
|
||||||
sk_console_readline(s, size, shim_console_vm());
|
sk_console_readline(s, size, shim_console_vm(), 0);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
/** @brief Kernel @c fputc(): ignores stream; emits @p c to kernel console. */
|
/** @brief Kernel @c fputc(): ignores stream; emits @p c to kernel console. */
|
||||||
|
|||||||
Reference in New Issue
Block a user