#include "opt_xen.h"
#ifndef XEN_CLOCK_DEBUG
#define XEN_CLOCK_DEBUG 0
#endif
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: xen_clock.c,v 1.23 2025/08/18 20:59:54 andvar Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/atomic.h>
#include <sys/callout.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/evcnt.h>
#include <sys/intr.h>
#include <sys/kernel.h>
#include <sys/lwp.h>
#include <sys/proc.h>
#include <sys/sdt.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/timetc.h>
#include <dev/clock_subr.h>
#include <machine/cpu.h>
#include <machine/cpu_counter.h>
#include <machine/lock.h>
#include <xen/evtchn.h>
#include <xen/hypervisor.h>
#include <xen/include/public/vcpu.h>
#include <xen/xen.h>
#include <x86/rtc.h>
#define NS_PER_TICK ((uint64_t)1000000000ULL/hz)
static uint64_t xen_vcputime_systime_ns(void);
static uint64_t xen_vcputime_raw_systime_ns(void);
static uint64_t xen_global_systime_ns(void);
static unsigned xen_get_timecount(struct timecounter *);
static int xen_timer_handler(void *, struct clockframe *);
SDT_PROBE_DEFINE7(sdt, xen, clock, tsc__backward,
"uint64_t",
"uint64_t",
"uint64_t",
"int",
"uint64_t",
"uint64_t",
"uint64_t");
SDT_PROBE_DEFINE7(sdt, xen, clock, tsc__delta__negative,
"uint64_t",
"uint64_t",
"uint64_t",
"int",
"uint64_t",
"uint64_t",
"uint64_t");
SDT_PROBE_DEFINE7(sdt, xen, clock, systime__wraparound,
"uint64_t",
"uint64_t",
"uint64_t",
"int",
"uint64_t",
"uint64_t",
"uint64_t");
SDT_PROBE_DEFINE7(sdt, xen, clock, systime__backward,
"uint64_t",
"uint64_t",
"uint64_t",
"int",
"uint64_t",
"uint64_t",
"uint64_t");
SDT_PROBE_DEFINE3(sdt, xen, timecounter, backward,
"uint64_t",
"uint64_t",
"uint64_t");
SDT_PROBE_DEFINE2(sdt, xen, hardclock, systime__backward,
"uint64_t",
"uint64_t");
SDT_PROBE_DEFINE2(sdt, xen, hardclock, tick,
"uint64_t",
"uint64_t");
SDT_PROBE_DEFINE3(sdt, xen, hardclock, jump,
"uint64_t",
"uint64_t",
"uint64_t");
SDT_PROBE_DEFINE3(sdt, xen, hardclock, missed,
"uint64_t",
"uint64_t",
"uint64_t");
static struct timecounter xen_timecounter = {
.tc_get_timecount = xen_get_timecount,
.tc_poll_pps = NULL,
.tc_counter_mask = ~0U,
.tc_frequency = 1000000000ULL,
.tc_name = "xen_system_time",
.tc_quality = 10000,
};
static volatile uint64_t xen_global_systime_ns_stamp __cacheline_aligned;
#ifdef DOM0OPS
static struct {
struct callout ch;
int ticks;
} xen_timepush;
static void xen_timepush_init(void);
static void xen_timepush_intr(void *);
static int sysctl_xen_timepush(SYSCTLFN_ARGS);
#endif
struct xen_vcputime_ticket {
uint64_t version;
};
static inline volatile struct vcpu_time_info *
xen_vcputime_enter(struct xen_vcputime_ticket *tp)
{
volatile struct vcpu_time_info *vt = &curcpu()->ci_vcpu->time;
while (__predict_false(1 & (tp->version = vt->version)))
SPINLOCK_BACKOFF_HOOK;
__insn_barrier();
return vt;
}
static inline bool
xen_vcputime_exit(volatile struct vcpu_time_info *vt,
struct xen_vcputime_ticket *tp)
{
KASSERT(vt == &curcpu()->ci_vcpu->time);
__insn_barrier();
return tp->version == vt->version;
}
static inline uint64_t
xen_tsc_to_ns_delta(uint64_t delta_tsc, uint32_t tsc_to_system_mul,
int8_t tsc_shift)
{
uint32_t delta_tsc_hi, delta_tsc_lo;
if (tsc_shift < 0)
delta_tsc >>= -tsc_shift;
else
delta_tsc <<= tsc_shift;
delta_tsc_hi = delta_tsc >> 32;
delta_tsc_lo = delta_tsc & 0xffffffffUL;
return ((uint64_t)delta_tsc_hi * tsc_to_system_mul) +
(((uint64_t)delta_tsc_lo * tsc_to_system_mul) >> 32);
}
static uint64_t
xen_vcputime_systime_ns(void)
{
volatile struct vcpu_time_info *vt;
struct cpu_info *ci = curcpu();
struct xen_vcputime_ticket ticket;
uint64_t raw_systime_ns, tsc_timestamp, tsc, delta_tsc, delta_ns;
uint32_t tsc_to_system_mul;
int8_t tsc_shift;
uint64_t systime_ns;
KASSERT(cpu_intr_p() || cpu_softintr_p() || kpreempt_disabled() ||
(curlwp->l_flag & LP_BOUND));
do {
vt = xen_vcputime_enter(&ticket);
raw_systime_ns = vt->system_time;
tsc_timestamp = vt->tsc_timestamp;
tsc_to_system_mul = vt->tsc_to_system_mul;
tsc_shift = vt->tsc_shift;
tsc = rdtsc();
} while (!xen_vcputime_exit(vt, &ticket));
if (__predict_false(tsc < tsc_timestamp)) {
SDT_PROBE7(sdt, xen, clock, tsc__backward,
raw_systime_ns, tsc_timestamp,
tsc_to_system_mul, tsc_shift, 0, tsc,
raw_systime_ns);
#if XEN_CLOCK_DEBUG
device_printf(ci->ci_dev, "xen cpu tsc %"PRIu64
" ran backwards from timestamp %"PRIu64
" by %"PRIu64"\n",
tsc, tsc_timestamp, tsc_timestamp - tsc);
#endif
ci->ci_xen_cpu_tsc_backwards_evcnt.ev_count++;
delta_ns = delta_tsc = 0;
} else {
delta_tsc = tsc - tsc_timestamp;
delta_ns = xen_tsc_to_ns_delta(delta_tsc, tsc_to_system_mul,
tsc_shift);
}
if (__predict_false((int64_t)delta_ns < 0)) {
SDT_PROBE7(sdt, xen, clock, tsc__delta__negative,
raw_systime_ns, tsc_timestamp,
tsc_to_system_mul, tsc_shift, delta_ns, tsc,
raw_systime_ns);
#if XEN_CLOCK_DEBUG
device_printf(ci->ci_dev, "xen tsc delta in ns went negative:"
" %"PRId64"\n", delta_ns);
#endif
ci->ci_xen_tsc_delta_negative_evcnt.ev_count++;
delta_ns = 0;
}
systime_ns = raw_systime_ns + delta_ns;
if (__predict_false(systime_ns < raw_systime_ns)) {
SDT_PROBE7(sdt, xen, clock, systime__wraparound,
raw_systime_ns, tsc_timestamp,
tsc_to_system_mul, tsc_shift, delta_ns, tsc,
systime_ns);
#if XEN_CLOCK_DEBUG
printf("xen raw systime + tsc delta wrapped around:"
" %"PRIu64" + %"PRIu64" = %"PRIu64"\n",
raw_systime_ns, delta_ns, systime_ns);
#endif
ci->ci_xen_raw_systime_wraparound_evcnt.ev_count++;
}
if (__predict_false(ci->ci_xen_last_systime_ns > systime_ns)) {
SDT_PROBE7(sdt, xen, clock, systime__backward,
raw_systime_ns, tsc_timestamp,
tsc_to_system_mul, tsc_shift, delta_ns, tsc,
systime_ns);
#if XEN_CLOCK_DEBUG
printf("xen raw systime + tsc delta went backwards:"
" %"PRIu64" > %"PRIu64"\n",
ci->ci_xen_last_systime_ns, systime_ns);
printf(" raw_systime_ns=%"PRIu64"\n tsc_timestamp=%"PRIu64"\n"
" tsc=%"PRIu64"\n tsc_to_system_mul=%"PRIu32"\n"
" tsc_shift=%"PRId8"\n delta_tsc=%"PRIu64"\n"
" delta_ns=%"PRIu64"\n",
raw_systime_ns, tsc_timestamp, tsc, tsc_to_system_mul,
tsc_shift, delta_tsc, delta_ns);
#endif
ci->ci_xen_raw_systime_backwards_evcnt.ev_count++;
systime_ns = ci->ci_xen_last_systime_ns + 1;
}
ci->ci_xen_last_systime_ns = systime_ns;
KASSERT(ci == curcpu());
return systime_ns;
}
static uint64_t
xen_vcputime_raw_systime_ns(void)
{
volatile struct vcpu_time_info *vt;
struct xen_vcputime_ticket ticket;
uint64_t raw_systime_ns;
do {
vt = xen_vcputime_enter(&ticket);
raw_systime_ns = vt->system_time;
} while (!xen_vcputime_exit(vt, &ticket));
return raw_systime_ns;
}
struct xen_wallclock_ticket {
uint32_t version;
};
static inline void
xen_wallclock_enter(struct xen_wallclock_ticket *tp)
{
while (__predict_false(1 & (tp->version =
HYPERVISOR_shared_info->wc_version)))
SPINLOCK_BACKOFF_HOOK;
membar_consumer();
}
static inline bool
xen_wallclock_exit(struct xen_wallclock_ticket *tp)
{
membar_consumer();
return tp->version == HYPERVISOR_shared_info->wc_version;
}
static uint64_t
xen_global_systime_ns(void)
{
struct cpu_info *ci;
uint64_t local, global, skew, result;
int s = splsched();
ci = curcpu();
do {
local = xen_vcputime_systime_ns();
skew = ci->ci_xen_systime_ns_skew;
global = xen_global_systime_ns_stamp;
if (__predict_false(local + skew < global + 1)) {
SDT_PROBE3(sdt, xen, timecounter, backward,
local, skew, global);
#if XEN_CLOCK_DEBUG
device_printf(ci->ci_dev,
"xen timecounter went backwards:"
" local=%"PRIu64" skew=%"PRIu64" global=%"PRIu64","
" adding %"PRIu64" to skew\n",
local, skew, global, global + 1 - (local + skew));
#endif
ci->ci_xen_timecounter_backwards_evcnt.ev_count++;
result = global + 1;
ci->ci_xen_systime_ns_skew += global + 1 -
(local + skew);
} else {
result = local + skew;
}
} while (atomic_cas_64(&xen_global_systime_ns_stamp, global, result)
!= global);
KASSERT(ci == curcpu());
splx(s);
return result;
}
static unsigned
xen_get_timecount(struct timecounter *tc)
{
KASSERT(tc == &xen_timecounter);
return (unsigned)xen_global_systime_ns();
}
void
xen_delay(unsigned n)
{
int bound;
bound = curlwp_bind();
if (curcpu()->ci_vcpu == NULL) {
curlwp_bindx(bound);
return;
}
if (n < 500000) {
volatile struct vcpu_time_info *vt;
struct xen_vcputime_ticket ticket;
uint64_t tsc_start, last_tsc, tsc;
uint32_t tsc_to_system_mul;
int8_t tsc_shift;
do {
vt = xen_vcputime_enter(&ticket);
tsc_start = last_tsc = rdtsc();
tsc_to_system_mul = vt->tsc_to_system_mul;
tsc_shift = vt->tsc_shift;
} while (!xen_vcputime_exit(vt, &ticket));
for (;;) {
tsc = rdtsc();
if (__predict_false(tsc < last_tsc))
break;
if (xen_tsc_to_ns_delta(tsc - tsc_start,
tsc_to_system_mul, tsc_shift)/1000 >= n)
break;
last_tsc = tsc;
}
} else {
uint64_t n_ns = 1000*(uint64_t)n;
uint64_t start_ns;
start_ns = xen_vcputime_raw_systime_ns();
do {
HYPERVISOR_yield();
} while (xen_vcputime_raw_systime_ns() - start_ns < n_ns);
}
curlwp_bindx(bound);
}
void
xen_suspendclocks(struct cpu_info *ci)
{
int evtch;
KASSERT(ci == curcpu());
KASSERT(kpreempt_disabled());
evtch = unbind_virq_from_evtch(VIRQ_TIMER);
KASSERT(evtch != -1);
hypervisor_mask_event(evtch);
event_remove_handler(evtch,
__FPTRCAST(int (*)(void *), xen_timer_handler), ci);
aprint_verbose("Xen clock: removed event channel %d\n", evtch);
KASSERT(ci == curcpu());
}
void
xen_resumeclocks(struct cpu_info *ci)
{
char intr_xname[INTRDEVNAMEBUF];
int evtch;
int error __diagused;
KASSERT(ci == curcpu());
KASSERT(kpreempt_disabled());
evtch = bind_virq_to_evtch(VIRQ_TIMER);
KASSERT(evtch != -1);
snprintf(intr_xname, sizeof(intr_xname), "%s clock",
device_xname(ci->ci_dev));
if (event_set_handler(evtch,
__FPTRCAST(int (*)(void *), xen_timer_handler),
ci, IPL_CLOCK, NULL, intr_xname, true, ci) == NULL)
panic("failed to establish timer interrupt handler");
aprint_verbose("Xen %s: using event channel %d\n", intr_xname, evtch);
if (XEN_MAJOR(xen_version) > 3 || XEN_MINOR(xen_version) > 0) {
error = HYPERVISOR_vcpu_op(VCPUOP_stop_periodic_timer,
ci->ci_vcpuid, NULL);
KASSERT(error == 0);
}
ci->ci_xen_hardclock_systime_ns = xen_vcputime_systime_ns();
error = HYPERVISOR_set_timer_op(ci->ci_xen_hardclock_systime_ns +
NS_PER_TICK);
KASSERT(error == 0);
hypervisor_unmask_event(evtch);
KASSERT(ci == curcpu());
}
static int
xen_timer_handler(void *cookie, struct clockframe *frame)
{
const uint64_t ns_per_tick = NS_PER_TICK;
struct cpu_info *ci = curcpu();
uint64_t last, now, delta, next;
int error;
KASSERT(cpu_intr_p());
KASSERT(cookie == ci);
#if defined(XENPV)
frame = NULL;
#endif
last = ci->ci_xen_hardclock_systime_ns;
now = xen_vcputime_systime_ns();
SDT_PROBE2(sdt, xen, hardclock, tick, last, now);
if (__predict_false(now < last)) {
SDT_PROBE2(sdt, xen, hardclock, systime__backward,
last, now);
#if XEN_CLOCK_DEBUG
device_printf(ci->ci_dev, "xen systime ran backwards"
" in hardclock %"PRIu64"ns\n",
last - now);
#endif
ci->ci_xen_systime_backwards_hardclock_evcnt.ev_count++;
ci->ci_xen_hardclock_systime_ns = last = now - ns_per_tick;
}
delta = now - last;
if (__predict_false(delta >= 2*ns_per_tick)) {
SDT_PROBE3(sdt, xen, hardclock, jump,
last, now, delta/ns_per_tick);
if (delta > xen_timecounter.tc_counter_mask) {
printf("WARNING: hardclock skipped %"PRIu64"ns"
" (%"PRIu64" -> %"PRIu64"),"
" exceeding maximum of %"PRIu32"ns"
" for timecounter(9)\n",
last, now, delta,
xen_timecounter.tc_counter_mask);
ci->ci_xen_timecounter_jump_evcnt.ev_count++;
}
if (delta > 1000000000UL)
delta = 1000000000UL;
}
while (delta >= ns_per_tick) {
ci->ci_xen_hardclock_systime_ns += ns_per_tick;
delta -= ns_per_tick;
hardclock(frame);
if (__predict_false(delta >= ns_per_tick)) {
SDT_PROBE3(sdt, xen, hardclock, missed,
last, now, delta);
ci->ci_xen_missed_hardclock_evcnt.ev_count++;
}
}
next = ci->ci_xen_hardclock_systime_ns + ns_per_tick;
error = HYPERVISOR_set_timer_op(next);
if (error) {
next = xen_vcputime_systime_ns() + ns_per_tick / 2;
error = HYPERVISOR_set_timer_op(next);
if (error) {
panic("failed to re-arm Xen timer %d", error);
}
}
return 0;
}
void
xen_initclocks(void)
{
struct cpu_info *ci = curcpu();
if (ci == &cpu_info_primary) {
tc_init(&xen_timecounter);
}
evcnt_attach_dynamic(&ci->ci_xen_cpu_tsc_backwards_evcnt,
EVCNT_TYPE_INTR, NULL, device_xname(ci->ci_dev),
"cpu tsc ran backwards");
evcnt_attach_dynamic(&ci->ci_xen_tsc_delta_negative_evcnt,
EVCNT_TYPE_INTR, NULL, device_xname(ci->ci_dev),
"tsc delta went negative");
evcnt_attach_dynamic(&ci->ci_xen_raw_systime_wraparound_evcnt,
EVCNT_TYPE_INTR, NULL, device_xname(ci->ci_dev),
"raw systime wrapped around");
evcnt_attach_dynamic(&ci->ci_xen_raw_systime_backwards_evcnt,
EVCNT_TYPE_INTR, NULL, device_xname(ci->ci_dev),
"raw systime went backwards");
evcnt_attach_dynamic(&ci->ci_xen_systime_backwards_hardclock_evcnt,
EVCNT_TYPE_INTR, NULL, device_xname(ci->ci_dev),
"systime went backwards in hardclock");
evcnt_attach_dynamic(&ci->ci_xen_missed_hardclock_evcnt,
EVCNT_TYPE_INTR, NULL, device_xname(ci->ci_dev),
"missed hardclock");
evcnt_attach_dynamic(&ci->ci_xen_timecounter_backwards_evcnt,
EVCNT_TYPE_INTR, NULL, device_xname(ci->ci_dev),
"timecounter went backwards");
evcnt_attach_dynamic(&ci->ci_xen_timecounter_jump_evcnt,
EVCNT_TYPE_INTR, NULL, device_xname(ci->ci_dev),
"hardclock jumped past timecounter max");
xen_resumeclocks(ci);
#ifdef DOM0OPS
if (ci == &cpu_info_primary && xendomain_is_privileged())
xen_timepush_init();
#endif
}
#ifdef DOM0OPS
static void
xen_timepush_init(void)
{
struct sysctllog *log = NULL;
const struct sysctlnode *node = NULL;
int error;
callout_init(&xen_timepush.ch, 0);
callout_setfunc(&xen_timepush.ch, xen_timepush_intr, NULL);
xen_timepush.ticks = 53*hz + 3;
error = sysctl_createv(&log, 0, NULL, &node, 0,
CTLTYPE_NODE, "xen",
SYSCTL_DESCR("Xen top level node"),
NULL, 0, NULL, 0,
CTL_MACHDEP, CTL_CREATE, CTL_EOL);
if (error)
goto fail;
KASSERT(node != NULL);
error = sysctl_createv(&log, 0, NULL, NULL, CTLFLAG_READWRITE,
CTLTYPE_INT, "timepush_ticks",
SYSCTL_DESCR("How often to update the hypervisor's time-of-day;"
" 0 to disable"),
sysctl_xen_timepush, 0, &xen_timepush.ticks, 0,
CTL_CREATE, CTL_EOL);
if (error)
goto fail;
callout_schedule(&xen_timepush.ch, xen_timepush.ticks);
return;
fail: sysctl_teardown(&log);
}
static void
xen_timepush_intr(void *cookie)
{
resettodr();
if (xen_timepush.ticks)
callout_schedule(&xen_timepush.ch, xen_timepush.ticks);
}
static int
sysctl_xen_timepush(SYSCTLFN_ARGS)
{
struct sysctlnode node;
int ticks;
int error;
ticks = xen_timepush.ticks;
node = *rnode;
node.sysctl_data = &ticks;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (ticks < 0)
return EINVAL;
if (ticks != xen_timepush.ticks) {
xen_timepush.ticks = ticks;
if (ticks == 0)
callout_stop(&xen_timepush.ch);
else
callout_schedule(&xen_timepush.ch, ticks);
}
return 0;
}
#endif
static int xen_rtc_get(struct todr_chip_handle *, struct timeval *);
static int xen_rtc_set(struct todr_chip_handle *, struct timeval *);
static void xen_wallclock_time(struct timespec *);
static struct todr_chip_handle xen_todr_chip = {
.todr_gettime = xen_rtc_get,
.todr_settime = xen_rtc_set,
};
void
xen_startrtclock(void)
{
todr_attach(&xen_todr_chip);
}
static int
xen_rtc_get(struct todr_chip_handle *todr, struct timeval *tvp)
{
struct timespec ts;
xen_wallclock_time(&ts);
TIMESPEC_TO_TIMEVAL(tvp, &ts);
return 0;
}
static int
xen_rtc_set(struct todr_chip_handle *todr, struct timeval *tvp)
{
#ifdef DOM0OPS
struct clock_ymdhms dt;
xen_platform_op_t op;
uint64_t systime_ns;
if (xendomain_is_privileged()) {
clock_secs_to_ymdhms(tvp->tv_sec, &dt);
rtc_set_ymdhms(NULL, &dt);
systime_ns = xen_global_systime_ns();
memset(&op, 0, sizeof(op));
op.cmd = XENPF_settime;
op.u.settime.secs = tvp->tv_sec;
op.u.settime.nsecs = tvp->tv_usec * 1000;
op.u.settime.system_time = systime_ns;
return HYPERVISOR_platform_op(&op);
}
#endif
return 0;
}
static void
xen_wallclock_time(struct timespec *tsp)
{
struct xen_wallclock_ticket ticket;
uint64_t systime_ns;
int s = splsched();
do {
xen_wallclock_enter(&ticket);
tsp->tv_sec = HYPERVISOR_shared_info->wc_sec;
tsp->tv_nsec = HYPERVISOR_shared_info->wc_nsec;
} while (!xen_wallclock_exit(&ticket));
systime_ns = xen_global_systime_ns();
splx(s);
systime_ns += tsp->tv_nsec;
tsp->tv_sec += systime_ns / 1000000000ull;
tsp->tv_nsec = systime_ns % 1000000000ull;
}
#ifdef XENPV
void
setstatclockrate(int rate)
{
}
#endif