Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drivers: net: cpsw: Support ALLMULTI and fix IFF_PROMISC in switch mode

The cpsw driver did not support the IFF_ALLMULTI flag which makes dynamic
multicast routing not work. Related to this, when enabling IFF_PROMISC
in switch mode, all registered multicast addresses are flushed, resulting
in only broadcast and unicast traffic being received.

A new cpsw_ale_set_allmulti function now scans through the ALE entry
table and adds/removes the host port from the unregistered multicast
port mask of each vlan entry depending on the state of IFF_ALLMULTI.
In promiscious mode, cpsw_ale_set_allmulti is used to force reception
of all multicast traffic in addition to the unicast and broadcast traffic.

With this change dynamic multicast and promiscious mode both work in
switch mode.

Signed-off-by: Len Sorensen <lsorense@csclub.uwaterloo.ca>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Lennart Sorensen and committed by
David S. Miller
10738eea 35532c21

+49 -2
+18 -2
drivers/net/ethernet/ti/cpsw.c
··· 638 638 if (ndev->flags & IFF_PROMISC) { 639 639 /* Enable promiscuous mode */ 640 640 cpsw_set_promiscious(ndev, true); 641 + cpsw_ale_set_allmulti(priv->ale, IFF_ALLMULTI); 641 642 return; 642 643 } else { 643 644 /* Disable promiscuous mode */ 644 645 cpsw_set_promiscious(ndev, false); 645 646 } 647 + 648 + /* Restore allmulti on vlans if necessary */ 649 + cpsw_ale_set_allmulti(priv->ale, priv->ndev->flags & IFF_ALLMULTI); 646 650 647 651 /* Clear all mcast from ALE */ 648 652 cpsw_ale_flush_multicast(priv->ale, ALE_ALL_PORTS << priv->host_port); ··· 1153 1149 const int port = priv->host_port; 1154 1150 u32 reg; 1155 1151 int i; 1152 + int unreg_mcast_mask; 1156 1153 1157 1154 reg = (priv->version == CPSW_VERSION_1) ? CPSW1_PORT_VLAN : 1158 1155 CPSW2_PORT_VLAN; ··· 1163 1158 for (i = 0; i < priv->data.slaves; i++) 1164 1159 slave_write(priv->slaves + i, vlan, reg); 1165 1160 1161 + if (priv->ndev->flags & IFF_ALLMULTI) 1162 + unreg_mcast_mask = ALE_ALL_PORTS; 1163 + else 1164 + unreg_mcast_mask = ALE_PORT_1 | ALE_PORT_2; 1165 + 1166 1166 cpsw_ale_add_vlan(priv->ale, vlan, ALE_ALL_PORTS << port, 1167 1167 ALE_ALL_PORTS << port, ALE_ALL_PORTS << port, 1168 - (ALE_PORT_1 | ALE_PORT_2) << port); 1168 + unreg_mcast_mask << port); 1169 1169 } 1170 1170 1171 1171 static void cpsw_init_host_port(struct cpsw_priv *priv) ··· 1630 1620 unsigned short vid) 1631 1621 { 1632 1622 int ret; 1623 + int unreg_mcast_mask; 1624 + 1625 + if (priv->ndev->flags & IFF_ALLMULTI) 1626 + unreg_mcast_mask = ALE_ALL_PORTS; 1627 + else 1628 + unreg_mcast_mask = ALE_PORT_1 | ALE_PORT_2; 1633 1629 1634 1630 ret = cpsw_ale_add_vlan(priv->ale, vid, 1635 1631 ALE_ALL_PORTS << priv->host_port, 1636 1632 0, ALE_ALL_PORTS << priv->host_port, 1637 - (ALE_PORT_1 | ALE_PORT_2) << priv->host_port); 1633 + unreg_mcast_mask << priv->host_port); 1638 1634 if (ret != 0) 1639 1635 return ret; 1640 1636
+29
drivers/net/ethernet/ti/cpsw_ale.c
··· 443 443 return 0; 444 444 } 445 445 446 + void cpsw_ale_set_allmulti(struct cpsw_ale *ale, int allmulti) 447 + { 448 + u32 ale_entry[ALE_ENTRY_WORDS]; 449 + int type, idx; 450 + int unreg_mcast = 0; 451 + 452 + /* Only bother doing the work if the setting is actually changing */ 453 + if (ale->allmulti == allmulti) 454 + return; 455 + 456 + /* Remember the new setting to check against next time */ 457 + ale->allmulti = allmulti; 458 + 459 + for (idx = 0; idx < ale->params.ale_entries; idx++) { 460 + cpsw_ale_read(ale, idx, ale_entry); 461 + type = cpsw_ale_get_entry_type(ale_entry); 462 + if (type != ALE_TYPE_VLAN) 463 + continue; 464 + 465 + unreg_mcast = cpsw_ale_get_vlan_unreg_mcast(ale_entry); 466 + if (allmulti) 467 + unreg_mcast |= 1; 468 + else 469 + unreg_mcast &= ~1; 470 + cpsw_ale_set_vlan_unreg_mcast(ale_entry, unreg_mcast); 471 + cpsw_ale_write(ale, idx, ale_entry); 472 + } 473 + } 474 + 446 475 struct ale_control_info { 447 476 const char *name; 448 477 int offset, port_offset;
+2
drivers/net/ethernet/ti/cpsw_ale.h
··· 27 27 struct cpsw_ale_params params; 28 28 struct timer_list timer; 29 29 unsigned long ageout; 30 + int allmulti; 30 31 }; 31 32 32 33 enum cpsw_ale_control { ··· 104 103 int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port, int untag, 105 104 int reg_mcast, int unreg_mcast); 106 105 int cpsw_ale_del_vlan(struct cpsw_ale *ale, u16 vid, int port); 106 + void cpsw_ale_set_allmulti(struct cpsw_ale *ale, int allmulti); 107 107 108 108 int cpsw_ale_control_get(struct cpsw_ale *ale, int port, int control); 109 109 int cpsw_ale_control_set(struct cpsw_ale *ale, int port,