Merge branch 'net-dsa-hsr-enable-hsr-hw-offloading-for-ksz9477'
Lukasz Majewski says: ==================== net: dsa: hsr: Enable HSR HW offloading for KSZ9477 This patch series provides support for HSR HW offloading in KSZ9477 switch IC. To test this feature: ip link add name hsr0 type hsr slave1 lan1 slave2 lan2 supervision 45 version 1 ip link set dev lan1 up ip link set dev lan2 up ip a add 192.168.0.1/24 dev hsr0 ip link set dev hsr0 up To remove HSR network device: ip link del hsr0 To test if one can adjust MAC address: ip link set lan2 address 00:01:02:AA:BB:CC It is also possible to create another HSR interface, but it will only support HSR is software - e.g. ip link add name hsr1 type hsr slave1 lan3 slave2 lan4 supervision 45 version 1 Test HW: Two KSZ9477-EVB boards with HSR ports set to "Port1" and "Port2". Performance SW used: nuttcp -S --nofork nuttcp -vv -T 60 -r 192.168.0.2 nuttcp -vv -T 60 -t 192.168.0.2 Code: v6.6.0-rc2+ Linux net-next repository SHA1: 5a1b322cb0b7d0d33a2d13462294dc0f46911172 Tested HSR v0 and v1 Results: With KSZ9477 offloading support added: RX: 100 Mbps TX: 98 Mbps With no offloading RX: 63 Mbps TX: 63 Mbps ==================== Link: https://lore.kernel.org/r/20230922133108.2090612-1-lukma@denx.de Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
commit
1412e667ef
@ -323,13 +323,6 @@
|
||||
((addr) + REG_PORT_1_CTRL_0 + (port) * \
|
||||
(REG_PORT_2_CTRL_0 - REG_PORT_1_CTRL_0))
|
||||
|
||||
#define REG_SW_MAC_ADDR_0 0x68
|
||||
#define REG_SW_MAC_ADDR_1 0x69
|
||||
#define REG_SW_MAC_ADDR_2 0x6A
|
||||
#define REG_SW_MAC_ADDR_3 0x6B
|
||||
#define REG_SW_MAC_ADDR_4 0x6C
|
||||
#define REG_SW_MAC_ADDR_5 0x6D
|
||||
|
||||
#define TABLE_EXT_SELECT_S 5
|
||||
#define TABLE_EEE_V 1
|
||||
#define TABLE_ACL_V 2
|
||||
|
@ -1143,6 +1143,83 @@ int ksz9477_tc_cbs_set_cinc(struct ksz_device *dev, int port, u32 val)
|
||||
return ksz_pwrite16(dev, port, REG_PORT_MTI_CREDIT_INCREMENT, val);
|
||||
}
|
||||
|
||||
/* The KSZ9477 provides following HW features to accelerate
|
||||
* HSR frames handling:
|
||||
*
|
||||
* 1. TX PACKET DUPLICATION FROM HOST TO SWITCH
|
||||
* 2. RX PACKET DUPLICATION DISCARDING
|
||||
* 3. PREVENTING PACKET LOOP IN THE RING BY SELF-ADDRESS FILTERING
|
||||
*
|
||||
* Only one from point 1. has the NETIF_F* flag available.
|
||||
*
|
||||
* Ones from point 2 and 3 are "best effort" - i.e. those will
|
||||
* work correctly most of the time, but it may happen that some
|
||||
* frames will not be caught - to be more specific; there is a race
|
||||
* condition in hardware such that, when duplicate packets are received
|
||||
* on member ports very close in time to each other, the hardware fails
|
||||
* to detect that they are duplicates.
|
||||
*
|
||||
* Hence, the SW needs to handle those special cases. However, the speed
|
||||
* up gain is considerable when above features are used.
|
||||
*
|
||||
* Moreover, the NETIF_F_HW_HSR_FWD feature is also enabled, as HSR frames
|
||||
* can be forwarded in the switch fabric between HSR ports.
|
||||
*/
|
||||
#define KSZ9477_SUPPORTED_HSR_FEATURES (NETIF_F_HW_HSR_DUP | NETIF_F_HW_HSR_FWD)
|
||||
|
||||
void ksz9477_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr)
|
||||
{
|
||||
struct ksz_device *dev = ds->priv;
|
||||
struct net_device *slave;
|
||||
struct dsa_port *hsr_dp;
|
||||
u8 data, hsr_ports = 0;
|
||||
|
||||
/* Program which port(s) shall support HSR */
|
||||
ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), BIT(port));
|
||||
|
||||
/* Forward frames between HSR ports (i.e. bridge together HSR ports) */
|
||||
if (dev->hsr_ports) {
|
||||
dsa_hsr_foreach_port(hsr_dp, ds, hsr)
|
||||
hsr_ports |= BIT(hsr_dp->index);
|
||||
|
||||
hsr_ports |= BIT(dsa_upstream_port(ds, port));
|
||||
dsa_hsr_foreach_port(hsr_dp, ds, hsr)
|
||||
ksz9477_cfg_port_member(dev, hsr_dp->index, hsr_ports);
|
||||
}
|
||||
|
||||
if (!dev->hsr_ports) {
|
||||
/* Enable discarding of received HSR frames */
|
||||
ksz_read8(dev, REG_HSR_ALU_CTRL_0__1, &data);
|
||||
data |= HSR_DUPLICATE_DISCARD;
|
||||
data &= ~HSR_NODE_UNICAST;
|
||||
ksz_write8(dev, REG_HSR_ALU_CTRL_0__1, data);
|
||||
}
|
||||
|
||||
/* Enable per port self-address filtering.
|
||||
* The global self-address filtering has already been enabled in the
|
||||
* ksz9477_reset_switch() function.
|
||||
*/
|
||||
ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, true);
|
||||
|
||||
/* Setup HW supported features for lan HSR ports */
|
||||
slave = dsa_to_port(ds, port)->slave;
|
||||
slave->features |= KSZ9477_SUPPORTED_HSR_FEATURES;
|
||||
}
|
||||
|
||||
void ksz9477_hsr_leave(struct dsa_switch *ds, int port, struct net_device *hsr)
|
||||
{
|
||||
struct ksz_device *dev = ds->priv;
|
||||
|
||||
/* Clear port HSR support */
|
||||
ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), 0);
|
||||
|
||||
/* Disable forwarding frames between HSR ports */
|
||||
ksz9477_cfg_port_member(dev, port, BIT(dsa_upstream_port(ds, port)));
|
||||
|
||||
/* Disable per port self-address filtering */
|
||||
ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, false);
|
||||
}
|
||||
|
||||
int ksz9477_switch_init(struct ksz_device *dev)
|
||||
{
|
||||
u8 data8;
|
||||
|
@ -56,6 +56,8 @@ int ksz9477_reset_switch(struct ksz_device *dev);
|
||||
int ksz9477_switch_init(struct ksz_device *dev);
|
||||
void ksz9477_switch_exit(struct ksz_device *dev);
|
||||
void ksz9477_port_queue_split(struct ksz_device *dev, int port);
|
||||
void ksz9477_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr);
|
||||
void ksz9477_hsr_leave(struct dsa_switch *ds, int port, struct net_device *hsr);
|
||||
|
||||
int ksz9477_port_acl_init(struct ksz_device *dev, int port);
|
||||
void ksz9477_port_acl_free(struct ksz_device *dev, int port);
|
||||
|
@ -153,13 +153,6 @@
|
||||
#define SW_DOUBLE_TAG BIT(7)
|
||||
#define SW_RESET BIT(1)
|
||||
|
||||
#define REG_SW_MAC_ADDR_0 0x0302
|
||||
#define REG_SW_MAC_ADDR_1 0x0303
|
||||
#define REG_SW_MAC_ADDR_2 0x0304
|
||||
#define REG_SW_MAC_ADDR_3 0x0305
|
||||
#define REG_SW_MAC_ADDR_4 0x0306
|
||||
#define REG_SW_MAC_ADDR_5 0x0307
|
||||
|
||||
#define REG_SW_MTU__2 0x0308
|
||||
#define REG_SW_MTU_MASK GENMASK(13, 0)
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/if_bridge.h>
|
||||
#include <linux/if_vlan.h>
|
||||
#include <linux/if_hsr.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/of.h>
|
||||
@ -364,6 +365,7 @@ static const struct ksz_dev_ops lan937x_dev_ops = {
|
||||
};
|
||||
|
||||
static const u16 ksz8795_regs[] = {
|
||||
[REG_SW_MAC_ADDR] = 0x68,
|
||||
[REG_IND_CTRL_0] = 0x6E,
|
||||
[REG_IND_DATA_8] = 0x70,
|
||||
[REG_IND_DATA_CHECK] = 0x72,
|
||||
@ -492,6 +494,7 @@ static u8 ksz8863_shifts[] = {
|
||||
};
|
||||
|
||||
static const u16 ksz9477_regs[] = {
|
||||
[REG_SW_MAC_ADDR] = 0x0302,
|
||||
[P_STP_CTRL] = 0x0B04,
|
||||
[S_START_CTRL] = 0x0300,
|
||||
[S_BROADCAST_CTRL] = 0x0332,
|
||||
@ -3539,6 +3542,149 @@ static int ksz_setup_tc(struct dsa_switch *ds, int port,
|
||||
}
|
||||
}
|
||||
|
||||
static int ksz_port_set_mac_address(struct dsa_switch *ds, int port,
|
||||
const unsigned char *addr)
|
||||
{
|
||||
struct dsa_port *dp = dsa_to_port(ds, port);
|
||||
|
||||
if (dp->hsr_dev) {
|
||||
dev_err(ds->dev,
|
||||
"Cannot change MAC address on port %d with active HSR offload\n",
|
||||
port);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Program the switch's MAC address register with the MAC address of the
|
||||
* requesting user port. This single address is used by the switch for multiple
|
||||
* features, like HSR self-address filtering and WoL. Other user ports are
|
||||
* allowed to share ownership of this address as long as their MAC address is
|
||||
* the same. The user ports' MAC addresses must not change while they have
|
||||
* ownership of the switch MAC address.
|
||||
*/
|
||||
static int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct net_device *slave = dsa_to_port(ds, port)->slave;
|
||||
const unsigned char *addr = slave->dev_addr;
|
||||
struct ksz_switch_macaddr *switch_macaddr;
|
||||
struct ksz_device *dev = ds->priv;
|
||||
const u16 *regs = dev->info->regs;
|
||||
int i;
|
||||
|
||||
/* Make sure concurrent MAC address changes are blocked */
|
||||
ASSERT_RTNL();
|
||||
|
||||
switch_macaddr = dev->switch_macaddr;
|
||||
if (switch_macaddr) {
|
||||
if (!ether_addr_equal(switch_macaddr->addr, addr)) {
|
||||
NL_SET_ERR_MSG_FMT_MOD(extack,
|
||||
"Switch already configured for MAC address %pM",
|
||||
switch_macaddr->addr);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
refcount_inc(&switch_macaddr->refcount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch_macaddr = kzalloc(sizeof(*switch_macaddr), GFP_KERNEL);
|
||||
if (!switch_macaddr)
|
||||
return -ENOMEM;
|
||||
|
||||
ether_addr_copy(switch_macaddr->addr, addr);
|
||||
refcount_set(&switch_macaddr->refcount, 1);
|
||||
dev->switch_macaddr = switch_macaddr;
|
||||
|
||||
/* Program the switch MAC address to hardware */
|
||||
for (i = 0; i < ETH_ALEN; i++)
|
||||
ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, addr[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ksz_switch_macaddr_put(struct dsa_switch *ds)
|
||||
{
|
||||
struct ksz_switch_macaddr *switch_macaddr;
|
||||
struct ksz_device *dev = ds->priv;
|
||||
const u16 *regs = dev->info->regs;
|
||||
int i;
|
||||
|
||||
/* Make sure concurrent MAC address changes are blocked */
|
||||
ASSERT_RTNL();
|
||||
|
||||
switch_macaddr = dev->switch_macaddr;
|
||||
if (!refcount_dec_and_test(&switch_macaddr->refcount))
|
||||
return;
|
||||
|
||||
for (i = 0; i < ETH_ALEN; i++)
|
||||
ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, 0);
|
||||
|
||||
dev->switch_macaddr = NULL;
|
||||
kfree(switch_macaddr);
|
||||
}
|
||||
|
||||
static int ksz_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct ksz_device *dev = ds->priv;
|
||||
enum hsr_version ver;
|
||||
int ret;
|
||||
|
||||
ret = hsr_get_version(hsr, &ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (dev->chip_id != KSZ9477_CHIP_ID) {
|
||||
NL_SET_ERR_MSG_MOD(extack, "Chip does not support HSR offload");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* KSZ9477 can support HW offloading of only 1 HSR device */
|
||||
if (dev->hsr_dev && hsr != dev->hsr_dev) {
|
||||
NL_SET_ERR_MSG_MOD(extack, "Offload supported for a single HSR");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* KSZ9477 only supports HSR v0 and v1 */
|
||||
if (!(ver == HSR_V0 || ver == HSR_V1)) {
|
||||
NL_SET_ERR_MSG_MOD(extack, "Only HSR v0 and v1 supported");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* Self MAC address filtering, to avoid frames traversing
|
||||
* the HSR ring more than once.
|
||||
*/
|
||||
ret = ksz_switch_macaddr_get(ds, port, extack);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ksz9477_hsr_join(ds, port, hsr);
|
||||
dev->hsr_dev = hsr;
|
||||
dev->hsr_ports |= BIT(port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ksz_hsr_leave(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr)
|
||||
{
|
||||
struct ksz_device *dev = ds->priv;
|
||||
|
||||
WARN_ON(dev->chip_id != KSZ9477_CHIP_ID);
|
||||
|
||||
ksz9477_hsr_leave(ds, port, hsr);
|
||||
dev->hsr_ports &= ~BIT(port);
|
||||
if (!dev->hsr_ports)
|
||||
dev->hsr_dev = NULL;
|
||||
|
||||
ksz_switch_macaddr_put(ds);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dsa_switch_ops ksz_switch_ops = {
|
||||
.get_tag_protocol = ksz_get_tag_protocol,
|
||||
.connect_tag_protocol = ksz_connect_tag_protocol,
|
||||
@ -3558,6 +3704,9 @@ static const struct dsa_switch_ops ksz_switch_ops = {
|
||||
.get_sset_count = ksz_sset_count,
|
||||
.port_bridge_join = ksz_port_bridge_join,
|
||||
.port_bridge_leave = ksz_port_bridge_leave,
|
||||
.port_hsr_join = ksz_hsr_join,
|
||||
.port_hsr_leave = ksz_hsr_leave,
|
||||
.port_set_mac_address = ksz_port_set_mac_address,
|
||||
.port_stp_state_set = ksz_port_stp_state_set,
|
||||
.port_teardown = ksz_port_teardown,
|
||||
.port_pre_bridge_flags = ksz_port_pre_bridge_flags,
|
||||
|
@ -101,6 +101,11 @@ struct ksz_ptp_irq {
|
||||
int num;
|
||||
};
|
||||
|
||||
struct ksz_switch_macaddr {
|
||||
unsigned char addr[ETH_ALEN];
|
||||
refcount_t refcount;
|
||||
};
|
||||
|
||||
struct ksz_port {
|
||||
bool remove_tag; /* Remove Tag flag set, for ksz8795 only */
|
||||
bool learning;
|
||||
@ -170,6 +175,10 @@ struct ksz_device {
|
||||
struct mutex lock_irq; /* IRQ Access */
|
||||
struct ksz_irq girq;
|
||||
struct ksz_ptp_data ptp_data;
|
||||
|
||||
struct ksz_switch_macaddr *switch_macaddr;
|
||||
struct net_device *hsr_dev; /* HSR */
|
||||
u8 hsr_ports;
|
||||
};
|
||||
|
||||
/* List of supported models */
|
||||
@ -212,6 +221,7 @@ enum ksz_chip_id {
|
||||
};
|
||||
|
||||
enum ksz_regs {
|
||||
REG_SW_MAC_ADDR,
|
||||
REG_IND_CTRL_0,
|
||||
REG_IND_DATA_8,
|
||||
REG_IND_DATA_CHECK,
|
||||
|
@ -548,7 +548,8 @@ static void xrs700x_bridge_leave(struct dsa_switch *ds, int port,
|
||||
}
|
||||
|
||||
static int xrs700x_hsr_join(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr)
|
||||
struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
unsigned int val = XRS_HSR_CFG_HSR_PRP;
|
||||
struct dsa_port *partner = NULL, *dp;
|
||||
@ -562,16 +563,21 @@ static int xrs700x_hsr_join(struct dsa_switch *ds, int port,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Only ports 1 and 2 can be HSR/PRP redundant ports. */
|
||||
if (port != 1 && port != 2)
|
||||
if (port != 1 && port != 2) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Only ports 1 and 2 can offload HSR/PRP");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (ver == HSR_V1)
|
||||
if (ver == HSR_V1) {
|
||||
val |= XRS_HSR_CFG_HSR;
|
||||
else if (ver == PRP_V1)
|
||||
} else if (ver == PRP_V1) {
|
||||
val |= XRS_HSR_CFG_PRP;
|
||||
else
|
||||
} else {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Only HSR v1 and PRP v1 can be offloaded");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
dsa_hsr_foreach_port(dp, ds, hsr) {
|
||||
if (dp->index != port) {
|
||||
|
@ -969,6 +969,16 @@ struct dsa_switch_ops {
|
||||
struct phy_device *phy);
|
||||
void (*port_disable)(struct dsa_switch *ds, int port);
|
||||
|
||||
|
||||
/*
|
||||
* Notification for MAC address changes on user ports. Drivers can
|
||||
* currently only veto operations. They should not use the method to
|
||||
* program the hardware, since the operation is not rolled back in case
|
||||
* of other errors.
|
||||
*/
|
||||
int (*port_set_mac_address)(struct dsa_switch *ds, int port,
|
||||
const unsigned char *addr);
|
||||
|
||||
/*
|
||||
* Compatibility between device trees defining multiple CPU ports and
|
||||
* drivers which are not OK to use by default the numerically smallest
|
||||
@ -1198,7 +1208,8 @@ struct dsa_switch_ops {
|
||||
* HSR integration
|
||||
*/
|
||||
int (*port_hsr_join)(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr);
|
||||
struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack);
|
||||
int (*port_hsr_leave)(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr);
|
||||
|
||||
|
@ -2024,7 +2024,8 @@ void dsa_shared_port_link_unregister_of(struct dsa_port *dp)
|
||||
dsa_shared_port_setup_phy_of(dp, false);
|
||||
}
|
||||
|
||||
int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr)
|
||||
int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
int err;
|
||||
@ -2034,7 +2035,7 @@ int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr)
|
||||
|
||||
dp->hsr_dev = hsr;
|
||||
|
||||
err = ds->ops->port_hsr_join(ds, dp->index, hsr);
|
||||
err = ds->ops->port_hsr_join(ds, dp->index, hsr, extack);
|
||||
if (err)
|
||||
dp->hsr_dev = NULL;
|
||||
|
||||
|
@ -103,7 +103,8 @@ int dsa_port_phylink_create(struct dsa_port *dp);
|
||||
void dsa_port_phylink_destroy(struct dsa_port *dp);
|
||||
int dsa_shared_port_link_register_of(struct dsa_port *dp);
|
||||
void dsa_shared_port_link_unregister_of(struct dsa_port *dp);
|
||||
int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr);
|
||||
int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack);
|
||||
void dsa_port_hsr_leave(struct dsa_port *dp, struct net_device *hsr);
|
||||
int dsa_port_tag_8021q_vlan_add(struct dsa_port *dp, u16 vid, bool broadcast);
|
||||
void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid, bool broadcast);
|
||||
|
@ -457,6 +457,13 @@ static int dsa_slave_set_mac_address(struct net_device *dev, void *a)
|
||||
if (!is_valid_ether_addr(addr->sa_data))
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
if (ds->ops->port_set_mac_address) {
|
||||
err = ds->ops->port_set_mac_address(ds, dp->index,
|
||||
addr->sa_data);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* If the port is down, the address isn't synced yet to hardware or
|
||||
* to the DSA master, so there is nothing to change.
|
||||
*/
|
||||
@ -2862,7 +2869,7 @@ static int dsa_slave_changeupper(struct net_device *dev,
|
||||
}
|
||||
} else if (is_hsr_master(info->upper_dev)) {
|
||||
if (info->linking) {
|
||||
err = dsa_port_hsr_join(dp, info->upper_dev);
|
||||
err = dsa_port_hsr_join(dp, info->upper_dev, extack);
|
||||
if (err == -EOPNOTSUPP) {
|
||||
NL_SET_ERR_MSG_WEAK_MOD(extack,
|
||||
"Offloading not supported");
|
||||
|
@ -293,6 +293,14 @@ static struct sk_buff *ksz9477_xmit(struct sk_buff *skb,
|
||||
if (is_link_local_ether_addr(hdr->h_dest))
|
||||
val |= KSZ9477_TAIL_TAG_OVERRIDE;
|
||||
|
||||
if (dev->features & NETIF_F_HW_HSR_DUP) {
|
||||
struct net_device *hsr_dev = dp->hsr_dev;
|
||||
struct dsa_port *other_dp;
|
||||
|
||||
dsa_hsr_foreach_port(other_dp, dp->ds, hsr_dev)
|
||||
val |= BIT(other_dp->index);
|
||||
}
|
||||
|
||||
*tag = cpu_to_be16(val);
|
||||
|
||||
return ksz_defer_xmit(dp, skb);
|
||||
|
Loading…
x
Reference in New Issue
Block a user