diff options
Diffstat (limited to 'drivers/hv/hv_util.c')
-rw-r--r-- | drivers/hv/hv_util.c | 283 |
1 files changed, 208 insertions, 75 deletions
diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index e7707747f56d..3042eaa13062 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -27,6 +27,9 @@ #include <linux/sysctl.h> #include <linux/reboot.h> #include <linux/hyperv.h> +#include <linux/clockchips.h> +#include <linux/ptp_clock_kernel.h> +#include <asm/mshyperv.h> #include "hyperv_vmbus.h" @@ -57,7 +60,31 @@ static int sd_srv_version; static int ts_srv_version; static int hb_srv_version; -static int util_fw_version; + +#define SD_VER_COUNT 2 +static const int sd_versions[] = { + SD_VERSION, + SD_VERSION_1 +}; + +#define TS_VER_COUNT 3 +static const int ts_versions[] = { + TS_VERSION, + TS_VERSION_3, + TS_VERSION_1 +}; + +#define HB_VER_COUNT 2 +static const int hb_versions[] = { + HB_VERSION, + HB_VERSION_1 +}; + +#define FW_VER_COUNT 2 +static const int fw_versions[] = { + UTIL_FW_VERSION, + UTIL_WS2K8_FW_VERSION +}; static void shutdown_onchannelcallback(void *context); static struct hv_util_service util_shutdown = { @@ -118,7 +145,6 @@ static void shutdown_onchannelcallback(void *context) struct shutdown_msg_data *shutdown_msg; struct icmsg_hdr *icmsghdrp; - struct icmsg_negotiate *negop = NULL; vmbus_recvpacket(channel, shut_txf_buf, PAGE_SIZE, &recvlen, &requestid); @@ -128,9 +154,14 @@ static void shutdown_onchannelcallback(void *context) sizeof(struct vmbuspipe_hdr)]; if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { - vmbus_prep_negotiate_resp(icmsghdrp, negop, - shut_txf_buf, util_fw_version, - sd_srv_version); + if (vmbus_prep_negotiate_resp(icmsghdrp, shut_txf_buf, + fw_versions, FW_VER_COUNT, + sd_versions, SD_VER_COUNT, + NULL, &sd_srv_version)) { + pr_info("Shutdown IC version %d.%d\n", + sd_srv_version >> 16, + sd_srv_version & 0xFFFF); + } } else { shutdown_msg = (struct shutdown_msg_data *)&shut_txf_buf[ @@ -181,31 +212,17 @@ struct adj_time_work { static void hv_set_host_time(struct work_struct *work) { - struct adj_time_work *wrk; - s64 host_tns; - u64 newtime; - struct timespec host_ts; + struct adj_time_work *wrk; + struct timespec64 host_ts; + u64 reftime, newtime; wrk = container_of(work, struct adj_time_work, work); - newtime = wrk->host_time; - if (ts_srv_version > TS_VERSION_3) { - /* - * Some latency has been introduced since Hyper-V generated - * its time sample. Take that latency into account before - * using TSC reference time sample from Hyper-V. - * - * This sample is given by TimeSync v4 and above hosts. - */ - u64 current_tick; - - rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick); - newtime += (current_tick - wrk->ref_time); - } - host_tns = (newtime - WLTIMEDELTA) * 100; - host_ts = ns_to_timespec(host_tns); + reftime = hyperv_cs->read(hyperv_cs); + newtime = wrk->host_time + (reftime - wrk->ref_time); + host_ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100); - do_settimeofday(&host_ts); + do_settimeofday64(&host_ts); } /* @@ -222,22 +239,60 @@ static void hv_set_host_time(struct work_struct *work) * to discipline the clock. */ static struct adj_time_work wrk; -static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 flags) + +/* + * 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; + struct system_time_snapshot snap; + spinlock_t lock; +} host_ts; + +static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 adj_flags) { + unsigned long flags; + u64 cur_reftime; /* * This check is safe since we are executing in the - * interrupt context and time synch messages arre always + * interrupt context and time synch messages are always * delivered on the same CPU. */ - if (work_pending(&wrk.work)) - return; - - wrk.host_time = hosttime; - wrk.ref_time = reftime; - wrk.flags = flags; - if ((flags & (ICTIMESYNCFLAG_SYNC | ICTIMESYNCFLAG_SAMPLE)) != 0) { + if (adj_flags & ICTIMESYNCFLAG_SYNC) { + /* Queue a job to do do_settimeofday64() */ + if (work_pending(&wrk.work)) + return; + + wrk.host_time = hosttime; + wrk.ref_time = reftime; + wrk.flags = adj_flags; schedule_work(&wrk.work); + } else { + /* + * Save the adjusted time sample from the host and the snapshot + * of the current system time for PTP device. + */ + spin_lock_irqsave(&host_ts.lock, flags); + + cur_reftime = hyperv_cs->read(hyperv_cs); + host_ts.host_time = hosttime; + host_ts.ref_time = cur_reftime; + ktime_get_snapshot(&host_ts.snap); + + /* + * 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); } } @@ -253,7 +308,6 @@ static void timesync_onchannelcallback(void *context) struct ictimesync_data *timedatap; struct ictimesync_ref_data *refdata; u8 *time_txf_buf = util_timesynch.recv_buffer; - struct icmsg_negotiate *negop = NULL; vmbus_recvpacket(channel, time_txf_buf, PAGE_SIZE, &recvlen, &requestid); @@ -263,12 +317,14 @@ static void timesync_onchannelcallback(void *context) sizeof(struct vmbuspipe_hdr)]; if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { - vmbus_prep_negotiate_resp(icmsghdrp, negop, - time_txf_buf, - util_fw_version, - ts_srv_version); - pr_info("Using TimeSync version %d.%d\n", - ts_srv_version >> 16, ts_srv_version & 0xFFFF); + if (vmbus_prep_negotiate_resp(icmsghdrp, time_txf_buf, + fw_versions, FW_VER_COUNT, + ts_versions, TS_VER_COUNT, + NULL, &ts_srv_version)) { + pr_info("TimeSync IC version %d.%d\n", + ts_srv_version >> 16, + ts_srv_version & 0xFFFF); + } } else { if (ts_srv_version > TS_VERSION_3) { refdata = (struct ictimesync_ref_data *) @@ -312,7 +368,6 @@ static void heartbeat_onchannelcallback(void *context) struct icmsg_hdr *icmsghdrp; struct heartbeat_msg_data *heartbeat_msg; u8 *hbeat_txf_buf = util_heartbeat.recv_buffer; - struct icmsg_negotiate *negop = NULL; while (1) { @@ -326,9 +381,16 @@ static void heartbeat_onchannelcallback(void *context) sizeof(struct vmbuspipe_hdr)]; if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { - vmbus_prep_negotiate_resp(icmsghdrp, negop, - hbeat_txf_buf, util_fw_version, - hb_srv_version); + if (vmbus_prep_negotiate_resp(icmsghdrp, + hbeat_txf_buf, + fw_versions, FW_VER_COUNT, + hb_versions, HB_VER_COUNT, + NULL, &hb_srv_version)) { + + pr_info("Heartbeat IC version %d.%d\n", + hb_srv_version >> 16, + hb_srv_version & 0xFFFF); + } } else { heartbeat_msg = (struct heartbeat_msg_data *)&hbeat_txf_buf[ @@ -373,38 +435,10 @@ static int util_probe(struct hv_device *dev, * Turn off batched reading for all util drivers before we open the * channel. */ - - set_channel_read_state(dev->channel, false); + set_channel_read_mode(dev->channel, HV_CALL_DIRECT); hv_set_drvdata(dev, srv); - /* - * Based on the host; initialize the framework and - * service version numbers we will negotiate. - */ - switch (vmbus_proto_version) { - case (VERSION_WS2008): - util_fw_version = UTIL_WS2K8_FW_VERSION; - sd_srv_version = SD_VERSION_1; - ts_srv_version = TS_VERSION_1; - hb_srv_version = HB_VERSION_1; - break; - case VERSION_WIN7: - case VERSION_WIN8: - case VERSION_WIN8_1: - util_fw_version = UTIL_FW_VERSION; - sd_srv_version = SD_VERSION; - ts_srv_version = TS_VERSION_3; - hb_srv_version = HB_VERSION; - break; - case VERSION_WIN10: - default: - util_fw_version = UTIL_FW_VERSION; - sd_srv_version = SD_VERSION; - ts_srv_version = TS_VERSION; - hb_srv_version = HB_VERSION; - } - ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0, srv->util_cb, dev->channel); if (ret) @@ -470,14 +504,113 @@ static struct hv_driver util_drv = { .remove = util_remove, }; +static int hv_ptp_enable(struct ptp_clock_info *info, + struct ptp_clock_request *request, int on) +{ + return -EOPNOTSUPP; +} + +static int hv_ptp_settime(struct ptp_clock_info *p, const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + +static int hv_ptp_adjfreq(struct ptp_clock_info *ptp, s32 delta) +{ + return -EOPNOTSUPP; +} +static int hv_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + return -EOPNOTSUPP; +} + +static int hv_ptp_gettime(struct ptp_clock_info *info, struct timespec64 *ts) +{ + unsigned long flags; + 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; +} + +static int hv_ptp_get_syncdevicetime(ktime_t *device, + struct system_counterval_t *system, + void *ctx) +{ + system->cs = hyperv_cs; + system->cycles = host_ts.ref_time; + *device = ns_to_ktime((host_ts.host_time - WLTIMEDELTA) * 100); + + return 0; +} + +static int hv_ptp_getcrosststamp(struct ptp_clock_info *ptp, + struct system_device_crosststamp *xtstamp) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&host_ts.lock, flags); + + /* + * host_ts contains the last time sample from the host and the snapshot + * of system time. We don't need to calculate the time delta between + * the reception and now as get_device_system_crosststamp() does the + * required interpolation. + */ + ret = get_device_system_crosststamp(hv_ptp_get_syncdevicetime, + NULL, &host_ts.snap, xtstamp); + + spin_unlock_irqrestore(&host_ts.lock, flags); + + return ret; +} + +static struct ptp_clock_info ptp_hyperv_info = { + .name = "hyperv", + .enable = hv_ptp_enable, + .adjtime = hv_ptp_adjtime, + .adjfreq = hv_ptp_adjfreq, + .gettime64 = hv_ptp_gettime, + .getcrosststamp = hv_ptp_getcrosststamp, + .settime64 = hv_ptp_settime, + .owner = THIS_MODULE, +}; + +static struct ptp_clock *hv_ptp_clock; + static int hv_timesync_init(struct hv_util_service *srv) { + /* TimeSync requires Hyper-V clocksource. */ + if (!hyperv_cs) + return -ENODEV; + INIT_WORK(&wrk.work, hv_set_host_time); + + /* + * ptp_clock_register() returns NULL when CONFIG_PTP_1588_CLOCK is + * disabled but the driver is still useful without the PTP device + * as it still handles the ICTIMESYNCFLAG_SYNC case. + */ + hv_ptp_clock = ptp_clock_register(&ptp_hyperv_info, NULL); + if (IS_ERR_OR_NULL(hv_ptp_clock)) { + pr_err("cannot register PTP clock: %ld\n", + PTR_ERR(hv_ptp_clock)); + hv_ptp_clock = NULL; + } + return 0; } static void hv_timesync_deinit(void) { + if (hv_ptp_clock) + ptp_clock_unregister(hv_ptp_clock); cancel_work_sync(&wrk.work); } |