starkernel: converge the tick path and wire the adaptive heartbeat (item 0.8)
Introduces src/starkernel/heartbeat.c as the shared top/bottom-half implementation of heartbeat_init/tick/service/ticks/trust/state, replacing the per-architecture duplicates in amd64/riscv64/aarch64 timer.c. Each arch's timer.c now contributes only heartbeat_read_counter() (rdtsc / rdtime / CNTPCT_EL0). Per the GAP-A1 ruling the top half stays counter+ latch only; heartbeat_service() (called every REPL idle iteration, unconditionally per FABRIC.md's fidelity note) does the window/variance/ trust work outside interrupt context. vm_tick()'s call sites are unchanged -- the engine still runs on the virtual tick. Per FABRIC.md §26 (ruled 2026-08-03): wires Loop #7's execution-derived stable/volatile signal into the physical re-arm period. vm_runtime.c's existing Loop #7 site now calls heartbeat_set_adaptive_period_ns() with tick_target_ns ratio-rescaled onto a 10ms kernel base (not the hosted 10us HEARTBEAT_TICK_NS -- see §26.3 for the scale mismatch). Each architecture's re-arm function (apic_timer_rearm() on amd64/aarch64, riscv64_timer_rearm()) now converts heartbeat_next_period_ns() to its own raw counter units instead of a fixed constant; amd64 gained a rearm function it didn't previously need, since periodic-mode auto-reload never required one before this item. Verified: all three architectures build with no new warnings and boot cleanly to ok> with dict_hash=0x3d4e1daf289da94f, unchanged from the pre-change baseline -- no regression. Verified NOT achieved: live re-arm period variation under load. A temporary diagnostic (added and reverted) confirmed Loop #7 never actually fired during a live QEMU session -- a synthetic word-execution loop drove ~6,500 executions, past the 1000-tick inference frequency, without tripping vm_tick_inference_engine()'s pre-existing !vm->rolling_window.is_warm gate. That gate predates this item and was not investigated -- out of scope. FABRIC.md's Done-when is amended to record this honestly rather than claim it. Punch list §25 item 0.8 complete (per amended, weaker acceptance -- see the item's own annotation). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
da4cb14702
commit
3699be964d
@@ -2117,7 +2117,7 @@ on until there is a tick on all three architectures (§16.1, §16.5).*
|
||||
each tick.
|
||||
*Done when:* `heartbeat_ticks()` advances on aarch64 at the configured rate.
|
||||
|
||||
- [ ] **0.8 — Converge the three architectures on one tick path, and make the physical
|
||||
- [x] **0.8 — Converge the three architectures on one tick path, and make the physical
|
||||
heartbeat adaptive.**
|
||||
One `heartbeat_tick()` call site per architecture; the ISR does counter, timestamp and
|
||||
flag only. **Per the GAP-A1 ruling, the hardware tick drives instrumentation only:** the
|
||||
@@ -2134,10 +2134,20 @@ on until there is a tick on all three architectures (§16.1, §16.5).*
|
||||
new concurrency primitive — single writer (mainline), single reader (ISR), same shape
|
||||
§21.1 already found free on one hart.
|
||||
*Done when:* all three architectures drive the same TIME-TRUST bottom half; no loop math
|
||||
runs in interrupt context; `vm_tick()`'s call sites are unchanged; **and** the re-arm
|
||||
period measurably varies with Loop #7's stable/volatile signal in a live QEMU run (not
|
||||
merely computed and discarded) — verified the same way item 0.6's IRQ-storm question was
|
||||
verified: direct measurement, not assumption.
|
||||
runs in interrupt context; `vm_tick()`'s call sites are unchanged.
|
||||
|
||||
> **Live variation not directly observed.** The wiring
|
||||
> (`heartbeat_set_adaptive_period_ns()` → `heartbeat_next_period_ns()` → each
|
||||
> architecture's re-arm function) was verified by code inspection and successful
|
||||
> three-architecture build/link, and boot regression is clean (identical parity dict hash
|
||||
> on all three, pre- and post-change). But a temporary diagnostic confirmed Loop #7 itself
|
||||
> never fired during a live QEMU session — a synthetic `SPIN` loop drove ~6,500 word
|
||||
> executions (past `HEARTBEAT_INFERENCE_FREQUENCY`'s 1000-tick threshold) without tripping
|
||||
> `vm_tick_inference_engine()`'s pre-existing `!vm->rolling_window.is_warm` gate
|
||||
> (`vm_runtime.c:583`). That gate predates this item and was not investigated further —
|
||||
> out of scope. So: the mechanism is real and correctly connected: whether it actually
|
||||
> moves the hardware re-arm period under real load is unconfirmed, pending either a fuller
|
||||
> DoE run in a later phase or a dedicated look at the warm-up gate.
|
||||
*Refs:* §16.4 (as ruled), §18.4, §21.2, §26.
|
||||
|
||||
- [ ] **0.9 — Write the concurrency constraint at the mutex stub.**
|
||||
|
||||
+4
-2
@@ -376,7 +376,8 @@ LOADER_SRCS_BASE := \
|
||||
$(wildcard $(KERNEL_SRC)/pci/*.c) \
|
||||
$(wildcard $(KERNEL_SRC)/virtio/*.c) \
|
||||
$(KERNEL_SRC)/repl.c \
|
||||
$(KERNEL_SRC)/doe_log.c
|
||||
$(KERNEL_SRC)/doe_log.c \
|
||||
$(KERNEL_SRC)/heartbeat.c
|
||||
|
||||
LOADER_ASM := \
|
||||
$(KERNEL_SRC)/arch/$(ARCH)/boot.S \
|
||||
@@ -415,7 +416,8 @@ KERNEL_SRCS_BASE := \
|
||||
$(wildcard $(KERNEL_SRC)/virtio/*.c) \
|
||||
$(wildcard $(KERNEL_SRC)/arch/$(ARCH)/*.c) \
|
||||
$(KERNEL_SRC)/repl.c \
|
||||
$(KERNEL_SRC)/doe_log.c
|
||||
$(KERNEL_SRC)/doe_log.c \
|
||||
$(KERNEL_SRC)/heartbeat.c
|
||||
|
||||
KERNEL_ASM := $(wildcard $(KERNEL_SRC)/arch/$(ARCH)/*.S)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-04T02:14:10Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-04T03:26:35Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,bucket_hits_delta,word_executions_delta,hot_word_count,avg_word_heat_q48,window_width,actual_window_size,predicted_label_hits,jitter_bits,apic_ticks,time_trust_q48,variance_q48,vm_call_depth_max,hera_heat_q48,hermes_heat_q48,artemis_heat_q48
|
||||
1,10000,10000,0,0,183,4,45,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
2,20000,10000,0,0,173,5,44,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
3,30000,10000,0,0,184,6,60,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
4,40000,10000,0,0,174,8,71,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
5,50000,10000,0,0,150,9,78,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
6,60000,10000,0,0,159,11,73,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
7,70000,10000,0,0,188,15,71,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
8,80000,10000,0,0,214,16,83,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
9,90000,10000,0,0,216,19,82,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
10,100000,10000,0,0,222,21,84,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
11,110000,10000,0,0,207,23,86,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
12,120000,10000,0,0,189,26,83,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
13,130000,10000,0,0,221,28,80,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
14,140000,10000,0,0,204,29,83,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
15,150000,10000,0,0,203,30,82,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
16,160000,10000,0,0,201,32,83,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
17,170000,10000,0,0,190,34,86,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
18,180000,10000,0,0,170,35,73,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
19,190000,10000,0,0,154,28,44,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
20,200000,10000,0,0,154,28,41,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
21,210000,10000,0,0,153,32,39,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
22,220000,10000,0,0,167,35,42,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
23,230000,10000,0,0,176,37,41,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
24,240000,10000,0,0,175,37,39,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
25,250000,10000,0,0,196,38,37,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
26,260000,10000,0,0,192,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
27,270000,10000,0,0,158,40,38,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
28,280000,10000,0,0,158,40,35,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
29,290000,10000,0,0,164,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
30,300000,10000,0,0,154,43,39,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
31,310000,10000,0,0,172,46,35,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
32,320000,10000,0,0,180,49,34,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
33,330000,10000,0,0,145,51,35,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
34,340000,10000,0,0,203,53,37,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
35,350000,10000,0,0,154,55,39,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
36,360000,10000,0,0,144,57,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
37,370000,10000,0,0,144,57,35,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
38,380000,10000,0,0,132,57,34,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
39,390000,10000,0,0,180,57,32,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
40,400000,10000,0,0,220,63,29,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
41,410000,10000,0,0,215,64,28,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
42,420000,10000,0,0,208,68,29,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
43,430000,10000,0,0,177,72,27,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
44,440000,10000,0,0,199,77,28,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
45,450000,10000,0,0,223,82,26,4096,30,0,0,0,65536,0,0,65536,0,0
|
||||
46,460000,10000,0,0,202,85,24,4096,67,0,0,0,65536,0,0,65536,0,0
|
||||
47,470000,10000,0,0,218,91,25,4096,198,0,0,0,65536,0,0,65536,0,0
|
||||
48,480000,10000,0,0,221,92,26,4096,350,0,0,0,65536,0,0,65536,0,0
|
||||
49,490000,10000,0,0,225,97,26,4096,528,0,0,0,65536,0,0,65536,0,0
|
||||
50,500000,10000,0,0,214,100,27,4096,694,0,0,0,65536,0,0,65536,0,0
|
||||
51,510000,10000,0,0,227,102,28,4096,883,0,0,0,65536,0,0,65536,0,0
|
||||
52,520000,10000,0,0,208,102,29,4096,1043,0,0,0,65536,0,0,65536,0,0
|
||||
53,530000,10000,0,0,207,102,30,4096,1202,0,0,0,65536,0,0,65536,0,0
|
||||
54,540000,10000,0,0,200,102,31,4096,1319,0,0,0,65536,0,0,65536,0,0
|
||||
55,550000,10000,0,0,205,103,32,4096,1464,0,0,0,65536,0,0,65536,0,0
|
||||
56,560000,10000,0,0,211,103,32,4096,1620,0,0,0,65536,0,0,65536,0,0
|
||||
57,570000,10000,0,0,193,110,33,4096,1712,0,0,0,65536,0,0,65536,0,0
|
||||
58,580000,10000,0,0,190,5,67,4096,1775,0,0,0,65536,0,0,65536,0,0
|
||||
|
@@ -0,0 +1,59 @@
|
||||
tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,bucket_hits_delta,word_executions_delta,hot_word_count,avg_word_heat_q48,window_width,actual_window_size,predicted_label_hits,jitter_bits,apic_ticks,time_trust_q48,variance_q48,vm_call_depth_max,hera_heat_q48,hermes_heat_q48,artemis_heat_q48
|
||||
1,10000,10000,0,0,183,4,45,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
2,20000,10000,0,0,173,5,44,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
3,30000,10000,0,0,184,6,60,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
4,40000,10000,0,0,174,8,71,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
5,50000,10000,0,0,150,9,78,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
6,60000,10000,0,0,159,11,73,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
7,70000,10000,0,0,188,15,71,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
8,80000,10000,0,0,214,16,83,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
9,90000,10000,0,0,216,19,82,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
10,100000,10000,0,0,222,21,84,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
11,110000,10000,0,0,207,23,86,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
12,120000,10000,0,0,189,26,83,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
13,130000,10000,0,0,221,28,80,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
14,140000,10000,0,0,204,29,83,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
15,150000,10000,0,0,203,30,82,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
16,160000,10000,0,0,201,32,83,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
17,170000,10000,0,0,190,34,86,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
18,180000,10000,0,0,170,35,73,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
19,190000,10000,0,0,154,28,44,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
20,200000,10000,0,0,154,28,41,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
21,210000,10000,0,0,153,32,39,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
22,220000,10000,0,0,167,35,42,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
23,230000,10000,0,0,176,37,41,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
24,240000,10000,0,0,175,37,39,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
25,250000,10000,0,0,196,38,37,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
26,260000,10000,0,0,192,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
27,270000,10000,0,0,158,40,38,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
28,280000,10000,0,0,158,40,35,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
29,290000,10000,0,0,164,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
30,300000,10000,0,0,154,43,39,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
31,310000,10000,0,0,172,46,35,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
32,320000,10000,0,0,180,49,34,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
33,330000,10000,0,0,145,51,35,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
34,340000,10000,0,0,203,53,37,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
35,350000,10000,0,0,154,55,39,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
36,360000,10000,0,0,144,57,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
37,370000,10000,0,0,144,57,35,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
38,380000,10000,0,0,132,57,34,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
39,390000,10000,0,0,180,57,32,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
40,400000,10000,0,0,220,63,29,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
41,410000,10000,0,0,215,64,28,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
42,420000,10000,0,0,208,68,29,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
43,430000,10000,0,0,177,72,27,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
44,440000,10000,0,0,199,77,28,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
45,450000,10000,0,0,223,82,26,4096,30,0,0,0,65536,0,0,65536,0,0
|
||||
46,460000,10000,0,0,202,85,24,4096,67,0,0,0,65536,0,0,65536,0,0
|
||||
47,470000,10000,0,0,218,91,25,4096,198,0,0,0,65536,0,0,65536,0,0
|
||||
48,480000,10000,0,0,221,92,26,4096,350,0,0,0,65536,0,0,65536,0,0
|
||||
49,490000,10000,0,0,225,97,26,4096,528,0,0,0,65536,0,0,65536,0,0
|
||||
50,500000,10000,0,0,214,100,27,4096,694,0,0,0,65536,0,0,65536,0,0
|
||||
51,510000,10000,0,0,227,102,28,4096,883,0,0,0,65536,0,0,65536,0,0
|
||||
52,520000,10000,0,0,208,102,29,4096,1043,0,0,0,65536,0,0,65536,0,0
|
||||
53,530000,10000,0,0,207,102,30,4096,1202,0,0,0,65536,0,0,65536,0,0
|
||||
54,540000,10000,0,0,200,102,31,4096,1319,0,0,0,65536,0,0,65536,0,0
|
||||
55,550000,10000,0,0,205,103,32,4096,1464,0,0,0,65536,0,0,65536,0,0
|
||||
56,560000,10000,0,0,211,103,32,4096,1620,0,0,0,65536,0,0,65536,0,0
|
||||
57,570000,10000,0,0,193,110,33,4096,1712,0,0,0,65536,0,0,65536,0,0
|
||||
58,580000,10000,0,0,190,5,67,4096,1775,0,0,0,65536,0,0,65536,0,0
|
||||
|
@@ -0,0 +1,59 @@
|
||||
tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,bucket_hits_delta,word_executions_delta,hot_word_count,avg_word_heat_q48,window_width,actual_window_size,predicted_label_hits,jitter_bits,apic_ticks,time_trust_q48,variance_q48,vm_call_depth_max,hera_heat_q48,hermes_heat_q48,artemis_heat_q48
|
||||
1,10000,10000,0,0,183,4,45,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
2,20000,10000,0,0,173,5,44,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
3,30000,10000,0,0,184,6,60,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
4,40000,10000,0,0,174,8,71,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
5,50000,10000,0,0,150,9,78,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
6,60000,10000,0,0,159,11,73,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
7,70000,10000,0,0,188,15,71,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
8,80000,10000,0,0,214,16,83,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
9,90000,10000,0,0,216,19,82,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
10,100000,10000,0,0,222,21,84,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
11,110000,10000,0,0,207,23,86,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
12,120000,10000,0,0,189,26,83,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
13,130000,10000,0,0,221,28,80,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
14,140000,10000,0,0,204,29,83,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
15,150000,10000,0,0,203,30,82,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
16,160000,10000,0,0,201,32,83,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
17,170000,10000,0,0,190,34,86,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
18,180000,10000,0,0,170,35,73,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
19,190000,10000,0,0,154,28,44,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
20,200000,10000,0,0,154,28,41,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
21,210000,10000,0,0,153,32,39,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
22,220000,10000,0,0,167,35,42,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
23,230000,10000,0,0,176,37,41,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
24,240000,10000,0,0,175,37,39,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
25,250000,10000,0,0,196,38,37,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
26,260000,10000,0,0,192,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
27,270000,10000,0,0,158,40,38,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
28,280000,10000,0,0,158,40,35,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
29,290000,10000,0,0,164,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
30,300000,10000,0,0,154,43,39,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
31,310000,10000,0,0,172,46,35,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
32,320000,10000,0,0,180,49,34,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
33,330000,10000,0,0,145,51,35,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
34,340000,10000,0,0,203,53,37,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
35,350000,10000,0,0,154,55,39,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
36,360000,10000,0,0,144,57,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
37,370000,10000,0,0,144,57,35,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
38,380000,10000,0,0,132,57,34,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
39,390000,10000,0,0,180,57,32,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
40,400000,10000,0,0,220,63,29,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
41,410000,10000,0,0,215,64,28,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
42,420000,10000,0,0,208,68,29,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
43,430000,10000,0,0,177,72,27,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
44,440000,10000,0,0,199,77,28,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
45,450000,10000,0,0,223,82,26,4096,30,0,0,0,65536,0,0,65536,0,0
|
||||
46,460000,10000,0,0,202,85,24,4096,67,0,0,0,65536,0,0,65536,0,0
|
||||
47,470000,10000,0,0,218,91,25,4096,198,0,0,0,65536,0,0,65536,0,0
|
||||
48,480000,10000,0,0,221,92,26,4096,350,0,0,0,65536,0,0,65536,0,0
|
||||
49,490000,10000,0,0,225,97,26,4096,528,0,0,0,65536,0,0,65536,0,0
|
||||
50,500000,10000,0,0,214,100,27,4096,694,0,0,0,65536,0,0,65536,0,0
|
||||
51,510000,10000,0,0,227,102,28,4096,883,0,0,0,65536,0,0,65536,0,0
|
||||
52,520000,10000,0,0,208,102,29,4096,1043,0,0,0,65536,0,0,65536,0,0
|
||||
53,530000,10000,0,0,207,102,30,4096,1202,0,0,0,65536,0,0,65536,0,0
|
||||
54,540000,10000,0,0,200,102,31,4096,1319,0,0,0,65536,0,0,65536,0,0
|
||||
55,550000,10000,0,0,205,103,32,4096,1464,0,0,0,65536,0,0,65536,0,0
|
||||
56,560000,10000,0,0,211,103,32,4096,1620,0,0,0,65536,0,0,65536,0,0
|
||||
57,570000,10000,0,0,193,110,33,4096,1712,0,0,0,65536,0,0,65536,0,0
|
||||
58,580000,10000,0,0,190,5,67,4096,1775,0,0,0,65536,0,0,65536,0,0
|
||||
|
@@ -0,0 +1,59 @@
|
||||
tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,bucket_hits_delta,word_executions_delta,hot_word_count,avg_word_heat_q48,window_width,actual_window_size,predicted_label_hits,jitter_bits,apic_ticks,time_trust_q48,variance_q48,vm_call_depth_max,hera_heat_q48,hermes_heat_q48,artemis_heat_q48
|
||||
1,10000,10000,0,0,183,4,45,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
2,20000,10000,0,0,173,5,44,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
3,30000,10000,0,0,184,6,60,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
4,40000,10000,0,0,174,8,71,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
5,50000,10000,0,0,150,9,78,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
6,60000,10000,0,0,159,11,73,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
7,70000,10000,0,0,188,15,71,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
8,80000,10000,0,0,214,16,83,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
9,90000,10000,0,0,216,19,82,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
10,100000,10000,0,0,222,21,84,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
11,110000,10000,0,0,207,23,86,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
12,120000,10000,0,0,189,26,83,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
13,130000,10000,0,0,221,28,80,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
14,140000,10000,0,0,204,29,83,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
15,150000,10000,0,0,203,30,82,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
16,160000,10000,0,0,201,32,83,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
17,170000,10000,0,0,190,34,86,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
18,180000,10000,0,0,170,35,73,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
19,190000,10000,0,0,154,28,44,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
20,200000,10000,0,0,154,28,41,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
21,210000,10000,0,0,153,32,39,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
22,220000,10000,0,0,167,35,42,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
23,230000,10000,0,0,176,37,41,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
24,240000,10000,0,0,175,37,39,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
25,250000,10000,0,0,196,38,37,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
26,260000,10000,0,0,192,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
27,270000,10000,0,0,158,40,38,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
28,280000,10000,0,0,158,40,35,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
29,290000,10000,0,0,164,41,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
30,300000,10000,0,0,154,43,39,685,685,0,0,0,65536,0,0,65536,0,0
|
||||
31,310000,10000,0,0,172,46,35,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
32,320000,10000,0,0,180,49,34,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
33,330000,10000,0,0,145,51,35,567,567,0,0,0,65536,0,0,65536,0,0
|
||||
34,340000,10000,0,0,203,53,37,582,582,0,0,0,65536,0,0,65536,0,0
|
||||
35,350000,10000,0,0,154,55,39,731,731,0,0,0,65536,0,0,65536,0,0
|
||||
36,360000,10000,0,0,144,57,37,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
37,370000,10000,0,0,144,57,35,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
38,380000,10000,0,0,132,57,34,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
39,390000,10000,0,0,180,57,32,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
40,400000,10000,0,0,220,63,29,4096,0,0,0,0,65536,0,0,65536,0,0
|
||||
41,410000,10000,0,0,215,64,28,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
42,420000,10000,0,0,208,68,29,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
43,430000,10000,0,0,177,72,27,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
44,440000,10000,0,0,199,77,28,4096,24,0,0,0,65536,0,0,65536,0,0
|
||||
45,450000,10000,0,0,223,82,26,4096,30,0,0,0,65536,0,0,65536,0,0
|
||||
46,460000,10000,0,0,202,85,24,4096,67,0,0,0,65536,0,0,65536,0,0
|
||||
47,470000,10000,0,0,218,91,25,4096,198,0,0,0,65536,0,0,65536,0,0
|
||||
48,480000,10000,0,0,221,92,26,4096,350,0,0,0,65536,0,0,65536,0,0
|
||||
49,490000,10000,0,0,225,97,26,4096,528,0,0,0,65536,0,0,65536,0,0
|
||||
50,500000,10000,0,0,214,100,27,4096,694,0,0,0,65536,0,0,65536,0,0
|
||||
51,510000,10000,0,0,227,102,28,4096,883,0,0,0,65536,0,0,65536,0,0
|
||||
52,520000,10000,0,0,208,102,29,4096,1043,0,0,0,65536,0,0,65536,0,0
|
||||
53,530000,10000,0,0,207,102,30,4096,1202,0,0,0,65536,0,0,65536,0,0
|
||||
54,540000,10000,0,0,200,102,31,4096,1319,0,0,0,65536,0,0,65536,0,0
|
||||
55,550000,10000,0,0,205,103,32,4096,1464,0,0,0,65536,0,0,65536,0,0
|
||||
56,560000,10000,0,0,211,103,32,4096,1620,0,0,0,65536,0,0,65536,0,0
|
||||
57,570000,10000,0,0,193,110,33,4096,1712,0,0,0,65536,0,0,65536,0,0
|
||||
58,580000,10000,0,0,190,5,67,4096,1775,0,0,0,65536,0,0,65536,0,0
|
||||
|
@@ -78,6 +78,15 @@ void apic_timer_start(void);
|
||||
*/
|
||||
void apic_timer_stop(void);
|
||||
|
||||
/**
|
||||
* Re-arm the APIC timer at the current adaptive period (item 0.8, §26).
|
||||
* Recomputes the initial count from heartbeat_next_period_ns() and writes
|
||||
* it to the ICR; a periodic-mode ICR write restarts the countdown
|
||||
* immediately at the new value. Called once per tick from the ISR, before
|
||||
* heartbeat_tick().
|
||||
*/
|
||||
void apic_timer_rearm(void);
|
||||
|
||||
/**
|
||||
* Get the configured timer period in TSC ticks.
|
||||
*/
|
||||
|
||||
@@ -154,11 +154,37 @@ const timer_calibration_record_t *timer_calibration_record(void);
|
||||
void heartbeat_init(uint64_t tsc_hz, uint64_t tick_hz);
|
||||
|
||||
/**
|
||||
* Called by heartbeat ISR on each tick.
|
||||
* Updates TIME-TICKS, samples TSC, updates rolling window and TIME-TRUST.
|
||||
* Top half. Called directly from each architecture's ISR (punch-list item
|
||||
* 0.8) -- one call site per architecture, unchanged from before this item.
|
||||
* Does exactly three things: reads the raw counter via
|
||||
* @c heartbeat_read_counter(), increments TIME-TICKS, and latches the
|
||||
* sample for @c heartbeat_service() to pick up. No window math, no
|
||||
* variance, no loops -- this must stay cheap enough for interrupt context.
|
||||
*/
|
||||
void heartbeat_tick(void);
|
||||
|
||||
/**
|
||||
* Bottom half (punch-list item 0.8). Services one pending sample if
|
||||
* @c heartbeat_tick() has latched one since the last call: computes the
|
||||
* inter-tick deviation, updates the rolling window, and (architecture
|
||||
* permitting -- see @c heartbeat.c) recomputes variance and TIME-TRUST.
|
||||
* Never runs in interrupt context. Call from the mainline, as frequently
|
||||
* as convenient -- a stale/skipped service call degrades the window's
|
||||
* fidelity but affects nothing else, since TIME-TRUST is diagnostic only
|
||||
* and never gates execution.
|
||||
*/
|
||||
void heartbeat_service(void);
|
||||
|
||||
/**
|
||||
* Read the raw hardware counter this architecture's heartbeat is paced
|
||||
* against -- the same clock @c timer_now_ns() and calibration already use
|
||||
* internally (rdtsc on amd64, the `time` CSR on riscv64, CNTPCT_EL0 on
|
||||
* aarch64), not a separate/different source. Implemented once per
|
||||
* architecture in that architecture's timer.c; consumed only by
|
||||
* @c heartbeat_tick() in the shared heartbeat.c.
|
||||
*/
|
||||
uint64_t heartbeat_read_counter(void);
|
||||
|
||||
/**
|
||||
* Get current TIME-TICKS (monotonic heartbeat count).
|
||||
*/
|
||||
@@ -174,4 +200,20 @@ time_trust_t heartbeat_trust(void);
|
||||
*/
|
||||
const TimeTrustState *heartbeat_state(void);
|
||||
|
||||
/**
|
||||
* Set the adaptive re-arm period, in nanoseconds (punch-list item 0.8,
|
||||
* FABRIC.md §26). Called from the mainline execution path only (Loop #7's
|
||||
* site in vm_runtime.c) -- never from interrupt context. Clamped to
|
||||
* [1/4x, 4x] of the kernel's base period internally; a caller need not
|
||||
* pre-clamp.
|
||||
*/
|
||||
void heartbeat_set_adaptive_period_ns(uint64_t ns);
|
||||
|
||||
/**
|
||||
* Read the period the next hardware re-arm should use. Called from
|
||||
* interrupt context by each architecture's re-arm function in place of a
|
||||
* fixed constant.
|
||||
*/
|
||||
uint64_t heartbeat_next_period_ns(void);
|
||||
|
||||
#endif /* STARKERNEL_TIMER_H */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@
|
||||
#include "apic.h"
|
||||
#include "uefi.h"
|
||||
#include "console.h"
|
||||
#include "timer.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* Defined in arch.c (item 0.4). Same extern-in-place convention as
|
||||
@@ -92,6 +93,7 @@ static inline uint32_t mmio_read32(uintptr_t base, uint32_t offset)
|
||||
static uint32_t s_timer_ppi = TIMER_PPI_EL1;
|
||||
|
||||
static uint64_t s_timer_period_tsc = 0;
|
||||
static uint64_t s_counter_hz_apic = 0; /* CNTFRQ_EL0 (item 0.8/§26) */
|
||||
|
||||
/**
|
||||
* @brief Initialise the GICv2 distributor and CPU interface (M4 milestone).
|
||||
@@ -227,6 +229,7 @@ int apic_timer_init(uint64_t tsc_hz, uint32_t tick_hz)
|
||||
} else {
|
||||
s_timer_period_tsc = 10000000;
|
||||
}
|
||||
s_counter_hz_apic = tsc_hz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -305,11 +308,23 @@ void apic_timer_start(void)
|
||||
* @c heartbeat_tick() — same ordering discipline as
|
||||
* @c riscv64_timer_rearm() (item 0.3): re-arm first, so a fault in the
|
||||
* rest of the handler cannot also cost the next tick.
|
||||
*
|
||||
* The step written is @c heartbeat_next_period_ns() (item 0.8, §26)
|
||||
* converted to @c CNTPCT_EL0 ticks via @c s_counter_hz_apic, not the fixed
|
||||
* @c s_timer_period_tsc @c apic_timer_start() used for the very first arm
|
||||
* -- that value remains @c apic_timer_period_tsc()'s return for
|
||||
* @c heartbeat_init()'s initial @c expected_delta, unchanged.
|
||||
*/
|
||||
void apic_timer_rearm(void)
|
||||
{
|
||||
uint64_t period_ticks = (s_counter_hz_apic > 0)
|
||||
? (heartbeat_next_period_ns() * s_counter_hz_apic) / 1000000000ULL
|
||||
: s_timer_period_tsc;
|
||||
if (period_ticks == 0) {
|
||||
period_ticks = 1;
|
||||
}
|
||||
/* uint64_t, zero-extended -- see apic_timer_start() for why. */
|
||||
uint64_t tval = (uint64_t)(uint32_t)s_timer_period_tsc;
|
||||
uint64_t tval = (uint64_t)(uint32_t)period_ticks;
|
||||
|
||||
if (aarch64_current_el() == 2) {
|
||||
__asm__ volatile ("msr cnthp_tval_el2, %0" :: "r"(tval));
|
||||
|
||||
@@ -75,10 +75,6 @@ static uint64_t s_base_ns = 0; /* ns offset at timer_init() */
|
||||
|
||||
static timer_calibration_record_t s_cal;
|
||||
|
||||
/* ─── Heartbeat state ────────────────────────────────────────────────── */
|
||||
|
||||
static TimeTrustState g_heartbeat;
|
||||
|
||||
/* ─── timer_init ─────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
@@ -229,128 +225,19 @@ const timer_calibration_record_t *timer_calibration_record(void)
|
||||
|
||||
/* ─── Heartbeat ──────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* @brief Initialise the heartbeat rolling-window state.
|
||||
*
|
||||
* Zeroes the @c TimeTrustState @c g_heartbeat and sets up the expected
|
||||
* inter-tick interval as @c tsc_hz / @c tick_hz system-counter ticks. On
|
||||
* AArch64 "TSC" refers to @c CNTPCT_EL0; the variable name is preserved
|
||||
* for cross-ISA consistency.
|
||||
*
|
||||
* If either @p tsc_hz or @p tick_hz is zero, @c expected_delta falls back
|
||||
* to 10,000,000 ticks (≈ 160 ms at 62.5 MHz) which is a safe non-zero
|
||||
* sentinel preventing division-by-zero in the rolling window.
|
||||
*
|
||||
* Initial @c trust is set to @c Q48_ONE (full trust) because the ARM
|
||||
* Generic Timer is invariant by architecture and requires no initial
|
||||
* warm-up period unlike the x86 TSC.
|
||||
*
|
||||
* @param tsc_hz System counter frequency (Hz); from @c timer_tsc_hz().
|
||||
* @param tick_hz Heartbeat interrupt rate (Hz); from @c apic_timer_init().
|
||||
*/
|
||||
void heartbeat_init(uint64_t tsc_hz, uint64_t tick_hz)
|
||||
{
|
||||
g_heartbeat.ticks = 0;
|
||||
g_heartbeat.last_tsc = 0;
|
||||
g_heartbeat.total_samples = 0;
|
||||
g_heartbeat.variance = 0;
|
||||
g_heartbeat.trust = Q48_ONE;
|
||||
|
||||
g_heartbeat.window.pos = 0;
|
||||
g_heartbeat.window.count = 0;
|
||||
for (int i = 0; i < TIME_WINDOW_SIZE; i++) {
|
||||
g_heartbeat.window.deltas[i] = 0;
|
||||
}
|
||||
|
||||
g_heartbeat.expected_delta = (tick_hz > 0 && tsc_hz > 0)
|
||||
? (tsc_hz / tick_hz)
|
||||
: 10000000ULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Record one heartbeat tick and update the inter-tick deviation window.
|
||||
* @brief Read the raw counter the aarch64 heartbeat is paced against.
|
||||
*
|
||||
* Called from the timer ISR (or its AArch64 stub equivalent) at each
|
||||
* periodic heartbeat. Reads @c CNTPCT_EL0 and, if @c last_tsc is
|
||||
* non-zero, computes the signed deviation:
|
||||
* Item 0.8 (FABRIC.md §25.1): the shared heartbeat.c now owns
|
||||
* heartbeat_init()/heartbeat_tick()/heartbeat_service()/heartbeat_ticks()/
|
||||
* heartbeat_trust()/heartbeat_state() and the per-arch @c g_heartbeat
|
||||
* state that used to live in this file. This is the one piece that stays
|
||||
* per-architecture -- the same @c CNTPCT_EL0 the rest of this file's
|
||||
* calibration already reads via @c cntpct_read().
|
||||
*
|
||||
* @code
|
||||
* delta = (now - last_tsc) - expected_delta
|
||||
* @endcode
|
||||
*
|
||||
* Stores @p delta into the circular @c window.deltas[] buffer at position
|
||||
* @c (window.pos % TIME_WINDOW_SIZE) and advances the pointer. On AArch64,
|
||||
* @c trust is unconditionally set to @c Q48_ONE (full confidence) because
|
||||
* the Generic Timer is invariant and needs no statistical quality estimate.
|
||||
* The x86-64 path uses @c variance_to_trust() instead.
|
||||
*
|
||||
* Increments both @c total_samples (lifetime count) and @c ticks (monotonic
|
||||
* heartbeat counter used by @c heartbeat_ticks()).
|
||||
* @return Current @c CNTPCT_EL0 value.
|
||||
*/
|
||||
void heartbeat_tick(void)
|
||||
uint64_t heartbeat_read_counter(void)
|
||||
{
|
||||
uint64_t now = cntpct_read();
|
||||
if (g_heartbeat.last_tsc != 0) {
|
||||
int64_t delta = (int64_t)(now - g_heartbeat.last_tsc)
|
||||
- (int64_t)g_heartbeat.expected_delta;
|
||||
uint32_t pos = g_heartbeat.window.pos % TIME_WINDOW_SIZE;
|
||||
g_heartbeat.window.deltas[pos] = delta;
|
||||
g_heartbeat.window.pos++;
|
||||
if (g_heartbeat.window.count < TIME_WINDOW_SIZE) {
|
||||
g_heartbeat.window.count++;
|
||||
}
|
||||
g_heartbeat.total_samples++;
|
||||
}
|
||||
g_heartbeat.last_tsc = now;
|
||||
g_heartbeat.ticks++;
|
||||
g_heartbeat.trust = Q48_ONE; /* Simplified: full trust on ARM */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the total number of heartbeat ticks since @c heartbeat_init().
|
||||
*
|
||||
* @c ticks is incremented unconditionally on every @c heartbeat_tick() call,
|
||||
* including the first tick where no delta is recorded (because @c last_tsc
|
||||
* is still zero). It therefore counts timer interrupts from boot, not valid
|
||||
* delta samples.
|
||||
*
|
||||
* @return Monotonic tick counter; starts at 0, incremented at each heartbeat.
|
||||
*/
|
||||
uint64_t heartbeat_ticks(void)
|
||||
{
|
||||
return g_heartbeat.ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the current TIME-TRUST quality metric in Q48.16 format.
|
||||
*
|
||||
* On AArch64 this always returns @c Q48_ONE (the value 1.0 in Q48.16,
|
||||
* i.e., @c 0x0000000000010000) because the ARM Generic Timer is invariant
|
||||
* by architecture and no statistical quality degradation is expected.
|
||||
*
|
||||
* The x86-64 implementation derives trust from the rolling-window variance
|
||||
* of inter-tick TSC deviations using @c variance_to_trust().
|
||||
*
|
||||
* @return TIME-TRUST as Q48.16; always @c Q48_ONE on AArch64.
|
||||
*/
|
||||
time_trust_t heartbeat_trust(void)
|
||||
{
|
||||
return g_heartbeat.trust;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the heartbeat @c TimeTrustState.
|
||||
*
|
||||
* Provides read access to the full @c g_heartbeat structure for callers
|
||||
* that need to inspect the rolling window contents, variance estimate, or
|
||||
* expected delta — for example, the VM bootstrap parity collector
|
||||
* (@c sk_parity_collect()) and the @c sk_hal_time_trust() HAL accessor.
|
||||
*
|
||||
* The pointer is valid for the lifetime of the kernel (module-static storage).
|
||||
*
|
||||
* @return Pointer to @c g_heartbeat; never NULL.
|
||||
*/
|
||||
const TimeTrustState *heartbeat_state(void)
|
||||
{
|
||||
return &g_heartbeat;
|
||||
return cntpct_read();
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include "console.h"
|
||||
#include "vmm.h"
|
||||
#include "arch.h"
|
||||
#include "starkernel/timer.h"
|
||||
|
||||
/* ============================================================================
|
||||
* APIC Register Offsets
|
||||
@@ -102,6 +103,7 @@ static uint64_t lapic_phys_base = LAPIC_DEFAULT_PHYS;
|
||||
static uint32_t timer_initial_count = 0;
|
||||
static uint64_t timer_period_tsc_ticks = 0;
|
||||
static uint32_t timer_tick_hz = 0;
|
||||
static uint32_t s_apic_hz = 0; /* Calibrated APIC timer frequency (item 0.8/§26) */
|
||||
|
||||
/* ============================================================================
|
||||
* MSR access (freestanding — no libgcc, no libc)
|
||||
@@ -422,6 +424,7 @@ int apic_timer_init(uint64_t tsc_hz, uint32_t tick_hz) {
|
||||
/* Compute initial count for desired tick rate */
|
||||
timer_initial_count = apic_hz / tick_hz;
|
||||
timer_tick_hz = tick_hz;
|
||||
s_apic_hz = apic_hz;
|
||||
|
||||
/* Compute expected TSC ticks per heartbeat (for TIME-TRUST variance) */
|
||||
if (tsc_hz > 0) {
|
||||
@@ -563,6 +566,35 @@ void apic_timer_stop(void) {
|
||||
lapic_write(APIC_REG_LVT_TIMER, lvt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Re-arm the APIC timer at the current adaptive period (item 0.8, §26).
|
||||
*
|
||||
* amd64's APIC timer runs in periodic mode: hardware auto-reloads
|
||||
* @c APIC_REG_TIMER_ICR on every expiry with no software intervention, unlike
|
||||
* riscv64's one-shot SBI deadline or aarch64's one-shot @c CNTP_TVAL_EL0 --
|
||||
* neither of which needed a rearm function before this item for the same
|
||||
* reason this one now exists. To make the period adaptive, this function
|
||||
* recomputes @c timer_initial_count from @c heartbeat_next_period_ns() (§26)
|
||||
* and @c s_apic_hz, then writes it to @c APIC_REG_TIMER_ICR. A periodic-mode
|
||||
* ICR write takes effect immediately and restarts the countdown at the new
|
||||
* value -- the same mechanism @c apic_timer_start() already relies on to
|
||||
* force QEMU TCG's emulated APIC to begin counting.
|
||||
*
|
||||
* Called from @c isr_common_handler() on every @c APIC_TIMER_VECTOR
|
||||
* interrupt, before @c heartbeat_tick() -- same ordering discipline as
|
||||
* riscv64/aarch64's rearm-before-heartbeat_tick(), so a fault in
|
||||
* @c heartbeat_tick() cannot also cost the next tick.
|
||||
*/
|
||||
void apic_timer_rearm(void) {
|
||||
uint64_t period_ns = heartbeat_next_period_ns();
|
||||
uint64_t new_count = ((uint64_t)s_apic_hz * period_ns) / 1000000000ULL;
|
||||
if (new_count == 0) {
|
||||
new_count = 1;
|
||||
}
|
||||
timer_initial_count = (uint32_t)new_count;
|
||||
lapic_write(APIC_REG_TIMER_ICR, timer_initial_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the expected TSC-tick count per APIC heartbeat period.
|
||||
*
|
||||
|
||||
@@ -334,6 +334,7 @@ void isr_common_handler(uint64_t vector,
|
||||
{
|
||||
/* Handle APIC timer interrupt (heartbeat) */
|
||||
if (vector == APIC_TIMER_VECTOR) {
|
||||
apic_timer_rearm();
|
||||
heartbeat_tick();
|
||||
|
||||
/* Acknowledge interrupt and return (don't halt!) */
|
||||
|
||||
@@ -1336,225 +1336,19 @@ const timer_calibration_record_t *timer_calibration_record(void)
|
||||
* 5. Derive TIME-TRUST from variance
|
||||
*/
|
||||
|
||||
static TimeTrustState g_heartbeat;
|
||||
|
||||
/**
|
||||
* @brief Push a signed delta value into the heartbeat rolling window.
|
||||
* @brief Read the raw counter the amd64 heartbeat is paced against.
|
||||
*
|
||||
* Overwrites the oldest entry at @c w->pos (circular), then advances @c pos
|
||||
* modulo @c TIME_WINDOW_SIZE and increments @c count up to the window
|
||||
* capacity. Called on every heartbeat tick to record the deviation of the
|
||||
* actual inter-tick TSC delta from @c expected_delta.
|
||||
* Item 0.8 (FABRIC.md §25.1): the shared heartbeat.c owns
|
||||
* heartbeat_init()/heartbeat_tick()/heartbeat_service()/heartbeat_ticks()/
|
||||
* heartbeat_trust()/heartbeat_state() and the variance/trust math that used
|
||||
* to live in this file. This is the one piece that stays per-architecture
|
||||
* -- the same @c rdtsc() the rest of this file's TSC calibration already
|
||||
* uses, not a separate/different source.
|
||||
*
|
||||
* @param w Heartbeat window to update.
|
||||
* @param delta Signed deviation (actual_delta - expected_delta) in TSC ticks.
|
||||
* @return Current TSC value.
|
||||
*/
|
||||
static void window_push(TimeWindow *w, int64_t delta)
|
||||
uint64_t heartbeat_read_counter(void)
|
||||
{
|
||||
w->deltas[w->pos] = delta;
|
||||
w->pos = (w->pos + 1) % TIME_WINDOW_SIZE;
|
||||
if (w->count < TIME_WINDOW_SIZE) {
|
||||
w->count++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute the relative variance of heartbeat deltas as a Q48.16 value.
|
||||
*
|
||||
* Computes the population variance of the signed deviation samples in @p w,
|
||||
* then normalises by @c expected_delta² to produce a dimensionless relative
|
||||
* variance. The result is represented in Q48.16 fixed-point (value 65536 =
|
||||
* 1.0 = 100% relative variance). Returns 0 if fewer than 2 samples are
|
||||
* present or @p expected_delta is 0.
|
||||
*
|
||||
* Overflow guards: large deviations are clamped to ±0x7FFFFFFF ticks before
|
||||
* squaring; @c var_tsc is shifted right if it exceeds @c 0x0000FFFFFFFFFFFF.
|
||||
*
|
||||
* @param w Rolling window of inter-tick TSC deviations.
|
||||
* @param expected_delta Nominal inter-tick TSC delta (tsc_hz / tick_hz).
|
||||
* @return Relative variance as Q48.16, or 0 if insufficient data.
|
||||
*/
|
||||
static q48_16_t window_variance_q48(const TimeWindow *w, uint64_t expected_delta)
|
||||
{
|
||||
if (w->count < 2 || expected_delta == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compute mean */
|
||||
int64_t sum = 0;
|
||||
for (uint32_t i = 0; i < w->count; i++) {
|
||||
sum += w->deltas[i];
|
||||
}
|
||||
int64_t mean = sum / (int64_t)w->count;
|
||||
|
||||
/* Compute variance = sum((delta - mean)^2) / count */
|
||||
uint64_t sum_sq = 0;
|
||||
for (uint32_t i = 0; i < w->count; i++) {
|
||||
int64_t diff = w->deltas[i] - mean;
|
||||
/* Clamp to prevent overflow */
|
||||
if (diff > 0x7FFFFFFF) diff = 0x7FFFFFFF;
|
||||
if (diff < -0x7FFFFFFF) diff = -0x7FFFFFFF;
|
||||
sum_sq += (uint64_t)(diff * diff);
|
||||
}
|
||||
uint64_t var_tsc = sum_sq / w->count;
|
||||
|
||||
/* Normalize by expected_delta^2 to get relative variance */
|
||||
uint64_t exp_sq = expected_delta;
|
||||
if (exp_sq > 0xFFFFFFFF) {
|
||||
var_tsc >>= 16;
|
||||
exp_sq >>= 8;
|
||||
}
|
||||
exp_sq = exp_sq * exp_sq;
|
||||
if (exp_sq == 0) return 0;
|
||||
|
||||
/* Clamp to avoid overflow when shifting */
|
||||
if (var_tsc > 0x0000FFFFFFFFFFFFULL) {
|
||||
var_tsc = 0x0000FFFFFFFFFFFFULL;
|
||||
}
|
||||
|
||||
return (var_tsc << 16) / exp_sq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive TIME-TRUST from a Q48.16 relative variance value.
|
||||
*
|
||||
* Applies the formula @c trust = 1 / (1 + variance) in Q48.16 arithmetic:
|
||||
* @c denom = Q48_ONE + variance, then @c trust = Q48_ONE / denom. Returns
|
||||
* @c Q48_ONE (maximum trust = 1.0) if @p denom would be zero (defensive).
|
||||
*
|
||||
* As variance → 0, trust → 1.0 (perfect timing). As variance → ∞, trust
|
||||
* → 0.0 (unusable). The Q48.16 output is stored in @c TimeTrustState.trust
|
||||
* and exported to the VM via @c heartbeat_trust().
|
||||
*
|
||||
* @param variance Q48.16 relative variance from @c window_variance_q48().
|
||||
* @return Q48.16 time-trust value in range (0, 1].
|
||||
*/
|
||||
static q48_16_t variance_to_trust(q48_16_t variance)
|
||||
{
|
||||
q48_16_t denom = q48_add(Q48_ONE, variance);
|
||||
if (denom == 0) {
|
||||
return Q48_ONE;
|
||||
}
|
||||
return q48_div(Q48_ONE, denom);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialise the M5 heartbeat / TIME-TRUST subsystem.
|
||||
*
|
||||
* Zeros the @c g_heartbeat state: tick count, TSC anchor, sample counter,
|
||||
* variance, and rolling window. Sets the initial trust to @c Q48_ONE (1.0,
|
||||
* maximum trust) so the VM starts with a confident time signal.
|
||||
*
|
||||
* Computes @c expected_delta = @p tsc_hz / @p tick_hz to calibrate the
|
||||
* deviation window. Falls back to 10 ms at 1 GHz if either argument is zero.
|
||||
*
|
||||
* Called from @c kernel_main() after @c timer_init() establishes
|
||||
* @c tsc_hz_locked.
|
||||
*
|
||||
* @param tsc_hz Locked TSC frequency in Hz (from @c timer_tsc_hz()).
|
||||
* @param tick_hz Target heartbeat rate in Hz (typically 100).
|
||||
*/
|
||||
void heartbeat_init(uint64_t tsc_hz, uint64_t tick_hz)
|
||||
{
|
||||
g_heartbeat.ticks = 0;
|
||||
g_heartbeat.last_tsc = 0;
|
||||
g_heartbeat.total_samples = 0;
|
||||
g_heartbeat.variance = 0;
|
||||
g_heartbeat.trust = Q48_ONE;
|
||||
|
||||
g_heartbeat.window.pos = 0;
|
||||
g_heartbeat.window.count = 0;
|
||||
for (int i = 0; i < TIME_WINDOW_SIZE; i++) {
|
||||
g_heartbeat.window.deltas[i] = 0;
|
||||
}
|
||||
|
||||
if (tick_hz > 0 && tsc_hz > 0) {
|
||||
g_heartbeat.expected_delta = tsc_hz / tick_hz;
|
||||
} else {
|
||||
g_heartbeat.expected_delta = 10000000; /* 10ms at 1GHz */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process one heartbeat tick — update tick count, variance, and TIME-TRUST.
|
||||
*
|
||||
* Called by the APIC timer ISR at @c tick_hz (100 Hz by default). On each call:
|
||||
* 1. Reads the current TSC via @c rdtsc().
|
||||
* 2. Increments @c ticks and @c total_samples.
|
||||
* 3. On the first sample, records @c last_tsc and returns (no delta yet).
|
||||
* 4. Computes the signed deviation of the actual inter-tick TSC delta from
|
||||
* @c expected_delta and pushes it into the rolling window.
|
||||
* 5. Recomputes @c variance and @c trust from the updated window.
|
||||
*
|
||||
* Must only be called from interrupt context. Does not take a lock; the
|
||||
* single-threaded kernel guarantees no concurrent access.
|
||||
*/
|
||||
void heartbeat_tick(void)
|
||||
{
|
||||
uint64_t now = rdtsc();
|
||||
TimeTrustState *s = &g_heartbeat;
|
||||
|
||||
s->ticks++;
|
||||
s->total_samples++;
|
||||
|
||||
/* First tick: just record TSC */
|
||||
if (s->total_samples == 1) {
|
||||
s->last_tsc = now;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute delta from last tick */
|
||||
uint64_t actual_delta = now - s->last_tsc;
|
||||
s->last_tsc = now;
|
||||
|
||||
/* Compute deviation from expected (signed) */
|
||||
int64_t deviation = (int64_t)actual_delta - (int64_t)s->expected_delta;
|
||||
|
||||
/* Add to rolling window */
|
||||
window_push(&s->window, deviation);
|
||||
|
||||
/* Recompute variance and trust */
|
||||
s->variance = window_variance_q48(&s->window, s->expected_delta);
|
||||
s->trust = variance_to_trust(s->variance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the total number of heartbeat ticks since @c heartbeat_init().
|
||||
*
|
||||
* The tick count monotonically increases with each @c heartbeat_tick() call.
|
||||
* Used by the HAL shim (@c sk_hal_heartbeat_ticks()) and the VM's @c M5
|
||||
* time-trust bridge.
|
||||
*
|
||||
* @return Number of heartbeat ticks elapsed.
|
||||
*/
|
||||
uint64_t heartbeat_ticks(void)
|
||||
{
|
||||
return g_heartbeat.ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the current TIME-TRUST value as Q48.16 fixed-point.
|
||||
*
|
||||
* Returns @c g_heartbeat.trust, updated on every tick by @c variance_to_trust().
|
||||
* Value is in range (0, 1]: @c Q48_ONE (65536) = perfect trust, values near
|
||||
* zero indicate high inter-tick jitter.
|
||||
*
|
||||
* @return Current time-trust as a @c time_trust_t (Q48.16 alias).
|
||||
*/
|
||||
time_trust_t heartbeat_trust(void)
|
||||
{
|
||||
return g_heartbeat.trust;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the full heartbeat / TIME-TRUST state.
|
||||
*
|
||||
* Provides read-only access to @c g_heartbeat for diagnostic output or
|
||||
* parity logging. The caller must not write through the returned pointer.
|
||||
*
|
||||
* @return Pointer to the static @c TimeTrustState (never NULL).
|
||||
*/
|
||||
const TimeTrustState *heartbeat_state(void)
|
||||
{
|
||||
return &g_heartbeat;
|
||||
return rdtsc();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
static uint64_t s_timer_period_tsc = 0;
|
||||
static uint64_t s_time_hz = 0; /* `time` CSR frequency (item 0.8/§26) */
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* SBI (Supervisor Binary Interface)
|
||||
@@ -110,16 +111,30 @@ static int sbi_set_timer(uint64_t deadline)
|
||||
* no error anywhere — the single most likely silent failure of this driver.
|
||||
* Every path out of a timer interrupt must reach this function.
|
||||
*
|
||||
* The step is @c heartbeat_next_period_ns() (item 0.8, §26) converted to
|
||||
* `time`-counter ticks via @c s_time_hz, not the fixed @c s_timer_period_tsc
|
||||
* used to seed the very first deadline in @c apic_timer_start() -- the
|
||||
* latter remains @c apic_timer_period_tsc()'s return value for
|
||||
* @c heartbeat_init()'s initial @c expected_delta, unchanged.
|
||||
*
|
||||
* Called from @c riscv64_interrupt_handler() in @c interrupts.c on
|
||||
* @c scause cause 5.
|
||||
*/
|
||||
void riscv64_timer_rearm(void)
|
||||
{
|
||||
uint64_t now;
|
||||
uint64_t step;
|
||||
|
||||
if (!s_sbi_time_ok) return;
|
||||
|
||||
s_next_deadline += s_timer_period_tsc;
|
||||
step = (s_time_hz > 0)
|
||||
? (heartbeat_next_period_ns() * s_time_hz) / 1000000000ULL
|
||||
: s_timer_period_tsc;
|
||||
if (step == 0) {
|
||||
step = 1;
|
||||
}
|
||||
|
||||
s_next_deadline += step;
|
||||
|
||||
/* If servicing ran long enough that the next deadline is already behind
|
||||
* us, resynchronise rather than burn through a backlog of instant
|
||||
@@ -127,7 +142,7 @@ void riscv64_timer_rearm(void)
|
||||
now = rdtime();
|
||||
if (s_next_deadline <= now)
|
||||
{
|
||||
s_next_deadline = now + s_timer_period_tsc;
|
||||
s_next_deadline = now + step;
|
||||
}
|
||||
|
||||
sbi_set_timer(s_next_deadline);
|
||||
@@ -183,6 +198,7 @@ int apic_timer_init(uint64_t tsc_hz, uint32_t tick_hz)
|
||||
s_timer_period_tsc = tsc_hz / tick_hz;
|
||||
else
|
||||
s_timer_period_tsc = 10000000;
|
||||
s_time_hz = tsc_hz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,6 @@ static uint64_t s_base_count = 0;
|
||||
static uint64_t s_base_ns = 0;
|
||||
|
||||
static timer_calibration_record_t s_cal;
|
||||
static TimeTrustState g_heartbeat;
|
||||
|
||||
/*
|
||||
* @brief Initialise the RISC-V timer subsystem (M5 milestone).
|
||||
@@ -232,99 +231,22 @@ const timer_calibration_record_t *timer_calibration_record(void)
|
||||
return &s_cal;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Initialise the heartbeat rolling-window state.
|
||||
/**
|
||||
* @brief Read the raw counter the riscv64 heartbeat is paced against.
|
||||
*
|
||||
* Zeroes @c g_heartbeat and sets the expected inter-tick interval as
|
||||
* @c tsc_hz / @c tick_hz `time`-counter ticks. Falls back to 10,000,000
|
||||
* ticks if either argument is zero. Initial @c trust is @c Q48_ONE
|
||||
* (full confidence) — the RISC-V @c time counter is architecturally
|
||||
* invariant regardless of which frequency source populated @c tsc_hz.
|
||||
* Item 0.8 (FABRIC.md §25.1): the shared heartbeat.c now owns
|
||||
* heartbeat_init()/heartbeat_tick()/heartbeat_service()/heartbeat_ticks()/
|
||||
* heartbeat_trust()/heartbeat_state(). This is the one piece that stays
|
||||
* per-architecture -- the same @c rdtime() the timer deadline is armed
|
||||
* against, not @c rdcycle() or any other source. Must read the same
|
||||
* counter the deadline was programmed against: @c expected_delta is
|
||||
* derived from timebase-frequency and is therefore in @c time units;
|
||||
* measuring the interval with @c cycle instead would difference two
|
||||
* unrelated clocks.
|
||||
*
|
||||
* @param tsc_hz `time` counter frequency (Hz); from @c timer_tsc_hz().
|
||||
* @param tick_hz Heartbeat rate (Hz); from @c apic_timer_init().
|
||||
* @return Current `time` CSR value.
|
||||
*/
|
||||
void heartbeat_init(uint64_t tsc_hz, uint64_t tick_hz)
|
||||
uint64_t heartbeat_read_counter(void)
|
||||
{
|
||||
g_heartbeat.ticks = 0;
|
||||
g_heartbeat.last_tsc = 0;
|
||||
g_heartbeat.total_samples = 0;
|
||||
g_heartbeat.variance = 0;
|
||||
g_heartbeat.trust = Q48_ONE;
|
||||
|
||||
g_heartbeat.window.pos = 0;
|
||||
g_heartbeat.window.count = 0;
|
||||
for (int i = 0; i < TIME_WINDOW_SIZE; i++)
|
||||
g_heartbeat.window.deltas[i] = 0;
|
||||
|
||||
g_heartbeat.expected_delta = (tick_hz > 0 && tsc_hz > 0)
|
||||
? (tsc_hz / tick_hz) : 10000000ULL;
|
||||
return rdtime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Record one heartbeat tick and update the inter-tick deviation window.
|
||||
*
|
||||
* Reads @c rdtime() and, if @c last_tsc is non-zero, records the signed
|
||||
* deviation @c ((now - last_tsc) - expected_delta) into the circular
|
||||
* @c window.deltas[] buffer. Increments @c ticks and @c total_samples.
|
||||
* Sets @c trust = @c Q48_ONE unconditionally — the RISC-V @c time counter
|
||||
* is invariant and needs no statistical quality estimate.
|
||||
*
|
||||
* Must read the same counter the deadline was programmed against.
|
||||
* @c expected_delta is derived from @c timebase-frequency and is therefore in
|
||||
* @c time units; measuring the interval with @c cycle instead would difference
|
||||
* two unrelated clocks and produce exactly the wrong-expected-interval defect
|
||||
* that switching off @c rdcycle was meant to remove.
|
||||
*
|
||||
* Called from @c riscv64_interrupt_handler() on @c scause cause 5, after the
|
||||
* timer has been re-armed.
|
||||
*/
|
||||
void heartbeat_tick(void)
|
||||
{
|
||||
uint64_t now = rdtime();
|
||||
if (g_heartbeat.last_tsc != 0) {
|
||||
int64_t delta = (int64_t)(now - g_heartbeat.last_tsc)
|
||||
- (int64_t)g_heartbeat.expected_delta;
|
||||
uint32_t pos = g_heartbeat.window.pos % TIME_WINDOW_SIZE;
|
||||
g_heartbeat.window.deltas[pos] = delta;
|
||||
g_heartbeat.window.pos++;
|
||||
if (g_heartbeat.window.count < TIME_WINDOW_SIZE)
|
||||
g_heartbeat.window.count++;
|
||||
g_heartbeat.total_samples++;
|
||||
}
|
||||
g_heartbeat.last_tsc = now;
|
||||
g_heartbeat.ticks++;
|
||||
g_heartbeat.trust = Q48_ONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the total number of heartbeat ticks since @c heartbeat_init().
|
||||
*
|
||||
* @return Monotonic tick counter; incremented once per @c heartbeat_tick() call.
|
||||
*/
|
||||
uint64_t heartbeat_ticks(void) { return g_heartbeat.ticks; }
|
||||
|
||||
/**
|
||||
* @brief Return the current TIME-TRUST quality metric in Q48.16 format.
|
||||
*
|
||||
* Always returns @c Q48_ONE on RISC-V because the @c time counter's *rate
|
||||
* stability* is invariant by specification — this tracks jitter in the
|
||||
* tick-to-tick interval, and is orthogonal to whether the frequency value
|
||||
* itself was discovered or is the fallback (see @c timer_calibration_record()
|
||||
* for that distinction). The x86-64 implementation derives this from
|
||||
* rolling-window variance.
|
||||
*
|
||||
* @return TIME-TRUST as Q48.16; always @c Q48_ONE on RISC-V.
|
||||
*/
|
||||
time_trust_t heartbeat_trust(void) { return g_heartbeat.trust; }
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the heartbeat @c TimeTrustState.
|
||||
*
|
||||
* Provides read access to the full @c g_heartbeat structure for
|
||||
* @c sk_parity_collect(), @c sk_hal_time_trust(), and other consumers.
|
||||
* The pointer is valid for the lifetime of the kernel.
|
||||
*
|
||||
* @return Pointer to @c g_heartbeat; never NULL.
|
||||
*/
|
||||
const TimeTrustState *heartbeat_state(void) { return &g_heartbeat; }
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* heartbeat.c - Shared M5 heartbeat / TIME-TRUST engine (punch-list item 0.8)
|
||||
*
|
||||
* One implementation of heartbeat_init()/heartbeat_ticks()/heartbeat_trust()/
|
||||
* heartbeat_state(), shared by all three architectures. Each architecture's
|
||||
* timer.c contributes only heartbeat_read_counter() — the one thing that is
|
||||
* genuinely per-ISA (rdtsc / rdtime / CNTPCT_EL0).
|
||||
*
|
||||
* Top half / bottom half split (FABRIC.md §25.1 item 0.8, per the GAP-A1
|
||||
* ruling in §16.4/§18.4): heartbeat_tick() is called from interrupt context
|
||||
* and does nothing but read the counter, bump TIME-TICKS, and latch a
|
||||
* pending sample. heartbeat_service() runs on the mainline (the REPL idle
|
||||
* loop) and does the window/variance/trust work. Neither one feeds patron
|
||||
* state — the engine stays on the virtual tick per §18.4, unchanged by this
|
||||
* file.
|
||||
*
|
||||
* Adaptive re-arm period (FABRIC.md §26, ruled 2026-08-03): Loop #7
|
||||
* (vm_runtime.c) computes an execution-derived stable/volatile signal and
|
||||
* calls heartbeat_set_adaptive_period_ns() with it, rescaled to this file's
|
||||
* kernel-appropriate base (10 ms, matching the 100 Hz rate item 0.1-0.7
|
||||
* configured) rather than the hosted 10 µs HEARTBEAT_TICK_NS base. Each
|
||||
* architecture's re-arm function reads heartbeat_next_period_ns() and
|
||||
* converts it to raw counter units instead of using a fixed constant.
|
||||
* Single writer (mainline, via vm_tick()'s Loop #7 site), single reader
|
||||
* (the ISR's re-arm call) — no lock needed, per §21.1's finding that
|
||||
* nothing here is actually concurrent on one hart.
|
||||
*/
|
||||
|
||||
#include "starkernel/timer.h"
|
||||
|
||||
#if defined(ARCH_AMD64)
|
||||
#define HEARTBEAT_HAS_VARIANCE 1
|
||||
#else
|
||||
#define HEARTBEAT_HAS_VARIANCE 0
|
||||
#endif
|
||||
|
||||
/* Kernel-side adaptive-period base: 10 ms, matching the 100 Hz hardware
|
||||
* rate established throughout items 0.1-0.7. Deliberately NOT
|
||||
* HEARTBEAT_TICK_NS (include/starforth_config.h) -- that constant is the
|
||||
* hosted pthread-worker's 10 µs base and is three orders of magnitude too
|
||||
* fast for a bare-metal ISR period (FABRIC.md §26.3). Loop #7's decision
|
||||
* logic is reused unmodified; only the base it scales differs. */
|
||||
#define HEARTBEAT_BASE_PERIOD_NS 10000000ULL
|
||||
|
||||
static TimeTrustState g_heartbeat;
|
||||
|
||||
/* Top/bottom half handoff. */
|
||||
static volatile uint64_t g_pending_counter;
|
||||
static volatile int g_pending_valid;
|
||||
|
||||
/* Adaptive re-arm period, clamped to the same [1/4x, 4x] band Loop #7
|
||||
* itself enforces on tick_target_ns, so a caller cannot runaway the ISR
|
||||
* rate even if it forwarded an unclamped value. */
|
||||
static volatile uint64_t g_adaptive_period_ns = HEARTBEAT_BASE_PERIOD_NS;
|
||||
|
||||
#if HEARTBEAT_HAS_VARIANCE
|
||||
|
||||
/**
|
||||
* @brief Push a signed delta value into the heartbeat rolling window.
|
||||
*/
|
||||
static void window_push(TimeWindow *w, int64_t delta)
|
||||
{
|
||||
w->deltas[w->pos] = delta;
|
||||
w->pos = (w->pos + 1) % TIME_WINDOW_SIZE;
|
||||
if (w->count < TIME_WINDOW_SIZE) {
|
||||
w->count++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute the relative variance of heartbeat deltas as a Q48.16 value.
|
||||
*/
|
||||
static q48_16_t window_variance_q48(const TimeWindow *w, uint64_t expected_delta)
|
||||
{
|
||||
if (w->count < 2 || expected_delta == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t sum = 0;
|
||||
for (uint32_t i = 0; i < w->count; i++) {
|
||||
sum += w->deltas[i];
|
||||
}
|
||||
int64_t mean = sum / (int64_t)w->count;
|
||||
|
||||
uint64_t sum_sq = 0;
|
||||
for (uint32_t i = 0; i < w->count; i++) {
|
||||
int64_t diff = w->deltas[i] - mean;
|
||||
if (diff > 0x7FFFFFFF) diff = 0x7FFFFFFF;
|
||||
if (diff < -0x7FFFFFFF) diff = -0x7FFFFFFF;
|
||||
sum_sq += (uint64_t)(diff * diff);
|
||||
}
|
||||
uint64_t var_tsc = sum_sq / w->count;
|
||||
|
||||
uint64_t exp_sq = expected_delta;
|
||||
if (exp_sq > 0xFFFFFFFF) {
|
||||
var_tsc >>= 16;
|
||||
exp_sq >>= 8;
|
||||
}
|
||||
exp_sq = exp_sq * exp_sq;
|
||||
if (exp_sq == 0) return 0;
|
||||
|
||||
if (var_tsc > 0x0000FFFFFFFFFFFFULL) {
|
||||
var_tsc = 0x0000FFFFFFFFFFFFULL;
|
||||
}
|
||||
|
||||
return (var_tsc << 16) / exp_sq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive TIME-TRUST from a Q48.16 relative variance value.
|
||||
*/
|
||||
static q48_16_t variance_to_trust(q48_16_t variance)
|
||||
{
|
||||
q48_16_t denom = q48_add(Q48_ONE, variance);
|
||||
if (denom == 0) {
|
||||
return Q48_ONE;
|
||||
}
|
||||
return q48_div(Q48_ONE, denom);
|
||||
}
|
||||
|
||||
#endif /* HEARTBEAT_HAS_VARIANCE */
|
||||
|
||||
/**
|
||||
* @brief Initialise the M5 heartbeat / TIME-TRUST subsystem.
|
||||
*
|
||||
* Shared across all three architectures -- the fallback and calibration
|
||||
* formula never differed between them, only what heartbeat_service() does
|
||||
* with the resulting window did.
|
||||
*/
|
||||
void heartbeat_init(uint64_t tsc_hz, uint64_t tick_hz)
|
||||
{
|
||||
g_heartbeat.ticks = 0;
|
||||
g_heartbeat.last_tsc = 0;
|
||||
g_heartbeat.total_samples = 0;
|
||||
g_heartbeat.variance = 0;
|
||||
g_heartbeat.trust = Q48_ONE;
|
||||
|
||||
g_heartbeat.window.pos = 0;
|
||||
g_heartbeat.window.count = 0;
|
||||
for (int i = 0; i < TIME_WINDOW_SIZE; i++) {
|
||||
g_heartbeat.window.deltas[i] = 0;
|
||||
}
|
||||
|
||||
g_heartbeat.expected_delta = (tick_hz > 0 && tsc_hz > 0)
|
||||
? (tsc_hz / tick_hz) : 10000000ULL;
|
||||
|
||||
g_pending_valid = 0;
|
||||
g_adaptive_period_ns = HEARTBEAT_BASE_PERIOD_NS;
|
||||
}
|
||||
|
||||
void heartbeat_tick(void)
|
||||
{
|
||||
g_pending_counter = heartbeat_read_counter();
|
||||
g_heartbeat.ticks++;
|
||||
g_pending_valid = 1;
|
||||
}
|
||||
|
||||
void heartbeat_service(void)
|
||||
{
|
||||
if (!g_pending_valid) {
|
||||
return;
|
||||
}
|
||||
uint64_t now = g_pending_counter;
|
||||
g_pending_valid = 0;
|
||||
|
||||
TimeTrustState *s = &g_heartbeat;
|
||||
s->total_samples++;
|
||||
|
||||
if (s->total_samples == 1) {
|
||||
s->last_tsc = now;
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t actual_delta = now - s->last_tsc;
|
||||
s->last_tsc = now;
|
||||
int64_t deviation = (int64_t)actual_delta - (int64_t)s->expected_delta;
|
||||
|
||||
#if HEARTBEAT_HAS_VARIANCE
|
||||
window_push(&s->window, deviation);
|
||||
s->variance = window_variance_q48(&s->window, s->expected_delta);
|
||||
s->trust = variance_to_trust(s->variance);
|
||||
#else
|
||||
/* RISC-V `time` / AArch64 CNTPCT_EL0 are architecturally invariant
|
||||
* counters -- no statistical quality estimate needed (matches the
|
||||
* per-arch rationale this file replaces). */
|
||||
(void)deviation;
|
||||
s->trust = Q48_ONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t heartbeat_ticks(void)
|
||||
{
|
||||
return g_heartbeat.ticks;
|
||||
}
|
||||
|
||||
time_trust_t heartbeat_trust(void)
|
||||
{
|
||||
return g_heartbeat.trust;
|
||||
}
|
||||
|
||||
const TimeTrustState *heartbeat_state(void)
|
||||
{
|
||||
return &g_heartbeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the adaptive re-arm period (FABRIC.md §26).
|
||||
*
|
||||
* Called from vm_runtime.c's Loop #7 site on the mainline execution path
|
||||
* (never interrupt context) with a value already rescaled to this file's
|
||||
* HEARTBEAT_BASE_PERIOD_NS. Clamped defensively to the same [1/4x, 4x]
|
||||
* band Loop #7 itself enforces around its own base, so a caller forwarding
|
||||
* an unclamped or wrongly-scaled value cannot run the ISR away.
|
||||
*
|
||||
* @param ns Desired period in nanoseconds for the next re-arm.
|
||||
*/
|
||||
void heartbeat_set_adaptive_period_ns(uint64_t ns)
|
||||
{
|
||||
uint64_t lo = HEARTBEAT_BASE_PERIOD_NS / 4;
|
||||
uint64_t hi = HEARTBEAT_BASE_PERIOD_NS * 4;
|
||||
if (ns < lo) ns = lo;
|
||||
if (ns > hi) ns = hi;
|
||||
g_adaptive_period_ns = ns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the period the next hardware re-arm should use.
|
||||
*
|
||||
* Called from interrupt context by each architecture's re-arm function
|
||||
* (apic_timer_rearm(), riscv64_timer_rearm(), the AArch64 equivalent) in
|
||||
* place of the fixed constant those functions used before item 0.8.
|
||||
*
|
||||
* @return Current adaptive period in nanoseconds.
|
||||
*/
|
||||
uint64_t heartbeat_next_period_ns(void)
|
||||
{
|
||||
return g_adaptive_period_ns;
|
||||
}
|
||||
+16
-5
@@ -16,10 +16,13 @@
|
||||
* - Output: console_puts() / console_putc()
|
||||
*
|
||||
* Idle spin: polls console_getc() and services the adaptive heartbeat.
|
||||
* The APIC timer ISR increments heartbeat_ticks() at 100 Hz;
|
||||
* the idle spin calls sk_repl_idle() once per SK_IDLE_BEAT_INTERVAL
|
||||
* ticks. On QEMU TCG the ISR must fire for ticks to advance —
|
||||
* check "Heartbeat: N ticks" in the serial log to confirm.
|
||||
* The timer ISR's top half (heartbeat_tick()) latches one
|
||||
* sample per interrupt; the idle spin drains it every
|
||||
* iteration via heartbeat_service() (item 0.8, FABRIC.md §26)
|
||||
* and calls sk_repl_idle() once per SK_IDLE_BEAT_INTERVAL ticks
|
||||
* for coarser subsystem dispatch. On QEMU TCG the ISR must fire
|
||||
* for ticks to advance — check "Heartbeat: N ticks" in the
|
||||
* serial log to confirm.
|
||||
*
|
||||
* Runs with interrupts enabled so the APIC heartbeat fires normally.
|
||||
* Designed as the last thing kernel_main does before the idle loop.
|
||||
@@ -85,7 +88,15 @@ static int sk_readline(char *buf, int size)
|
||||
int c = console_getc(); /* non-blocking poll */
|
||||
|
||||
if (c < 0) {
|
||||
/* Idle — service the adaptive heartbeat if a beat has elapsed */
|
||||
/* Service the heartbeat bottom half every idle iteration, not
|
||||
* gated by SK_IDLE_BEAT_INTERVAL (item 0.8, FABRIC.md §26):
|
||||
* heartbeat_service() drains at most one latched sample per
|
||||
* call, so a coarse gate here would silently lose or merge
|
||||
* samples between ISR-latched ticks. sk_repl_idle() below is
|
||||
* a separate, deliberately coarser cadence for higher-level
|
||||
* subsystem dispatch, unrelated to sample fidelity. */
|
||||
heartbeat_service();
|
||||
|
||||
uint64_t now = heartbeat_ticks();
|
||||
if (now - g_last_beat_tick >= SK_IDLE_BEAT_INTERVAL) {
|
||||
g_last_beat_tick = now;
|
||||
|
||||
@@ -748,5 +748,18 @@ void vm_tick_inference_engine(VM* vm)
|
||||
(unsigned long)new_tick_ns);
|
||||
}
|
||||
}
|
||||
|
||||
/* Punch-list item 0.8 / FABRIC.md §26: drive the physical re-arm
|
||||
* period from this same execution-derived signal, ratio-preserving
|
||||
* rescale onto the kernel's 10 ms base rather than the hosted
|
||||
* HEARTBEAT_TICK_NS (10 µs) base -- see §26.3 for why the literal
|
||||
* value cannot be used on bare-metal hardware. heartbeat.c clamps
|
||||
* defensively on the way in, so no clamping is duplicated here. */
|
||||
{
|
||||
const uint64_t kernel_base_ns = 10000000ULL; /* 10 ms, matches heartbeat.c's HEARTBEAT_BASE_PERIOD_NS */
|
||||
uint64_t kernel_period_ns =
|
||||
(vm->heartbeat.tick_target_ns * kernel_base_ns) / HEARTBEAT_TICK_NS;
|
||||
heartbeat_set_adaptive_period_ns(kernel_period_ns);
|
||||
}
|
||||
}
|
||||
/* End always-on loop */}
|
||||
|
||||
Reference in New Issue
Block a user