Commit 89934dbf authored by Vineeth Karumanchi's avatar Vineeth Karumanchi Committed by Paolo Abeni
Browse files

net: macb: Add TAPRIO traffic scheduling support



Implement Time-Aware Traffic Scheduling (TAPRIO) offload support
for Cadence MACB/GEM ethernet controllers to enable IEEE 802.1Qbv
compliant time-sensitive networking (TSN) capabilities.

Key Features:

1. Enhanced Scheduled Traffic (ENST) Register Management
   - Per-queue ENST registers: ENST_START_TIME, ENST_ON_TIME, ENST_OFF_TIME
   - Centralized control via ENST_CONTROL for gate enable/disable
   - Infrastructure enhancements:
     * Extended macb_queue structure with ENST timing control registers
     * Mapped ENST register offsets into queue management framework
     * Introduced macb_queue_enst_config for per-entry TC configuration
   - Timing conversion utility:
     * enst_ns_to_hw_units(): Converts nanoseconds to hardware units
     * Timing values are programmed as hardware units based on link speed
     * Conversion formula: time_bytes = time_ns / divisor
     * Speed-specific divisors: 1Gbps=8, 100Mbps=80, 10Mbps=800
   - Hardware limit utility:
     * enst_max_hw_interval(): Returns max interval for given speed

2. TAPRIO Configuration via "tc qdisc replace"
   - macb_taprio_setup_replace(): Configures TAPRIO hardware offload
   - Parameter validation checks performed:
     * TC entry limit validation against available hardware queues
     * Base time non-negativity enforcement
     * Speed-adaptive timing constraint verification
     * Cycle time vs. total gate time consistency checks
     * Single-queue gate mask enforcement per scheduling entry
   - Programming sequence:
     * GEM doesn't support changing ENST registers if ENST is enabled,
       hence disable ENST before programming
     * Atomic timing register configuration (START_TIME, ON_TIME, OFF_TIME)
     * Enable queues via ENST_CONTROL

3. TAPRIO Cleanup via "tc qdisc destroy"
   - macb_taprio_destroy(): Safely removes TAPRIO configuration
   - Restores default queue behavior
   - Cleanup steps:
     * Reset TC state
     * Disable ENST
     * Clear timing registers
     * Ensure atomic updates with locking

4. Traffic Control Offload Infrastructure
   - macb_setup_taprio(): TAPRIO command dispatcher
     * Verifies hardware support
     * Handles runtime suspend state
   - macb_setup_tc(): TC_SETUP_QDISC_TAPRIO entry point
   - Supports REPLACE and DESTROY operations

Tested on Xilinx Versal platforms with QBV-capable MACB controllers.

Signed-off-by: default avatarVineeth Karumanchi <vineeth.karumanchi@amd.com>
Link: https://patch.msgid.link/20250814071058.3062453-2-vineeth.karumanchi@amd.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent 6089970b
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
@@ -184,6 +184,13 @@
#define GEM_DCFG8		0x029C /* Design Config 8 */
#define GEM_DCFG10		0x02A4 /* Design Config 10 */
#define GEM_DCFG12		0x02AC /* Design Config 12 */
#define GEM_ENST_START_TIME_Q0	0x0800 /* ENST Q0 start time */
#define GEM_ENST_START_TIME_Q1	0x0804 /* ENST Q1 start time */
#define GEM_ENST_ON_TIME_Q0	0x0820 /* ENST Q0 on time */
#define GEM_ENST_ON_TIME_Q1	0x0824 /* ENST Q1 on time */
#define GEM_ENST_OFF_TIME_Q0	0x0840 /* ENST Q0 off time */
#define GEM_ENST_OFF_TIME_Q1	0x0844 /* ENST Q1 off time */
#define GEM_ENST_CONTROL	0x0880 /* ENST control register */
#define GEM_USX_CONTROL		0x0A80 /* High speed PCS control register */
#define GEM_USX_STATUS		0x0A88 /* High speed PCS status register */

@@ -221,6 +228,13 @@
#define GEM_IDR(hw_q)		(0x0620 + ((hw_q) << 2))
#define GEM_IMR(hw_q)		(0x0640 + ((hw_q) << 2))

#define GEM_ENST_START_TIME(hw_q)	(0x0800 + ((hw_q) << 2))
#define GEM_ENST_ON_TIME(hw_q)		(0x0820 + ((hw_q) << 2))
#define GEM_ENST_OFF_TIME(hw_q)		(0x0840 + ((hw_q) << 2))

