Skip to content

{bp-19559} arch/x86_64: six fixes to make BUILD_KERNEL and the HPET alarm work - #19653

Merged
xiaoxiang781216 merged 6 commits into
apache:releases/13.0from
jerpelea:bp-19559
Aug 3, 2026
Merged

{bp-19559} arch/x86_64: six fixes to make BUILD_KERNEL and the HPET alarm work#19653
xiaoxiang781216 merged 6 commits into
apache:releases/13.0from
jerpelea:bp-19559

Conversation

@jerpelea

@jerpelea jerpelea commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Six independent x86_64 bugs. Each is a separate commit; together they are what
had to be fixed before an x86_64 target could be built and run at all.

  | Commit | Area | What it breaks -- | -- | -- | -- 1 | make ARCH_INTEL64_HPET_ALARM buildable | Kconfig | anything selecting the HPET 2 | do not demand a TSC frequency for a clock that has none | boot | anything selecting the HPET 3 | define the missing CONFIG_ARCH_HAVE_SYSCALL | Kconfig | knsh_romfs, knsh_romfs_pci 4 | give the page allocator the physical page pool base | addrenv | knsh_romfs, knsh_romfs_pci 5 | inherit the kernel low memory mapping in an address environment | addrenv | knsh_romfs, knsh_romfs_pci 6 | do not wrap the HPET oneshot on a deadline that has passed | timer | anything selecting the HPET

There is a reason there are six of them, and it is bug 3: CONFIG_BUILD_KERNEL
on x86_64 has been unbuildable since May 2025, so nothing behind it has been
exercised in a year, and the HPET alarm looks like it has never been exercised
at all.

Impact

RELEASE

Testing

CI

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 <marco.casaroli@gmail.com>
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 <marco.casaroli@gmail.com>
794c325 ("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 <marco.casaroli@gmail.com>
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 <marco.casaroli@gmail.com>
…ronment.

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 <marco.casaroli@gmail.com>
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 <marco.casaroli@gmail.com>
@github-actions github-actions Bot added Arch: x86_64 Issues related to the x86_64 architecture Size: S The size of the change in this PR is small labels Aug 3, 2026
@xiaoxiang781216
xiaoxiang781216 merged commit 67c34e3 into apache:releases/13.0 Aug 3, 2026
13 checks passed
@jerpelea
jerpelea deleted the bp-19559 branch August 4, 2026 06:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: x86_64 Issues related to the x86_64 architecture Size: S The size of the change in this PR is small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants