mirror of
https://github.com/RhapsodiOS/RhapsodiOS.git
synced 2026-11-03 22:01:40 -07:00
[GH-ISSUE #26] kernel: i386 IOGetTimestamp / clock_get_counter(System) steps backwards by one tick #23
Labels
No labels
bug
documentation
enhancement
enhancement
good first issue
help wanted
help wanted
pull-request
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
github-starred/RhapsodiOS#23
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @raynorpat on GitHub (Sep 25, 2026).
Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/26
Symptom
On i386 (QEMU 11.1 TCG,
-M pc -cpu pentium),IOGetTimestamp()sometimes returns a lower value than a call made microseconds earlier. It showed up inPS2MouseIntHandler(src/drivers-i386/input/drvPS2Mouse/.../PS2Mouse.m): two consecutive bytes of one 3-byte packet hadnewStamp < lastTimeStamp. The unsignedns_time_tdifference wrapped, and a diagnostic(int)((newStamp - lastTimeStamp) / 1000000)printed-140462621 msseven times during one set of mouse sweeps. The PS/2 driver now guards against this (1a80dece9onclaude/ps2-mouse-qemu-erratic-35b897), but the clock is still wrong.-140462621is what the int cast gives for an unsigned wrap of a step between about 9.55 and 10.55 ms, so the clock goes back by one tick (HZ=100).Cause
IOGetTimestamp()→clock_get_counter(System)→system_time_stamp()insrc/kernel-7/machdep/i386/machine_clock.c. It adds the latched 8254 channel-0 count to a tick counter that the IRQ0 handler advances. If the PIT has wrapped but IRQ0 has not been serviced yet, the read seescurrent > last_timer_countand adds one tick. But it also stores the post-wrap count aslast_timer_count, so the next read before the interrupt (current below that stored value) does not detect the wrap and drops the tick. The clock goes back by one tick. TCG's late interrupt delivery makes that window easy to hit.Proposed fix (Apple divergence)
Once a wrap is detected, store 0 in
last_timer_count. The count is never 0 in mode 2 (NDIV), so every later read until IRQ0 runs still counts the pending tick. The interrupt handler then resetslast_timer_counttotimer_constas before. Fix in progress; kernel build running on the i386 guest.Remaining limit (not addressed): if two wraps pass before IRQ0 is serviced, a tick can still be lost.
Other callers affected by a backward step
Being audited. Callers of
clock_get_counter(System)includesched_usec_elapsed/tick_stamp(kern/mach_clock.c),hardclockSIMPLE_CLOCK delta (bsd/kern/kern_clock.c),microboot(kern/ns_timer.c),kern_timestamp(kern/time_stamp.c),deadline_from_interval(kern/thread_call.c), andIOGetTimestampusers in drivers;clock_get_counter(Calendar)(microtime) shares the same base.@raynorpat commented on GitHub (Sep 25, 2026):
Fix committed as
cd9fc5abdonclaude/compassionate-zhukovsky-279f1a(not yet merged).rbuild kernel --toolchainfor i386 built it on the 127.0.0.1:2222 guest in a private root (/build/clockfix/out/kernel-154.5.1-7-i386.apk);machine_clock.ccompiles with no warnings. Not boot-tested yet.Also fixed in
d585eea92: the sharedgcc-darwin-i386.confhad no/usr/local/binonpath=, sorbuild kernelfailed withrelpath: not found. The guest's/build/srccopy is updated too.Callers affected by a backward step (not changed)
Kernel, low risk:
hardclock(SIMPLE_CLOCK delta),tick_stampandsched_usec_elapsed(viarecompute_priorities) all read the clock inside the IRQ0 handler, right after the tick is added.kern_timestampandmicrobootpass the error through to user space and to the user-space driverkitIOGetTimestamp.Spurious timeouts (
now - startunsigned, compared to a limit):drvNE2k/.../NE2K.mlines 164, 1106 and 1144: false "Card failure" or Tx RDC timeout and a card reset (the NIC the QEMU guest uses)drvAMDPCNet32II/.../AMDPCNet32II.m:146Same bug as PS/2:
drvSerialPointingDevice/.../SerialPointingDevice.mlines 656, 748 and 772: the 40 ms gap check would throw away a valid packet.Wrong statistics only:
AMD_x86.m:701interval test; the totalTime/latentTime numbers in EIDEIdeDiskInternal.m, BusLogic, Adaptec 1542B/6X60 and AMD SCSI.Harmless:
IOEthernetandIOTokenRingcompare absolute deadlines, so a backward step only makes a timeout slightly longer.EventInput's periodic scheduling already handles a clock that appears to wrap around.@raynorpat commented on GitHub (Sep 25, 2026):
Driver follow-ups committed on
claude/compassionate-zhukovsky-279f1a:b34ff37aaNE2K, AMDPCNet32II: timeouts fire only on a forward clock gap. NE2K's three ISR waits (NICGetISR(base) & 0x80 == 0) never ran because of operator precedence; they're parenthesised now, so the reset-ack and Tx RDC waits really poll. Boot-tested on a throwaway-snapshotguest: en0 comes up, pings work, 3× 1.49 MB transmits and a 2 MB receive all had matching checksums, with no timeout or reset messages on the console. AMDPCNet32II is not built: it already fails in its headers (if_ether.h:125/143, incompleteea_hdr/ac_if), before reaching this change.5d1d2f845SerialPointingDevice: all three 40 ms dispatch gates also dispatch whencurrentTimeStamp < lastTimeStamp; recorded inreconstruction/divergences.md. Built; not tested with a serial mouse.@raynorpat commented on GitHub (Sep 25, 2026):
Merged to master in
fe5c4d04e(local master; not yet pushed to origin):cd9fc5abdkernel clock fix,b34ff37aaNE2K/AMDPCNet32II guards (NE2K now 0.92 in8f8a681fe),5d1d2f845SerialPointingDevice, andd585eea92for the i386 toolchain profile. Still open: the i386 kernel with the clock fix has been built but not boot-tested, and the AMDPCNet32II header build break is being fixed separately.