]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
net: macb: Add TAPRIO traffic scheduling support
authorVineeth Karumanchi <vineeth.karumanchi@amd.com>
Thu, 14 Aug 2025 07:10:57 +0000 (12:40 +0530)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 19 Aug 2025 10:13:03 +0000 (12:13 +0200)
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>
drivers/net/ethernet/cadence/macb.h
drivers/net/ethernet/cadence/macb_main.c

index c9a5c8beb2fa8166195d1d83f187d2d0c62668a8..d1a98b45c92ceec288aaf08c80cdb99a512950ff 100644 (file)
 #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 */
 
 #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
 #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 */
index ce55a1f59b50dd85fa92bf139d06e6120d109e89..a42304eb2bf87d5e0df266a1f1a8f45f4b26b2e2 100644 (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