Skip to content

Commit

Permalink
net: ethernet: ti: am65-cpsw: add mqprio qdisc offload in channel mode
Browse files Browse the repository at this point in the history
This patch adds MQPRIO Qdisc offload in full 'channel' mode which allows
not only setting up pri:tc mapping, but also configuring TX shapers
(rate-limiting) on external port FIFOs.

The MQPRIO Qdisc offload is expected to work with or without VLAN/priority
tagged packets.

The CPSW external Port FIFO has 8 Priority queues. The rate-limit can be
set for each of these priority queues. Which Priority queue a packet is
assigned to depends on PN_REG_TX_PRI_MAP register which maps header
priority to switch priority.

The header priority of a packet is assigned via the RX_PRI_MAP_REG which
maps packet priority to header priority.

The packet priority is either the VLAN priority (for VLAN tagged packets)
or the thread/channel offset.

For simplicity, we assign the same priority queue to all queues of a
Traffic Class so it can be rate-limited correctly.

Configuration example:
 ethtool -L eth1 tx 5
 ethtool --set-priv-flags eth1 p0-rx-ptype-rrobin off

 tc qdisc add dev eth1 parent root handle 100: mqprio num_tc 3 \
 map 0 0 1 2 0 0 0 0 0 0 0 0 0 0 0 0 \
 queues 1@0 1@1 1@2 hw 1 mode channel \
 shaper bw_rlimit min_rate 0 100mbit 200mbit max_rate 0 101mbit 202mbit

 tc qdisc replace dev eth2 handle 100: parent root mqprio num_tc 1 \
 map 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 hw 1

 ip link add link eth1 name eth1.100 type vlan id 100
 ip link set eth1.100 type vlan egress 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7

In the above example two ports share the same TX CPPI queue 0 for low
priority traffic. 3 traffic classes are defined for eth1 and mapped to:
TC0 - low priority, TX CPPI queue 0 -> ext Port 1 fifo0, no rate limit
TC1 - prio 2, TX CPPI queue 1 -> ext Port 1 fifo1, CIR=100Mbit/s, EIR=1Mbit/s
TC2 - prio 3, TX CPPI queue 2 -> ext Port 1 fifo2, CIR=200Mbit/s, EIR=2Mbit/s

Signed-off-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Roger Quadros <rogerq@kernel.org>
Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Grygorii Strashko authored and David S. Miller committed Dec 23, 2023
1 parent 8f5a756 commit bc8d62e
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 4 deletions.
3 changes: 2 additions & 1 deletion drivers/net/ethernet/ti/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ config TI_AM65_CPSW_QOS
depends on TI_K3_AM65_CPSW_NUSS && NET_SCH_TAPRIO && TI_K3_AM65_CPTS
help
This option enables QoS offload features in AM65 CPSW like
Time Aware Shaper (TAS) / Enhanced Scheduled Traffic (EST).
Time Aware Shaper (TAS) / Enhanced Scheduled Traffic (EST)
and MQPRIO qdisc offload.
The EST scheduler runs on CPTS and the TAS/EST schedule is
updated in the Fetch RAM memory of the CPSW.

Expand Down
3 changes: 3 additions & 0 deletions drivers/net/ethernet/ti/am65-cpsw-nuss.c
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,9 @@ static int am65_cpsw_nuss_init_slave_ports(struct am65_cpsw_common *common)
dev_err(dev, "Use random MAC address\n");
}
}

/* Reset all Queue priorities to 0 */
writel(0, port->port_base + AM65_CPSW_PN_REG_TX_PRI_MAP);
}
of_node_put(node);

Expand Down
256 changes: 253 additions & 3 deletions drivers/net/ethernet/ti/am65-cpsw-qos.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@
*/

#include <linux/pm_runtime.h>
#include <linux/math.h>
#include <linux/time.h>
#include <linux/units.h>
#include <net/pkt_cls.h>