/* Bitfields in ENST_CONTROL */
#define GEM_ENST_DISABLE_QUEUE_OFFSET	16

/* Bitfields in NCR */
#define MACB_LB_OFFSET		0 /* reserved */
#define MACB_LB_SIZE		1
@@ -554,6 +568,23 @@
#define GEM_HIGH_SPEED_OFFSET			26
#define GEM_HIGH_SPEED_SIZE			1

/* Bitfields in ENST_START_TIME_Qx. */
#define GEM_START_TIME_SEC_OFFSET		30
#define GEM_START_TIME_SEC_SIZE			2
#define GEM_START_TIME_NSEC_OFFSET		0
#define GEM_START_TIME_NSEC_SIZE		30

/* Bitfields in ENST_ON_TIME_Qx. */
#define GEM_ON_TIME_OFFSET			0
#define GEM_ON_TIME_SIZE			17

/* Bitfields in ENST_OFF_TIME_Qx. */
#define GEM_OFF_TIME_OFFSET			0
#define GEM_OFF_TIME_SIZE			17

/* Hardware ENST timing registers granularity */
#define ENST_TIME_GRANULARITY_NS		8

/* Bitfields in USX_CONTROL. */
#define GEM_USX_CTRL_SPEED_OFFSET		14
#define GEM_USX_CTRL_SPEED_SIZE			3
@@ -1219,6 +1250,11 @@ struct macb_queue {
	unsigned int		RBQP;
	unsigned int		RBQPH;

	/* ENST register offsets for this queue */
	unsigned int		ENST_START_TIME;
	unsigned int		ENST_ON_TIME;
	unsigned int		ENST_OFF_TIME;

	/* Lock to protect tx_head and tx_tail */
	spinlock_t		tx_ptr_lock;
	unsigned int		tx_head, tx_tail;
@@ -1397,6 +1433,19 @@ static inline bool gem_has_ptp(struct macb *bp)
	return IS_ENABLED(CONFIG_MACB_USE_HWSTAMP) && (bp->caps & MACB_CAPS_GEM_HAS_PTP);
}

/* ENST Helper functions */
static inline u64 enst_ns_to_hw_units(size_t ns, u32 speed_mbps)
{
	return DIV_ROUND_UP((ns) * (speed_mbps),
			    (ENST_TIME_GRANULARITY_NS * 1000));
}

static inline u64 enst_max_hw_interval(u32 speed_mbps)
{
	return DIV_ROUND_UP(GENMASK(GEM_ON_TIME_SIZE - 1, 0) *
			    ENST_TIME_GRANULARITY_NS * 1000, (speed_mbps));
}

/**
 * struct macb_platform_data - platform data for MACB Ethernet used for PCI registration
 * @pclk:		platform clock
@@ -1407,4 +1456,21 @@ struct macb_platform_data {
	struct clk	*hclk;
};

/**
 * struct macb_queue_enst_config - Configuration for Enhanced Scheduled Traffic
 * @start_time_mask:  Bitmask representing the start time for the queue
 * @on_time_bytes:    "on" time nsec expressed in bytes
 * @off_time_bytes:   "off" time nsec expressed in bytes
 * @queue_id:         Identifier for the queue
 *
 * This structure holds the configuration parameters for an ENST queue,
 * used to control time-based transmission scheduling in the MACB driver.
 */
struct macb_queue_enst_config {
	u32 start_time_mask;
	u32 on_time_bytes;
	u32 off_time_bytes;
	u8 queue_id;
};

#endif /* _MACB_H */
+223 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@
#include <linux/reset.h>
#include <linux/firmware/xlnx-zynqmp.h>
#include <linux/inetdevice.h>
#include <net/pkt_sched.h>
#include "macb.h"

/* This structure is only used for MACB on SiFive FU540 devices */
@@ -4084,6 +4085,223 @@ static void macb_restore_features(struct macb *bp)
	macb_set_rxflow_feature(bp, features);
}

static int macb_taprio_setup_replace(struct net_device *ndev,
				     struct tc_taprio_qopt_offload *conf)
{
	u64 total_on_time = 0, start_time_sec = 0, start_time = conf->base_time;
	u32 configured_queues = 0, speed = 0, start_time_nsec;
	struct macb_queue_enst_config *enst_queue;
	struct tc_taprio_sched_entry *entry;
	struct macb *bp = netdev_priv(ndev);
	struct ethtool_link_ksettings kset;
	struct macb_queue *queue;
	size_t i;
	int err;

