Hi, In the future when the LAPIC timer is run in oneshot mode there will be no lapic_delay().
This is fine if you have a constant TSC, because we have tsc_delay(). This is *very* bad for older amd64 machines, because you are left with i8254_delay(). I would like to offer a less awful delay(9) implementation for this class of hardware. Otherwise we may trip over bizarre phantom bugs on MP kernels because only one CPU can read the i8254 at a time. I think patrick@ was struggling with some version of that problem last year, but in a VM. Real i386 hardware should be fine. Later models with an ACPI PM timer will be fine using acpitimer_delay() instead of i8254_delay(). If this seems reasonable to people I will come back with a cleaned up patch for testing. Thoughts? Preferences? -Scott Here are the sample measurements from my 2017 laptop (kaby lake refresh) running the attached patch. It takes longer than a microsecond to read either of the ACPI timers. The PM timer is better than the HPET. The HPET is a bit better than the i8254. I hope the numbers are a little better on older hardware. acpitimer_test_delay: expected 0.000001000 actual 0.000010638 error 0.000009638 acpitimer_test_delay: expected 0.000010000 actual 0.000015464 error 0.000005464 acpitimer_test_delay: expected 0.000100000 actual 0.000107619 error 0.000007619 acpitimer_test_delay: expected 0.001000000 actual 0.001007275 error 0.000007275 acpitimer_test_delay: expected 0.010000000 actual 0.010007891 error 0.000007891 acpihpet_test_delay: expected 0.000001000 actual 0.000022208 error 0.000021208 acpihpet_test_delay: expected 0.000010000 actual 0.000031690 error 0.000021690 acpihpet_test_delay: expected 0.000100000 actual 0.000112647 error 0.000012647 acpihpet_test_delay: expected 0.001000000 actual 0.001021480 error 0.000021480 acpihpet_test_delay: expected 0.010000000 actual 0.010013736 error 0.000013736 i8254_test_delay: expected 0.000001000 actual 0.000040110 error 0.000039110 i8254_test_delay: expected 0.000010000 actual 0.000039471 error 0.000029471 i8254_test_delay: expected 0.000100000 actual 0.000128031 error 0.000028031 i8254_test_delay: expected 0.001000000 actual 0.001024586 error 0.000024586 i8254_test_delay: expected 0.010000000 actual 0.010021859 error 0.000021859 Index: dev/acpi/acpihpet.c =================================================================== RCS file: /cvs/src/sys/dev/acpi/acpihpet.c,v retrieving revision 1.26 diff -u -p -r1.26 acpihpet.c --- dev/acpi/acpihpet.c 6 Apr 2022 18:59:27 -0000 1.26 +++ dev/acpi/acpihpet.c 15 Aug 2022 04:21:58 -0000 @@ -18,6 +18,7 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/device.h> +#include <sys/stdint.h> #include <sys/timetc.h> #include <machine/bus.h> @@ -31,8 +32,9 @@ int acpihpet_attached; int acpihpet_match(struct device *, void *, void *); void acpihpet_attach(struct device *, struct device *, void *); int acpihpet_activate(struct device *, int); - +void acpiphet_delay(u_int); u_int acpihpet_gettime(struct timecounter *tc); +void acpihpet_test_delay(u_int); uint64_t acpihpet_r(bus_space_tag_t _iot, bus_space_handle_t _ioh, bus_size_t _ioa); @@ -262,7 +264,7 @@ acpihpet_attach(struct device *parent, s freq = 1000000000000000ull / period; printf(": %lld Hz\n", freq); - hpet_timecounter.tc_frequency = (uint32_t)freq; + hpet_timecounter.tc_frequency = freq; hpet_timecounter.tc_priv = sc; hpet_timecounter.tc_name = sc->sc_dev.dv_xname; tc_init(&hpet_timecounter); @@ -273,10 +275,43 @@ acpihpet_attach(struct device *parent, s acpihpet_attached++; } +void +acpihpet_delay(u_int usecs) +{ + uint64_t d, s; + struct acpihpet_softc *sc = hpet_timecounter.tc_priv; + + d = usecs * hpet_timecounter.tc_frequency / 1000000; + s = acpihpet_r(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER); + while (acpihpet_r(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER) - s < d) + CPU_BUSY_CYCLE(); +} + u_int acpihpet_gettime(struct timecounter *tc) { struct acpihpet_softc *sc = tc->tc_priv; return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, HPET_MAIN_COUNTER)); +} + +void +acpihpet_test_delay(u_int usecs) +{ + struct timespec ac, er, ex, t0, t1; + + if (!acpihpet_attached) { + printf("%s: (no hpet attached)\n", __func__); + return; + } + + nanouptime(&t0); + acpihpet_delay(usecs); + nanouptime(&t1); + timespecsub(&t1, &t0, &ac); + NSEC_TO_TIMESPEC(usecs * 1000ULL, &ex); + timespecsub(&ac, &ex, &er); + printf("%s: expected %lld.%09ld actual %lld.%09ld error %lld.%09ld\n", + __func__, ex.tv_sec, ex.tv_nsec, ac.tv_sec, ac.tv_nsec, + er.tv_sec, er.tv_nsec); } Index: dev/acpi/acpitimer.c =================================================================== RCS file: /cvs/src/sys/dev/acpi/acpitimer.c,v retrieving revision 1.15 diff -u -p -r1.15 acpitimer.c --- dev/acpi/acpitimer.c 6 Apr 2022 18:59:27 -0000 1.15 +++ dev/acpi/acpitimer.c 15 Aug 2022 04:21:58 -0000 @@ -25,10 +25,14 @@ #include <dev/acpi/acpireg.h> #include <dev/acpi/acpivar.h> +struct acpitimer_softc; + int acpitimermatch(struct device *, void *, void *); void acpitimerattach(struct device *, struct device *, void *); - +void acpitimer_delay(u_int); +void acpitimer_test_delay(u_int); u_int acpi_get_timecount(struct timecounter *tc); +uint32_t acpitimer_read(struct acpitimer_softc *); static struct timecounter acpi_timecounter = { .tc_get_timecount = acpi_get_timecount, @@ -104,12 +108,34 @@ acpitimerattach(struct device *parent, s #endif } +void +acpitimer_delay(u_int usecs) +{ + uint64_t count = 0, cycles; + struct acpitimer_softc *sc = acpi_timecounter.tc_priv; + uint32_t mask = acpi_timecounter.tc_counter_mask; + uint32_t val1, val2; + + val2 = acpitimer_read(sc); + cycles = usecs * acpi_timecounter.tc_frequency / 1000000; + while (count < cycles) { + CPU_BUSY_CYCLE(); + val1 = val2; + val2 = acpitimer_read(sc); + count += (val2 - val1) & mask; + } +} u_int acpi_get_timecount(struct timecounter *tc) { - struct acpitimer_softc *sc = tc->tc_priv; - u_int u1, u2, u3; + return acpitimer_read(tc->tc_priv); +} + +uint32_t +acpitimer_read(struct acpitimer_softc *sc) +{ + uint32_t u1, u2, u3; u2 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 0); u3 = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 0); @@ -120,4 +146,20 @@ acpi_get_timecount(struct timecounter *t } while (u1 > u2 || u2 > u3); return (u2); +} + +void +acpitimer_test_delay(u_int usecs) +{ + struct timespec ac, er, ex, t0, t1; + + nanouptime(&t0); + acpitimer_delay(usecs); + nanouptime(&t1); + timespecsub(&t1, &t0, &ac); + NSEC_TO_TIMESPEC(usecs * 1000ULL, &ex); + timespecsub(&ac, &ex, &er); + printf("%s: expected %lld.%09ld actual %lld.%09ld error %lld.%09ld\n", + __func__, ex.tv_sec, ex.tv_nsec, ac.tv_sec, ac.tv_nsec, + er.tv_sec, er.tv_nsec); } Index: arch/amd64/amd64/lapic.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/amd64/lapic.c,v retrieving revision 1.59 diff -u -p -r1.59 lapic.c --- arch/amd64/amd64/lapic.c 31 Aug 2021 15:53:36 -0000 1.59 +++ arch/amd64/amd64/lapic.c 15 Aug 2022 04:21:58 -0000 @@ -468,6 +468,19 @@ lapic_initclocks(void) lapic_startclock(); i8254_inittimecounter_simple(); + + extern void acpitimer_test_delay(u_int); + extern void acpihpet_test_delay(u_int); + extern void i8254_test_delay(u_int); + u_int usec[] = { 1, 10, 100, 1000, 10000 }; + size_t i; + delay(20000); /* wait for real timecounter to activate */ + for (i = 0; i < nitems(usec); i++) + acpitimer_test_delay(usec[i]); + for (i = 0; i < nitems(usec); i++) + acpihpet_test_delay(usec[i]); + for (i = 0; i < nitems(usec); i++) + i8254_test_delay(usec[i]); } Index: arch/amd64/isa/clock.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/isa/clock.c,v retrieving revision 1.36 diff -u -p -r1.36 clock.c --- arch/amd64/isa/clock.c 13 Feb 2022 19:15:09 -0000 1.36 +++ arch/amd64/isa/clock.c 15 Aug 2022 04:21:59 -0000 @@ -266,6 +266,22 @@ i8254_delay(int n) } void +i8254_test_delay(u_int usecs) +{ + struct timespec ac, er, ex, t0, t1; + + nanouptime(&t0); + i8254_delay(usecs); + nanouptime(&t1); + timespecsub(&t1, &t0, &ac); + NSEC_TO_TIMESPEC(usecs * 1000ULL, &ex); + timespecsub(&ac, &ex, &er); + printf("%s: expected %lld.%09ld actual %lld.%09ld error %lld.%09ld\n", + __func__, ex.tv_sec, ex.tv_nsec, ac.tv_sec, ac.tv_nsec, + er.tv_sec, er.tv_nsec); +} + +void rtcdrain(void *v) { struct timeout *to = (struct timeout *)v;