drivers/net/ethernet/meta/fbnic/fbnic_time.c

Source file repositories/reference/linux-study-clean/drivers/net/ethernet/meta/fbnic/fbnic_time.c

File Facts

System
Linux kernel
Corpus path
drivers/net/ethernet/meta/fbnic/fbnic_time.c
Extension
.c
Size
8413 bytes
Lines
304
Domain
Driver Families
Bucket
drivers/net
Inferred role
Driver Families: implementation source
Status
source implementation candidate

Why This File Exists

Repeatable hardware-adapter layer. Deep compatibility for every driver is out of scope; this atlas records patterns, probe lifecycles, bus glue, IRQ/DMA usage, and links back to core abstractions.

Dependency Surface

Detected Declarations

Annotated Snippet

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */

#include <linux/bitfield.h>
#include <linux/jiffies.h>
#include <linux/limits.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/timer.h>

#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:

Annotation

Implementation Notes