Commit 1d10602d authored by Vitaly Kuznetsov's avatar Vitaly Kuznetsov Committed by Greg Kroah-Hartman

hv_utils: fix TimeSync work on pre-TimeSync-v4 hosts

It was found that ICTIMESYNCFLAG_SYNC packets are handled incorrectly
on WS2012R2, e.g. after the guest is paused and resumed its time is set
to something different from host's time. The problem is that we call
adj_guesttime() with reftime=0 for these old hosts and we don't account
for that in 'if (adj_flags & ICTIMESYNCFLAG_SYNC)' branch and
hv_set_host_time().

While we could've solved this by adding a check like
'if (ts_srv_version > TS_VERSION_3)' to hv_set_host_time() I prefer
to do some refactoring. We don't need to have two separate containers
for host samples, struct host_ts which we use for PTP is enough.

Throw away 'struct adj_time_work' and create hv_get_adj_host_time()
accessor to host_ts to avoid code duplication.

Fixes: 3716a49a ("hv_utils: implement Hyper-V PTP source")
Signed-off-by: default avatarVitaly Kuznetsov <vkuznets@redhat.com>
Signed-off-by: default avatarK. Y. Srinivasan <kys@microsoft.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 4f9bac03
...@@ -202,27 +202,39 @@ static void shutdown_onchannelcallback(void *context) ...@@ -202,27 +202,39 @@ static void shutdown_onchannelcallback(void *context)
/* /*
* Set the host time in a process context. * Set the host time in a process context.
*/ */
static struct work_struct adj_time_work;
struct adj_time_work { /*
struct work_struct work; * The last time sample, received from the host. PTP device responds to
u64 host_time; * requests by using this data and the current partition-wide time reference
u64 ref_time; * count.
u8 flags; */
}; static struct {
u64 host_time;
u64 ref_time;
spinlock_t lock;
} host_ts;
static void hv_set_host_time(struct work_struct *work) static struct timespec64 hv_get_adj_host_time(void)
{ {
struct adj_time_work *wrk; struct timespec64 ts;
struct timespec64 host_ts; u64 newtime, reftime;
u64 reftime, newtime; unsigned long flags;
wrk = container_of(work, struct adj_time_work, work);
spin_lock_irqsave(&host_ts.lock, flags);
reftime = hyperv_cs->read(hyperv_cs); reftime = hyperv_cs->read(hyperv_cs);
newtime = wrk->host_time + (reftime - wrk->ref_time); newtime = host_ts.host_time + (reftime - host_ts.ref_time);
host_ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100); ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
spin_unlock_irqrestore(&host_ts.lock, flags);
do_settimeofday64(&host_ts); return ts;
}
static void hv_set_host_time(struct work_struct *work)
{
struct timespec64 ts = hv_get_adj_host_time();
do_settimeofday64(&ts);
} }
/* /*
...@@ -238,60 +250,35 @@ static void hv_set_host_time(struct work_struct *work) ...@@ -238,60 +250,35 @@ static void hv_set_host_time(struct work_struct *work)
* typically used as a hint to the guest. The guest is under no obligation * typically used as a hint to the guest. The guest is under no obligation
* to discipline the clock. * to discipline the clock.
*/ */
static struct adj_time_work wrk;
/*
* The last time sample, received from the host. PTP device responds to
* requests by using this data and the current partition-wide time reference
* count.
*/
static struct {
u64 host_time;
u64 ref_time;
spinlock_t lock;
} host_ts;
static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 adj_flags) static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 adj_flags)
{ {
unsigned long flags; unsigned long flags;
u64 cur_reftime; u64 cur_reftime;
/* /*
* This check is safe since we are executing in the * Save the adjusted time sample from the host and the snapshot
* interrupt context and time synch messages are always * of the current system time.
* delivered on the same CPU.
*/ */
if (adj_flags & ICTIMESYNCFLAG_SYNC) { spin_lock_irqsave(&host_ts.lock, flags);
/* Queue a job to do do_settimeofday64() */
if (work_pending(&wrk.work)) cur_reftime = hyperv_cs->read(hyperv_cs);
return; host_ts.host_time = hosttime;
host_ts.ref_time = cur_reftime;
wrk.host_time = hosttime;
wrk.ref_time = reftime; /*
wrk.flags = adj_flags; * TimeSync v4 messages contain reference time (guest's Hyper-V
schedule_work(&wrk.work); * clocksource read when the time sample was generated), we can
} else { * improve the precision by adding the delta between now and the
/* * time of generation. For older protocols we set
* Save the adjusted time sample from the host and the snapshot * reftime == cur_reftime on call.
* of the current system time for PTP device. */
*/ host_ts.host_time += (cur_reftime - reftime);
spin_lock_irqsave(&host_ts.lock, flags);
spin_unlock_irqrestore(&host_ts.lock, flags);
cur_reftime = hyperv_cs->read(hyperv_cs);
host_ts.host_time = hosttime; /* Schedule work to do do_settimeofday64() */
host_ts.ref_time = cur_reftime; if (adj_flags & ICTIMESYNCFLAG_SYNC)
schedule_work(&adj_time_work);
/*
* TimeSync v4 messages contain reference time (guest's Hyper-V
* clocksource read when the time sample was generated), we can
* improve the precision by adding the delta between now and the
* time of generation.
*/
if (ts_srv_version > TS_VERSION_3)
host_ts.host_time += (cur_reftime - reftime);
spin_unlock_irqrestore(&host_ts.lock, flags);
}
} }
/* /*
...@@ -339,8 +326,8 @@ static void timesync_onchannelcallback(void *context) ...@@ -339,8 +326,8 @@ static void timesync_onchannelcallback(void *context)
sizeof(struct vmbuspipe_hdr) + sizeof(struct vmbuspipe_hdr) +
sizeof(struct icmsg_hdr)]; sizeof(struct icmsg_hdr)];
adj_guesttime(timedatap->parenttime, adj_guesttime(timedatap->parenttime,
0, hyperv_cs->read(hyperv_cs),
timedatap->flags); timedatap->flags);
} }
} }
...@@ -524,14 +511,7 @@ static int hv_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) ...@@ -524,14 +511,7 @@ static int hv_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
static int hv_ptp_gettime(struct ptp_clock_info *info, struct timespec64 *ts) static int hv_ptp_gettime(struct ptp_clock_info *info, struct timespec64 *ts)
{ {
unsigned long flags; *ts = hv_get_adj_host_time();
u64 newtime, reftime;
spin_lock_irqsave(&host_ts.lock, flags);
reftime = hyperv_cs->read(hyperv_cs);
newtime = host_ts.host_time + (reftime - host_ts.ref_time);
*ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
spin_unlock_irqrestore(&host_ts.lock, flags);
return 0; return 0;
} }
...@@ -556,7 +536,7 @@ static int hv_timesync_init(struct hv_util_service *srv) ...@@ -556,7 +536,7 @@ static int hv_timesync_init(struct hv_util_service *srv)
spin_lock_init(&host_ts.lock); spin_lock_init(&host_ts.lock);
INIT_WORK(&wrk.work, hv_set_host_time); INIT_WORK(&adj_time_work, hv_set_host_time);
/* /*
* ptp_clock_register() returns NULL when CONFIG_PTP_1588_CLOCK is * ptp_clock_register() returns NULL when CONFIG_PTP_1588_CLOCK is
...@@ -577,7 +557,7 @@ static void hv_timesync_deinit(void) ...@@ -577,7 +557,7 @@ static void hv_timesync_deinit(void)
{ {
if (hv_ptp_clock) if (hv_ptp_clock)
ptp_clock_unregister(hv_ptp_clock); ptp_clock_unregister(hv_ptp_clock);
cancel_work_sync(&wrk.work); cancel_work_sync(&adj_time_work);
} }
static int __init init_hyperv_utils(void) static int __init init_hyperv_utils(void)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment