diff options
Diffstat (limited to 'drivers/net/ethernet/google/gve/gve_ptp.c')
-rw-r--r-- | drivers/net/ethernet/google/gve/gve_ptp.c | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/drivers/net/ethernet/google/gve/gve_ptp.c b/drivers/net/ethernet/google/gve/gve_ptp.c new file mode 100644 index 000000000000..e96247c9d68d --- /dev/null +++ b/drivers/net/ethernet/google/gve/gve_ptp.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* Google virtual Ethernet (gve) driver + * + * Copyright (C) 2025 Google LLC + */ + +#include "gve.h" +#include "gve_adminq.h" + +/* Interval to schedule a nic timestamp calibration, 250ms. */ +#define GVE_NIC_TS_SYNC_INTERVAL_MS 250 + +/* Read the nic timestamp from hardware via the admin queue. */ +int gve_clock_nic_ts_read(struct gve_priv *priv) +{ + u64 nic_raw; + int err; + + err = gve_adminq_report_nic_ts(priv, priv->nic_ts_report_bus); + if (err) + return err; + + nic_raw = be64_to_cpu(priv->nic_ts_report->nic_timestamp); + WRITE_ONCE(priv->last_sync_nic_counter, nic_raw); + + return 0; +} + +static long gve_ptp_do_aux_work(struct ptp_clock_info *info) +{ + const struct gve_ptp *ptp = container_of(info, struct gve_ptp, info); + struct gve_priv *priv = ptp->priv; + int err; + + if (gve_get_reset_in_progress(priv) || !gve_get_admin_queue_ok(priv)) + goto out; + + err = gve_clock_nic_ts_read(priv); + if (err && net_ratelimit()) + dev_err(&priv->pdev->dev, + "%s read err %d\n", __func__, err); + +out: + return msecs_to_jiffies(GVE_NIC_TS_SYNC_INTERVAL_MS); +} + +static const struct ptp_clock_info gve_ptp_caps = { + .owner = THIS_MODULE, + .name = "gve clock", + .do_aux_work = gve_ptp_do_aux_work, +}; + +static int gve_ptp_init(struct gve_priv *priv) +{ + struct gve_ptp *ptp; + int err; + + if (!priv->nic_timestamp_supported) { + dev_dbg(&priv->pdev->dev, "Device does not support PTP\n"); + return -EOPNOTSUPP; + } + + priv->ptp = kzalloc(sizeof(*priv->ptp), GFP_KERNEL); + if (!priv->ptp) + return -ENOMEM; + + ptp = priv->ptp; + ptp->info = gve_ptp_caps; + ptp->clock = ptp_clock_register(&ptp->info, &priv->pdev->dev); + + if (IS_ERR(ptp->clock)) { + dev_err(&priv->pdev->dev, "PTP clock registration failed\n"); + err = PTR_ERR(ptp->clock); + goto free_ptp; + } + + ptp->priv = priv; + return 0; + +free_ptp: + kfree(ptp); + priv->ptp = NULL; + return err; +} + +static void gve_ptp_release(struct gve_priv *priv) +{ + struct gve_ptp *ptp = priv->ptp; + + if (!ptp) + return; + + if (ptp->clock) + ptp_clock_unregister(ptp->clock); + + kfree(ptp); + priv->ptp = NULL; +} + +int gve_init_clock(struct gve_priv *priv) +{ + int err; + + if (!priv->nic_timestamp_supported) + return 0; + + err = gve_ptp_init(priv); + if (err) + return err; + + priv->nic_ts_report = + dma_alloc_coherent(&priv->pdev->dev, + sizeof(struct gve_nic_ts_report), + &priv->nic_ts_report_bus, + GFP_KERNEL); + if (!priv->nic_ts_report) { + dev_err(&priv->pdev->dev, "%s dma alloc error\n", __func__); + err = -ENOMEM; + goto release_ptp; + } + + return 0; + +release_ptp: + gve_ptp_release(priv); + return err; +} + +void gve_teardown_clock(struct gve_priv *priv) +{ + gve_ptp_release(priv); + + if (priv->nic_ts_report) { + dma_free_coherent(&priv->pdev->dev, + sizeof(struct gve_nic_ts_report), + priv->nic_ts_report, priv->nic_ts_report_bus); + priv->nic_ts_report = NULL; + } +} |