enetc: add support Credit Based Shaper(CBS) for hardware offload
authorPo Liu <po.liu@nxp.com>
Mon, 25 Nov 2019 05:56:56 +0000 (05:56 +0000)
committerDavid S. Miller <davem@davemloft.net>
Mon, 25 Nov 2019 18:53:15 +0000 (10:53 -0800)
The ENETC hardware support the Credit Based Shaper(CBS) which part
of the IEEE-802.1Qav. The CBS driver was loaded by the sch_cbs
interface when set in the QOS in the kernel.

Here is an example command to set 20Mbits bandwidth in 1Gbits port
for taffic class 7:

tc qdisc add dev eth0 root handle 1: mqprio \
   num_tc 8 map 0 1 2 3 4 5 6 7 hw 1

tc qdisc replace dev eth0 parent 1:8 cbs \
   locredit -1470 hicredit 30 \
   sendslope -980000 idleslope 20000 offload 1

Signed-off-by: Po Liu <Po.Liu@nxp.com>
Reviewed-by: Claudiu Manoil <claudiu.manoil@nxp.com>
Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/freescale/enetc/Kconfig
drivers/net/ethernet/freescale/enetc/enetc.c
drivers/net/ethernet/freescale/enetc/enetc.h
drivers/net/ethernet/freescale/enetc/enetc_hw.h
drivers/net/ethernet/freescale/enetc/enetc_qos.c

index 491659fe3e35f845261c2e553ab3fff14e898b75..edad4ca46327dafdf77476974c2b4f5778600d88 100644 (file)
@@ -53,10 +53,10 @@ config FSL_ENETC_HW_TIMESTAMPING
 
 config FSL_ENETC_QOS
        bool "ENETC hardware Time-sensitive Network support"
-       depends on (FSL_ENETC || FSL_ENETC_VF) && NET_SCH_TAPRIO
+       depends on (FSL_ENETC || FSL_ENETC_VF) && (NET_SCH_TAPRIO || NET_SCH_CBS)
        help
          There are Time-Sensitive Network(TSN) capabilities(802.1Qbv/802.1Qci
          /802.1Qbu etc.) supported by ENETC. These TSN capabilities can be set
          enable/disable from user space via Qos commands(tc). In the kernel
          side, it can be loaded by Qos driver. Currently, it is only support
-         taprio(802.1Qbv).
+         taprio(802.1Qbv) and Credit Based Shaper(802.1Qbu).
index 27f6fd1708f0a8e7fa71317ef6d8c6401eff64c1..9db1b96ed9b97ab34ce0830705d9d072ad5277d9 100644 (file)
@@ -1496,6 +1496,8 @@ int enetc_setup_tc(struct net_device *ndev, enum tc_setup_type type,
                return enetc_setup_tc_mqprio(ndev, type_data);
        case TC_SETUP_QDISC_TAPRIO:
                return enetc_setup_tc_taprio(ndev, type_data);
+       case TC_SETUP_QDISC_CBS:
+               return enetc_setup_tc_cbs(ndev, type_data);
        default:
                return -EOPNOTSUPP;
        }
index 89f23156f330c52e85b127c9b6df02978961551b..7ee0da6d0015ff06bd458e14e803cd372cbe3f2b 100644 (file)
@@ -255,7 +255,9 @@ int enetc_send_cmd(struct enetc_si *si, struct enetc_cbd *cbd);
 #ifdef CONFIG_FSL_ENETC_QOS
 int enetc_setup_tc_taprio(struct net_device *ndev, void *type_data);
 void enetc_sched_speed_set(struct net_device *ndev);
+int enetc_setup_tc_cbs(struct net_device *ndev, void *type_data);
 #else
 #define enetc_setup_tc_taprio(ndev, type_data) -EOPNOTSUPP
 #define enetc_sched_speed_set(ndev) (void)0
+#define enetc_setup_tc_cbs(ndev, type_data) -EOPNOTSUPP
 #endif
index 924ddb6d358af90b4ed56df023fa3cb222fb20ee..51f543ef37a8d1fbb304af1969c41741821a8a27 100644 (file)
@@ -185,6 +185,8 @@ enum enetc_bdr_type {TX, RX};
 #define ENETC_PSICFGR0_SIVC(bmp)       (((bmp) & 0xff) << 24) /* VLAN_TYPE */
 
 #define ENETC_PTCCBSR0(n)      (0x1110 + (n) * 8) /* n = 0 to 7*/
+#define ENETC_CBSE             BIT(31)
+#define ENETC_CBS_BW_MASK      GENMASK(6, 0)
 #define ENETC_PTCCBSR1(n)      (0x1114 + (n) * 8) /* n = 0 to 7*/
 #define ENETC_RSSHASH_KEY_SIZE 40
 #define ENETC_PRSSK(n)         (0x1410 + (n) * 4) /* n = [0..9] */
@@ -603,6 +605,8 @@ struct enetc_cbd {
        u8 status_flags;
 };
 
+#define ENETC_CLK  400000000ULL
+
 /* port time gating control register */
 #define ENETC_QBV_PTGCR_OFFSET         0x11a00
 #define ENETC_QBV_TGE                  BIT(31)
index 66a3da61ca16618dd17d63d3df369a6a4761af7b..2e99438cb1bf37e2ece7959192b1e09ff1d02330 100644 (file)
@@ -4,6 +4,7 @@
 #include "enetc.h"
 
 #include <net/pkt_sched.h>
