// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) Meta Platforms, Inc. and affiliates. */ #include #include #include #include #include #include "fbnic.h" #include "fbnic_csr.h" #include "fbnic_netdev.h" /* FBNIC timing & PTP implementation * Datapath uses truncated 40b timestamps for scheduling and event reporting. * We need to promote those to full 64b, hence we periodically cache the top * 32bit of the HW time counter. Since this makes our time reporting non-atomic * we leave the HW clock free running and adjust time offsets in SW as needed. * Time offset is 64bit - we need a seq counter for 32bit machines. * Time offset and the cache of top bits are independent so we don't need * a coherent snapshot of both - READ_ONCE()/WRITE_ONCE() + writer side lock * are enough. */ /* Period of refresh of top bits of timestamp, give ourselves a 8x margin. * This should translate to once a minute. * The use of nsecs_to_jiffies() should be safe for a <=40b nsec value. */ #define FBNIC_TS_HIGH_REFRESH_JIF nsecs_to_jiffies((1ULL << 40) / 16) static struct fbnic_dev *fbnic_from_ptp_info(struct ptp_clock_info *ptp) { return container_of(ptp, struct fbnic_dev, ptp_info); } /* This function is "slow" because we could try guessing which high part * is correct based on low instead of re-reading, and skip reading @hi * twice altogether if @lo is far enough from 0. */ static u64 __fbnic_time_get_slow(struct fbnic_dev *fbd) { u32 hi, lo; lockdep_assert_held(&fbd->time_lock); do { hi = fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_HI); lo = fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_LO); } while (hi != fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_HI)); return (u64)hi << 32 | lo; } static void __fbnic_time_set_addend(struct fbnic_dev *fbd, u64 addend) { lockdep_assert_held(&fbd->time_lock); fbnic_wr32(fbd, FBNIC_PTP_ADD_VAL_NS, FIELD_PREP(FBNIC_PTP_ADD_VAL_NS_MASK, addend >> 32)); fbnic_wr32(fbd, FBNIC_PTP_ADD_VAL_SUBNS, (u32)addend); } static void fbnic_ptp_fresh_check(struct fbnic_dev *fbd) { if (time_is_after_jiffies(fbd->last_read + FBNIC_TS_HIGH_REFRESH_JIF * 3 / 2)) return; dev_warn(fbd->dev, "NIC timestamp refresh stall, delayed by %lu sec\n", (jiffies - fbd->last_read - FBNIC_TS_HIGH_REFRESH_JIF) / HZ); } static void fbnic_ptp_refresh_time(struct fbnic_dev *fbd, struct fbnic_net *fbn) { unsigned long flags; u32 hi; spin_lock_irqsave(&fbd->time_lock, flags); hi = fbnic_rd32(fbn->fbd, FBNIC_PTP_CTR_VAL_HI); if (!fbnic_present(fbd)) goto out; /* Don't bother handling, reset is pending */ /* Let's keep high cached value a bit lower to avoid race with * incoming timestamps. The logic in fbnic_ts40_to_ns() will * take care of overflow in this case. It will make cached time * ~1 minute lower and incoming timestamp will always be later * then cached time. */ WRITE_ONCE(fbn->time_high, hi - 16); fbd->last_read = jiffies; out: spin_unlock_irqrestore(&fbd->time_lock, flags); } static long fbnic_ptp_do_aux_work(struct ptp_clock_info *ptp) { struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp); struct fbnic_net *fbn; fbn = netdev_priv(fbd->netdev); fbnic_ptp_fresh_check(fbd); fbnic_ptp_refresh_time(fbd, fbn); return FBNIC_TS_HIGH_REFRESH_JIF; } static int fbnic_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) { struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp); u64 addend, dclk_period; unsigned long flags; /* d_clock is 600 MHz; which in Q16.32 fixed point ns is: */ dclk_period = (((u64)1000000000) << 32) / FBNIC_CLOCK_FREQ; addend = adjust_by_scaled_ppm(dclk_period, scaled_ppm); spin_lock_irqsave(&fbd->time_lock, flags); __fbnic_time_set_addend(fbd, addend); fbnic_wr32(fbd, FBNIC_PTP_ADJUST, FBNIC_PTP_ADJUST_ADDEND_SET); /* Flush, make sure FBNIC_PTP_ADD_VAL_* is stable for at least 4 clks */ fbnic_rd32(fbd, FBNIC_PTP_SPARE); spin_unlock_irqrestore(&fbd->time_lock, flags); return fbnic_present(fbd) ? 0 : -EIO; } static int fbnic_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) { struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp); struct fbnic_net *fbn; unsigned long flags; fbn = netdev_priv(fbd->netdev); spin_lock_irqsave(&fbd->time_lock, flags); u64_stats_update_begin(&fbn->time_seq); WRITE_ONCE(fbn->time_offset, READ_ONCE(fbn->time_offset) + delta); u64_stats_update_end(&fbn->time_seq); spin_unlock_irqrestore(&fbd->time_lock, flags); return 0; } static int fbnic_ptp_gettimex64(struct ptp_clock_info *ptp, struct timespec64 *ts, struct ptp_system_timestamp *sts) { struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp); struct fbnic_net *fbn; unsigned long flags; u64 time_ns; u32 hi, lo; fbn = netdev_priv(fbd->netdev); spin_lock_irqsave(&fbd->time_lock, flags); do { hi = fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_HI); ptp_read_system_prets(sts); lo = fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_LO); ptp_read_system_postts(sts); /* Similarly to comment above __fbnic_time_get_slow() * - this can be optimized if needed. */ } while (hi != fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_HI)); time_ns = ((u64)hi << 32 | lo) + fbn->time_offset; spin_unlock_irqrestore(&fbd->time_lock, flags); if (!fbnic_present(fbd)) return -EIO; *ts = ns_to_timespec64(time_ns); return 0; } static int fbnic_ptp_settime64(struct ptp_clock_info *ptp, const struct timespec64 *ts) { struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp); struct fbnic_net *fbn; unsigned long flags; u64 dev_ns, host_ns; int ret; fbn = netdev_priv(fbd->netdev); host_ns = timespec64_to_ns(ts); spin_lock_irqsave(&fbd->time_lock, flags); dev_ns = __fbnic_time_get_slow(fbd); if (fbnic_present(fbd)) { u64_stats_update_begin(&fbn->time_seq); WRITE_ONCE(fbn->time_offset, host_ns - dev_ns); u64_stats_update_end(&fbn->time_seq); ret = 0; } else { ret = -EIO; } spin_unlock_irqrestore(&fbd->time_lock, flags); return ret; } static const struct ptp_clock_info fbnic_ptp_info = { .owner = THIS_MODULE, /* 1,000,000,000 - 1 PPB to ensure increment is positive * after max negative adjustment. */ .max_adj = 999999999, .do_aux_work = fbnic_ptp_do_aux_work, .adjfine = fbnic_ptp_adjfine, .adjtime = fbnic_ptp_adjtime, .gettimex64 = fbnic_ptp_gettimex64, .settime64 = fbnic_ptp_settime64, }; static void fbnic_ptp_reset(struct fbnic_dev *fbd) { struct fbnic_net *fbn = netdev_priv(fbd->netdev); u64 dclk_period; fbnic_wr32(fbd, FBNIC_PTP_CTRL, FBNIC_PTP_CTRL_EN | FIELD_PREP(FBNIC_PTP_CTRL_TICK_IVAL, 1)); /* d_clock is 600 MHz; which in Q16.32 fixed point ns is: */ dclk_period = (((u64)1000000000) << 32) / FBNIC_CLOCK_FREQ; __fbnic_time_set_addend(fbd, dclk_period); fbnic_wr32(fbd, FBNIC_PTP_INIT_HI, 0); fbnic_wr32(fbd, FBNIC_PTP_INIT_LO, 0); fbnic_wr32(fbd, FBNIC_PTP_ADJUST, FBNIC_PTP_ADJUST_INIT); fbnic_wr32(fbd, FBNIC_PTP_CTRL, FBNIC_PTP_CTRL_EN | FBNIC_PTP_CTRL_TQS_OUT_EN | FIELD_PREP(FBNIC_PTP_CTRL_MAC_OUT_IVAL, 3) | FIELD_PREP(FBNIC_PTP_CTRL_TICK_IVAL, 1)); fbnic_rd32(fbd, FBNIC_PTP_SPARE); fbn->time_offset = 0; fbn->time_high = 0; } void fbnic_time_init(struct fbnic_net *fbn) { /* This is not really a statistic, but the lockng primitive fits * our usecase perfectly, we need an atomic 8 bytes READ_ONCE() / * WRITE_ONCE() behavior. */ u64_stats_init(&fbn->time_seq); } int fbnic_time_start(struct fbnic_net *fbn) { fbnic_ptp_refresh_time(fbn->fbd, fbn); /* Assume that fbnic_ptp_do_aux_work() will never be called if not * scheduled here */ return ptp_schedule_worker(fbn->fbd->ptp, FBNIC_TS_HIGH_REFRESH_JIF); } void fbnic_time_stop(struct fbnic_net *fbn) { ptp_cancel_worker_sync(fbn->fbd->ptp); fbnic_ptp_fresh_check(fbn->fbd); } int fbnic_ptp_setup(struct fbnic_dev *fbd) { struct device *dev = fbd->dev; unsigned long flags; spin_lock_init(&fbd->time_lock); spin_lock_irqsave(&fbd->time_lock, flags); /* Appease lockdep */ fbnic_ptp_reset(fbd); spin_unlock_irqrestore(&fbd->time_lock, flags); memcpy(&fbd->ptp_info, &fbnic_ptp_info, sizeof(fbnic_ptp_info)); fbd->ptp = ptp_clock_register(&fbd->ptp_info, dev); if (IS_ERR(fbd->ptp)) dev_err(dev, "Failed to register PTP: %pe\n", fbd->ptp); return PTR_ERR_OR_ZERO(fbd->ptp); } void fbnic_ptp_destroy(struct fbnic_dev *fbd) { if (!fbd->ptp) return; ptp_clock_unregister(fbd->ptp); }