Commit 9f481cea authored by David S. Miller's avatar David S. Miller

Merge branch 'ksz-dcb-dscp'

Oleksij Rempel says:

====================
add DCB and DSCP support for KSZ switches

This patch series is aimed at improving support for DCB (Data Center
Bridging) and DSCP (Differentiated Services Code Point) on KSZ switches.

The main goal is to introduce global DSCP and PCP (Priority Code Point)
mapping support, addressing the limitation of KSZ switches not having
per-port DSCP priority mapping. This involves extending the DSA
framework with new callbacks for managing trust settings for global DSCP
and PCP maps. Additionally, we introduce IEEE 802.1q helpers for default
configurations, benefiting other drivers too.

Change logs are in separate patches.

Compared to v6 this series includes some new patches for DSCP global
mapping support and QoS selftest script for KSZ9477 switches.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 09ca9940 cbc7afff
......@@ -4,6 +4,8 @@ menuconfig NET_DSA_MICROCHIP_KSZ_COMMON
depends on NET_DSA
select NET_DSA_TAG_KSZ
select NET_DSA_TAG_NONE
select NET_IEEE8021Q_HELPERS
select DCB
help
This driver adds support for Microchip KSZ9477 series switch and
KSZ8795/KSZ88x3 switch chips.
......
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_COMMON) += ksz_switch.o
ksz_switch-objs := ksz_common.o
ksz_switch-objs := ksz_common.o ksz_dcb.o
ksz_switch-objs += ksz9477.o ksz9477_acl.o ksz9477_tc_flower.o
ksz_switch-objs += ksz8795.o
ksz_switch-objs += lan937x_main.o
......
......@@ -58,5 +58,6 @@ void ksz8_phylink_mac_link_up(struct phylink_config *config,
struct phy_device *phydev, unsigned int mode,
phy_interface_t interface, int speed, int duplex,
bool tx_pause, bool rx_pause);
int ksz8_all_queues_split(struct ksz_device *dev, int queues);
#endif
......@@ -127,37 +127,71 @@ int ksz8_change_mtu(struct ksz_device *dev, int port, int mtu)
return -EOPNOTSUPP;
}
static void ksz8795_set_prio_queue(struct ksz_device *dev, int port, int queue)
static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
{
u8 hi, lo;
u8 mask_4q, mask_2q;
u8 reg_4q, reg_2q;
u8 data_4q = 0;
u8 data_2q = 0;
int ret;
/* Number of queues can only be 1, 2, or 4. */
switch (queue) {
case 4:
case 3:
queue = PORT_QUEUE_SPLIT_4;
break;
case 2:
queue = PORT_QUEUE_SPLIT_2;
break;
default:
queue = PORT_QUEUE_SPLIT_1;
if (ksz_is_ksz88x3(dev)) {
mask_4q = KSZ8873_PORT_4QUEUE_SPLIT_EN;
mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN;
reg_4q = REG_PORT_CTRL_0;
reg_2q = REG_PORT_CTRL_2;
/* KSZ8795 family switches have Weighted Fair Queueing (WFQ)
* enabled by default. Enable it for KSZ8873 family switches
* too. Default value for KSZ8873 family is strict priority,
* which should be enabled by using TC_SETUP_QDISC_ETS, not
* by default.
*/
ret = ksz_rmw8(dev, REG_SW_CTRL_3, WEIGHTED_FAIR_QUEUE_ENABLE,
WEIGHTED_FAIR_QUEUE_ENABLE);
if (ret)
return ret;
} else {
mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN;
mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
reg_4q = REG_PORT_CTRL_13;
reg_2q = REG_PORT_CTRL_0;
/* TODO: this is legacy from initial KSZ8795 driver, should be
* moved to appropriate place in the future.
*/
ret = ksz_rmw8(dev, REG_SW_CTRL_19,
SW_OUT_RATE_LIMIT_QUEUE_BASED,
SW_OUT_RATE_LIMIT_QUEUE_BASED);
if (ret)
return ret;
}
if (queues == 4)
data_4q = mask_4q;
else if (queues == 2)
data_2q = mask_2q;
ret = ksz_prmw8(dev, port, reg_4q, mask_4q, data_4q);
if (ret)
return ret;
return ksz_prmw8(dev, port, reg_2q, mask_2q, data_2q);
}
int ksz8_all_queues_split(struct ksz_device *dev, int queues)
{
struct dsa_switch *ds = dev->ds;
const struct dsa_port *dp;
dsa_switch_for_each_port(dp, ds) {
int ret = ksz8_port_queue_split(dev, dp->index, queues);
if (ret)
return ret;
}
ksz_pread8(dev, port, REG_PORT_CTRL_0, &lo);
ksz_pread8(dev, port, P_DROP_TAG_CTRL, &hi);
lo &= ~PORT_QUEUE_SPLIT_L;
if (queue & PORT_QUEUE_SPLIT_2)
lo |= PORT_QUEUE_SPLIT_L;
hi &= ~PORT_QUEUE_SPLIT_H;
if (queue & PORT_QUEUE_SPLIT_4)
hi |= PORT_QUEUE_SPLIT_H;
ksz_pwrite8(dev, port, REG_PORT_CTRL_0, lo);
ksz_pwrite8(dev, port, P_DROP_TAG_CTRL, hi);
/* Default is port based for egress rate limit. */
if (queue != PORT_QUEUE_SPLIT_1)
ksz_cfg(dev, REG_SW_CTRL_19, SW_OUT_RATE_LIMIT_QUEUE_BASED,
true);
return 0;
}
void ksz8_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt)
......@@ -1513,6 +1547,7 @@ void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
{
struct dsa_switch *ds = dev->ds;
const u32 *masks;
int queues;
u8 member;
masks = dev->info->masks;
......@@ -1520,19 +1555,20 @@ void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
/* enable broadcast storm limit */
ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true);
if (!ksz_is_ksz88x3(dev))
ksz8795_set_prio_queue(dev, port, 4);
/* For KSZ88x3 enable only one queue by default, otherwise we won't
* be able to get rid of PCP prios on Port 2.
*/
if (ksz_is_ksz88x3(dev))
queues = 1;
else
queues = dev->info->num_tx_queues;
/* disable DiffServ priority */
ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_ENABLE, false);
ksz8_port_queue_split(dev, port, queues);
/* replace priority */
ksz_port_cfg(dev, port, P_802_1P_CTRL,
masks[PORT_802_1P_REMAPPING], false);
/* enable 802.1p priority */
ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_ENABLE, true);
if (cpu_port)
member = dsa_user_ports(ds);
else
......
......@@ -124,7 +124,8 @@
#define PORT_BASED_PRIO_3 3
#define PORT_INSERT_TAG BIT(2)
#define PORT_REMOVE_TAG BIT(1)
#define PORT_QUEUE_SPLIT_L BIT(0)
#define KSZ8795_PORT_2QUEUE_SPLIT_EN BIT(0)
#define KSZ8873_PORT_4QUEUE_SPLIT_EN BIT(0)
#define REG_PORT_1_CTRL_1 0x11
#define REG_PORT_2_CTRL_1 0x21
......@@ -143,6 +144,7 @@
#define REG_PORT_4_CTRL_2 0x42
#define REG_PORT_5_CTRL_2 0x52
#define KSZ8873_PORT_2QUEUE_SPLIT_EN BIT(7)
#define PORT_INGRESS_FILTER BIT(6)
#define PORT_DISCARD_NON_VID BIT(5)
#define PORT_FORCE_FLOW_CTRL BIT(4)
......@@ -463,10 +465,7 @@
#define REG_PORT_4_CTRL_13 0xE1
#define REG_PORT_5_CTRL_13 0xF1
#define PORT_QUEUE_SPLIT_H BIT(1)
#define PORT_QUEUE_SPLIT_1 0
#define PORT_QUEUE_SPLIT_2 1
#define PORT_QUEUE_SPLIT_4 2
#define KSZ8795_PORT_4QUEUE_SPLIT_EN BIT(1)
#define PORT_DROP_TAG BIT(0)
#define REG_PORT_1_CTRL_14 0xB2
......
......@@ -1158,18 +1158,12 @@ void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port)
/* enable broadcast storm limit */
ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true);
/* disable DiffServ priority */
ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_PRIO_ENABLE, false);
/* replace priority */
ksz_port_cfg(dev, port, REG_PORT_MRI_MAC_CTRL, PORT_USER_PRIO_CEILING,
false);
ksz9477_port_cfg32(dev, port, REG_PORT_MTI_QUEUE_CTRL_0__4,
MTI_PVID_REPLACE, false);
/* enable 802.1p priority */
ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_PRIO_ENABLE, true);
/* force flow control for non-PHY ports only */
ksz_port_cfg(dev, port, REG_PORT_CTRL_0,
PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL,
......
......@@ -24,10 +24,12 @@
#include <linux/of_net.h>
#include <linux/micrel_phy.h>
#include <net/dsa.h>
#include <net/ieee8021q.h>
#include <net/pkt_cls.h>
#include <net/switchdev.h>
#include "ksz_common.h"
#include "ksz_dcb.h"
#include "ksz_ptp.h"
#include "ksz8.h"
#include "ksz9477.h"
......@@ -1225,8 +1227,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 3, /* total port count */
.port_nirqs = 3,
.num_tx_queues = 4,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &ksz9477_dev_ops,
.phylink_mac_ops = &ksz9477_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1255,6 +1257,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.cpu_ports = 0x10, /* can be configured as cpu port */
.port_cnt = 5, /* total cpu and user ports */
.num_tx_queues = 4,
.num_ipvs = 4,
.ops = &ksz8_dev_ops,
.phylink_mac_ops = &ksz8_phylink_mac_ops,
.ksz87xx_eee_link_erratum = true,
......@@ -1295,6 +1298,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.cpu_ports = 0x10, /* can be configured as cpu port */
.port_cnt = 5, /* total cpu and user ports */
.num_tx_queues = 4,
.num_ipvs = 4,
.ops = &ksz8_dev_ops,
.phylink_mac_ops = &ksz8_phylink_mac_ops,
.ksz87xx_eee_link_erratum = true,
......@@ -1321,6 +1325,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.cpu_ports = 0x10, /* can be configured as cpu port */
.port_cnt = 5, /* total cpu and user ports */
.num_tx_queues = 4,
.num_ipvs = 4,
.ops = &ksz8_dev_ops,
.phylink_mac_ops = &ksz8_phylink_mac_ops,
.ksz87xx_eee_link_erratum = true,
......@@ -1347,6 +1352,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.cpu_ports = 0x4, /* can be configured as cpu port */
.port_cnt = 3,
.num_tx_queues = 4,
.num_ipvs = 4,
.ops = &ksz8_dev_ops,
.phylink_mac_ops = &ksz8830_phylink_mac_ops,
.mib_names = ksz88xx_mib_names,
......@@ -1372,8 +1378,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 7, /* total physical port count */
.port_nirqs = 4,
.num_tx_queues = 4,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &ksz9477_dev_ops,
.phylink_mac_ops = &ksz9477_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1407,6 +1413,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 6, /* total physical port count */
.port_nirqs = 2,
.num_tx_queues = 4,
.num_ipvs = 8,
.ops = &ksz9477_dev_ops,
.phylink_mac_ops = &ksz9477_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1440,6 +1447,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 7, /* total physical port count */
.port_nirqs = 2,
.num_tx_queues = 4,
.num_ipvs = 8,
.ops = &ksz9477_dev_ops,
.phylink_mac_ops = &ksz9477_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1471,6 +1479,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 3, /* total port count */
.port_nirqs = 2,
.num_tx_queues = 4,
.num_ipvs = 8,
.ops = &ksz9477_dev_ops,
.phylink_mac_ops = &ksz9477_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1498,8 +1507,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 3, /* total port count */
.port_nirqs = 3,
.num_tx_queues = 4,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &ksz9477_dev_ops,
.phylink_mac_ops = &ksz9477_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1527,8 +1536,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 7, /* total port count */
.port_nirqs = 3,
.num_tx_queues = 4,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &ksz9477_dev_ops,
.phylink_mac_ops = &ksz9477_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1561,8 +1570,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 7, /* total physical port count */
.port_nirqs = 3,
.num_tx_queues = 4,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &ksz9477_dev_ops,
.mib_names = ksz9477_mib_names,
.mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
......@@ -1593,8 +1602,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 5, /* total physical port count */
.port_nirqs = 6,
.num_tx_queues = 8,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &lan937x_dev_ops,
.phylink_mac_ops = &lan937x_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1621,8 +1630,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 6, /* total physical port count */
.port_nirqs = 6,
.num_tx_queues = 8,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &lan937x_dev_ops,
.phylink_mac_ops = &lan937x_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1649,8 +1658,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 8, /* total physical port count */
.port_nirqs = 6,
.num_tx_queues = 8,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &lan937x_dev_ops,
.phylink_mac_ops = &lan937x_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1681,8 +1690,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 5, /* total physical port count */
.port_nirqs = 6,
.num_tx_queues = 8,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &lan937x_dev_ops,
.phylink_mac_ops = &lan937x_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -1713,8 +1722,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.port_cnt = 8, /* total physical port count */
.port_nirqs = 6,
.num_tx_queues = 8,
.num_ipvs = 8,
.tc_cbs_supported = true,
.tc_ets_supported = true,
.ops = &lan937x_dev_ops,
.phylink_mac_ops = &lan937x_phylink_mac_ops,
.mib_names = ksz9477_mib_names,
......@@ -2351,6 +2360,7 @@ static int ksz_setup(struct dsa_switch *ds)
ksz_init_mib_timer(dev);
ds->configure_vlan_while_not_filtering = false;
ds->dscp_prio_mapping_is_global = true;
if (dev->dev_ops->setup) {
ret = dev->dev_ops->setup(ds);
......@@ -2394,6 +2404,10 @@ static int ksz_setup(struct dsa_switch *ds)
goto out_ptp_clock_unregister;
}
ret = ksz_dcb_init(dev);
if (ret)
goto out_ptp_clock_unregister;
/* start switch */
regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL],
SW_START, SW_START);
......@@ -2708,9 +2722,33 @@ static int ksz_port_mdb_del(struct dsa_switch *ds, int port,
return dev->dev_ops->mdb_del(dev, port, mdb, db);
}
static int ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev,
int port)
{
u32 queue_map = 0;
int ipv;
for (ipv = 0; ipv < dev->info->num_ipvs; ipv++) {
int queue;
/* Traffic Type (TT) is corresponding to the Internal Priority
* Value (IPV) in the switch. Traffic Class (TC) is
* corresponding to the queue in the switch.
*/
queue = ieee8021q_tt_to_tc(ipv, dev->info->num_tx_queues);
if (queue < 0)
return queue;
queue_map |= queue << (ipv * KSZ9477_PORT_TC_MAP_S);
}
return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map);
}
static int ksz_port_setup(struct dsa_switch *ds, int port)
{
struct ksz_device *dev = ds->priv;
int ret;
if (!dsa_is_user_port(ds, port))
return 0;
......@@ -2718,11 +2756,17 @@ static int ksz_port_setup(struct dsa_switch *ds, int port)
/* setup user port */
dev->dev_ops->port_setup(dev, port, false);
if (!is_ksz8(dev)) {
ret = ksz9477_set_default_prio_queue_mapping(dev, port);
if (ret)
return ret;
}
/* port_stp_state_set() will be called after to enable the port so
* there is no need to do anything.
*/
return 0;
return ksz_dcb_init_port(dev, port);
}
void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
......@@ -3565,7 +3609,7 @@ static int ksz_tc_ets_add(struct ksz_device *dev, int port,
for (tc_prio = 0; tc_prio < ARRAY_SIZE(p->priomap); tc_prio++) {
int queue;
if (tc_prio > KSZ9477_MAX_TC_PRIO)
if (tc_prio >= dev->info->num_ipvs)
break;
queue = ksz_ets_band_to_queue(p, p->priomap[tc_prio]);
......@@ -3577,8 +3621,7 @@ static int ksz_tc_ets_add(struct ksz_device *dev, int port,
static int ksz_tc_ets_del(struct ksz_device *dev, int port)
{
int ret, queue, tc_prio, s;
u32 queue_map = 0;
int ret, queue;
/* To restore the default chip configuration, set all queues to use the
* WRR scheduler with a weight of 1.
......@@ -3590,31 +3633,10 @@ static int ksz_tc_ets_del(struct ksz_device *dev, int port)
return ret;
}
switch (dev->info->num_tx_queues) {
case 2:
s = 2;
break;
case 4:
s = 1;
break;
case 8:
s = 0;
break;
default:
return -EINVAL;
}
/* Revert the queue mapping for TC-priority to its default setting on
* the chip.
*/
for (tc_prio = 0; tc_prio <= KSZ9477_MAX_TC_PRIO; tc_prio++) {
int queue;
queue = tc_prio >> s;
queue_map |= queue << (tc_prio * KSZ9477_PORT_TC_MAP_S);
}
return ksz_pwrite32(dev, port, KSZ9477_PORT_MRI_TC_MAP__4, queue_map);
return ksz9477_set_default_prio_queue_mapping(dev, port);
}
static int ksz_tc_ets_validate(struct ksz_device *dev, int port,
......@@ -3659,7 +3681,7 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port,
struct ksz_device *dev = ds->priv;
int ret;
if (!dev->info->tc_ets_supported)
if (is_ksz8(dev))
return -EOPNOTSUPP;
if (qopt->parent != TC_H_ROOT) {
......@@ -3965,6 +3987,13 @@ static const struct dsa_switch_ops ksz_switch_ops = {
.port_setup_tc = ksz_setup_tc,
.get_mac_eee = ksz_get_mac_eee,
.set_mac_eee = ksz_set_mac_eee,
.port_get_default_prio = ksz_port_get_default_prio,
.port_set_default_prio = ksz_port_set_default_prio,
.port_get_dscp_prio = ksz_port_get_dscp_prio,
.port_add_dscp_prio = ksz_port_add_dscp_prio,
.port_del_dscp_prio = ksz_port_del_dscp_prio,
.port_get_apptrust = ksz_port_get_apptrust,
.port_set_apptrust = ksz_port_set_apptrust,
};
struct ksz_device *ksz_switch_alloc(struct device *base, void *priv)
......
......@@ -19,6 +19,9 @@
#include "ksz_ptp.h"
#define KSZ_MAX_NUM_PORTS 8
/* all KSZ switches count ports from 1 */
#define KSZ_PORT_1 0
#define KSZ_PORT_2 1
struct ksz_device;
struct ksz_port;
......@@ -59,8 +62,8 @@ struct ksz_chip_data {
int port_cnt;
u8 port_nirqs;
u8 num_tx_queues;
u8 num_ipvs; /* number of Internal Priority Values */
bool tc_cbs_supported;
bool tc_ets_supported;
const struct ksz_dev_ops *ops;
const struct phylink_mac_ops *phylink_mac_ops;
bool ksz87xx_eee_link_erratum;
......@@ -619,6 +622,11 @@ static inline bool ksz_is_ksz88x3(struct ksz_device *dev)
return dev->chip_id == KSZ8830_CHIP_ID;
}
static inline bool is_ksz8(struct ksz_device *dev)
{
return ksz_is_ksz87xx(dev) || ksz_is_ksz88x3(dev);
}
static inline int is_lan937x(struct ksz_device *dev)
{
return dev->chip_id == LAN9370_CHIP_ID ||
......@@ -721,7 +729,6 @@ static inline int is_lan937x(struct ksz_device *dev)
#define KSZ9477_PORT_MRI_TC_MAP__4 0x0808
#define KSZ9477_PORT_TC_MAP_S 4
#define KSZ9477_MAX_TC_PRIO 7
/* CBS related registers */
#define REG_PORT_MTI_QUEUE_INDEX__4 0x0900
......
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2024 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> */
#ifndef __KSZ_DCB_H
#define __KSZ_DCB_H
#include <net/dsa.h>
#include "ksz_common.h"
int ksz_port_get_default_prio(struct dsa_switch *ds, int port);
int ksz_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio);
int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp);
int ksz_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio);
int ksz_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio);
int ksz_port_set_apptrust(struct dsa_switch *ds, int port,
const unsigned char *sel,
int nsel);
int ksz_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel, int *nsel);
int ksz_dcb_init_port(struct ksz_device *dev, int port);
int ksz_dcb_init(struct ksz_device *dev);
#endif /* __KSZ_DCB_H */
......@@ -433,6 +433,11 @@ struct dsa_switch {
*/
u32 fdb_isolation:1;
/* Drivers that have global DSCP mapping settings must set this to
* true to automatically apply the settings to all ports.
*/
u32 dscp_prio_mapping_is_global:1;
/* Listener for switch fabric events */
struct notifier_block nb;
......@@ -586,6 +591,10 @@ static inline bool dsa_is_user_port(struct dsa_switch *ds, int p)
dsa_switch_for_each_port((_dp), (_ds)) \
if (dsa_port_is_user((_dp)))
#define dsa_switch_for_each_user_port_continue_reverse(_dp, _ds) \
dsa_switch_for_each_port_continue_reverse((_dp), (_ds)) \
if (dsa_port_is_user((_dp)))
#define dsa_switch_for_each_cpu_port(_dp, _ds) \
dsa_switch_for_each_port((_dp), (_ds)) \
if (dsa_port_is_cpu((_dp)))
......@@ -955,6 +964,10 @@ struct dsa_switch_ops {
u8 prio);
int (*port_del_dscp_prio)(struct dsa_switch *ds, int port, u8 dscp,
u8 prio);
int (*port_set_apptrust)(struct dsa_switch *ds, int port,
const u8 *sel, int nsel);
int (*port_get_apptrust)(struct dsa_switch *ds, int port, u8 *sel,
int *nsel);
/*
* Suspend and resume
......
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2024 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> */
#ifndef __DSCP_H__
#define __DSCP_H__
/*
* DSCP Pools and Codepoint Space Division:
*
* The Differentiated Services (Diffserv) architecture defines a method for
* classifying and managing network traffic using the DS field in IPv4 and IPv6
* packet headers. This field can carry one of 64 distinct DSCP (Differentiated
* Services Code Point) values, which are divided into three pools based on
* their Least Significant Bits (LSB) patterns and intended usage. Each pool has
* a specific registration procedure for assigning DSCP values:
*
* Pool 1 (Standards Action Pool):
* - Codepoint Space: xxxxx0
* This pool includes DSCP values ending in '0' (binary), allocated via
* Standards Action. It is intended for globally recognized traffic classes,
* ensuring interoperability across the internet. This pool encompasses
* well-known DSCP values such as CS0-CS7, AFxx, EF, and VOICE-ADMIT.
*
* Pool 2 (Experimental/Local Use Pool):
* - Codepoint Space: xxxx11
* Reserved for DSCP values ending in '11' (binary), this pool is designated
* for Experimental or Local Use. It allows for private or temporary traffic
* marking schemes not intended for standardized global use, facilitating
* testing and network-specific configurations without impacting
* interoperability.
*
* Pool 3 (Preferential Standardization Pool):
* - Codepoint Space: xxxx01
* Initially reserved for experimental or local use, this pool now serves as
* a secondary standardization resource should Pool 1 become exhausted. DSCP
* values ending in '01' (binary) are assigned via Standards Action, with a
* focus on adopting new, standardized traffic classes as the need arises.
*
* For pool updates see:
* https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml
*/
/* Pool 1: Standardized DSCP values as per [RFC8126] */
#define DSCP_CS0 0 /* 000000, [RFC2474] */
/* CS0 is some times called default (DF) */
#define DSCP_DF 0 /* 000000, [RFC2474] */
#define DSCP_CS1 8 /* 001000, [RFC2474] */
#define DSCP_CS2 16 /* 010000, [RFC2474] */
#define DSCP_CS3 24 /* 011000, [RFC2474] */
#define DSCP_CS4 32 /* 100000, [RFC2474] */
#define DSCP_CS5 40 /* 101000, [RFC2474] */
#define DSCP_CS6 48 /* 110000, [RFC2474] */
#define DSCP_CS7 56 /* 111000, [RFC2474] */
#define DSCP_AF11 10 /* 001010, [RFC2597] */
#define DSCP_AF12 12 /* 001100, [RFC2597] */
#define DSCP_AF13 14 /* 001110, [RFC2597] */
#define DSCP_AF21 18 /* 010010, [RFC2597] */
#define DSCP_AF22 20 /* 010100, [RFC2597] */
#define DSCP_AF23 22 /* 010110, [RFC2597] */
#define DSCP_AF31 26 /* 011010, [RFC2597] */
#define DSCP_AF32 28 /* 011100, [RFC2597] */
#define DSCP_AF33 30 /* 011110, [RFC2597] */
#define DSCP_AF41 34 /* 100010, [RFC2597] */
#define DSCP_AF42 36 /* 100100, [RFC2597] */
#define DSCP_AF43 38 /* 100110, [RFC2597] */
#define DSCP_EF 46 /* 101110, [RFC3246] */
#define DSCP_VOICE_ADMIT 44 /* 101100, [RFC5865] */
/* Pool 3: Standardized assignments, previously available for experimental/local
* use
*/
#define DSCP_LE 1 /* 000001, [RFC8622] */
#define DSCP_MAX 64
#endif /* __DSCP_H__ */
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2024 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> */
#ifndef _NET_IEEE8021Q_H
#define _NET_IEEE8021Q_H
#include <linux/errno.h>
/**
* enum ieee8021q_traffic_type - 802.1Q traffic type priority values (802.1Q-2022)
*
* @IEEE8021Q_TT_BK: Background
* @IEEE8021Q_TT_BE: Best Effort (default). According to 802.1Q-2022, BE is 0
* but has higher priority than BK which is 1.
* @IEEE8021Q_TT_EE: Excellent Effort
* @IEEE8021Q_TT_CA: Critical Applications
* @IEEE8021Q_TT_VI: Video, < 100 ms latency and jitter
* @IEEE8021Q_TT_VO: Voice, < 10 ms latency and jitter
* @IEEE8021Q_TT_IC: Internetwork Control
* @IEEE8021Q_TT_NC: Network Control
*/
enum ieee8021q_traffic_type {
IEEE8021Q_TT_BK = 0,
IEEE8021Q_TT_BE = 1,
IEEE8021Q_TT_EE = 2,
IEEE8021Q_TT_CA = 3,
IEEE8021Q_TT_VI = 4,
IEEE8021Q_TT_VO = 5,
IEEE8021Q_TT_IC = 6,
IEEE8021Q_TT_NC = 7,
/* private: */
IEEE8021Q_TT_MAX,
};
#define SIMPLE_IETF_DSCP_TO_IEEE8021Q_TT(dscp) ((dscp >> 3) & 0x7)
#if IS_ENABLED(CONFIG_NET_IEEE8021Q_HELPERS)
int ietf_dscp_to_ieee8021q_tt(u8 dscp);
int ieee8021q_tt_to_tc(enum ieee8021q_traffic_type tt, unsigned int num_queues);
#else
static inline int ietf_dscp_to_ieee8021q_tt(u8 dscp)
{
return -EOPNOTSUPP;
}
static inline int ieee8021q_tt_to_tc(enum ieee8021q_traffic_type tt,
unsigned int num_queues)
{
return -EOPNOTSUPP;
}
#endif
#endif /* _NET_IEEE8021Q_H */
......@@ -452,6 +452,9 @@ config GRO_CELLS
config SOCK_VALIDATE_XMIT
bool
config NET_IEEE8021Q_HELPERS
bool
config NET_SELFTESTS
def_tristate PHYLIB
depends on PHYLIB && INET
......
......@@ -26,6 +26,7 @@ obj-$(CONFIG_NETPOLL) += netpoll.o
obj-$(CONFIG_FIB_RULES) += fib_rules.o
obj-$(CONFIG_TRACEPOINTS) += net-traces.o
obj-$(CONFIG_NET_DROP_MONITOR) += drop_monitor.o
obj-$(CONFIG_NET_IEEE8021Q_HELPERS) += ieee8021q_helpers.o
obj-$(CONFIG_NET_SELFTESTS) += selftests.o
obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += timestamping.o
obj-$(CONFIG_NET_PTP_CLASSIFY) += ptp_classifier.o
......
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2024 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
#include <linux/array_size.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <net/dscp.h>
#include <net/ieee8021q.h>
/* The following arrays map Traffic Types (TT) to traffic classes (TC) for
* different number of queues as shown in the example provided by
* IEEE 802.1Q-2022 in Annex I "I.3 Traffic type to traffic class mapping" and
* Table I-1 "Traffic type to traffic class mapping".
*/
static const u8 ieee8021q_8queue_tt_tc_map[] = {
[IEEE8021Q_TT_BK] = 0,
[IEEE8021Q_TT_BE] = 1,
[IEEE8021Q_TT_EE] = 2,
[IEEE8021Q_TT_CA] = 3,
[IEEE8021Q_TT_VI] = 4,
[IEEE8021Q_TT_VO] = 5,
[IEEE8021Q_TT_IC] = 6,
[IEEE8021Q_TT_NC] = 7,
};
static const u8 ieee8021q_7queue_tt_tc_map[] = {
[IEEE8021Q_TT_BK] = 0,
[IEEE8021Q_TT_BE] = 1,
[IEEE8021Q_TT_EE] = 2,
[IEEE8021Q_TT_CA] = 3,
[IEEE8021Q_TT_VI] = 4, [IEEE8021Q_TT_VO] = 4,
[IEEE8021Q_TT_IC] = 5,
[IEEE8021Q_TT_NC] = 6,
};
static const u8 ieee8021q_6queue_tt_tc_map[] = {
[IEEE8021Q_TT_BK] = 0,
[IEEE8021Q_TT_BE] = 1,
[IEEE8021Q_TT_EE] = 2, [IEEE8021Q_TT_CA] = 2,
[IEEE8021Q_TT_VI] = 3, [IEEE8021Q_TT_VO] = 3,
[IEEE8021Q_TT_IC] = 4,
[IEEE8021Q_TT_NC] = 5,
};
static const u8 ieee8021q_5queue_tt_tc_map[] = {
[IEEE8021Q_TT_BK] = 0, [IEEE8021Q_TT_BE] = 0,
[IEEE8021Q_TT_EE] = 1, [IEEE8021Q_TT_CA] = 1,
[IEEE8021Q_TT_VI] = 2, [IEEE8021Q_TT_VO] = 2,
[IEEE8021Q_TT_IC] = 3,
[IEEE8021Q_TT_NC] = 4,
};
static const u8 ieee8021q_4queue_tt_tc_map[] = {
[IEEE8021Q_TT_BK] = 0, [IEEE8021Q_TT_BE] = 0,
[IEEE8021Q_TT_EE] = 1, [IEEE8021Q_TT_CA] = 1,
[IEEE8021Q_TT_VI] = 2, [IEEE8021Q_TT_VO] = 2,
[IEEE8021Q_TT_IC] = 3, [IEEE8021Q_TT_NC] = 3,
};
static const u8 ieee8021q_3queue_tt_tc_map[] = {
[IEEE8021Q_TT_BK] = 0, [IEEE8021Q_TT_BE] = 0,
[IEEE8021Q_TT_EE] = 0, [IEEE8021Q_TT_CA] = 0,
[IEEE8021Q_TT_VI] = 1, [IEEE8021Q_TT_VO] = 1,
[IEEE8021Q_TT_IC] = 2, [IEEE8021Q_TT_NC] = 2,
};
static const u8 ieee8021q_2queue_tt_tc_map[] = {
[IEEE8021Q_TT_BK] = 0, [IEEE8021Q_TT_BE] = 0,
[IEEE8021Q_TT_EE] = 0, [IEEE8021Q_TT_CA] = 0,
[IEEE8021Q_TT_VI] = 1, [IEEE8021Q_TT_VO] = 1,
[IEEE8021Q_TT_IC] = 1, [IEEE8021Q_TT_NC] = 1,
};
static const u8 ieee8021q_1queue_tt_tc_map[] = {
[IEEE8021Q_TT_BK] = 0, [IEEE8021Q_TT_BE] = 0,
[IEEE8021Q_TT_EE] = 0, [IEEE8021Q_TT_CA] = 0,
[IEEE8021Q_TT_VI] = 0, [IEEE8021Q_TT_VO] = 0,
[IEEE8021Q_TT_IC] = 0, [IEEE8021Q_TT_NC] = 0,
};
/**
* ieee8021q_tt_to_tc - Map IEEE 802.1Q Traffic Type to Traffic Class
* @tt: IEEE 802.1Q Traffic Type
* @num_queues: Number of queues
*
* This function maps an IEEE 802.1Q Traffic Type to a Traffic Class (TC) based
* on the number of queues configured on the NIC. The mapping is based on the
* example provided by IEEE 802.1Q-2022 in Annex I "I.3 Traffic type to traffic
* class mapping" and Table I-1 "Traffic type to traffic class mapping".
*
* Return: Traffic Class corresponding to the given Traffic Type or negative
* value in case of error.
*/
int ieee8021q_tt_to_tc(enum ieee8021q_traffic_type tt, unsigned int num_queues)
{
if (tt < 0 || tt >= IEEE8021Q_TT_MAX) {
pr_err("Requested Traffic Type (%d) is out of range (%d)\n", tt,
IEEE8021Q_TT_MAX);
return -EINVAL;
}
switch (num_queues) {
case 8:
compiletime_assert(ARRAY_SIZE(ieee8021q_8queue_tt_tc_map) !=
IEEE8021Q_TT_MAX - 1,
"ieee8021q_8queue_tt_tc_map != max - 1");
return ieee8021q_8queue_tt_tc_map[tt];
case 7:
compiletime_assert(ARRAY_SIZE(ieee8021q_7queue_tt_tc_map) !=
IEEE8021Q_TT_MAX - 1,
"ieee8021q_7queue_tt_tc_map != max - 1");
return ieee8021q_7queue_tt_tc_map[tt];
case 6:
compiletime_assert(ARRAY_SIZE(ieee8021q_6queue_tt_tc_map) !=
IEEE8021Q_TT_MAX - 1,
"ieee8021q_6queue_tt_tc_map != max - 1");
return ieee8021q_6queue_tt_tc_map[tt];
case 5:
compiletime_assert(ARRAY_SIZE(ieee8021q_5queue_tt_tc_map) !=
IEEE8021Q_TT_MAX - 1,
"ieee8021q_5queue_tt_tc_map != max - 1");
return ieee8021q_5queue_tt_tc_map[tt];
case 4:
compiletime_assert(ARRAY_SIZE(ieee8021q_4queue_tt_tc_map) !=
IEEE8021Q_TT_MAX - 1,
"ieee8021q_4queue_tt_tc_map != max - 1");
return ieee8021q_4queue_tt_tc_map[tt];
case 3:
compiletime_assert(ARRAY_SIZE(ieee8021q_3queue_tt_tc_map) !=
IEEE8021Q_TT_MAX - 1,
"ieee8021q_3queue_tt_tc_map != max - 1");
return ieee8021q_3queue_tt_tc_map[tt];
case 2:
compiletime_assert(ARRAY_SIZE(ieee8021q_2queue_tt_tc_map) !=
IEEE8021Q_TT_MAX - 1,
"ieee8021q_2queue_tt_tc_map != max - 1");
return ieee8021q_2queue_tt_tc_map[tt];
case 1:
compiletime_assert(ARRAY_SIZE(ieee8021q_1queue_tt_tc_map) !=
IEEE8021Q_TT_MAX - 1,
"ieee8021q_1queue_tt_tc_map != max - 1");
return ieee8021q_1queue_tt_tc_map[tt];
}
pr_err("Invalid number of queues %d\n", num_queues);
return -EINVAL;
}
EXPORT_SYMBOL_GPL(ieee8021q_tt_to_tc);
/**
* ietf_dscp_to_ieee8021q_tt - Map IETF DSCP to IEEE 802.1Q Traffic Type
* @dscp: IETF DSCP value
*
* This function maps an IETF DSCP value to an IEEE 802.1Q Traffic Type (TT).
* Since there is no corresponding mapping between DSCP and IEEE 802.1Q Traffic
* Type, this function is inspired by the RFC8325 documentation which describe
* the mapping between DSCP and 802.11 User Priority (UP) values.
*
* Return: IEEE 802.1Q Traffic Type corresponding to the given DSCP value
*/
int ietf_dscp_to_ieee8021q_tt(u8 dscp)
{
switch (dscp) {
case DSCP_CS0:
/* Comment from RFC8325:
* [RFC4594], Section 4.8, recommends High-Throughput Data be marked
* AF1x (that is, AF11, AF12, and AF13, according to the rules defined
* in [RFC2475]).
*
* By default (as described in Section 2.3), High-Throughput Data will
* map to UP 1 and, thus, to the Background Access Category (AC_BK),
* which is contrary to the intent expressed in [RFC4594].
* Unfortunately, there really is no corresponding fit for the High-
* Throughput Data service class within the constrained 4 Access
* Category [IEEE.802.11-2016] model. If the High-Throughput Data
* service class is assigned to the Best Effort Access Category (AC_BE),
* then it would contend with Low-Latency Data (while [RFC4594]
* recommends a distinction in servicing between these service classes)
* as well as with the default service class; alternatively, if it is
* assigned to the Background Access Category (AC_BK), then it would
* receive a less-then-best-effort service and contend with Low-Priority
* Data (as discussed in Section 4.2.10).
*
* As such, since there is no directly corresponding fit for the High-
* Throughout Data service class within the [IEEE.802.11-2016] model, it
* is generally RECOMMENDED to map High-Throughput Data to UP 0, thereby
* admitting it to the Best Effort Access Category (AC_BE).
*
* Note: The above text is from RFC8325 which is describing the mapping
* between DSCP and 802.11 User Priority (UP) values. The mapping
* between UP and IEEE 802.1Q Traffic Type is not defined in the RFC but
* the 802.11 AC_BK and AC_BE are closely related to the IEEE 802.1Q
* Traffic Types BE and BK.
*/
case DSCP_AF11:
case DSCP_AF12:
case DSCP_AF13:
return IEEE8021Q_TT_BE;
/* Comment from RFC8325:
* RFC3662 and RFC4594 both recommend Low-Priority Data be marked
* with DSCP CS1. The Low-Priority Data service class loosely
* corresponds to the [IEEE.802.11-2016] Background Access Category
*/
case DSCP_CS1:
return IEEE8021Q_TT_BK;
case DSCP_CS2:
case DSCP_AF21:
case DSCP_AF22:
case DSCP_AF23:
return IEEE8021Q_TT_EE;
case DSCP_CS3:
case DSCP_AF31:
case DSCP_AF32:
case DSCP_AF33:
return IEEE8021Q_TT_CA;
case DSCP_CS4:
case DSCP_AF41:
case DSCP_AF42:
case DSCP_AF43:
return IEEE8021Q_TT_VI;
case DSCP_CS5:
case DSCP_EF:
case DSCP_VOICE_ADMIT:
return IEEE8021Q_TT_VO;
case DSCP_CS6:
return IEEE8021Q_TT_IC;
case DSCP_CS7:
return IEEE8021Q_TT_NC;
}
return SIMPLE_IETF_DSCP_TO_IEEE8021Q_TT(dscp);
}
EXPORT_SYMBOL_GPL(ietf_dscp_to_ieee8021q_tt);
......@@ -2136,6 +2136,32 @@ int dsa_user_change_mtu(struct net_device *dev, int new_mtu)
return err;
}
static int __maybe_unused
dsa_user_dcbnl_set_apptrust(struct net_device *dev, u8 *sel, int nsel)
{
struct dsa_port *dp = dsa_user_to_port(dev);
struct dsa_switch *ds = dp->ds;
int port = dp->index;
if (!ds->ops->port_set_apptrust)
return -EOPNOTSUPP;
return ds->ops->port_set_apptrust(ds, port, sel, nsel);
}
static int __maybe_unused
dsa_user_dcbnl_get_apptrust(struct net_device *dev, u8 *sel, int *nsel)
{
struct dsa_port *dp = dsa_user_to_port(dev);
struct dsa_switch *ds = dp->ds;
int port = dp->index;
if (!ds->ops->port_get_apptrust)
return -EOPNOTSUPP;
return ds->ops->port_get_apptrust(ds, port, sel, nsel);
}
static int __maybe_unused
dsa_user_dcbnl_set_default_prio(struct net_device *dev, struct dcb_app *app)
{
......@@ -2163,6 +2189,58 @@ dsa_user_dcbnl_set_default_prio(struct net_device *dev, struct dcb_app *app)
return 0;
}
/* Update the DSCP prio entries on all user ports of the switch in case
* the switch supports global DSCP prio instead of per port DSCP prios.
*/
static int dsa_user_dcbnl_ieee_global_dscp_setdel(struct net_device *dev,
struct dcb_app *app, bool del)
{
int (*setdel)(struct net_device *dev, struct dcb_app *app);
struct dsa_port *dp = dsa_user_to_port(dev);
struct dsa_switch *ds = dp->ds;
struct dsa_port *other_dp;
int err, restore_err;
if (del)
setdel = dcb_ieee_delapp;
else
setdel = dcb_ieee_setapp;
dsa_switch_for_each_user_port(other_dp, ds) {
struct net_device *user = other_dp->user;
if (!user || user == dev)
continue;
err = setdel(user, app);
if (err)
goto err_try_to_restore;
}
return 0;
err_try_to_restore:
/* Revert logic to restore previous state of app entries */
if (!del)
setdel = dcb_ieee_delapp;
else
setdel = dcb_ieee_setapp;
dsa_switch_for_each_user_port_continue_reverse(other_dp, ds) {
struct net_device *user = other_dp->user;
if (!user || user == dev)
continue;
restore_err = setdel(user, app);
if (restore_err)
netdev_err(user, "Failed to restore DSCP prio entry configuration\n");
}
return err;
}
static int __maybe_unused
dsa_user_dcbnl_add_dscp_prio(struct net_device *dev, struct dcb_app *app)
{
......@@ -2194,6 +2272,17 @@ dsa_user_dcbnl_add_dscp_prio(struct net_device *dev, struct dcb_app *app)
return err;
}
if (!ds->dscp_prio_mapping_is_global)
return 0;
err = dsa_user_dcbnl_ieee_global_dscp_setdel(dev, app, false);
if (err) {
if (ds->ops->port_del_dscp_prio)
ds->ops->port_del_dscp_prio(ds, port, dscp, new_prio);
dcb_ieee_delapp(dev, app);
return err;
}
return 0;
}
......@@ -2264,6 +2353,18 @@ dsa_user_dcbnl_del_dscp_prio(struct net_device *dev, struct dcb_app *app)
return err;
}
if (!ds->dscp_prio_mapping_is_global)
return 0;
err = dsa_user_dcbnl_ieee_global_dscp_setdel(dev, app, true);
if (err) {
if (ds->ops->port_add_dscp_prio)
ds->ops->port_add_dscp_prio(ds, port, dscp,
app->priority);
dcb_ieee_setapp(dev, app);
return err;
}
return 0;
}
......@@ -2376,6 +2477,8 @@ static const struct ethtool_ops dsa_user_ethtool_ops = {
static const struct dcbnl_rtnl_ops __maybe_unused dsa_user_dcbnl_ops = {
.ieee_setapp = dsa_user_dcbnl_ieee_setapp,
.ieee_delapp = dsa_user_dcbnl_ieee_delapp,
.dcbnl_setapptrust = dsa_user_dcbnl_set_apptrust,
.dcbnl_getapptrust = dsa_user_dcbnl_get_apptrust,
};
static void dsa_user_get_stats64(struct net_device *dev,
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment