From d061272e40fc2e6532107441e3d28e8f2a22159e Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 26 Jul 2026 17:24:57 +0200 Subject: [PATCH 1/6] arch/x86_64: make ARCH_INTEL64_HPET_ALARM buildable. ARCH_INTEL64_HPET_ALARM is one of the three members of the "System Timer Source" choice, but selecting it does not build. Taking qemu-intel64:nsh on master and moving the choice off ARCH_INTEL64_TSC_DEADLINE onto it: intel64/intel64_hpet_alarm.c:41:24: error: 'CONFIG_ARCH_INTEL64_HPET_ALARM_CHAN' undeclared ARCH_INTEL64_HPET_ALARM_CHAN lives inside "if INTEL64_HPET" and nothing selects INTEL64_HPET. Enabling that by hand moves the failure to link time, because intel64_oneshot_lower.c is built only when INTEL64_ONESHOT is set: undefined reference to `oneshot_initialize' Enabling INTEL64_ONESHOT as well finally reaches the real problem. intel64_oneshot_lower.c implements the counter flavour of struct oneshot_operations_s, which exists only with ONESHOT_COUNT: intel64_oneshot_lower.c: error: 'const struct oneshot_operations_s' has no member named 'start_absolute' intel64_oneshot_lower.c: error: implicit declaration of function 'oneshot_count_init' intel64_oneshot_lower.c: error: initialization of 'int (*)(struct oneshot_lowerhalf_s *, const struct timespec *)' from incompatible pointer type ... (four more of these) ONESHOT, ONESHOT_COUNT and ONESHOT_FAST_DIVISION were selected by ARCH_INTEL64_TSC_DEADLINE and by nothing else, so the other members of the same choice could never be built. Select the four from ARCH_INTEL64_HPET_ALARM as well. INTEL64_ONESHOT selects INTEL64_HPET in turn, which is what brings ARCH_INTEL64_HPET_ALARM_CHAN into existence, so one added select closes all three stages. Impact: build only, and only for a configuration that could not be built before. No existing defconfig selects ARCH_INTEL64_HPET_ALARM. Assisted-by: Claude:claude-opus-5 Signed-off-by: Marco Casaroli --- arch/x86_64/src/intel64/Kconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/x86_64/src/intel64/Kconfig b/arch/x86_64/src/intel64/Kconfig index 0a5af97318981..d79f4112b7bbf 100644 --- a/arch/x86_64/src/intel64/Kconfig +++ b/arch/x86_64/src/intel64/Kconfig @@ -70,6 +70,10 @@ config ARCH_INTEL64_TSC config ARCH_INTEL64_HPET_ALARM bool "HPET timer alarm support" + select INTEL64_ONESHOT + select ONESHOT + select ONESHOT_COUNT + select ONESHOT_FAST_DIVISION select ALARM_ARCH ---help--- With this option you can enable ALARM_ARCH features that works on top of From d81810a3a13ce3246b78b609b531a390fe4672a9 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 26 Jul 2026 17:24:57 +0200 Subject: [PATCH 2/6] arch/x86_64: do not demand a TSC frequency for a clock that has none. g_x86_64_timer_freq is assigned only under ARCH_INTEL64_TSC_DEADLINE or ARCH_INTEL64_TSC, and is read only by the two intel64_tsc_*.c files those options build. With the HPET as the system clock it stays 0, which is correct and harmless -- but x86_64_timer_calibrate_freq() panics on 0 unconditionally, so the board dies during x86_64_lowsetup(). Require a frequency only where something needs one, which is ARCH_INTEL64_HAVE_TSC. The failure mode is worth recording, because it gives nothing to work from: the PANIC() happens before x86_64_earlyserialinit(), and the panic handler itself then triple-faults, because _assert() reads up_interrupt_context() -- a %gs-relative load -- and the GS base is not programmed until x86_64_cpu_priv_set(). The console stays completely empty and the machine resets. Impact: runtime, ARCH_INTEL64_HPET_ALARM only. Configurations with a TSC are unchanged -- the PANIC() is still compiled for them. Assisted-by: Claude:claude-opus-5 Signed-off-by: Marco Casaroli --- arch/x86_64/src/intel64/intel64_freq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/x86_64/src/intel64/intel64_freq.c b/arch/x86_64/src/intel64/intel64_freq.c index bdf0025179952..7cfd1ad58c67c 100644 --- a/arch/x86_64/src/intel64/intel64_freq.c +++ b/arch/x86_64/src/intel64/intel64_freq.c @@ -150,10 +150,12 @@ void x86_64_timer_calibrate_freq(void) g_x86_64_timer_freq = CONFIG_ARCH_INTEL64_APIC_FREQ_KHZ * 1000ul; #endif +#ifdef CONFIG_ARCH_INTEL64_HAVE_TSC if (g_x86_64_timer_freq == 0) { /* The TSC frequency is not available */ PANIC(); } +#endif } From c1dd661d00159f2416996fe4281b40c8204e1b05 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 26 Jul 2026 17:24:57 +0200 Subject: [PATCH 3/6] arch/x86_64: define the missing CONFIG_ARCH_HAVE_SYSCALL. 794c325947 ("arch/x64:Syscall support is enabled by default", 2025-05-27) changed nine guards from CONFIG_LIB_SYSCALL to CONFIG_ARCH_HAVE_SYSCALL but never added the Kconfig symbol. Only ARCH_HAVE_SYSCALL_HOOKS exists in tree; ARCH_HAVE_SYSCALL itself is defined nowhere, so it is always unset and since that commit x86_64 has had no x86_64_syscall_entry(), no x86_64_syscall(), no IA32_LSTAR/IA32_STAR programming and no syscall stub layer in any configuration: arch/x86_64/src/common/Make.defs:37 x86_64_syscall.c not compiled arch/x86_64/src/common/CMakeLists.txt:41 likewise arch/x86_64/include/irq.h:86 arch/x86_64/src/intel64/intel64_cpu.c:247, :386 arch/x86_64/src/intel64/intel64_head.S:83, :351, :538 arch/x86_64/src/intel64/intel64_saveusercontext.S:108 qemu-intel64:knsh_romfs and qemu-intel64:knsh_romfs_pci are the two CONFIG_BUILD_KERNEL configurations in tree, and neither can have worked in that time. They still link -- nothing references the missing pieces, so libstubs.a is simply never pulled in -- and then die the first time user code executes SYSCALL. Define the symbol with the condition the code had before that commit, which is LIB_SYSCALL: every protected and every kernel build needs the interface, and a flat build is left exactly as it is today. Impact: restores the system call interface for CONFIG_BUILD_KERNEL and CONFIG_BUILD_PROTECTED on x86_64. CONFIG_BUILD_FLAT is unaffected -- qemu-intel64:nsh still builds with ARCH_HAVE_SYSCALL unset. Assisted-by: Claude:claude-opus-5 Signed-off-by: Marco Casaroli --- arch/Kconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/Kconfig b/arch/Kconfig index 0085b50d1ad9b..ab0a0c066c7e5 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -151,6 +151,7 @@ config ARCH_X86_64 select ARCH_HAVE_SETJMP select ARCH_HAVE_PERF_EVENTS select ARCH_HAVE_POWEROFF + select ARCH_HAVE_SYSCALL if LIB_SYSCALL ---help--- x86-64 architectures. @@ -611,6 +612,12 @@ config ARCH_HAVE_RTC_SUBSECONDS bool default n +config ARCH_HAVE_SYSCALL + bool + default n + ---help--- + Indicates that the architecture provides the system call interface. + config ARCH_HAVE_SYSCALL_HOOKS bool default n From 11cde55dadcf113b7a09dd4a10c7fa8ff5b8ef3a Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 26 Jul 2026 17:24:57 +0200 Subject: [PATCH 4/6] arch/x86_64: give the page allocator the physical page pool base. mm_pginitialize() documents heap_start as "the physical address of the start of memory region", and every x86_64 consumer of mm_pgalloc() agrees: create_spgtables(), x86_64_get_pgtable() and up_addrenv_create() all put the result through x86_64_pgvaddr() before touching it. x86_64_pgvaddr() in turn range-checks against CONFIG_ARCH_PGPOOL_PBASE (arch/x86_64/src/common/ pgalloc.h:67). arm64's equivalent passes CONFIG_ARCH_PGPOOL_PBASE. up_allocate_pgheap() passed CONFIG_ARCH_PGPOOL_VBASE instead, and in the other branch X86_64_PGPOOL_BASE + X86_64_LOAD_OFFSET, which is the same mistake spelled out. Every page handed out was therefore a virtual address that fell outside the pool's physical window, x86_64_pgvaddr() returned 0, and the first x86_64_pgwipe() memset NULL. It presents as a page fault in memset() under create_spgtables() the first time a process address environment is created, which is loading the init program. qemu-intel64:knsh_romfs sets PGPOOL_PBASE=0x00c000000 and PGPOOL_VBASE=0x10c000000, so the value passed was off by the 4GB load offset. Impact: runtime, CONFIG_ARCH_ADDRENV builds only (CONFIG_MM_PGALLOC). Assisted-by: Claude:claude-opus-5 Signed-off-by: Marco Casaroli --- arch/x86_64/src/intel64/intel64_pgalloc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/x86_64/src/intel64/intel64_pgalloc.c b/arch/x86_64/src/intel64/intel64_pgalloc.c index 86031baa25e1c..98e0ead34c67b 100644 --- a/arch/x86_64/src/intel64/intel64_pgalloc.c +++ b/arch/x86_64/src/intel64/intel64_pgalloc.c @@ -61,15 +61,17 @@ void up_allocate_pgheap(void **heap_start, size_t *heap_size) { DEBUGASSERT(heap_start && heap_size); + /* The page allocator uses physical addresses. */ + #ifndef CONFIG_ARCH_PGPOOL_MAPPING /* pgheap at the end of RAM */ - *heap_start = (void *)(X86_64_PGPOOL_BASE + X86_64_LOAD_OFFSET); + *heap_start = (void *)X86_64_PGPOOL_BASE; *heap_size = (size_t)X86_64_PGPOOL_SIZE; #else /* pgheap defined with Kconfig options */ - *heap_start = (void *)CONFIG_ARCH_PGPOOL_VBASE; + *heap_start = (void *)CONFIG_ARCH_PGPOOL_PBASE; *heap_size = (size_t)CONFIG_ARCH_PGPOOL_SIZE; #endif } From 399dd316a5b06963daf77eb9ce465b7b6513fc42 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 26 Jul 2026 17:24:57 +0200 Subject: [PATCH 5/6] arch/x86_64: inherit the kernel low memory mapping in an address environment. copy_kernel_mappings() copies exactly one PDPT entry, the 1GB linear window that maps physical 0-1GB at 4GB-5GB. The boot identity mapping of the low 4GB, which lives in PDPT entries 0-3 of g_pdpt_low (intel64_head.S:756, one page directory per 1GB) and is where every MMIO register is reached, is not carried over. A kernel thread never gets an address environment of its own and addrenv_switch() leaves the last one in place for it, so as soon as any process exists, kernel code touching MMIO faults. The HPET at 0xfed00000 finds it immediately -- CR2=fed000f0, in intel64_hpet_getreg() under clock_systime_ticks() on the lpwork thread -- and any MMIO driver would. Inherit the four boot PDPT entries. They point at the boot page directories rather than at copies, so anything intel64_map_region() adds later is inherited too, and they carry no X86_PAGE_USER, so user code still cannot reach them. Impact: runtime, CONFIG_ARCH_ADDRENV builds only. User-space access is unchanged; the entries added are supervisor-only. Assisted-by: Claude:claude-opus-5 Signed-off-by: Marco Casaroli --- arch/x86_64/src/common/x86_64_addrenv.c | 10 ++++++++++ arch/x86_64/src/common/x86_64_mmu.h | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/arch/x86_64/src/common/x86_64_addrenv.c b/arch/x86_64/src/common/x86_64_addrenv.c index b8396b84bc072..1473987b37b99 100644 --- a/arch/x86_64/src/common/x86_64_addrenv.c +++ b/arch/x86_64/src/common/x86_64_addrenv.c @@ -70,6 +70,8 @@ #include +#include + #include "addrenv.h" #include "pgalloc.h" #include "x86_64_mmu.h" @@ -192,10 +194,18 @@ static int create_spgtables(arch_addrenv_t *addrenv) static void copy_kernel_mappings(arch_addrenv_t *addrenv) { uintptr_t *pdpt = (uintptr_t *)x86_64_pgvaddr(addrenv->spgtables[1]); + int i; /* Kernel mapping - lower 1GB maps to 4GB-5GB */ pdpt[4] = X86_PDPT_KERNEL_MAP; + + /* Inherit the boot identity mapping of the low 4GB. */ + + for (i = 0; i < X86_MMU_LOWMEM_PDPTS; i++) + { + pdpt[i] = g_pdpt[i]; + } } /**************************************************************************** diff --git a/arch/x86_64/src/common/x86_64_mmu.h b/arch/x86_64/src/common/x86_64_mmu.h index e029075a194fe..634a60d1b9730 100644 --- a/arch/x86_64/src/common/x86_64_mmu.h +++ b/arch/x86_64/src/common/x86_64_mmu.h @@ -57,6 +57,10 @@ #define X86_MMU_VADDR_INDEX(vaddr, ptlevel) \ ((vaddr >> X86_MMU_VADDR_SHIFT(ptlevel)) & X86_MMU_VPN_MASK) +/* Number of PDPT entries in the boot low-memory identity mapping. */ + +#define X86_MMU_LOWMEM_PDPTS 4 + /**************************************************************************** * Public Function Prototypes ****************************************************************************/ From 05fa1d445748ba3dc6247a05c7adbd4ab74d81f4 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 26 Jul 2026 17:24:57 +0200 Subject: [PATCH 6/6] arch/x86_64: do not wrap the HPET oneshot on a deadline that has passed. intel64_timer_start_absolute() computed "expected - current_us" unsigned. A watchdog started with a delay of zero asks for a deadline that is already current, the subtraction wraps to nearly 2^64, and the comparator is set so far ahead that the timer never fires. The CONFIG_INTEL64_HPET_MIN_DELAY clamp in intel64_oneshot_start() cannot help: the wrapped value is enormous, not small. Ask for the shortest delay the hardware can take instead of wrapping, and let that existing minimum-delay logic pick it. It presents as ostest hanging in wdog_test with no output and no fault. apps/testing/ostest/wdog.c:281 calls wdtest_once(&test_wdog, param, 0), and NSEC2TICK() takes the next few delays (1ns, 10ns, ...) to zero ticks as well; wdtest_once() then spins forever in its "wait until the callback is triggered exactly once" loop. Impact: runtime, ARCH_INTEL64_HPET_ALARM only. Assisted-by: Claude:claude-opus-5 Signed-off-by: Marco Casaroli --- arch/x86_64/src/intel64/intel64_oneshot_lower.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/x86_64/src/intel64/intel64_oneshot_lower.c b/arch/x86_64/src/intel64/intel64_oneshot_lower.c index 86e8ecdd75a5a..40fbeec7754d6 100644 --- a/arch/x86_64/src/intel64/intel64_oneshot_lower.c +++ b/arch/x86_64/src/intel64/intel64_oneshot_lower.c @@ -149,10 +149,14 @@ static void intel64_timer_start_absolute(struct oneshot_lowerhalf_s *lower, (struct intel64_oneshot_lowerhalf_s *)lower; irqstate_t flags = spin_lock_irqsave(&g_oneshotlow_spin); int ret = intel64_oneshot_current(&priv->oneshot, ¤t_us); - uint64_t delta_us = expected - current_us; + uint64_t delta_us; DEBUGASSERT(ret == OK); + /* Use the shortest delay when the deadline has passed. */ + + delta_us = expected > current_us ? expected - current_us : 0; + ts.tv_sec = delta_us / USEC_PER_SEC; ts.tv_nsec = delta_us % USEC_PER_SEC * 1000ull; ret = intel64_oneshot_start(&priv->oneshot, intel64_oneshot_handler,