Merge branch 'net-macb-add-taprio-traffic-scheduling-support'

Vineeth Karumanchi says:

====================
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 implemented:
- Complete TAPRIO qdisc offload infrastructure with TC_SETUP_QDISC_TAPRIO
- Hardware-accelerated time-based gate control for multiple queues
- Enhanced Scheduled Traffic (ENST) register configuration and management
- Gate state scheduling with configurable start times, on/off intervals
- Support for cycle-time based traffic scheduling with validation
- Hardware capability detection via MACB_CAPS_QBV flag
- Robust error handling and parameter validation
- Queue-specific timing register programming
  (ENST_START_TIME, ENST_ON_TIME, ENST_OFF_TIME)

Changes include:
- Add enst_ns_to_hw_units(): Converts nanoseconds to hardware units
- Add enst_max_hw_interval(): Returns max interval for given speed
- Add macb_taprio_setup_replace() for TAPRIO configuration
- Add macb_taprio_destroy() for cleanup and reset
- Add macb_setup_tc() as TC offload entry point
- Enable NETIF_F_HW_TC feature for QBV-capable hardware
- Add ENST register offsets to queue configuration

The implementation validates timing constraints against hardware limits,
supports per-queue gate mask configuration, and provides comprehensive
logging for debugging and monitoring. Hardware registers are programmed
atomically with proper locking to ensure consistent state.

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-1-vineeth.karumanchi@amd.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni
2025-08-19 12:13:05 +02:00
2 changed files with 297 additions and 2 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
@@ -739,6 +770,7 @@
#define MACB_CAPS_MIIONRGMII 0x00000200
#define MACB_CAPS_NEED_TSUCLK 0x00000400
#define MACB_CAPS_QUEUE_DISABLE 0x00000800
#define MACB_CAPS_QBV 0x00001000
#define MACB_CAPS_PCS 0x01000000
#define MACB_CAPS_HIGH_SPEED 0x02000000
#define MACB_CAPS_CLK_HW_CHG 0x04000000
@@ -1219,6 +1251,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 +1434,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 +1457,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
@@ -4379,6 +4602,10 @@ static int macb_init(struct platform_device *pdev)
dev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM;
if (bp->caps & MACB_CAPS_SG_DISABLED)
dev->hw_features &= ~NETIF_F_SG;
/* Enable HW_TC if hardware supports QBV */
if (bp->caps & MACB_CAPS_QBV)
dev->hw_features |= NETIF_F_HW_TC;
dev->features = dev->hw_features;
/* Check RX Flow Filters support.
@@ -5131,8 +5358,9 @@ static const struct macb_config sama7g5_emac_config = {
static const struct macb_config versal_config = {
.caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO |
MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_BD_RD_PREFETCH | MACB_CAPS_NEED_TSUCLK |
MACB_CAPS_QUEUE_DISABLE,
MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_BD_RD_PREFETCH |
MACB_CAPS_NEED_TSUCLK | MACB_CAPS_QUEUE_DISABLE |
MACB_CAPS_QBV,
.dma_burst_length = 16,
.clk_init = macb_clk_init,
.init = init_reset_optional,