From: Kevin Yang <yyd@google.com>
To: Willem de Bruijn <willemb@google.com>,
Harshitha Ramamurthy <hramamurthy@google.com>,
Andrew Lunn <andrew+netdev@lunn.ch>,
David Miller <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Joshua Washington <joshwash@google.com>,
Gerhard Engleder <gerhard@engleder-embedded.com>,
Richard Cochran <richardcochran@gmail.com>
Cc: netdev@vger.kernel.org, yyd@google.com
Subject: [PATCH net-next v2 2/2] gve: implement ndo_get_tstamp
Date: Wed, 21 Jan 2026 16:04:58 +0000 [thread overview]
Message-ID: <20260121160458.990785-3-yyd@google.com> (raw)
In-Reply-To: <20260121160458.990785-1-yyd@google.com>
This patch implements ndo_get_tstamp in gve to support converting a
hwtstamp to the system's realtime clock.
The implementation does not assume the NIC clock is disciplined,
in other word, the NIC clock can be free-running. A periodic
job, embedded in gve's ptp_aux_work, updates the offset and slope
for the conversion.
Signed-off-by: Kevin Yang <yyd@google.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Reviewed-by: Harshitha Ramamurthy <hramamurthy@google.com>
---
drivers/net/ethernet/google/gve/gve.h | 8 ++
drivers/net/ethernet/google/gve/gve_adminq.h | 4 +-
drivers/net/ethernet/google/gve/gve_main.c | 27 +++++
drivers/net/ethernet/google/gve/gve_ptp.c | 107 ++++++++++++++++++-
4 files changed, 143 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
index 970d5ca8cddee..13a4c450e7635 100644
--- a/drivers/net/ethernet/google/gve/gve.h
+++ b/drivers/net/ethernet/google/gve/gve.h
@@ -774,6 +774,13 @@ struct gve_flow_rule {
struct gve_flow_spec mask;
};
+struct gve_tstamp_conversion {
+ u64 last_sync_ns;
+ seqlock_t lock; /* protects tc and cc */
+ struct timecounter tc;
+ struct cyclecounter cc;
+};
+
struct gve_flow_rules_cache {
bool rules_cache_synced; /* False if the driver's rules_cache is outdated */
struct gve_adminq_queried_flow_rule *rules_cache;
@@ -925,6 +932,7 @@ struct gve_priv {
struct gve_nic_ts_report *nic_ts_report;
dma_addr_t nic_ts_report_bus;
u64 last_sync_nic_counter; /* Clock counter from last NIC TS report */
+ struct gve_tstamp_conversion ts_real;
};
enum gve_service_task_flags_bit {
diff --git a/drivers/net/ethernet/google/gve/gve_adminq.h b/drivers/net/ethernet/google/gve/gve_adminq.h
index 22a74b6aa17ea..812160b87b143 100644
--- a/drivers/net/ethernet/google/gve/gve_adminq.h
+++ b/drivers/net/ethernet/google/gve/gve_adminq.h
@@ -411,8 +411,8 @@ static_assert(sizeof(struct gve_adminq_report_nic_ts) == 16);
struct gve_nic_ts_report {
__be64 nic_timestamp; /* NIC clock in nanoseconds */
- __be64 reserved1;
- __be64 reserved2;
+ __be64 cycle_pre;
+ __be64 cycle_post;
__be64 reserved3;
__be64 reserved4;
};
diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c
index c42640da15a5a..c44b4526ccced 100644
--- a/drivers/net/ethernet/google/gve/gve_main.c
+++ b/drivers/net/ethernet/google/gve/gve_main.c
@@ -2198,6 +2198,32 @@ static int gve_set_ts_config(struct net_device *dev,
return 0;
}
+static ktime_t gve_get_tstamp(struct net_device *dev,
+ const struct skb_shared_hwtstamps *hwtstamps,
+ enum netdev_tstamp_type type)
+{
+ struct gve_priv *priv = netdev_priv(dev);
+ unsigned int seq;
+ u64 ns;
+
+ if (type == NETDEV_TSTAMP_PHC)
+ return hwtstamps->hwtstamp;
+
+ if (type != NETDEV_TSTAMP_REALTIME)
+ return 0;
+
+ /* Skip if never synced */
+ if (!READ_ONCE(priv->ts_real.last_sync_ns))
+ return 0;
+
+ do {
+ seq = read_seqbegin(&priv->ts_real.lock);
+ ns = timecounter_cyc2time(&priv->ts_real.tc,
+ hwtstamps->hwtstamp);
+ } while (read_seqretry(&priv->ts_real.lock, seq));
+ return ns_to_ktime(ns);
+}
+
static const struct net_device_ops gve_netdev_ops = {
.ndo_start_xmit = gve_start_xmit,
.ndo_features_check = gve_features_check,
@@ -2209,6 +2235,7 @@ static const struct net_device_ops gve_netdev_ops = {
.ndo_bpf = gve_xdp,
.ndo_xdp_xmit = gve_xdp_xmit,
.ndo_xsk_wakeup = gve_xsk_wakeup,
+ .ndo_get_tstamp = gve_get_tstamp,
.ndo_hwtstamp_get = gve_get_ts_config,
.ndo_hwtstamp_set = gve_set_ts_config,
};
diff --git a/drivers/net/ethernet/google/gve/gve_ptp.c b/drivers/net/ethernet/google/gve/gve_ptp.c
index 073677d82ee8e..dfe353ae75fb1 100644
--- a/drivers/net/ethernet/google/gve/gve_ptp.c
+++ b/drivers/net/ethernet/google/gve/gve_ptp.c
@@ -10,10 +10,92 @@
/* Interval to schedule a nic timestamp calibration, 250ms. */
#define GVE_NIC_TS_SYNC_INTERVAL_MS 250
+/* Scale ts_real.cc.mult by 1 << 31. Maximize mult for finer adjustment
+ * granularity, but ensure (mult * cycle) does not overflow in
+ * cyclecounter_cyc2ns.
+ */
+#define GVE_HWTS_REAL_CC_SHIFT 31
+#define GVE_HWTS_REAL_CC_NOMINAL BIT_ULL(GVE_HWTS_REAL_CC_SHIFT)
+
+/* Get the cross time stamp info */
+static int gve_get_cross_time(ktime_t *device,
+ struct system_counterval_t *system, void *ctx)
+{
+ struct gve_priv *priv = ctx;
+
+ *device = ns_to_ktime(be64_to_cpu(priv->nic_ts_report->nic_timestamp));
+ system->cycles = be64_to_cpu(priv->nic_ts_report->cycle_pre) +
+ (be64_to_cpu(priv->nic_ts_report->cycle_post) -
+ be64_to_cpu(priv->nic_ts_report->cycle_pre)) / 2;
+ system->use_nsecs = false;
+ if (IS_ENABLED(CONFIG_X86))
+ system->cs_id = CSID_X86_TSC;
+ else if (IS_ENABLED(CONFIG_ARM_ARCH_TIMER))
+ system->cs_id = CSID_ARM_ARCH_COUNTER;
+ else
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int gve_hwts_realtime_update(struct gve_priv *priv, u64 prev_nic)
+{
+ u64 nic_delta = priv->last_sync_nic_counter - prev_nic;
+ struct system_device_crosststamp cts = {};
+ struct system_time_snapshot history = {};
+ s64 nic_real_off_ns;
+ u64 real_ns;
+ int ret;
+
+ /* Step 1: Get the realtime of when NIC clock was read */
+ ktime_get_snapshot(&history);
+ ret = get_device_system_crosststamp(gve_get_cross_time, priv, &history,
+ &cts);
+ if (ret) {
+ dev_err_ratelimited(&priv->pdev->dev,
+ "%s crosststamp err %d\n", __func__, ret);
+ return ret;
+ }
+
+ real_ns = ktime_to_ns(cts.sys_realtime);
+
+ /* Step 2: Adjust NIC clock's offset */
+ /* Read-side ndo_get_tstamp can be called from TCP rx softirq */
+ write_seqlock_bh(&priv->ts_real.lock);
+ nic_real_off_ns = real_ns - timecounter_read(&priv->ts_real.tc);
+ timecounter_adjtime(&priv->ts_real.tc, nic_real_off_ns);
+
+ /* Step 3: Adjust NIC clock's ratio (when this is not the first sync).
+ * The NIC clock's nominal tick ratio is 1 tick per nanosecond,
+ * scaled by 1 << GVE_HWTS_REAL_CC_SHIFT. Adjust it to
+ * (ktime - prev_ktime) / (nic - prev_nic). The ratio should not
+ * deviate more than 1% from the nominal, otherwise it may suggest
+ * there was a sudden change on NIC clock. In that case, reset ratio
+ * to nominal. And since each sync only compares to the previous read,
+ * this is a one-time error, not a persistent failure.
+ */
+ if (prev_nic && nic_delta) {
+ const u64 lower = GVE_HWTS_REAL_CC_NOMINAL * 99 / 100;
+ const u64 upper = GVE_HWTS_REAL_CC_NOMINAL * 101 / 100;
+ u64 numer = real_ns - priv->ts_real.last_sync_ns;
+ u64 mult, quot, rem;
+
+ quot = div64_u64_rem(GVE_HWTS_REAL_CC_NOMINAL, nic_delta, &rem);
+ mult = (quot * numer) + div64_u64(rem * numer, nic_delta);
+ if (mult < lower || mult > upper)
+ mult = GVE_HWTS_REAL_CC_NOMINAL;
+ priv->ts_real.cc.mult = mult;
+ }
+
+ write_sequnlock_bh(&priv->ts_real.lock);
+ WRITE_ONCE(priv->ts_real.last_sync_ns, real_ns);
+ return 0;
+}
+
/* Read the nic timestamp from hardware via the admin queue. */
int gve_clock_nic_ts_read(struct gve_priv *priv)
{
- u64 nic_raw;
+ u64 nic_raw, prev_nic;
int err;
err = gve_adminq_report_nic_ts(priv, priv->nic_ts_report_bus);
@@ -21,7 +103,11 @@ int gve_clock_nic_ts_read(struct gve_priv *priv)
return err;
nic_raw = be64_to_cpu(priv->nic_ts_report->nic_timestamp);
+ prev_nic = priv->last_sync_nic_counter;
WRITE_ONCE(priv->last_sync_nic_counter, nic_raw);
+ err = gve_hwts_realtime_update(priv, prev_nic);
+ if (err)
+ return err;
return 0;
}
@@ -57,6 +143,14 @@ static long gve_ptp_do_aux_work(struct ptp_clock_info *info)
return msecs_to_jiffies(GVE_NIC_TS_SYNC_INTERVAL_MS);
}
+static u64 gve_cycles_read(struct cyclecounter *cc)
+{
+ const struct gve_priv *priv = container_of(cc, struct gve_priv,
+ ts_real.cc);
+
+ return READ_ONCE(priv->last_sync_nic_counter);
+}
+
static const struct ptp_clock_info gve_ptp_caps = {
.owner = THIS_MODULE,
.name = "gve clock",
@@ -89,6 +183,17 @@ static int gve_ptp_init(struct gve_priv *priv)
goto free_ptp;
}
+ priv->last_sync_nic_counter = 0;
+ priv->ts_real.last_sync_ns = 0;
+ seqlock_init(&priv->ts_real.lock);
+ memset(&priv->ts_real.cc, 0, sizeof(priv->ts_real.cc));
+ priv->ts_real.cc.mask = U32_MAX;
+ priv->ts_real.cc.shift = GVE_HWTS_REAL_CC_SHIFT;
+ priv->ts_real.cc.mult = GVE_HWTS_REAL_CC_NOMINAL;
+ priv->ts_real.cc.read = gve_cycles_read;
+ timecounter_init(&priv->ts_real.tc, &priv->ts_real.cc,
+ ktime_get_real_ns());
+
ptp->priv = priv;
return 0;
--
2.52.0.457.g6b5491de43-goog
prev parent reply other threads:[~2026-01-21 16:06 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-21 16:04 [PATCH net-next v2 0/2] net: extend ndo_get_tstamp and implement in gve Kevin Yang
2026-01-21 16:04 ` [PATCH net-next v2 1/2] net: extend ndo_get_tstamp for other timestamp types Kevin Yang
2026-01-22 20:04 ` Gerhard Engleder
2026-01-22 22:28 ` Willem de Bruijn
2026-01-25 19:45 ` Gerhard Engleder
2026-01-25 21:41 ` Willem de Bruijn
2026-01-27 23:13 ` Kevin Yang
2026-01-21 16:04 ` Kevin Yang [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260121160458.990785-3-yyd@google.com \
--to=yyd@google.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=gerhard@engleder-embedded.com \
--cc=hramamurthy@google.com \
--cc=joshwash@google.com \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=richardcochran@gmail.com \
--cc=willemb@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.