#include "am65-cpsw-nuss.h"
#include "am65-cpsw-qos.h"
#include "am65-cpts.h"
#include "cpsw_ale.h"

#define TO_MBPS(x) DIV_ROUND_UP((x), BYTES_PER_MBIT)

enum timer_act {
TACT_PROG, /* need program timer */
TACT_NEED_STOP, /* need stop first */
Expand All @@ -31,6 +35,232 @@ am65_cpsw_qos_tx_rate_calc(u32 rate_mbps, unsigned long bus_freq)
return ir;
}

static void am65_cpsw_tx_pn_shaper_reset(struct am65_cpsw_port *port)
{
int prio;

for (prio = 0; prio < AM65_CPSW_PN_FIFO_PRIO_NUM; prio++) {
writel(0, port->port_base + AM65_CPSW_PN_REG_PRI_CIR(prio));
writel(0, port->port_base + AM65_CPSW_PN_REG_PRI_EIR(prio));
}
}

static void am65_cpsw_tx_pn_shaper_apply(struct am65_cpsw_port *port)
{
struct am65_cpsw_mqprio *p_mqprio = &port->qos.mqprio;
struct am65_cpsw_common *common = port->common;
struct tc_mqprio_qopt_offload *mqprio;
bool enable, shaper_susp = false;
u32 rate_mbps;
int tc, prio;

mqprio = &p_mqprio->mqprio_hw;
/* takes care of no link case as well */
if (p_mqprio->max_rate_total > port->qos.link_speed)
shaper_susp = true;

am65_cpsw_tx_pn_shaper_reset(port);

enable = p_mqprio->shaper_en && !shaper_susp;
if (!enable)
return;

/* Rate limit is specified per Traffic Class but
* for CPSW, rate limit can be applied per priority
* at port FIFO.
*
* We have assigned the same priority (TCn) to all queues
* of a Traffic Class so they share the same shaper
* bandwidth.
*/
for (tc = 0; tc < mqprio->qopt.num_tc; tc++) {
prio = tc;

rate_mbps = TO_MBPS(mqprio->min_rate[tc]);
rate_mbps = am65_cpsw_qos_tx_rate_calc(rate_mbps,
common->bus_freq);
writel(rate_mbps,
port->port_base + AM65_CPSW_PN_REG_PRI_CIR(prio));

rate_mbps = 0;

if (mqprio->max_rate[tc]) {
rate_mbps = mqprio->max_rate[tc] - mqprio->min_rate[tc];
rate_mbps = TO_MBPS(rate_mbps);
rate_mbps = am65_cpsw_qos_tx_rate_calc(rate_mbps,
common->bus_freq);
}

writel(rate_mbps,
port->port_base + AM65_CPSW_PN_REG_PRI_EIR(prio));
}
}

static int am65_cpsw_mqprio_verify_shaper(struct am65_cpsw_port *port,
struct tc_mqprio_qopt_offload *mqprio)
{
struct am65_cpsw_mqprio *p_mqprio = &port->qos.mqprio;
struct netlink_ext_ack *extack = mqprio->extack;
u64 min_rate_total = 0, max_rate_total = 0;
u32 min_rate_msk = 0, max_rate_msk = 0;
bool has_min_rate, has_max_rate;
int num_tc, i;

if (!(mqprio->flags & TC_MQPRIO_F_SHAPER))
return 0;

if (mqprio->shaper != TC_MQPRIO_SHAPER_BW_RATE)
return 0;

has_min_rate = !!(mqprio->flags & TC_MQPRIO_F_MIN_RATE);
has_max_rate = !!(mqprio->flags & TC_MQPRIO_F_MAX_RATE);

if (!has_min_rate && has_max_rate) {
NL_SET_ERR_MSG_MOD(extack, "min_rate is required with max_rate");
return -EOPNOTSUPP;
}

if (!has_min_rate)
return 0;

num_tc = mqprio->qopt.num_tc;

for (i = num_tc - 1; i >= 0; i--) {
u32 ch_msk;

if (mqprio->min_rate[i])
min_rate_msk |= BIT(i);
min_rate_total += mqprio->min_rate[i];

if (has_max_rate) {
if (mqprio->max_rate[i])
max_rate_msk |= BIT(i);
max_rate_total += mqprio->max_rate[i];

if (!mqprio->min_rate[i] && mqprio->max_rate[i]) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"TX tc%d rate max>0 but min=0",
i);
return -EINVAL;
}

if (mqprio->max_rate[i] &&
mqprio->max_rate[i] < mqprio->min_rate[i]) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"TX tc%d rate min(%llu)>max(%llu)",
i, mqprio->min_rate[i],
mqprio->max_rate[i]);
return -EINVAL;
}
}

