Hello,

Zhaoming Luo, le sam. 22 mars 2025 15:24:50 +0800, a ecrit:
> 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.
> 

> 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
> ```

Nice!

> BTW, I found that CLOCK_REALTIME is still using host_get_time() instead
> of host_get_time64() [0].

Indeed, contribution welcome ;)

> 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);

Please rather put them into ./kern/mach_clock.h, and avoid
using "hpet" in the name, so that interface is general and not
specific to i386. You can call them e.g. clock_read_counter() and
clock_get_counter_period_nsec()

> 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_ */

No need to declare them several times :)

> 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.  */

Rather move #endif above the hpet_init() call. And put a #ifdef APIC
around it.

You should also cope with the case where hpet_addr is NULL.

> 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.  */

Here as well, avoid using the HPET name, just call it "high-precision
hardware clock".

> +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();

Please cap that value to the period of a clock interrupt. Otherwise, if
for any reason the computation is not that precise and we end up having
ns greater than the clock interrupt period, time would go *back*, which
we really don't want :) Better see time stuck at the very end of the
clock interrupt period in case of such computation imprecision.

> +     time_value64_add_nanos(value, ns);
> +}

Samuel

Reply via email to