The cpsw holds separate mcast entires for vlan entries. At this moment
driver adds only not vlan mcast addresses, omitting vlan/mcast entries.
As result mcast for vlans doesn't work. It can be fixed by adding same
mcast entries for every created vlan, but this patchseries uses more
sophisticated way and allows to create mcast entries only for vlans
that really require it. Generic functions from this series can be
reused for fixing vlan and macvlan unicast.
Simple example of ALE table before and after this series, having same
mcast entries as for vlan 100 as for real device (reserved vlan 2),
and one mcast address only for vlan 100 - 01:1b:19:00:00:00.
<---- Before this patchset ---->
vlan , vid = 2, untag_force = 0x5, reg_mcast = 0x5, mem_list = 0x5
mcast, vid = 2, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
ucast, vid = 2, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
vlan , vid = 0, untag_force = 0x7, reg_mcast = 0x0, mem_list = 0x7
mcast, vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 2, addr = 01:00:5e:00:00:01, port_mask = 0x1
vlan , vid = 1, untag_force = 0x3, reg_mcast = 0x3, mem_list = 0x3
mcast, vid = 1, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
ucast, vid = 1, addr = 74:da:ea:47:7d:9c, persistant, port_num = 0x0
mcast, vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 1, addr = 01:00:5e:00:00:01, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 2, addr = 33:33:ff:47:7d:9d, port_mask = 0x1
mcast, vid = 2, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 2, addr = 33:33:00:01:00:03, port_mask = 0x1
mcast, vid = 1, addr = 33:33:ff:47:7d:9c, port_mask = 0x1
mcast, vid = 1, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 1, addr = 33:33:00:01:00:03, port_mask = 0x1
mcast, vid = 1, addr = 01:00:5e:00:00:fb, port_mask = 0x1
mcast, vid = 1, addr = 01:00:5e:00:00:fc, port_mask = 0x1
vlan , vid = 100, untag_force = 0x0, reg_mcast = 0x5, mem_list = 0x5
ucast, vid = 100, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
mcast, vid = 100, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
mcast, vid = 2, addr = 01:1b:19:00:00:00, port_mask = 0x1
^^^
Here mcast entry (ptpl2), has to be added only for vlan 100
but added for reserved vlan 2...that's not enough.
<---- After this patchset ---->
vlan , vid = 2, untag_force = 0x5, reg_mcast = 0x5, mem_list = 0x5
mcast, vid = 2, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
ucast, vid = 2, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
vlan , vid = 0, untag_force = 0x7, reg_mcast = 0x0, mem_list = 0x7
mcast, vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 2, addr = 01:00:5e:00:00:01, port_mask = 0x1
vlan , vid = 1, untag_force = 0x3, reg_mcast = 0x3, mem_list = 0x3
mcast, vid = 1, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
ucast, vid = 1, addr = 74:da:ea:47:7d:9c, persistant, port_num = 0x0
mcast, vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 1, addr = 01:00:5e:00:00:01, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 2, addr = 33:33:ff:47:7d:9d, port_mask = 0x1
mcast, vid = 1, addr = 33:33:ff:47:7d:9c, port_mask = 0x1
mcast, vid = 2, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 2, addr = 33:33:00:01:00:03, port_mask = 0x1
mcast, vid = 1, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 1, addr = 33:33:00:01:00:03, port_mask = 0x1
vlan , vid = 100, untag_force = 0x0, reg_mcast = 0x5, mem_list = 0x5
ucast, vid = 100, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
mcast, vid = 100, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
mcast, vid = 100, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 100, addr = 01:00:5e:00:00:01, port_mask = 0x1
mcast, vid = 100, addr = 33:33:ff:47:7d:9d, port_mask = 0x1
mcast, vid = 100, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 100, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 100, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 100, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 100, addr = 33:33:00:01:00:03, port_mask = 0x1
mcast, vid = 100, addr = 01:1b:19:00:00:00, port_mask = 0x1
^^^
Here mcast entry (ptpl2), is added only for vlan 100
as it should be.
Based on net-next/master
Ivan Khoronzhuk (4):
net: core: dev_addr_lists: add auxiliary func to handle reference
address updates
net: 8021q: vlan_core: allow use list of vlans for real device
net: ethernet: ti: cpsw: fix vlan mcast
net: ethernet: ti: cpsw: fix vlan configuration while down/up
drivers/net/ethernet/ti/cpsw.c | 189 +++++++++++++++++++++++++++------
include/linux/if_vlan.h | 10 ++
include/linux/netdevice.h | 10 ++
net/8021q/vlan_core.c | 27 +++++
net/core/dev_addr_lists.c | 97 +++++++++++++++++
5 files changed, 303 insertions(+), 30 deletions(-)
--
2.17.1
The vlan configuration is not restored after interface donw/up sequence
(if dual-emac - both interfaces). Tested on am572x EVM.
Steps to check:
~# ip link add link eth1 name eth1.100 type vlan id 100
~# ifconfig eth0 down
~# ifconfig eth1 down
This is TI sdk tool, dumping all ALE entries, see they are present
~# switch-config -d
~# ifconfig eth1 up
See entry for vid 100 is absent
~# switch-config -d
Try to remove vid and observe warning:
~# ip link del eth1.100
[ 739.526757] net eth1: removing vlanid 100 from vlan filter
[ 739.533322] failed to kill vid 0081/100 for device eth1
This patch fixes it, restoring only vlan ALE entries and all other
unicast/multicast entries are restored by system calling rx_mode ndo.
Signed-off-by: Ivan Khoronzhuk <[email protected]>
---
drivers/net/ethernet/ti/cpsw.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c
index 5967484619d4..0b28a90b62bb 100644
--- a/drivers/net/ethernet/ti/cpsw.c
+++ b/drivers/net/ethernet/ti/cpsw.c
@@ -565,6 +565,9 @@ static const struct cpsw_stats cpsw_gstrings_ch_stats[] = {
(func)(slave++, ##arg); \
} while (0)
+static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev,
+ __be16 proto, u16 vid);
+
static inline int cpsw_get_slave_port(u32 slave_num)
{
return slave_num + 1;
@@ -1955,9 +1958,23 @@ static void cpsw_mqprio_resume(struct cpsw_slave *slave, struct cpsw_priv *priv)
slave_write(slave, tx_prio_map, tx_prio_rg);
}
+static int cpsw_restore_vlans(struct net_device *vdev, int vid, void *arg)
+{
+ struct cpsw_priv *priv = arg;
+
+ if (!vdev)
+ return 0;
+
+ cpsw_ndo_vlan_rx_add_vid(priv->ndev, 0, vid);
+ return 0;
+}
+
/* restore resources after port reset */
static void cpsw_restore(struct cpsw_priv *priv)
{
+ /* restore vlan configurations */
+ vlan_for_each(priv->ndev, cpsw_restore_vlans, priv);
+
/* restore MQPRIO offload */
for_each_slave(priv, cpsw_mqprio_resume, priv);
--
2.17.1
At this moment, mcast addresses are added only for real device only
(reserved vlans for dual-emac mode), even if a mcast address was added
for some vlan only, thus ALE doesn't have corresponding vlan mcast
entries after vlan socket joined multicast group. So ALE drops vlan
frames with mcast addresses intended for vlans and potentially can
receive mcast frames for base ndev. That's not correct. So, fix it by
creating only vlan/mcast entries as requested. Patch doesn't use any
additional lists and is based on device mc address list and cpsw ALE
table entries.
Also, move device to allmulti state if no space for new mcast entries.
Signed-off-by: Ivan Khoronzhuk <[email protected]>
---
drivers/net/ethernet/ti/cpsw.c | 172 +++++++++++++++++++++++++++------
1 file changed, 142 insertions(+), 30 deletions(-)
diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c
index 226be2a56c1f..5967484619d4 100644
--- a/drivers/net/ethernet/ti/cpsw.c
+++ b/drivers/net/ethernet/ti/cpsw.c
@@ -570,21 +570,6 @@ static inline int cpsw_get_slave_port(u32 slave_num)
return slave_num + 1;
}
-static void cpsw_add_mcast(struct cpsw_priv *priv, const u8 *addr)
-{
- struct cpsw_common *cpsw = priv->cpsw;
-
- if (cpsw->data.dual_emac) {
- struct cpsw_slave *slave = cpsw->slaves + priv->emac_port;
-
- cpsw_ale_add_mcast(cpsw->ale, addr, ALE_PORT_HOST,
- ALE_VLAN, slave->port_vlan, 0);
- return;
- }
-
- cpsw_ale_add_mcast(cpsw->ale, addr, ALE_ALL_PORTS, 0, 0, 0);
-}
-
static void cpsw_set_promiscious(struct net_device *ndev, bool enable)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
@@ -660,29 +645,153 @@ static void cpsw_set_promiscious(struct net_device *ndev, bool enable)
}
}
-static int cpsw_add_mc_addr(struct net_device *ndev, const u8 *addr)
+struct addr_sync_ctx {
+ struct net_device *ndev;
+ const u8 *addr; /* address to be sync or unsync */
+ int keep_num; /* number of address instances to be kept */
+ int flush; /* flush flag */
+};
+
+/**
+ * cpsw_set_mc - adds multicast entry to the table if it's not added or deletes
+ * if it's not deleted
+ * @ndev: device to sync
+ * @addr: address to be added or deleted
+ * @vid: vlan id, if vid < 0 set/unset address for real device
+ * @add: add address if the flag is set or remove otherwise
+ */
+static int cpsw_set_mc(struct net_device *ndev, const u8 *addr,
+ int vid, int add)
{
struct cpsw_priv *priv = netdev_priv(ndev);
+ struct cpsw_common *cpsw = priv->cpsw;
+ int mask, flags, ret;
+
+ if (vid < 0) {
+ if (cpsw->data.dual_emac)
+ vid = cpsw->slaves[priv->emac_port].port_vlan;
+ else
+ vid = 0;
+ }
+
+ mask = cpsw->data.dual_emac ? ALE_PORT_HOST : ALE_ALL_PORTS;
+ flags = vid ? ALE_VLAN : 0;
+
+ if (add)
+ ret = cpsw_ale_add_mcast(cpsw->ale, addr, mask, flags, vid, 0);
+ else
+ ret = cpsw_ale_del_mcast(cpsw->ale, addr, 0, flags, vid);
+
+ return ret;
+}
+
+static int cpsw_update_vlan_mc(struct net_device *vdev, int vid, void *ctx)
+{
+ struct addr_sync_ctx *sync_ctx = ctx;
+ struct netdev_hw_addr *ha;
+ int found = 0, ret = 0;
+
+ if (!vdev || !(vdev->flags & IFF_UP))
+ return ret;
+
+ /* vlan address is relevant if it's sync_cnt != 0 */
+ netdev_for_each_mc_addr(ha, vdev) {
+ if (ether_addr_equal(ha->addr, sync_ctx->addr)) {
+ found = ha->sync_cnt;
+ break;
+ }
+ }
+
+ if (found)
+ sync_ctx->keep_num--;
+
+ if (sync_ctx->flush) {
+ if (!found)
+ cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 0);
+ return ret;
+ }
+
+ if (found)
+ ret = cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 1);
+
+ return ret;
+}
+
+static int cpsw_add_mc_addr(struct net_device *ndev, const u8 *addr, int num)
+{
+ struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
+ struct addr_sync_ctx sync_ctx;
+ int ret;
+
+ sync_ctx.keep_num = num;
+ sync_ctx.addr = addr;
+ sync_ctx.ndev = ndev;
+ sync_ctx.flush = 0;
+
+ ret = vlan_for_each(ndev, cpsw_update_vlan_mc, &sync_ctx);
+ if (sync_ctx.keep_num && !ret)
+ ret = cpsw_set_mc(ndev, addr, -1, 1);
+
+ /* table is overflowed, set ALLMULTI */
+ if (ret == -ENOMEM)
+ cpsw_ale_set_allmulti(cpsw->ale, IFF_ALLMULTI);
+
+ return ret;
+}
+
+static int cpsw_del_mc_addr(struct net_device *ndev, const u8 *addr, int num)
+{
+ struct addr_sync_ctx sync_ctx;
+
+ sync_ctx.keep_num = num;
+ sync_ctx.addr = addr;
+ sync_ctx.ndev = ndev;
+ sync_ctx.flush = 1;
+
+ vlan_for_each(ndev, cpsw_update_vlan_mc, &sync_ctx);
+ if (!sync_ctx.keep_num)
+ cpsw_set_mc(ndev, addr, -1, 0);
- cpsw_add_mcast(priv, addr);
return 0;
}
-static int cpsw_del_mc_addr(struct net_device *ndev, const u8 *addr)
+static int cpsw_purge_vlan_mc(struct net_device *vdev, int vid, void *ctx)
{
- struct cpsw_priv *priv = netdev_priv(ndev);
- struct cpsw_common *cpsw = priv->cpsw;
- int vid, flags;
+ struct addr_sync_ctx *sync_ctx = ctx;
+ struct netdev_hw_addr *ha;
+ int found = 0;
- if (cpsw->data.dual_emac) {
- vid = cpsw->slaves[priv->emac_port].port_vlan;
- flags = ALE_VLAN;
- } else {
- vid = 0;
- flags = 0;
+ if (!vdev || !(vdev->flags & IFF_UP))
+ return 0;
+
+ /* vlan address is relevant if it's sync_cnt != 0 */
+ netdev_for_each_mc_addr(ha, vdev) {
+ if (ether_addr_equal(ha->addr, sync_ctx->addr)) {
+ found = ha->sync_cnt;
+ break;
+ }
}
- cpsw_ale_del_mcast(cpsw->ale, addr, 0, flags, vid);
+ if (!found)
+ return 0;
+
+ sync_ctx->keep_num--;
+ cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 0);
+ return 0;
+}
+
+static int cpsw_purge_all_mc(struct net_device *ndev, const u8 *addr, int num)
+{
+ struct addr_sync_ctx sync_ctx;
+
+ sync_ctx.addr = addr;
+ sync_ctx.ndev = ndev;
+ sync_ctx.keep_num = num;
+
+ vlan_for_each(ndev, cpsw_purge_vlan_mc, &sync_ctx);
+ if (sync_ctx.keep_num)
+ cpsw_set_mc(ndev, addr, -1, 0);
+
return 0;
}
@@ -703,7 +812,9 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev)
/* Restore allmulti on vlans if necessary */
cpsw_ale_set_allmulti(cpsw->ale, ndev->flags & IFF_ALLMULTI);
- __dev_mc_sync(ndev, cpsw_add_mc_addr, cpsw_del_mc_addr);
+ /* add/remove mcast address either for real netdev or for vlan */
+ __hw_addr_ref_sync_dev(&ndev->mc, ndev, cpsw_add_mc_addr,
+ cpsw_del_mc_addr);
}
static void cpsw_intr_enable(struct cpsw_common *cpsw)
@@ -1963,7 +2074,7 @@ static int cpsw_ndo_stop(struct net_device *ndev)
struct cpsw_common *cpsw = priv->cpsw;
cpsw_info(priv, ifdown, "shutting down cpsw device\n");
- __dev_mc_unsync(priv->ndev, cpsw_del_mc_addr);
+ __hw_addr_ref_unsync_dev(&ndev->mc, ndev, cpsw_purge_all_mc);
netif_tx_stop_all_queues(priv->ndev);
netif_carrier_off(priv->ndev);
@@ -2414,6 +2525,7 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev,
HOST_PORT_NUM, ALE_VLAN, vid);
ret |= cpsw_ale_del_mcast(cpsw->ale, priv->ndev->broadcast,
0, ALE_VLAN, vid);
+ ret |= cpsw_ale_flush_multicast(cpsw->ale, 0, vid);
err:
pm_runtime_put(cpsw->dev);
return ret;
--
2.17.1
In order to avoid all table update, and only remove or add new
address, the auxiliary function exists, named __hw_addr_sync_dev().
It allows end driver do nothing when nothing changed and add/rm when
concrete address is firstly added or lastly removed. But it doesn't
include cases when an address of real device or vlan was reused by
other vlans or vlan/macval devices.
For handaling events when address was reused/unreused the patch adds
new auxiliary routine - __hw_addr_ref_sync_dev(). It allows to do
nothing when nothing was changed and do updates only for an address
being added/reused/deleted/unreused. Thus, clone address changes for
vlans can be mirrored in the table. The function is exclusive with
__hw_addr_sync_dev(). It's responsibility of the end driver to
identify address vlan device, if it needs so.
Signed-off-by: Ivan Khoronzhuk <[email protected]>
---
include/linux/netdevice.h | 10 ++++
net/core/dev_addr_lists.c | 97 +++++++++++++++++++++++++++++++++++++++
2 files changed, 107 insertions(+)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index dc1d9ed33b31..de95f96a6352 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -4048,6 +4048,16 @@ int __hw_addr_sync_dev(struct netdev_hw_addr_list *list,
int (*sync)(struct net_device *, const unsigned char *),
int (*unsync)(struct net_device *,
const unsigned char *));
+int __hw_addr_ref_sync_dev(struct netdev_hw_addr_list *list,
+ struct net_device *dev,
+ int (*sync)(struct net_device *,
+ const unsigned char *, int),
+ int (*unsync)(struct net_device *,
+ const unsigned char *, int));
+void __hw_addr_ref_unsync_dev(struct netdev_hw_addr_list *list,
+ struct net_device *dev,
+ int (*unsync)(struct net_device *,
+ const unsigned char *, int));
void __hw_addr_unsync_dev(struct netdev_hw_addr_list *list,
struct net_device *dev,
int (*unsync)(struct net_device *,
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index d884d8f5f0e5..1385d75fe5ea 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -277,6 +277,103 @@ int __hw_addr_sync_dev(struct netdev_hw_addr_list *list,
}
EXPORT_SYMBOL(__hw_addr_sync_dev);
+/**
+ * __hw_addr_ref_sync_dev - Synchronize device's multicast address list taking
+ * into account references
+ * @list: address list to synchronize
+ * @dev: device to sync
+ * @sync: function to call if address or reference on it should be added
+ * @unsync: function to call if address or some reference on it should removed
+ *
+ * This function is intended to be called from the ndo_set_rx_mode
+ * function of devices that require explicit address or references on it
+ * add/remove notifications. The unsync function may be NULL in which case
+ * the addresses or references on it requiring removal will simply be
+ * removed without any notification to the device. That is responsibility of
+ * the driver to identify and distribute address or references on it between
+ * internal address tables.
+ **/
+int __hw_addr_ref_sync_dev(struct netdev_hw_addr_list *list,
+ struct net_device *dev,
+ int (*sync)(struct net_device *,
+ const unsigned char *, int),
+ int (*unsync)(struct net_device *,
+ const unsigned char *, int))
+{
+ struct netdev_hw_addr *ha, *tmp;
+ int err, keep_sync;
+
+ /* first go through and flush out any unsynced/stale entries */
+ list_for_each_entry_safe(ha, tmp, &list->list, list) {
+ /* sync if address is not used */
+ if ((ha->sync_cnt << 1) <= ha->refcount)
+ continue;
+
+ /* if fails defer unsyncing address */
+ keep_sync = ha->refcount - ha->sync_cnt;
+ if (unsync && unsync(dev, ha->addr, keep_sync))
+ continue;
+
+ ha->refcount = (keep_sync << 1) + 1;
+ ha->sync_cnt = keep_sync;
+ __hw_addr_del_entry(list, ha, false, false);
+ }
+
+ /* go through and sync updated/new entries to the list */
+ list_for_each_entry_safe(ha, tmp, &list->list, list) {
+ /* sync if address added or reused */
+ if ((ha->sync_cnt << 1) >= ha->refcount)
+ continue;
+
+ keep_sync = ha->refcount - ha->sync_cnt;
+ err = sync(dev, ha->addr, keep_sync);
+ if (err)
+ return err;
+
+ ha->refcount = keep_sync << 1;
+ ha->sync_cnt = keep_sync;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(__hw_addr_ref_sync_dev);
+
+/**
+ * __hw_addr_ref_unsync_dev - Remove synchronized addresses and references on
+ * it from device
+ * @list: address list to remove synchronized addresses (references on it) from
+ * @dev: device to sync
+ * @unsync: function to call if address and references on it should be removed
+ *
+ * Remove all addresses that were added to the device by
+ * __hw_addr_ref_sync_dev(). This function is intended to be called from the
+ * ndo_stop or ndo_open functions on devices that require explicit address (or
+ * references on it) add/remove notifications. If the unsync function pointer
+ * is NULL then this function can be used to just reset the sync_cnt for the
+ * addresses in the list.
+ **/
+void __hw_addr_ref_unsync_dev(struct netdev_hw_addr_list *list,
+ struct net_device *dev,
+ int (*unsync)(struct net_device *,
+ const unsigned char *, int))
+{
+ struct netdev_hw_addr *ha, *tmp;
+
+ list_for_each_entry_safe(ha, tmp, &list->list, list) {
+ if (!ha->sync_cnt)
+ continue;
+
+ /* if fails defer unsyncing address */
+ if (unsync && unsync(dev, ha->addr, ha->sync_cnt))
+ continue;
+
+ ha->refcount -= ha->sync_cnt - 1;
+ ha->sync_cnt = 0;
+ __hw_addr_del_entry(list, ha, false, false);
+ }
+}
+EXPORT_SYMBOL(__hw_addr_ref_unsync_dev);
+
/**
* __hw_addr_unsync_dev - Remove synchronized addresses from device
* @list: address list to remove synchronized addresses from
--
2.17.1
It's redundancy for the drivers to hold the list of vlans when
absolutely the same list exists in vlan core. In most cases it's
needed only to traverse the vlan devices, their vids and sync some
settings with h/w, so add API to simplify this.
At least some of these drivers also can benefit:
grep "for_each.*vid" -r drivers/net/ethernet/
drivers/net/ethernet/hisilicon/hns3/hns3_enet.c:
drivers/net/ethernet/synopsys/dwc-xlgmac-hw.c:
drivers/net/ethernet/qlogic/qlge/qlge_main.c:
drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c:
drivers/net/ethernet/via/via-rhine.c:
drivers/net/ethernet/via/via-velocity.c:
drivers/net/ethernet/intel/igb/igb_main.c:
drivers/net/ethernet/intel/ice/ice_main.c:
drivers/net/ethernet/intel/e1000/e1000_main.c:
drivers/net/ethernet/intel/i40e/i40e_main.c:
drivers/net/ethernet/intel/e1000e/netdev.c:
drivers/net/ethernet/intel/igbvf/netdev.c:
drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c:
drivers/net/ethernet/intel/ixgb/ixgb_main.c:
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c:
drivers/net/ethernet/amd/xgbe/xgbe-dev.c:
drivers/net/ethernet/emulex/benet/be_main.c:
drivers/net/ethernet/neterion/vxge/vxge-main.c:
drivers/net/ethernet/adaptec/starfire.c:
drivers/net/ethernet/brocade/bna/bnad.c:
Signed-off-by: Ivan Khoronzhuk <[email protected]>
---
include/linux/if_vlan.h | 10 ++++++++++
net/8021q/vlan_core.c | 27 +++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h
index 83ea4df6ab81..4ae3993f7166 100644
--- a/include/linux/if_vlan.h
+++ b/include/linux/if_vlan.h
@@ -133,6 +133,9 @@ struct vlan_pcpu_stats {
extern struct net_device *__vlan_find_dev_deep_rcu(struct net_device *real_dev,
__be16 vlan_proto, u16 vlan_id);
+extern int vlan_for_each(struct net_device *dev,
+ int (*action)(struct net_device *dev, int vid,
+ void *arg), void *arg);
extern struct net_device *vlan_dev_real_dev(const struct net_device *dev);
extern u16 vlan_dev_vlan_id(const struct net_device *dev);
extern __be16 vlan_dev_vlan_proto(const struct net_device *dev);
@@ -236,6 +239,13 @@ __vlan_find_dev_deep_rcu(struct net_device *real_dev,
return NULL;
}
+static inline int
+vlan_for_each(struct net_device *dev,
+ int (*action)(struct net_device *dev, int vid, void *arg),
+ void *arg)
+{
+}
+
static inline struct net_device *vlan_dev_real_dev(const struct net_device *dev)
{
BUG();
diff --git a/net/8021q/vlan_core.c b/net/8021q/vlan_core.c
index 4f60e86f4b8d..6308b5427a66 100644
--- a/net/8021q/vlan_core.c
+++ b/net/8021q/vlan_core.c
@@ -223,6 +223,33 @@ static int vlan_kill_rx_filter_info(struct net_device *dev, __be16 proto, u16 vi
return -ENODEV;
}
+int vlan_for_each(struct net_device *dev,
+ int (*action)(struct net_device *dev, int vid, void *arg),
+ void *arg)
+{
+ struct vlan_vid_info *vid_info;
+ struct vlan_info *vlan_info;
+ struct net_device *vdev;
+ int ret;
+
+ ASSERT_RTNL();
+
+ vlan_info = rtnl_dereference(dev->vlan_info);
+ if (!vlan_info)
+ return 0;
+
+ list_for_each_entry(vid_info, &vlan_info->vid_list, list) {
+ vdev = vlan_group_get_device(&vlan_info->grp, vid_info->proto,
+ vid_info->vid);
+ ret = action(vdev, vid_info->vid, arg);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(vlan_for_each);
+
int vlan_filter_push_vids(struct vlan_info *vlan_info, __be16 proto)
{
struct net_device *real_dev = vlan_info->real_dev;
--
2.17.1
On 10/16/2018 01:20 PM, Ivan Khoronzhuk wrote:
> It's redundancy for the drivers to hold the list of vlans when
> absolutely the same list exists in vlan core. In most cases it's
> needed only to traverse the vlan devices, their vids and sync some
> settings with h/w, so add API to simplify this.
>
below has to be under ---.
> At least some of these drivers also can benefit:
> grep "for_each.*vid" -r drivers/net/ethernet/
>
> drivers/net/ethernet/hisilicon/hns3/hns3_enet.c:
> drivers/net/ethernet/synopsys/dwc-xlgmac-hw.c:
> drivers/net/ethernet/qlogic/qlge/qlge_main.c:
> drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c:
> drivers/net/ethernet/via/via-rhine.c:
> drivers/net/ethernet/via/via-velocity.c:
> drivers/net/ethernet/intel/igb/igb_main.c:
> drivers/net/ethernet/intel/ice/ice_main.c:
> drivers/net/ethernet/intel/e1000/e1000_main.c:
> drivers/net/ethernet/intel/i40e/i40e_main.c:
> drivers/net/ethernet/intel/e1000e/netdev.c:
> drivers/net/ethernet/intel/igbvf/netdev.c:
> drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c:
> drivers/net/ethernet/intel/ixgb/ixgb_main.c:
> drivers/net/ethernet/intel/ixgbe/ixgbe_main.c:
> drivers/net/ethernet/amd/xgbe/xgbe-dev.c:
> drivers/net/ethernet/emulex/benet/be_main.c:
> drivers/net/ethernet/neterion/vxge/vxge-main.c:
> drivers/net/ethernet/adaptec/starfire.c:
> drivers/net/ethernet/brocade/bna/bnad.c:
>
> Signed-off-by: Ivan Khoronzhuk <[email protected]>
> ---
> include/linux/if_vlan.h | 10 ++++++++++
> net/8021q/vlan_core.c | 27 +++++++++++++++++++++++++++
> 2 files changed, 37 insertions(+)
>
> diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h
> index 83ea4df6ab81..4ae3993f7166 100644
> --- a/include/linux/if_vlan.h
> +++ b/include/linux/if_vlan.h
> @@ -133,6 +133,9 @@ struct vlan_pcpu_stats {
>
> extern struct net_device *__vlan_find_dev_deep_rcu(struct net_device *real_dev,
> __be16 vlan_proto, u16 vlan_id);
> +extern int vlan_for_each(struct net_device *dev,
> + int (*action)(struct net_device *dev, int vid,
> + void *arg), void *arg);
> extern struct net_device *vlan_dev_real_dev(const struct net_device *dev);
> extern u16 vlan_dev_vlan_id(const struct net_device *dev);
> extern __be16 vlan_dev_vlan_proto(const struct net_device *dev);
> @@ -236,6 +239,13 @@ __vlan_find_dev_deep_rcu(struct net_device *real_dev,
> return NULL;
> }
>
> +static inline int
> +vlan_for_each(struct net_device *dev,
> + int (*action)(struct net_device *dev, int vid, void *arg),
> + void *arg)
> +{
> +}
> +
> static inline struct net_device *vlan_dev_real_dev(const struct net_device *dev)
> {
> BUG();
> diff --git a/net/8021q/vlan_core.c b/net/8021q/vlan_core.c
> index 4f60e86f4b8d..6308b5427a66 100644
> --- a/net/8021q/vlan_core.c
> +++ b/net/8021q/vlan_core.c
> @@ -223,6 +223,33 @@ static int vlan_kill_rx_filter_info(struct net_device *dev, __be16 proto, u16 vi
> return -ENODEV;
> }
>
> +int vlan_for_each(struct net_device *dev,
> + int (*action)(struct net_device *dev, int vid, void *arg),
> + void *arg)
> +{
> + struct vlan_vid_info *vid_info;
> + struct vlan_info *vlan_info;
> + struct net_device *vdev;
> + int ret;
> +
> + ASSERT_RTNL();
> +
> + vlan_info = rtnl_dereference(dev->vlan_info);
> + if (!vlan_info)
> + return 0;
> +
> + list_for_each_entry(vid_info, &vlan_info->vid_list, list) {
> + vdev = vlan_group_get_device(&vlan_info->grp, vid_info->proto,
> + vid_info->vid);
> + ret = action(vdev, vid_info->vid, arg);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(vlan_for_each);
> +
> int vlan_filter_push_vids(struct vlan_info *vlan_info, __be16 proto)
> {
> struct net_device *real_dev = vlan_info->real_dev;
>
--
regards,
-grygorii
On 10/16/2018 01:20 PM, Ivan Khoronzhuk wrote:
> The cpsw holds separate mcast entires for vlan entries. At this moment
> driver adds only not vlan mcast addresses, omitting vlan/mcast entries.
> As result mcast for vlans doesn't work. It can be fixed by adding same
> mcast entries for every created vlan, but this patchseries uses more
> sophisticated way and allows to create mcast entries only for vlans
> that really require it. Generic functions from this series can be
> reused for fixing vlan and macvlan unicast.
I assume this is first of all for dual_mac mode.
>
> Simple example of ALE table before and after this series, having same
> mcast entries as for vlan 100 as for real device (reserved vlan 2),
> and one mcast address only for vlan 100 - 01:1b:19:00:00:00.
>
[...]
> vlan , vid = 100, untag_force = 0x0, reg_mcast = 0x5, mem_list = 0x5
> ucast, vid = 100, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
> mcast, vid = 100, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
> mcast, vid = 100, addr = 33:33:00:00:00:01, port_mask = 0x1
> mcast, vid = 100, addr = 01:00:5e:00:00:01, port_mask = 0x1
> mcast, vid = 100, addr = 33:33:ff:47:7d:9d, port_mask = 0x1
> mcast, vid = 100, addr = 01:80:c2:00:00:00, port_mask = 0x1
> mcast, vid = 100, addr = 01:80:c2:00:00:03, port_mask = 0x1
> mcast, vid = 100, addr = 01:80:c2:00:00:0e, port_mask = 0x1
> mcast, vid = 100, addr = 33:33:00:00:00:fb, port_mask = 0x1
> mcast, vid = 100, addr = 33:33:00:01:00:03, port_mask = 0x1
> mcast, vid = 100, addr = 01:1b:19:00:00:00, port_mask = 0x1
> ^^^
> Here mcast entry (ptpl2), is added only for vlan 100
> as it should be.
>
> Based on net-next/master
Thank you.
For patches 2-4
Reviewed-by: Grygorii Strashko <[email protected]>
For patch 1 - I'm not sure, so it'd good to get more comments.
>
> Ivan Khoronzhuk (4):
> net: core: dev_addr_lists: add auxiliary func to handle reference
> address updates
> net: 8021q: vlan_core: allow use list of vlans for real device
> net: ethernet: ti: cpsw: fix vlan mcast
> net: ethernet: ti: cpsw: fix vlan configuration while down/up
>
> drivers/net/ethernet/ti/cpsw.c | 189 +++++++++++++++++++++++++++------
> include/linux/if_vlan.h | 10 ++
> include/linux/netdevice.h | 10 ++
> net/8021q/vlan_core.c | 27 +++++
> net/core/dev_addr_lists.c | 97 +++++++++++++++++
> 5 files changed, 303 insertions(+), 30 deletions(-)
>
--
regards,
-grygorii
From: Grygorii Strashko <[email protected]>
Date: Tue, 16 Oct 2018 14:39:43 -0500
>
>
> On 10/16/2018 01:20 PM, Ivan Khoronzhuk wrote:
>> It's redundancy for the drivers to hold the list of vlans when
>> absolutely the same list exists in vlan core. In most cases it's
>> needed only to traverse the vlan devices, their vids and sync some
>> settings with h/w, so add API to simplify this.
>>
>
> below has to be under ---.
I think it is good to docuement this in the commit message.
More information in the repository can never hurt.
On Tue, Oct 16, 2018 at 02:38:33PM -0500, Grygorii Strashko wrote:
>
>
>On 10/16/2018 01:20 PM, Ivan Khoronzhuk wrote:
>>The cpsw holds separate mcast entires for vlan entries. At this moment
>>driver adds only not vlan mcast addresses, omitting vlan/mcast entries.
>>As result mcast for vlans doesn't work. It can be fixed by adding same
>>mcast entries for every created vlan, but this patchseries uses more
>>sophisticated way and allows to create mcast entries only for vlans
>>that really require it. Generic functions from this series can be
>>reused for fixing vlan and macvlan unicast.
>
>I assume this is first of all for dual_mac mode.
Mainly yes, but affects on switch mode also (and single port) adding
appropriate mcast entries.
-- Regards,
Ivan Khoronzhuk
Ivan Khoronzhuk <[email protected]> writes:
> @@ -236,6 +239,13 @@ __vlan_find_dev_deep_rcu(struct net_device *real_dev,
> return NULL;
> }
>
> +static inline int
> +vlan_for_each(struct net_device *dev,
> + int (*action)(struct net_device *dev, int vid, void *arg),
> + void *arg)
> +{
> +}
> +
This stub should return 0, shouldn't it?
Bjørn
On Fri, Oct 19, 2018 at 01:22:20PM +0200, Bj?rn Mork wrote:
>Ivan Khoronzhuk <[email protected]> writes:
>
>> @@ -236,6 +239,13 @@ __vlan_find_dev_deep_rcu(struct net_device *real_dev,
>> return NULL;
>> }
>>
>> +static inline int
>> +vlan_for_each(struct net_device *dev,
>> + int (*action)(struct net_device *dev, int vid, void *arg),
>> + void *arg)
>> +{
>> +}
>> +
>
>
>This stub should return 0, shouldn't it?
yes, it has.
>
>
>Bj?rn
--
Regards,
Ivan Khoronzhuk
On Tue, Oct 16, 2018 at 09:20:34PM +0300, Ivan Khoronzhuk wrote:
>At this moment, mcast addresses are added only for real device only
>(reserved vlans for dual-emac mode), even if a mcast address was added
>for some vlan only, thus ALE doesn't have corresponding vlan mcast
>entries after vlan socket joined multicast group. So ALE drops vlan
>frames with mcast addresses intended for vlans and potentially can
>receive mcast frames for base ndev. That's not correct. So, fix it by
>creating only vlan/mcast entries as requested. Patch doesn't use any
>additional lists and is based on device mc address list and cpsw ALE
>table entries.
>
>Also, move device to allmulti state if no space for new mcast entries.
>
>Signed-off-by: Ivan Khoronzhuk <[email protected]>
I won't update allmulti state on v2, will do that separately as it
requires also splitting allmulti between interfaces in dual-mac mode.
Would be nice to move allmulti and promisc to ndo_change_rx_flags()
and do updates only if flag is changed, not for every address change.
Also there is an ability to set allmulti per vlan device and even
dev->allmulti counter can be used for that and it works, but in case
of allmulti flag there is no event from dev core if it's already set
(only allmuilti counter is changed w/o event). Thus no event to
segregate it between vlans. Adding update to dev core can lead to
more frequent rx_mode event and w/o reason for those devices who
doesn't care. I'm just wondering, why not to add smth like
IFF_VLAN_MCAST_FLT to dev->priv_flags and based on it generate
event differently or even do more stuff ....
Or leave it as is and set allmulti for every vlan, that seems
like a stub.
[...]
--
Regards,
Ivan Khoronzhuk