+#include <linux/math64.h>
 
 static u16 enetc_get_max_gcl_len(struct enetc_hw *hw)
 {
@@ -170,3 +171,130 @@ int enetc_setup_tc_taprio(struct net_device *ndev, void *type_data)
 
        return err;
 }
+
+static u32 enetc_get_cbs_enable(struct enetc_hw *hw, u8 tc)
+{
+       return enetc_port_rd(hw, ENETC_PTCCBSR0(tc)) & ENETC_CBSE;
+}
+
+static u8 enetc_get_cbs_bw(struct enetc_hw *hw, u8 tc)
+{
+       return enetc_port_rd(hw, ENETC_PTCCBSR0(tc)) & ENETC_CBS_BW_MASK;
+}
+
+int enetc_setup_tc_cbs(struct net_device *ndev, void *type_data)
+{
+       struct enetc_ndev_priv *priv = netdev_priv(ndev);
+       struct tc_cbs_qopt_offload *cbs = type_data;
+       u32 port_transmit_rate = priv->speed;
+       u8 tc_nums = netdev_get_num_tc(ndev);
+       struct enetc_si *si = priv->si;
+       u32 hi_credit_bit, hi_credit_reg;
+       u32 max_interference_size;
+       u32 port_frame_max_size;
+       u32 tc_max_sized_frame;
+       u8 tc = cbs->queue;
+       u8 prio_top, prio_next;
+       int bw_sum = 0;
+       u8 bw;
+
+       prio_top = netdev_get_prio_tc_map(ndev, tc_nums - 1);
+       prio_next = netdev_get_prio_tc_map(ndev, tc_nums - 2);
+
+       /* Support highest prio and second prio tc in cbs mode */
+       if (tc != prio_top && tc != prio_next)
+               return -EOPNOTSUPP;
+
+       if (!cbs->enable) {
+               /* Make sure the other TC that are numerically
+                * lower than this TC have been disabled.
+                */
+               if (tc == prio_top &&
+                   enetc_get_cbs_enable(&si->hw, prio_next)) {
+                       dev_err(&ndev->dev,
+                               "Disable TC%d before disable TC%d\n",
+                               prio_next, tc);
+                       return -EINVAL;
+               }
+
+               enetc_port_wr(&si->hw, ENETC_PTCCBSR1(tc), 0);
+               enetc_port_wr(&si->hw, ENETC_PTCCBSR0(tc), 0);
+
+               return 0;
+       }
+
+       if (cbs->idleslope - cbs->sendslope != port_transmit_rate * 1000L ||
+           cbs->idleslope < 0 || cbs->sendslope > 0)
+               return -EOPNOTSUPP;
+
+       port_frame_max_size = ndev->mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;
+
+       bw = cbs->idleslope / (port_transmit_rate * 10UL);
+
+       /* Make sure the other TC that are numerically
+        * higher than this TC have been enabled.
+        */
+       if (tc == prio_next) {
+               if (!enetc_get_cbs_enable(&si->hw, prio_top)) {
+                       dev_err(&ndev->dev,
+                               "Enable TC%d first before enable TC%d\n",
+                               prio_top, prio_next);
+                       return -EINVAL;
+               }
+               bw_sum += enetc_get_cbs_bw(&si->hw, prio_top);
+       }
+
+       if (bw_sum + bw >= 100) {
+               dev_err(&ndev->dev,
+                       "The sum of all CBS Bandwidth can't exceed 100\n");
+               return -EINVAL;
+       }
+
+       tc_max_sized_frame = enetc_port_rd(&si->hw, ENETC_PTCMSDUR(tc));
+
+       /* For top prio TC, the max_interfrence_size is maxSizedFrame.
+        *
+        * For next prio TC, the max_interfrence_size is calculated as below:
+        *
+        *      max_interference_size = M0 + Ma + Ra * M0 / (R0 - Ra)
+        *
+        *      - RA: idleSlope for AVB Class A
+        *      - R0: port transmit rate
+        *      - M0: maximum sized frame for the port
+        *      - MA: maximum sized frame for AVB Class A
+        */
+
+       if (tc == prio_top) {
+               max_interference_size = port_frame_max_size * 8;
+       } else {
+               u32 m0, ma, r0, ra;
+
+               m0 = port_frame_max_size * 8;
+               ma = enetc_port_rd(&si->hw, ENETC_PTCMSDUR(prio_top)) * 8;
+               ra = enetc_get_cbs_bw(&si->hw, prio_top) *
+                       port_transmit_rate * 10000ULL;
+               r0 = port_transmit_rate * 1000000ULL;
+               max_interference_size = m0 + ma +
+                       (u32)div_u64((u64)ra * m0, r0 - ra);
+       }
+
+       /* hiCredit bits calculate by:
+        *
+        * maxSizedFrame * (idleSlope/portTxRate)
+        */
+       hi_credit_bit = max_interference_size * bw / 100;
+
+       /* hiCredit bits to hiCredit register need to calculated as:
+        *
+        * (enetClockFrequency / portTransmitRate) * 100
+        */
+       hi_credit_reg = (u32)div_u64((ENETC_CLK * 100ULL) * hi_credit_bit,
+                                    port_transmit_rate * 1000000ULL);
+
+       enetc_port_wr(&si->hw, ENETC_PTCCBSR1(tc), hi_credit_reg);
+
+       /* Set bw register and enable this traffic class */
+       enetc_port_wr(&si->hw, ENETC_PTCCBSR0(tc), bw | ENETC_CBSE);
+
+       return 0;
+}