ch_msk = GENMASK(num_tc - 1, i);
if ((min_rate_msk & BIT(i)) && (min_rate_msk ^ ch_msk)) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"Min rate must be set sequentially hi->lo tx_rate_msk%x",
min_rate_msk);
return -EINVAL;
}

if ((max_rate_msk & BIT(i)) && (max_rate_msk ^ ch_msk)) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"Max rate must be set sequentially hi->lo tx_rate_msk%x",
max_rate_msk);
return -EINVAL;
}
}

min_rate_total = TO_MBPS(min_rate_total);
max_rate_total = TO_MBPS(max_rate_total);

p_mqprio->shaper_en = true;
p_mqprio->max_rate_total = max_t(u64, min_rate_total, max_rate_total);

return 0;
}

static void am65_cpsw_reset_tc_mqprio(struct net_device *ndev)
{
struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
struct am65_cpsw_mqprio *p_mqprio = &port->qos.mqprio;

p_mqprio->shaper_en = false;
p_mqprio->max_rate_total = 0;

am65_cpsw_tx_pn_shaper_reset(port);
netdev_reset_tc(ndev);

/* Reset all Queue priorities to 0 */
writel(0, port->port_base + AM65_CPSW_PN_REG_TX_PRI_MAP);
}

static int am65_cpsw_setup_mqprio(struct net_device *ndev, void *type_data)
{
struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
struct am65_cpsw_mqprio *p_mqprio = &port->qos.mqprio;
struct tc_mqprio_qopt_offload *mqprio = type_data;
struct am65_cpsw_common *common = port->common;
struct tc_mqprio_qopt *qopt = &mqprio->qopt;
int i, tc, offset, count, prio, ret;
u8 num_tc = qopt->num_tc;
u32 tx_prio_map = 0;

memcpy(&p_mqprio->mqprio_hw, mqprio, sizeof(*mqprio));

ret = pm_runtime_get_sync(common->dev);
if (ret < 0) {
pm_runtime_put_noidle(common->dev);
return ret;
}

if (!num_tc) {
am65_cpsw_reset_tc_mqprio(ndev);
ret = 0;
goto exit_put;
}

ret = am65_cpsw_mqprio_verify_shaper(port, mqprio);
if (ret)
goto exit_put;

netdev_set_num_tc(ndev, num_tc);

/* Multiple Linux priorities can map to a Traffic Class
* A Traffic Class can have multiple contiguous Queues,
* Queues get mapped to Channels (thread_id),
* if not VLAN tagged, thread_id is used as packet_priority
* if VLAN tagged. VLAN priority is used as packet_priority
* packet_priority gets mapped to header_priority in p0_rx_pri_map,
* header_priority gets mapped to switch_priority in pn_tx_pri_map.
* As p0_rx_pri_map is left at defaults (0x76543210), we can
* assume that Queue_n gets mapped to header_priority_n. We can then
* set the switch priority in pn_tx_pri_map.
*/

for (tc = 0; tc < num_tc; tc++) {
prio = tc;

/* For simplicity we assign the same priority (TCn) to
* all queues of a Traffic Class.
*/
for (i = qopt->offset[tc]; i < qopt->offset[tc] + qopt->count[tc]; i++)
tx_prio_map |= prio << (4 * i);

count = qopt->count[tc];
offset = qopt->offset[tc];
netdev_set_tc_queue(ndev, tc, count, offset);
}

writel(tx_prio_map, port->port_base + AM65_CPSW_PN_REG_TX_PRI_MAP);

am65_cpsw_tx_pn_shaper_apply(port);

exit_put:
pm_runtime_put(common->dev);

return ret;
}