	if (conf->num_entries > bp->num_queues) {
		netdev_err(ndev, "Too many TAPRIO entries: %zu > %d queues\n",
			   conf->num_entries, bp->num_queues);
		return -EINVAL;
	}

	if (start_time < 0) {
		netdev_err(ndev, "Invalid base_time: must be 0 or positive, got %lld\n",
			   conf->base_time);
		return -ERANGE;
	}

	/* Get the current link speed */
	err = phylink_ethtool_ksettings_get(bp->phylink, &kset);
	if (unlikely(err)) {
		netdev_err(ndev, "Failed to get link settings: %d\n", err);
		return err;
	}

	speed = kset.base.speed;
	if (unlikely(speed <= 0)) {
		netdev_err(ndev, "Invalid speed: %d\n", speed);
		return -EINVAL;
	}

	enst_queue = kcalloc(conf->num_entries, sizeof(*enst_queue), GFP_KERNEL);
	if (unlikely(!enst_queue))
		return -ENOMEM;

	/* Pre-validate all entries before making any hardware changes */
	for (i = 0; i < conf->num_entries; i++) {
		entry = &conf->entries[i];

		if (entry->command != TC_TAPRIO_CMD_SET_GATES) {
			netdev_err(ndev, "Entry %zu: unsupported command %d\n",
				   i, entry->command);
			err = -EOPNOTSUPP;
			goto cleanup;
		}

		/* Validate gate_mask: must be nonzero, single queue, and within range */
		if (!is_power_of_2(entry->gate_mask)) {
			netdev_err(ndev, "Entry %zu: gate_mask 0x%x is not a power of 2 (only one queue per entry allowed)\n",
				   i, entry->gate_mask);
			err = -EINVAL;
			goto cleanup;
		}

		/* gate_mask must not select queues outside the valid queue_mask */
		if (entry->gate_mask & ~bp->queue_mask) {
			netdev_err(ndev, "Entry %zu: gate_mask 0x%x exceeds queue range (max_queues=%d)\n",
				   i, entry->gate_mask, bp->num_queues);
			err = -EINVAL;
			goto cleanup;
		}

		/* Check for start time limits */
		start_time_sec = start_time;
		start_time_nsec = do_div(start_time_sec, NSEC_PER_SEC);
		if (start_time_sec > GENMASK(GEM_START_TIME_SEC_SIZE - 1, 0)) {
			netdev_err(ndev, "Entry %zu: Start time %llu s exceeds hardware limit\n",
				   i, start_time_sec);
			err = -ERANGE;
			goto cleanup;
		}

		/* Check for on time limit */
		if (entry->interval > enst_max_hw_interval(speed)) {
			netdev_err(ndev, "Entry %zu: interval %u ns exceeds hardware limit %llu ns\n",
				   i, entry->interval, enst_max_hw_interval(speed));
			err = -ERANGE;
			goto cleanup;
		}

		/* Check for off time limit*/
		if ((conf->cycle_time - entry->interval) > enst_max_hw_interval(speed)) {
			netdev_err(ndev, "Entry %zu: off_time %llu ns exceeds hardware limit %llu ns\n",
				   i, conf->cycle_time - entry->interval,
				   enst_max_hw_interval(speed));
			err = -ERANGE;
			goto cleanup;
		}

		enst_queue[i].queue_id = order_base_2(entry->gate_mask);
		enst_queue[i].start_time_mask =
			(start_time_sec << GEM_START_TIME_SEC_OFFSET) |
			start_time_nsec;
		enst_queue[i].on_time_bytes =
			enst_ns_to_hw_units(entry->interval, speed);
		enst_queue[i].off_time_bytes =
			enst_ns_to_hw_units(conf->cycle_time - entry->interval, speed);

		configured_queues |= entry->gate_mask;
		total_on_time += entry->interval;
		start_time += entry->interval;
	}

	/* Check total interval doesn't exceed cycle time */
	if (total_on_time > conf->cycle_time) {
		netdev_err(ndev, "Total ON %llu ns exceeds cycle time %llu ns\n",
			   total_on_time, conf->cycle_time);
		err = -EINVAL;
		goto cleanup;
	}

	netdev_dbg(ndev, "TAPRIO setup: %zu entries, base_time=%lld ns, cycle_time=%llu ns\n",
		   conf->num_entries, conf->base_time, conf->cycle_time);

