| From 3426e5e4339f124f00eef8815b56a80481364550 Mon Sep 17 00:00:00 2001 |
| From: Po Liu <po.liu@nxp.com> |
| Date: Mon, 25 Nov 2019 05:56:56 +0000 |
| Subject: [PATCH] enetc: add support Credit Based Shaper(CBS) for hardware |
| offload |
| |
| 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 | 4 +- |
| drivers/net/ethernet/freescale/enetc/enetc.c | 2 + |
| drivers/net/ethernet/freescale/enetc/enetc.h | 2 + |
| drivers/net/ethernet/freescale/enetc/enetc_hw.h | 4 + |
| drivers/net/ethernet/freescale/enetc/enetc_qos.c | 128 +++++++++++++++++++++++ |
| 5 files changed, 138 insertions(+), 2 deletions(-) |
| |
| --- a/drivers/net/ethernet/freescale/enetc/Kconfig |
| +++ b/drivers/net/ethernet/freescale/enetc/Kconfig |
| @@ -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). |
| --- a/drivers/net/ethernet/freescale/enetc/enetc.c |
| +++ b/drivers/net/ethernet/freescale/enetc/enetc.c |
| @@ -1521,6 +1521,8 @@ int enetc_setup_tc(struct net_device *nd |
| 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; |
| } |
| --- a/drivers/net/ethernet/freescale/enetc/enetc.h |
| +++ b/drivers/net/ethernet/freescale/enetc/enetc.h |
| @@ -260,7 +260,9 @@ int enetc_send_cmd(struct enetc_si *si, |
| #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 |
| --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h |
| +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h |
| @@ -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_PRSSCAPR 0x1404 |
| @@ -679,6 +681,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) |
| --- a/drivers/net/ethernet/freescale/enetc/enetc_qos.c |
| +++ b/drivers/net/ethernet/freescale/enetc/enetc_qos.c |
| @@ -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_dev |
| |
| 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; |
| +} |