var timekeepSharedPage *vdsoTimekeep
 
 //go:nosplit
-func (bt bintime) Add(bt2 bintime) bintime {
+func (bt *bintime) Add(bt2 *bintime) {
        u := bt.frac
        bt.frac += bt2.frac
        if u > bt.frac {
                bt.sec++
        }
        bt.sec += bt2.sec
-       return bt
 }
 
 //go:nosplit
-func (bt bintime) AddX(x uint64) bintime {
+func (bt *bintime) AddX(x uint64) {
        u := bt.frac
        bt.frac += x
        if u > bt.frac {
                bt.sec++
        }
-       return bt
 }
 
-var binuptimeDummy uint32
+var (
+       // binuptimeDummy is used in binuptime as the address of an atomic.Load, to simulate
+       // an atomic_thread_fence_acq() call which behaves as an instruction reordering and
+       // memory barrier.
+       binuptimeDummy uint32
+
+       zeroBintime bintime
+)
 
 // based on /usr/src/lib/libc/sys/__vdso_gettimeofday.c
 //
 //go:nosplit
-func binuptime(abs bool) (bintime, bool) {
-       var bt bintime
+func binuptime(abs bool) (bt bintime) {
        timehands := (*[_VDSO_TH_NUM]vdsoTimehands)(add(unsafe.Pointer(timekeepSharedPage), vdsoTimekeepSize))
        for {
                if timekeepSharedPage.enabled == 0 {
-                       return bt, false
+                       return zeroBintime
                }
 
                curr := atomic.Load(&timekeepSharedPage.current) // atomic_load_acq_32
                bt = th.offset
 
                if tc, ok := th.getTimecounter(); !ok {
-                       return bt, false
+                       return zeroBintime
                } else {
                        delta := (tc - th.offset_count) & th.counter_mask
-                       bt = bt.AddX(th.scale * uint64(delta))
+                       bt.AddX(th.scale * uint64(delta))
                }
                if abs {
-                       bt = bt.Add(th.boottime)
+                       bt.Add(&th.boottime)
                }
 
                atomic.Load(&binuptimeDummy) // atomic_thread_fence_acq()
                        break
                }
        }
-       return bt, true
+       return bt
 }
 
 //go:nosplit
-func vdsoClockGettime(clockID int32) (bintime, bool) {
+func vdsoClockGettime(clockID int32) bintime {
        if timekeepSharedPage == nil || timekeepSharedPage.ver != _VDSO_TK_VER_CURR {
-               return bintime{}, false
+               return zeroBintime
        }
        abs := false
        switch clockID {
        case _CLOCK_REALTIME:
                abs = true
        default:
-               return bintime{}, false
+               return zeroBintime
        }
-
        return binuptime(abs)
 }
 
 
 //go:nosplit
 func nanotime() int64 {
-       bt, ok := vdsoClockGettime(_CLOCK_MONOTONIC)
-       if !ok {
+       bt := vdsoClockGettime(_CLOCK_MONOTONIC)
+       if bt == zeroBintime {
                return fallback_nanotime()
        }
        return int64((1e9 * uint64(bt.sec)) + ((1e9 * uint64(bt.frac>>32)) >> 32))
 }
 
 func walltime() (sec int64, nsec int32) {
-       bt, ok := vdsoClockGettime(_CLOCK_REALTIME)
-       if !ok {
+       bt := vdsoClockGettime(_CLOCK_REALTIME)
+       if bt == zeroBintime {
                return fallback_walltime()
        }
        return int64(bt.sec), int32((1e9 * uint64(bt.frac>>32)) >> 32)
 
 
 package runtime
 
+import (
+       "runtime/internal/atomic"
+       "unsafe"
+)
+
+const (
+       _VDSO_TH_ALGO_X86_TSC  = 1
+       _VDSO_TH_ALGO_X86_HPET = 2
+)
+
 const (
-       _VDSO_TH_ALGO_X86_TSC = 1
+       _HPET_DEV_MAP_MAX  = 10
+       _HPET_MAIN_COUNTER = 0xf0 /* Main counter register */
+)
+
+var (
+       hpetDevMap  [_HPET_DEV_MAP_MAX]uintptr
+       hpetDevPath = [_HPET_DEV_MAP_MAX][11]byte{
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '0', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '1', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '2', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '3', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '4', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '5', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '6', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '7', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '8', 0},
+               {'/', 'd', 'e', 'v', '/', 'h', 'p', 'e', 't', '9', 0},
+       }
 )
 
 //go:nosplit
        return uint32(tsc)
 }
 
+//go:nosplit
+func (th *vdsoTimehands) getHPETTimecounter() (uint32, bool) {
+       idx := int(th.x86_hpet_idx)
+       if idx >= len(hpetDevMap) {
+               return 0, false
+       }
+
+       p := atomic.Loaduintptr(&hpetDevMap[idx])
+       if p == 0 {
+               fd := open(&hpetDevPath[idx][0], 0 /* O_RDONLY */, 0)
+               if fd < 0 {
+                       atomic.Casuintptr(&hpetDevMap[idx], 0, ^uintptr(0))
+                       return 0, false
+               }
+
+               addr, mmapErr := mmap(nil, physPageSize, _PROT_READ, _MAP_SHARED, fd, 0)
+               closefd(fd)
+               newP := uintptr(addr)
+               if mmapErr != 0 {
+                       newP = ^uintptr(0)
+               }
+               if !atomic.Casuintptr(&hpetDevMap[idx], 0, newP) && mmapErr == 0 {
+                       munmap(addr, physPageSize)
+               }
+               p = atomic.Loaduintptr(&hpetDevMap[idx])
+       }
+       if p == ^uintptr(0) {
+               return 0, false
+       }
+       return *(*uint32)(unsafe.Pointer(p + _HPET_MAIN_COUNTER)), true
+}
+
 //go:nosplit
 func (th *vdsoTimehands) getTimecounter() (uint32, bool) {
        switch th.algo {
        case _VDSO_TH_ALGO_X86_TSC:
                return th.getTSCTimecounter(), true
+       case _VDSO_TH_ALGO_X86_HPET:
+               return th.getHPETTimecounter()
        default:
                return 0, false
        }