diff options
Diffstat (limited to 'net/dsa/slave.c')
| -rw-r--r-- | net/dsa/slave.c | 186 | 
1 files changed, 153 insertions, 33 deletions
diff --git a/net/dsa/slave.c b/net/dsa/slave.c index a3fcc1d01615..093eef6f2599 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -122,7 +122,7 @@ static int dsa_slave_close(struct net_device *dev)  	phylink_stop(dp->pl); -	dsa_port_disable(dp, dev->phydev); +	dsa_port_disable(dp);  	dev_mc_unsync(master, dev);  	dev_uc_unsync(master, dev); @@ -140,11 +140,14 @@ static int dsa_slave_close(struct net_device *dev)  static void dsa_slave_change_rx_flags(struct net_device *dev, int change)  {  	struct net_device *master = dsa_slave_to_master(dev); - -	if (change & IFF_ALLMULTI) -		dev_set_allmulti(master, dev->flags & IFF_ALLMULTI ? 1 : -1); -	if (change & IFF_PROMISC) -		dev_set_promiscuity(master, dev->flags & IFF_PROMISC ? 1 : -1); +	if (dev->flags & IFF_UP) { +		if (change & IFF_ALLMULTI) +			dev_set_allmulti(master, +					 dev->flags & IFF_ALLMULTI ? 1 : -1); +		if (change & IFF_PROMISC) +			dev_set_promiscuity(master, +					    dev->flags & IFF_PROMISC ? 1 : -1); +	}  }  static void dsa_slave_set_rx_mode(struct net_device *dev) @@ -292,6 +295,13 @@ static int dsa_slave_port_attr_set(struct net_device *dev,  	case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:  		ret = dsa_port_ageing_time(dp, attr->u.ageing_time, trans);  		break; +	case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS: +		ret = dsa_port_pre_bridge_flags(dp, attr->u.brport_flags, +						trans); +		break; +	case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS: +		ret = dsa_port_bridge_flags(dp, attr->u.brport_flags, trans); +		break;  	default:  		ret = -EOPNOTSUPP;  		break; @@ -362,24 +372,15 @@ static int dsa_slave_port_obj_del(struct net_device *dev,  	return err;  } -static int dsa_slave_port_attr_get(struct net_device *dev, -				   struct switchdev_attr *attr) +static int dsa_slave_get_port_parent_id(struct net_device *dev, +					struct netdev_phys_item_id *ppid)  {  	struct dsa_port *dp = dsa_slave_to_port(dev);  	struct dsa_switch *ds = dp->ds;  	struct dsa_switch_tree *dst = ds->dst; -	switch (attr->id) { -	case SWITCHDEV_ATTR_ID_PORT_PARENT_ID: -		attr->u.ppid.id_len = sizeof(dst->index); -		memcpy(&attr->u.ppid.id, &dst->index, attr->u.ppid.id_len); -		break; -	case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS_SUPPORT: -		attr->u.brport_flags_support = 0; -		break; -	default: -		return -EOPNOTSUPP; -	} +	ppid->id_len = sizeof(dst->index); +	memcpy(&ppid->id, &dst->index, ppid->id_len);  	return 0;  } @@ -639,7 +640,7 @@ static int dsa_slave_set_eee(struct net_device *dev, struct ethtool_eee *e)  	int ret;  	/* Port's PHY and MAC both need to be EEE capable */ -	if (!dev->phydev && !dp->pl) +	if (!dev->phydev || !dp->pl)  		return -ENODEV;  	if (!ds->ops->set_mac_eee) @@ -659,7 +660,7 @@ static int dsa_slave_get_eee(struct net_device *dev, struct ethtool_eee *e)  	int ret;  	/* Port's PHY and MAC both need to be EEE capable */ -	if (!dev->phydev && !dp->pl) +	if (!dev->phydev || !dp->pl)  		return -ENODEV;  	if (!ds->ops->get_mac_eee) @@ -982,6 +983,75 @@ static int dsa_slave_get_ts_info(struct net_device *dev,  	return ds->ops->get_ts_info(ds, p->dp->index, ts);  } +static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto, +				     u16 vid) +{ +	struct dsa_port *dp = dsa_slave_to_port(dev); +	struct switchdev_obj_port_vlan vlan = { +		.vid_begin = vid, +		.vid_end = vid, +		/* This API only allows programming tagged, non-PVID VIDs */ +		.flags = 0, +	}; +	struct switchdev_trans trans; +	struct bridge_vlan_info info; +	int ret; + +	/* Check for a possible bridge VLAN entry now since there is no +	 * need to emulate the switchdev prepare + commit phase. +	 */ +	if (dp->bridge_dev) { +		/* br_vlan_get_info() returns -EINVAL or -ENOENT if the +		 * device, respectively the VID is not found, returning +		 * 0 means success, which is a failure for us here. +		 */ +		ret = br_vlan_get_info(dp->bridge_dev, vid, &info); +		if (ret == 0) +			return -EBUSY; +	} + +	trans.ph_prepare = true; +	ret = dsa_port_vlan_add(dp, &vlan, &trans); +	if (ret == -EOPNOTSUPP) +		return 0; + +	trans.ph_prepare = false; +	return dsa_port_vlan_add(dp, &vlan, &trans); +} + +static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, +				      u16 vid) +{ +	struct dsa_port *dp = dsa_slave_to_port(dev); +	struct switchdev_obj_port_vlan vlan = { +		.vid_begin = vid, +		.vid_end = vid, +		/* This API only allows programming tagged, non-PVID VIDs */ +		.flags = 0, +	}; +	struct bridge_vlan_info info; +	int ret; + +	/* Check for a possible bridge VLAN entry now since there is no +	 * need to emulate the switchdev prepare + commit phase. +	 */ +	if (dp->bridge_dev) { +		/* br_vlan_get_info() returns -EINVAL or -ENOENT if the +		 * device, respectively the VID is not found, returning +		 * 0 means success, which is a failure for us here. +		 */ +		ret = br_vlan_get_info(dp->bridge_dev, vid, &info); +		if (ret == 0) +			return -EBUSY; +	} + +	ret = dsa_port_vlan_del(dp, &vlan); +	if (ret == -EOPNOTSUPP) +		ret = 0; + +	return ret; +} +  static const struct ethtool_ops dsa_slave_ethtool_ops = {  	.get_drvinfo		= dsa_slave_get_drvinfo,  	.get_regs_len		= dsa_slave_get_regs_len, @@ -1009,7 +1079,8 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = {  int dsa_legacy_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],  		       struct net_device *dev,  		       const unsigned char *addr, u16 vid, -		       u16 flags) +		       u16 flags, +		       struct netlink_ext_ack *extack)  {  	struct dsa_port *dp = dsa_slave_to_port(dev); @@ -1045,11 +1116,9 @@ static const struct net_device_ops dsa_slave_netdev_ops = {  	.ndo_get_phys_port_name	= dsa_slave_get_phys_port_name,  	.ndo_setup_tc		= dsa_slave_setup_tc,  	.ndo_get_stats64	= dsa_slave_get_stats64, -}; - -static const struct switchdev_ops dsa_slave_switchdev_ops = { -	.switchdev_port_attr_get	= dsa_slave_port_attr_get, -	.switchdev_port_attr_set	= dsa_slave_port_attr_set, +	.ndo_get_port_parent_id	= dsa_slave_get_port_parent_id, +	.ndo_vlan_rx_add_vid	= dsa_slave_vlan_rx_add_vid, +	.ndo_vlan_rx_kill_vid	= dsa_slave_vlan_rx_kill_vid,  };  static struct device_type dsa_type = { @@ -1305,13 +1374,13 @@ int dsa_slave_create(struct dsa_port *port)  	if (slave_dev == NULL)  		return -ENOMEM; -	slave_dev->features = master->vlan_features | NETIF_F_HW_TC; +	slave_dev->features = master->vlan_features | NETIF_F_HW_TC | +				NETIF_F_HW_VLAN_CTAG_FILTER;  	slave_dev->hw_features |= NETIF_F_HW_TC;  	slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;  	eth_hw_addr_inherit(slave_dev, master);  	slave_dev->priv_flags |= IFF_NO_QUEUE;  	slave_dev->netdev_ops = &dsa_slave_netdev_ops; -	slave_dev->switchdev_ops = &dsa_slave_switchdev_ops;  	slave_dev->min_mtu = 0;  	slave_dev->max_mtu = ETH_MAX_MTU;  	SET_NETDEV_DEVTYPE(slave_dev, &dsa_type); @@ -1406,20 +1475,66 @@ static int dsa_slave_changeupper(struct net_device *dev,  	return err;  } +static int dsa_slave_upper_vlan_check(struct net_device *dev, +				      struct netdev_notifier_changeupper_info * +				      info) +{ +	struct netlink_ext_ack *ext_ack; +	struct net_device *slave; +	struct dsa_port *dp; + +	ext_ack = netdev_notifier_info_to_extack(&info->info); + +	if (!is_vlan_dev(dev)) +		return NOTIFY_DONE; + +	slave = vlan_dev_real_dev(dev); +	if (!dsa_slave_dev_check(slave)) +		return NOTIFY_DONE; + +	dp = dsa_slave_to_port(slave); +	if (!dp->bridge_dev) +		return NOTIFY_DONE; + +	/* Deny enslaving a VLAN device into a VLAN-aware bridge */ +	if (br_vlan_enabled(dp->bridge_dev) && +	    netif_is_bridge_master(info->upper_dev) && info->linking) { +		NL_SET_ERR_MSG_MOD(ext_ack, +				   "Cannot enslave VLAN device into VLAN aware bridge"); +		return notifier_from_errno(-EINVAL); +	} + +	return NOTIFY_DONE; +} +  static int dsa_slave_netdevice_event(struct notifier_block *nb,  				     unsigned long event, void *ptr)  {  	struct net_device *dev = netdev_notifier_info_to_dev(ptr); -	if (!dsa_slave_dev_check(dev)) -		return NOTIFY_DONE; +	if (event == NETDEV_CHANGEUPPER) { +		if (!dsa_slave_dev_check(dev)) +			return dsa_slave_upper_vlan_check(dev, ptr); -	if (event == NETDEV_CHANGEUPPER)  		return dsa_slave_changeupper(dev, ptr); +	}  	return NOTIFY_DONE;  } +static int +dsa_slave_switchdev_port_attr_set_event(struct net_device *netdev, +		struct switchdev_notifier_port_attr_info *port_attr_info) +{ +	int err; + +	err = dsa_slave_port_attr_set(netdev, port_attr_info->attr, +				      port_attr_info->trans); + +	port_attr_info->handled = true; +	return notifier_from_errno(err); +} +  struct dsa_switchdev_event_work {  	struct work_struct work;  	struct switchdev_notifier_fdb_info fdb_info; @@ -1450,7 +1565,7 @@ static void dsa_slave_switchdev_event_work(struct work_struct *work)  		}  		fdb_info->offloaded = true;  		call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev, -					 &fdb_info->info); +					 &fdb_info->info, NULL);  		break;  	case SWITCHDEV_FDB_DEL_TO_DEVICE: @@ -1498,6 +1613,9 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused,  	if (!dsa_slave_dev_check(dev))  		return NOTIFY_DONE; +	if (event == SWITCHDEV_PORT_ATTR_SET) +		return dsa_slave_switchdev_port_attr_set_event(dev, ptr); +  	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);  	if (!switchdev_work)  		return NOTIFY_BAD; @@ -1560,6 +1678,8 @@ static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,  	case SWITCHDEV_PORT_OBJ_ADD: /* fall through */  	case SWITCHDEV_PORT_OBJ_DEL:  		return dsa_slave_switchdev_port_obj_event(event, dev, ptr); +	case SWITCHDEV_PORT_ATTR_SET: +		return dsa_slave_switchdev_port_attr_set_event(dev, ptr);  	}  	return NOTIFY_DONE;  |