static int am65_cpsw_port_est_enabled(struct am65_cpsw_port *port)
{
return port->qos.est_oper || port->qos.est_admin;
Expand Down Expand Up @@ -414,6 +644,8 @@ static void am65_cpsw_taprio_destroy(struct net_device *ndev)

port->qos.est_oper = NULL;
port->qos.est_admin = NULL;

am65_cpsw_reset_tc_mqprio(ndev);
}

static void am65_cpsw_cp_taprio(struct tc_taprio_qopt_offload *from,
Expand Down Expand Up @@ -462,6 +694,10 @@ static int am65_cpsw_taprio_replace(struct net_device *ndev,
if (!est_new)
return -ENOMEM;

ret = am65_cpsw_setup_mqprio(ndev, &taprio->mqprio);
if (ret)
return ret;

am65_cpsw_cp_taprio(taprio, &est_new->taprio);

am65_cpsw_est_update_state(ndev);
Expand Down Expand Up @@ -505,6 +741,7 @@ static int am65_cpsw_taprio_replace(struct net_device *ndev,
return 0;

fail:
am65_cpsw_reset_tc_mqprio(ndev);
devm_kfree(&ndev->dev, est_new);
return ret;
}
Expand All @@ -515,7 +752,6 @@ static void am65_cpsw_est_link_up(struct net_device *ndev, int link_speed)
ktime_t cur_time;
s64 delta;

port->qos.link_speed = link_speed;
if (!am65_cpsw_port_est_enabled(port))
return;

Expand Down Expand Up @@ -559,6 +795,14 @@ static int am65_cpsw_tc_query_caps(struct net_device *ndev, void *type_data)
struct tc_query_caps_base *base = type_data;

switch (base->type) {
case TC_SETUP_QDISC_MQPRIO: {
struct tc_mqprio_caps *caps = base->caps;

caps->validate_queue_counts = true;

return 0;
}

case TC_SETUP_QDISC_TAPRIO: {
struct tc_taprio_caps *caps = base->caps;

Expand Down Expand Up @@ -857,6 +1101,8 @@ int am65_cpsw_qos_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
return am65_cpsw_tc_query_caps(ndev, type_data);
case TC_SETUP_QDISC_TAPRIO:
return am65_cpsw_setup_taprio(ndev, type_data);
case TC_SETUP_QDISC_MQPRIO:
return am65_cpsw_setup_mqprio(ndev, type_data);
case TC_SETUP_BLOCK:
return am65_cpsw_qos_setup_tc_block(ndev, type_data);
default:
Expand All @@ -868,6 +1114,9 @@ void am65_cpsw_qos_link_up(struct net_device *ndev, int link_speed)
{
struct am65_cpsw_port *port = am65_ndev_to_port(ndev);

port->qos.link_speed = link_speed;
am65_cpsw_tx_pn_shaper_apply(port);

am65_cpsw_est_link_up(ndev, link_speed);
port->qos.link_down_time = 0;
}
Expand All @@ -876,8 +1125,9 @@ void am65_cpsw_qos_link_down(struct net_device *ndev)
{
struct am65_cpsw_port *port = am65_ndev_to_port(ndev);

port->qos.link_speed = SPEED_UNKNOWN;
am65_cpsw_tx_pn_shaper_apply(port);

if (!port->qos.link_down_time)
port->qos.link_down_time = ktime_get();

port->qos.link_speed = SPEED_UNKNOWN;
}
Loading

0 comments on commit bc8d62e

Please sign in to comment.