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: Vineeth Karumanchi <vineeth.karumanchi@amd.com>
Link: https://patch.msgid.link/20250814071058.3062453-2-vineeth.karumanchi@amd.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Vineeth Karumanchi 2025-08-14 12:40:57 +05:30 committed by Paolo Abeni
parent 6089970b07
commit 89934dbf16
2 changed files with 289 additions and 0 deletions

View File

@ -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 */

View File

@ -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