The HPET counter is read once in every clock interrupt. When any of the functions used for getting time is used, the HPET counter is read again and the difference between these two read is multiplied by the HPET period and added to the read value from time or uptime to get a more accurate time read.
Compile gnumach with '../configure --enable-apic --enable-ncpus=1 --disable-ide' and run qemu with (thanks almuhs :-)): ``` qemu-system-i386 -enable-kvm -m 6G -device ahci,id=ahci1 \ -drive if=none,id=root,format=raw,cache=writeback,file=debian-hurd.img \ -device ide-hd,drive=root,bus=ahci1.1 \ -nic user,hostfwd=tcp::60025-:22 -display curses -vga std ``` When compile and run the following program: ``` #include <stdio.h> #include <time.h> int main () { struct timespec ts; int err; err = clock_gettime (CLOCK_MONOTONIC, &ts); // err = clock_gettime (CLOCK_REALTIME, &ts); if (err == -1) { printf ("clock_gettime() error!\n"); perror (NULL); return 1; } printf ("Seconds: %ld, Nanoseconds: %ld\n", ts.tv_sec, ts.tv_nsec); return 0; } ``` It seems not bad, we can have the accuracy of 10ns: ``` demo@debian:~$ ./main Seconds: 1405, Nanoseconds: 688178790 demo@debian:~$ ./main Seconds: 1406, Nanoseconds: 216391110 ``` BTW, I found that CLOCK_REALTIME is still using host_get_time() instead of host_get_time64() [0]. [0]: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/mach/clock_gettime.c;h=381ccb8451fa99f4da3900429893feb74ae9ffc2;hb=HEAD#l65 --- i386/i386/apic.c | 11 +++++++++++ i386/i386/apic.h | 2 ++ i386/i386/model_dep.h | 10 ++++++++++ i386/i386at/model_dep.c | 5 +++++ kern/mach_clock.c | 27 ++++++++++++++++++++++++++- 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/i386/i386/apic.c b/i386/i386/apic.c index 77d555b5..cb3de3e2 100644 --- a/i386/i386/apic.c +++ b/i386/i386/apic.c @@ -479,3 +479,14 @@ hpet_mdelay(uint32_t ms) hpet_udelay(ms * 1000); } +uint32_t +read_hpet_counter(void) +{ + return HPET32(HPET_COUNTER); +} + +uint32_t +get_hpet_period_nsec(void) +{ + return hpet_period_nsec; +} diff --git a/i386/i386/apic.h b/i386/i386/apic.h index 92fb900a..30aa8521 100644 --- a/i386/i386/apic.h +++ b/i386/i386/apic.h @@ -268,6 +268,8 @@ void ioapic_configure(void); void hpet_init(void); void hpet_udelay(uint32_t us); void hpet_mdelay(uint32_t ms); +uint32_t read_hpet_counter(void); +uint32_t get_hpet_period_nsec(void); extern int timer_pin; extern void intnull(int unit); diff --git a/i386/i386/model_dep.h b/i386/i386/model_dep.h index 5369e288..6a46cbbf 100644 --- a/i386/i386/model_dep.h +++ b/i386/i386/model_dep.h @@ -65,4 +65,14 @@ extern void machine_relax (void); */ extern void c_boot_entry(vm_offset_t bi); +/* + * Return the tick value of hpet + */ +extern uint32_t read_hpet_counter (void); + +/* + * Return the period of HPET timer in nanoseconds + */ +extern uint32_t get_hpet_period_nsec (void); + #endif /* _I386AT_MODEL_DEP_H_ */ diff --git a/i386/i386at/model_dep.c b/i386/i386at/model_dep.c index 30449c37..aa8451ac 100644 --- a/i386/i386at/model_dep.c +++ b/i386/i386at/model_dep.c @@ -229,6 +229,11 @@ void machine_init(void) */ hpet_init(); #endif + + /* + * Initialize the HPET + */ + hpet_init(); } /* Conserve power on processor CPU. */ diff --git a/kern/mach_clock.c b/kern/mach_clock.c index 5501b7b8..63a6988f 100644 --- a/kern/mach_clock.c +++ b/kern/mach_clock.c @@ -83,6 +83,15 @@ unsigned tickadj = 500 / HZ; /* can adjust 100 usecs per second */ unsigned bigadj = 1000000; /* adjust 10*tickadj if adjustment > bigadj */ +/* HPET is taken into account to increase the accuracy of the functions + * used for getting time (e.g. host_get_time64()). The HPET counter is + * read once in every clock interrupt. When any of the functions used + * for getting time is used, the HPET counter is read again and the + * difference between these two read is multiplied by the HPET period + * and added to the read value from time or uptime to get a more accurate + * time read. */ +uint32_t last_hpet_read = 0; + /* * This update protocol, with a check value, allows * do { @@ -128,7 +137,8 @@ MACRO_BEGIN \ __sync_synchronize(); \ (time)->nanoseconds = mtime->time_value.nanoseconds; \ __sync_synchronize(); \ - } while ((time)->seconds != mtime->check_seconds64); \ + } while ((time)->seconds != mtime->check_seconds64); \ + time_value64_add_hpet(time); \ MACRO_END #define read_mapped_uptime(uptime) \ @@ -139,6 +149,7 @@ MACRO_BEGIN \ (uptime)->nanoseconds = mtime->uptime_value.nanoseconds;\ __sync_synchronize(); \ } while ((uptime)->seconds != mtime->check_upseconds64); \ + time_value64_add_hpet(uptime); \ MACRO_END def_simple_lock_irq_data(static, timer_lock) /* lock for ... */ @@ -292,6 +303,7 @@ void clock_interrupt( } } } + last_hpet_read = read_hpet_counter(); } /* @@ -426,6 +438,19 @@ clock_boottime_update(const struct time_value64 *new_time) time_value64_add(&clock_boottime_offset, &delta); } +/* + * Add the time value since last clock interrupt in nanosecond. + */ +static void +time_value64_add_hpet(time_value64_t *value) +{ + uint32_t now = read_hpet_counter(); + /* Time since last clock interrupt in nanosecond. */ + int ns = (now - last_hpet_read) * get_hpet_period_nsec(); + time_value64_add_nanos(value, ns); +} + + /* * Record a timestamp in STAMP. Records values in the boot-time clock * frame. -- 2.48.1