	/* All validations passed - proceed with hardware configuration */
	scoped_guard(spinlock_irqsave, &bp->lock) {
		/* Disable ENST queues if running before configuring */
		gem_writel(bp, ENST_CONTROL,
			   bp->queue_mask << GEM_ENST_DISABLE_QUEUE_OFFSET);

		for (i = 0; i < conf->num_entries; i++) {
			queue = &bp->queues[enst_queue[i].queue_id];
			/* Configure queue timing registers */
			queue_writel(queue, ENST_START_TIME,
				     enst_queue[i].start_time_mask);
			queue_writel(queue, ENST_ON_TIME,
				     enst_queue[i].on_time_bytes);
			queue_writel(queue, ENST_OFF_TIME,
				     enst_queue[i].off_time_bytes);
		}

		/* Enable ENST for all configured queues in one write */
		gem_writel(bp, ENST_CONTROL, configured_queues);
	}

	netdev_info(ndev, "TAPRIO configuration completed successfully: %zu entries, %d queues configured\n",
		    conf->num_entries, hweight32(configured_queues));

cleanup:
	kfree(enst_queue);
	return err;
}

static void macb_taprio_destroy(struct net_device *ndev)
{
	struct macb *bp = netdev_priv(ndev);
	struct macb_queue *queue;
	u32 enst_disable_mask;
	unsigned int q;

	netdev_reset_tc(ndev);
	enst_disable_mask = bp->queue_mask << GEM_ENST_DISABLE_QUEUE_OFFSET;

	scoped_guard(spinlock_irqsave, &bp->lock) {
		/* Single disable command for all queues */
		gem_writel(bp, ENST_CONTROL, enst_disable_mask);

		/* Clear all queue ENST registers in batch */
		for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
			queue_writel(queue, ENST_START_TIME, 0);
			queue_writel(queue, ENST_ON_TIME, 0);
			queue_writel(queue, ENST_OFF_TIME, 0);
		}
	}
	netdev_info(ndev, "TAPRIO destroy: All gates disabled\n");
}

static int macb_setup_taprio(struct net_device *ndev,
			     struct tc_taprio_qopt_offload *taprio)
{
	struct macb *bp = netdev_priv(ndev);
	int err = 0;

	if (unlikely(!(ndev->hw_features & NETIF_F_HW_TC)))
		return -EOPNOTSUPP;

	/* Check if Device is in runtime suspend */
	if (unlikely(pm_runtime_suspended(&bp->pdev->dev))) {
		netdev_err(ndev, "Device is in runtime suspend\n");
		return -EOPNOTSUPP;
	}

	switch (taprio->cmd) {
	case TAPRIO_CMD_REPLACE:
		err = macb_taprio_setup_replace(ndev, taprio);
		break;
	case TAPRIO_CMD_DESTROY:
		macb_taprio_destroy(ndev);
		break;
	default:
		err = -EOPNOTSUPP;
	}

	return err;
}

static int macb_setup_tc(struct net_device *dev, enum tc_setup_type type,
			 void *type_data)
{
	if (!dev || !type_data)
		return -EINVAL;

	switch (type) {
	case TC_SETUP_QDISC_TAPRIO:
		return macb_setup_taprio(dev, type_data);
	default:
		return -EOPNOTSUPP;
	}
}

static const struct net_device_ops macb_netdev_ops = {
	.ndo_open		= macb_open,
	.ndo_stop		= macb_close,
@@ -4101,6 +4319,7 @@ static const struct net_device_ops macb_netdev_ops = {
	.ndo_features_check	= macb_features_check,
	.ndo_hwtstamp_set	= macb_hwtstamp_set,
	.ndo_hwtstamp_get	= macb_hwtstamp_get,
	.ndo_setup_tc		= macb_setup_tc,
};

/* Configure peripheral capabilities according to device tree
@@ -4327,6 +4546,10 @@ static int macb_init(struct platform_device *pdev)
#endif
		}

		queue->ENST_START_TIME = GEM_ENST_START_TIME(hw_q);
		queue->ENST_ON_TIME = GEM_ENST_ON_TIME(hw_q);
		queue->ENST_OFF_TIME = GEM_ENST_OFF_TIME(hw_q);

		/* get irq: here we use the linux queue index, not the hardware
		 * queue index. the queue irq definitions in the device tree
		 * must remove the optional gaps that could exist in the