mirror of
https://github.com/torvalds/linux.git
synced 2024-11-21 19:41:42 +00:00
net: mscc: ocelot: add VLAN filtering
Add hardware VLAN filtering offloading on ocelot. Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
dc96ee3730
commit
7142529f16
@ -148,12 +148,191 @@ static inline int ocelot_vlant_wait_for_completion(struct ocelot *ocelot)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ocelot_vlant_set_mask(struct ocelot *ocelot, u16 vid, u32 mask)
|
||||
{
|
||||
/* Select the VID to configure */
|
||||
ocelot_write(ocelot, ANA_TABLES_VLANTIDX_V_INDEX(vid),
|
||||
ANA_TABLES_VLANTIDX);
|
||||
/* Set the vlan port members mask and issue a write command */
|
||||
ocelot_write(ocelot, ANA_TABLES_VLANACCESS_VLAN_PORT_MASK(mask) |
|
||||
ANA_TABLES_VLANACCESS_CMD_WRITE,
|
||||
ANA_TABLES_VLANACCESS);
|
||||
|
||||
return ocelot_vlant_wait_for_completion(ocelot);
|
||||
}
|
||||
|
||||
static void ocelot_vlan_mode(struct ocelot_port *port,
|
||||
netdev_features_t features)
|
||||
{
|
||||
struct ocelot *ocelot = port->ocelot;
|
||||
u8 p = port->chip_port;
|
||||
u32 val;
|
||||
|
||||
/* Filtering */
|
||||
val = ocelot_read(ocelot, ANA_VLANMASK);
|
||||
if (features & NETIF_F_HW_VLAN_CTAG_FILTER)
|
||||
val |= BIT(p);
|
||||
else
|
||||
val &= ~BIT(p);
|
||||
ocelot_write(ocelot, val, ANA_VLANMASK);
|
||||
}
|
||||
|
||||
static void ocelot_vlan_port_apply(struct ocelot *ocelot,
|
||||
struct ocelot_port *port)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Ingress clasification (ANA_PORT_VLAN_CFG) */
|
||||
/* Default vlan to clasify for untagged frames (may be zero) */
|
||||
val = ANA_PORT_VLAN_CFG_VLAN_VID(port->pvid);
|
||||
if (port->vlan_aware)
|
||||
val |= ANA_PORT_VLAN_CFG_VLAN_AWARE_ENA |
|
||||
ANA_PORT_VLAN_CFG_VLAN_POP_CNT(1);
|
||||
|
||||
ocelot_rmw_gix(ocelot, val,
|
||||
ANA_PORT_VLAN_CFG_VLAN_VID_M |
|
||||
ANA_PORT_VLAN_CFG_VLAN_AWARE_ENA |
|
||||
ANA_PORT_VLAN_CFG_VLAN_POP_CNT_M,
|
||||
ANA_PORT_VLAN_CFG, port->chip_port);
|
||||
|
||||
/* Drop frames with multicast source address */
|
||||
val = ANA_PORT_DROP_CFG_DROP_MC_SMAC_ENA;
|
||||
if (port->vlan_aware && !port->vid)
|
||||
/* If port is vlan-aware and tagged, drop untagged and priority
|
||||
* tagged frames.
|
||||
*/
|
||||
val |= ANA_PORT_DROP_CFG_DROP_UNTAGGED_ENA |
|
||||
ANA_PORT_DROP_CFG_DROP_PRIO_S_TAGGED_ENA |
|
||||
ANA_PORT_DROP_CFG_DROP_PRIO_C_TAGGED_ENA;
|
||||
ocelot_write_gix(ocelot, val, ANA_PORT_DROP_CFG, port->chip_port);
|
||||
|
||||
/* Egress configuration (REW_TAG_CFG): VLAN tag type to 8021Q. */
|
||||
val = REW_TAG_CFG_TAG_TPID_CFG(0);
|
||||
|
||||
if (port->vlan_aware) {
|
||||
if (port->vid)
|
||||
/* Tag all frames except when VID == DEFAULT_VLAN */
|
||||
val |= REW_TAG_CFG_TAG_CFG(1);
|
||||
else
|
||||
/* Tag all frames */
|
||||
val |= REW_TAG_CFG_TAG_CFG(3);
|
||||
}
|
||||
ocelot_rmw_gix(ocelot, val,
|
||||
REW_TAG_CFG_TAG_TPID_CFG_M |
|
||||
REW_TAG_CFG_TAG_CFG_M,
|
||||
REW_TAG_CFG, port->chip_port);
|
||||
|
||||
/* Set default VLAN and tag type to 8021Q. */
|
||||
val = REW_PORT_VLAN_CFG_PORT_TPID(ETH_P_8021Q) |
|
||||
REW_PORT_VLAN_CFG_PORT_VID(port->vid);
|
||||
ocelot_rmw_gix(ocelot, val,
|
||||
REW_PORT_VLAN_CFG_PORT_TPID_M |
|
||||
REW_PORT_VLAN_CFG_PORT_VID_M,
|
||||
REW_PORT_VLAN_CFG, port->chip_port);
|
||||
}
|
||||
|
||||
static int ocelot_vlan_vid_add(struct net_device *dev, u16 vid, bool pvid,
|
||||
bool untagged)
|
||||
{
|
||||
struct ocelot_port *port = netdev_priv(dev);
|
||||
struct ocelot *ocelot = port->ocelot;
|
||||
int ret;
|
||||
|
||||
/* Add the port MAC address to with the right VLAN information */
|
||||
ocelot_mact_learn(ocelot, PGID_CPU, dev->dev_addr, vid,
|
||||
ENTRYTYPE_LOCKED);
|
||||
|
||||
/* Make the port a member of the VLAN */
|
||||
ocelot->vlan_mask[vid] |= BIT(port->chip_port);
|
||||
ret = ocelot_vlant_set_mask(ocelot, vid, ocelot->vlan_mask[vid]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Default ingress vlan classification */
|
||||
if (pvid)
|
||||
port->pvid = vid;
|
||||
|
||||
/* Untagged egress vlan clasification */
|
||||
if (untagged)
|
||||
port->vid = vid;
|
||||
|
||||
ocelot_vlan_port_apply(ocelot, port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ocelot_vlan_vid_del(struct net_device *dev, u16 vid)
|
||||
{
|
||||
struct ocelot_port *port = netdev_priv(dev);
|
||||
struct ocelot *ocelot = port->ocelot;
|
||||
int ret;
|
||||
|
||||
/* 8021q removes VID 0 on module unload for all interfaces
|
||||
* with VLAN filtering feature. We need to keep it to receive
|
||||
* untagged traffic.
|
||||
*/
|
||||
if (vid == 0)
|
||||
return 0;
|
||||
|
||||
/* Del the port MAC address to with the right VLAN information */
|
||||
ocelot_mact_forget(ocelot, dev->dev_addr, vid);
|
||||
|
||||
/* Stop the port from being a member of the vlan */
|
||||
ocelot->vlan_mask[vid] &= ~BIT(port->chip_port);
|
||||
ret = ocelot_vlant_set_mask(ocelot, vid, ocelot->vlan_mask[vid]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Ingress */
|
||||
if (port->pvid == vid)
|
||||
port->pvid = 0;
|
||||
|
||||
/* Egress */
|
||||
if (port->vid == vid)
|
||||
port->vid = 0;
|
||||
|
||||
ocelot_vlan_port_apply(ocelot, port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ocelot_vlan_init(struct ocelot *ocelot)
|
||||
{
|
||||
u16 port, vid;
|
||||
|
||||
/* Clear VLAN table, by default all ports are members of all VLANs */
|
||||
ocelot_write(ocelot, ANA_TABLES_VLANACCESS_CMD_INIT,
|
||||
ANA_TABLES_VLANACCESS);
|
||||
ocelot_vlant_wait_for_completion(ocelot);
|
||||
|
||||
/* Configure the port VLAN memberships */
|
||||
for (vid = 1; vid < VLAN_N_VID; vid++) {
|
||||
ocelot->vlan_mask[vid] = 0;
|
||||
ocelot_vlant_set_mask(ocelot, vid, ocelot->vlan_mask[vid]);
|
||||
}
|
||||
|
||||
/* Because VLAN filtering is enabled, we need VID 0 to get untagged
|
||||
* traffic. It is added automatically if 8021q module is loaded, but
|
||||
* we can't rely on it since module may be not loaded.
|
||||
*/
|
||||
ocelot->vlan_mask[0] = GENMASK(ocelot->num_phys_ports - 1, 0);
|
||||
ocelot_vlant_set_mask(ocelot, 0, ocelot->vlan_mask[0]);
|
||||
|
||||
/* Configure the CPU port to be VLAN aware */
|
||||
ocelot_write_gix(ocelot, ANA_PORT_VLAN_CFG_VLAN_VID(0) |
|
||||
ANA_PORT_VLAN_CFG_VLAN_AWARE_ENA |
|
||||
ANA_PORT_VLAN_CFG_VLAN_POP_CNT(1),
|
||||
ANA_PORT_VLAN_CFG, ocelot->num_phys_ports);
|
||||
|
||||
/* Set vlan ingress filter mask to all ports but the CPU port by
|
||||
* default.
|
||||
*/
|
||||
ocelot_write(ocelot, GENMASK(9, 0), ANA_VLANMASK);
|
||||
|
||||
for (port = 0; port < ocelot->num_phys_ports; port++) {
|
||||
ocelot_write_gix(ocelot, 0, REW_PORT_VLAN_CFG, port);
|
||||
ocelot_write_gix(ocelot, 0, REW_TAG_CFG, port);
|
||||
}
|
||||
}
|
||||
|
||||
/* Watermark encode
|
||||
@ -539,6 +718,20 @@ static int ocelot_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
|
||||
struct ocelot_port *port = netdev_priv(dev);
|
||||
struct ocelot *ocelot = port->ocelot;
|
||||
|
||||
if (!vid) {
|
||||
if (!port->vlan_aware)
|
||||
/* If the bridge is not VLAN aware and no VID was
|
||||
* provided, set it to pvid to ensure the MAC entry
|
||||
* matches incoming untagged packets
|
||||
*/
|
||||
vid = port->pvid;
|
||||
else
|
||||
/* If the bridge is VLAN aware a VID must be provided as
|
||||
* otherwise the learnt entry wouldn't match any frame.
|
||||
*/
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ocelot_mact_learn(ocelot, port->chip_port, addr, vid,
|
||||
ENTRYTYPE_NORMAL);
|
||||
}
|
||||
@ -690,6 +883,30 @@ end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ocelot_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
|
||||
u16 vid)
|
||||
{
|
||||
return ocelot_vlan_vid_add(dev, vid, false, true);
|
||||
}
|
||||
|
||||
static int ocelot_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
|
||||
u16 vid)
|
||||
{
|
||||
return ocelot_vlan_vid_del(dev, vid);
|
||||
}
|
||||
|
||||
static int ocelot_set_features(struct net_device *dev,
|
||||
netdev_features_t features)
|
||||
{
|
||||
struct ocelot_port *port = netdev_priv(dev);
|
||||
netdev_features_t changed = dev->features ^ features;
|
||||
|
||||
if (changed & NETIF_F_HW_VLAN_CTAG_FILTER)
|
||||
ocelot_vlan_mode(port, features);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct net_device_ops ocelot_port_netdev_ops = {
|
||||
.ndo_open = ocelot_port_open,
|
||||
.ndo_stop = ocelot_port_stop,
|
||||
@ -701,6 +918,9 @@ static const struct net_device_ops ocelot_port_netdev_ops = {
|
||||
.ndo_fdb_add = ocelot_fdb_add,
|
||||
.ndo_fdb_del = ocelot_fdb_del,
|
||||
.ndo_fdb_dump = ocelot_fdb_dump,
|
||||
.ndo_vlan_rx_add_vid = ocelot_vlan_rx_add_vid,
|
||||
.ndo_vlan_rx_kill_vid = ocelot_vlan_rx_kill_vid,
|
||||
.ndo_set_features = ocelot_set_features,
|
||||
};
|
||||
|
||||
static void ocelot_get_strings(struct net_device *netdev, u32 sset, u8 *data)
|
||||
@ -916,6 +1136,10 @@ static int ocelot_port_attr_set(struct net_device *dev,
|
||||
case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
|
||||
ocelot_port_attr_ageing_set(ocelot_port, attr->u.ageing_time);
|
||||
break;
|
||||
case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
|
||||
ocelot_port->vlan_aware = attr->u.vlan_filtering;
|
||||
ocelot_vlan_port_apply(ocelot_port->ocelot, ocelot_port);
|
||||
break;
|
||||
case SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED:
|
||||
ocelot_port_attr_mc_set(ocelot_port, !attr->u.mc_disabled);
|
||||
break;
|
||||
@ -927,6 +1151,40 @@ static int ocelot_port_attr_set(struct net_device *dev,
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ocelot_port_obj_add_vlan(struct net_device *dev,
|
||||
const struct switchdev_obj_port_vlan *vlan,
|
||||
struct switchdev_trans *trans)
|
||||
{
|
||||
int ret;
|
||||
u16 vid;
|
||||
|
||||
for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
|
||||
ret = ocelot_vlan_vid_add(dev, vid,
|
||||
vlan->flags & BRIDGE_VLAN_INFO_PVID,
|
||||
vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ocelot_port_vlan_del_vlan(struct net_device *dev,
|
||||
const struct switchdev_obj_port_vlan *vlan)
|
||||
{
|
||||
int ret;
|
||||
u16 vid;
|
||||
|
||||
for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
|
||||
ret = ocelot_vlan_vid_del(dev, vid);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ocelot_multicast *ocelot_multicast_get(struct ocelot *ocelot,
|
||||
const unsigned char *addr,
|
||||
u16 vid)
|
||||
@ -953,7 +1211,7 @@ static int ocelot_port_obj_add_mdb(struct net_device *dev,
|
||||
bool new = false;
|
||||
|
||||
if (!vid)
|
||||
vid = 1;
|
||||
vid = port->pvid;
|
||||
|
||||
mc = ocelot_multicast_get(ocelot, mdb->addr, vid);
|
||||
if (!mc) {
|
||||
@ -994,7 +1252,7 @@ static int ocelot_port_obj_del_mdb(struct net_device *dev,
|
||||
u16 vid = mdb->vid;
|
||||
|
||||
if (!vid)
|
||||
vid = 1;
|
||||
vid = port->pvid;
|
||||
|
||||
mc = ocelot_multicast_get(ocelot, mdb->addr, vid);
|
||||
if (!mc)
|
||||
@ -1026,6 +1284,11 @@ static int ocelot_port_obj_add(struct net_device *dev,
|
||||
int ret = 0;
|
||||
|
||||
switch (obj->id) {
|
||||
case SWITCHDEV_OBJ_ID_PORT_VLAN:
|
||||
ret = ocelot_port_obj_add_vlan(dev,
|
||||
SWITCHDEV_OBJ_PORT_VLAN(obj),
|
||||
trans);
|
||||
break;
|
||||
case SWITCHDEV_OBJ_ID_PORT_MDB:
|
||||
ret = ocelot_port_obj_add_mdb(dev, SWITCHDEV_OBJ_PORT_MDB(obj),
|
||||
trans);
|
||||
@ -1043,6 +1306,10 @@ static int ocelot_port_obj_del(struct net_device *dev,
|
||||
int ret = 0;
|
||||
|
||||
switch (obj->id) {
|
||||
case SWITCHDEV_OBJ_ID_PORT_VLAN:
|
||||
ret = ocelot_port_vlan_del_vlan(dev,
|
||||
SWITCHDEV_OBJ_PORT_VLAN(obj));
|
||||
break;
|
||||
case SWITCHDEV_OBJ_ID_PORT_MDB:
|
||||
ret = ocelot_port_obj_del_mdb(dev, SWITCHDEV_OBJ_PORT_MDB(obj));
|
||||
break;
|
||||
@ -1088,6 +1355,11 @@ static void ocelot_port_bridge_leave(struct ocelot_port *ocelot_port,
|
||||
|
||||
if (!ocelot->bridge_mask)
|
||||
ocelot->hw_bridge_dev = NULL;
|
||||
|
||||
/* Clear bridge vlan settings before calling ocelot_vlan_port_apply */
|
||||
ocelot_port->vlan_aware = 0;
|
||||
ocelot_port->pvid = 0;
|
||||
ocelot_port->vid = 0;
|
||||
}
|
||||
|
||||
static void ocelot_set_aggr_pgids(struct ocelot *ocelot)
|
||||
@ -1246,6 +1518,9 @@ static int ocelot_netdevice_port_event(struct net_device *dev,
|
||||
else
|
||||
ocelot_port_bridge_leave(ocelot_port,
|
||||
info->upper_dev);
|
||||
|
||||
ocelot_vlan_port_apply(ocelot_port->ocelot,
|
||||
ocelot_port);
|
||||
}
|
||||
if (netif_is_lag_master(info->upper_dev)) {
|
||||
if (info->linking)
|
||||
@ -1331,6 +1606,9 @@ int ocelot_probe_port(struct ocelot *ocelot, u8 port,
|
||||
dev->ethtool_ops = &ocelot_ethtool_ops;
|
||||
dev->switchdev_ops = &ocelot_port_switchdev_ops;
|
||||
|
||||
dev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER;
|
||||
dev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
|
||||
|
||||
memcpy(dev->dev_addr, ocelot->base_mac, ETH_ALEN);
|
||||
dev->dev_addr[ETH_ALEN - 1] += port;
|
||||
ocelot_mact_learn(ocelot, PGID_CPU, dev->dev_addr, ocelot_port->pvid,
|
||||
@ -1342,6 +1620,9 @@ int ocelot_probe_port(struct ocelot *ocelot, u8 port,
|
||||
goto err_register_netdev;
|
||||
}
|
||||
|
||||
/* Basic L2 initialization */
|
||||
ocelot_vlan_port_apply(ocelot, ocelot_port);
|
||||
|
||||
return 0;
|
||||
|
||||
err_register_netdev:
|
||||
|
Loading…
Reference in New Issue
Block a user