diff options
Diffstat (limited to 'net/dsa/slave.c')
| -rw-r--r-- | net/dsa/slave.c | 397 | 
1 files changed, 328 insertions, 69 deletions
diff --git a/net/dsa/slave.c b/net/dsa/slave.c index 0917123790ea..cce97385f743 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -18,6 +18,7 @@  #include <net/rtnetlink.h>  #include <net/switchdev.h>  #include <linux/if_bridge.h> +#include <linux/netpoll.h>  #include "dsa_priv.h"  /* slave mii_bus handling ***************************************************/ @@ -199,103 +200,212 @@ out:  	return 0;  } -static int dsa_slave_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], -			     struct net_device *dev, -			     const unsigned char *addr, u16 vid, u16 nlm_flags) +static int dsa_bridge_check_vlan_range(struct dsa_switch *ds, +				       const struct net_device *bridge, +				       u16 vid_begin, u16 vid_end)  { +	struct dsa_slave_priv *p; +	struct net_device *dev, *vlan_br; +	DECLARE_BITMAP(members, DSA_MAX_PORTS); +	DECLARE_BITMAP(untagged, DSA_MAX_PORTS); +	u16 vid; +	int member, err; + +	if (!ds->drv->vlan_getnext || !vid_begin) +		return -EOPNOTSUPP; + +	vid = vid_begin - 1; + +	do { +		err = ds->drv->vlan_getnext(ds, &vid, members, untagged); +		if (err) +			break; + +		if (vid > vid_end) +			break; + +		member = find_first_bit(members, DSA_MAX_PORTS); +		if (member == DSA_MAX_PORTS) +			continue; + +		dev = ds->ports[member]; +		p = netdev_priv(dev); +		vlan_br = p->bridge_dev; +		if (vlan_br == bridge) +			continue; + +		netdev_dbg(vlan_br, "hardware VLAN %d already in use\n", vid); +		return -EOPNOTSUPP; +	} while (vid < vid_end); + +	return err == -ENOENT ? 0 : err; +} + +static int dsa_slave_port_vlan_add(struct net_device *dev, +				   struct switchdev_obj *obj) +{ +	struct switchdev_obj_vlan *vlan = &obj->u.vlan;  	struct dsa_slave_priv *p = netdev_priv(dev);  	struct dsa_switch *ds = p->parent; -	int ret = -EOPNOTSUPP; +	u16 vid; +	int err; -	if (ds->drv->fdb_add) -		ret = ds->drv->fdb_add(ds, p->port, addr, vid); +	switch (obj->trans) { +	case SWITCHDEV_TRANS_PREPARE: +		if (!ds->drv->port_vlan_add || !ds->drv->port_pvid_set) +			return -EOPNOTSUPP; -	return ret; +		/* If the requested port doesn't belong to the same bridge as +		 * the VLAN members, fallback to software VLAN (hopefully). +		 */ +		err = dsa_bridge_check_vlan_range(ds, p->bridge_dev, +						  vlan->vid_begin, +						  vlan->vid_end); +		if (err) +			return err; +		break; +	case SWITCHDEV_TRANS_COMMIT: +		for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { +			err = ds->drv->port_vlan_add(ds, p->port, vid, +						     vlan->flags & +						     BRIDGE_VLAN_INFO_UNTAGGED); +			if (!err && vlan->flags & BRIDGE_VLAN_INFO_PVID) +				err = ds->drv->port_pvid_set(ds, p->port, vid); +			if (err) +				return err; +		} +		break; +	default: +		return -EOPNOTSUPP; +	} + +	return 0;  } -static int dsa_slave_fdb_del(struct ndmsg *ndm, struct nlattr *tb[], -			     struct net_device *dev, -			     const unsigned char *addr, u16 vid) +static int dsa_slave_port_vlan_del(struct net_device *dev, +				   struct switchdev_obj *obj)  { +	struct switchdev_obj_vlan *vlan = &obj->u.vlan;  	struct dsa_slave_priv *p = netdev_priv(dev);  	struct dsa_switch *ds = p->parent; -	int ret = -EOPNOTSUPP; +	u16 vid; +	int err; -	if (ds->drv->fdb_del) -		ret = ds->drv->fdb_del(ds, p->port, addr, vid); +	if (!ds->drv->port_vlan_del) +		return -EOPNOTSUPP; -	return ret; +	for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { +		err = ds->drv->port_vlan_del(ds, p->port, vid); +		if (err) +			return err; +	} + +	return 0;  } -static int dsa_slave_fill_info(struct net_device *dev, struct sk_buff *skb, -			       const unsigned char *addr, u16 vid, -			       bool is_static, -			       u32 portid, u32 seq, int type, -			       unsigned int flags) +static int dsa_slave_port_vlan_dump(struct net_device *dev, +				    struct switchdev_obj *obj)  { -	struct nlmsghdr *nlh; -	struct ndmsg *ndm; +	struct switchdev_obj_vlan *vlan = &obj->u.vlan; +	struct dsa_slave_priv *p = netdev_priv(dev); +	struct dsa_switch *ds = p->parent; +	DECLARE_BITMAP(members, DSA_MAX_PORTS); +	DECLARE_BITMAP(untagged, DSA_MAX_PORTS); +	u16 pvid, vid = 0; +	int err; -	nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags); -	if (!nlh) -		return -EMSGSIZE; +	if (!ds->drv->vlan_getnext || !ds->drv->port_pvid_get) +		return -EOPNOTSUPP; -	ndm = nlmsg_data(nlh); -	ndm->ndm_family	 = AF_BRIDGE; -	ndm->ndm_pad1    = 0; -	ndm->ndm_pad2    = 0; -	ndm->ndm_flags	 = NTF_EXT_LEARNED; -	ndm->ndm_type	 = 0; -	ndm->ndm_ifindex = dev->ifindex; -	ndm->ndm_state   = is_static ? NUD_NOARP : NUD_REACHABLE; +	err = ds->drv->port_pvid_get(ds, p->port, &pvid); +	if (err) +		return err; -	if (nla_put(skb, NDA_LLADDR, ETH_ALEN, addr)) -		goto nla_put_failure; +	for (;;) { +		err = ds->drv->vlan_getnext(ds, &vid, members, untagged); +		if (err) +			break; -	if (vid && nla_put_u16(skb, NDA_VLAN, vid)) -		goto nla_put_failure; +		if (!test_bit(p->port, members)) +			continue; -	nlmsg_end(skb, nlh); -	return 0; +		memset(vlan, 0, sizeof(*vlan)); +		vlan->vid_begin = vlan->vid_end = vid; + +		if (vid == pvid) +			vlan->flags |= BRIDGE_VLAN_INFO_PVID; + +		if (test_bit(p->port, untagged)) +			vlan->flags |= BRIDGE_VLAN_INFO_UNTAGGED; + +		err = obj->cb(dev, obj); +		if (err) +			break; +	} + +	return err == -ENOENT ? 0 : err; +} -nla_put_failure: -	nlmsg_cancel(skb, nlh); -	return -EMSGSIZE; +static int dsa_slave_port_fdb_add(struct net_device *dev, +				  struct switchdev_obj *obj) +{ +	struct switchdev_obj_fdb *fdb = &obj->u.fdb; +	struct dsa_slave_priv *p = netdev_priv(dev); +	struct dsa_switch *ds = p->parent; +	int ret = -EOPNOTSUPP; + +	if (obj->trans == SWITCHDEV_TRANS_PREPARE) +		ret = ds->drv->port_fdb_add ? 0 : -EOPNOTSUPP; +	else if (obj->trans == SWITCHDEV_TRANS_COMMIT) +		ret = ds->drv->port_fdb_add(ds, p->port, fdb->addr, fdb->vid); + +	return ret;  } -/* Dump information about entries, in response to GETNEIGH */ -static int dsa_slave_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb, -			      struct net_device *dev, -			      struct net_device *filter_dev, int idx) +static int dsa_slave_port_fdb_del(struct net_device *dev, +				  struct switchdev_obj *obj) +{ +	struct switchdev_obj_fdb *fdb = &obj->u.fdb; +	struct dsa_slave_priv *p = netdev_priv(dev); +	struct dsa_switch *ds = p->parent; +	int ret = -EOPNOTSUPP; + +	if (ds->drv->port_fdb_del) +		ret = ds->drv->port_fdb_del(ds, p->port, fdb->addr, fdb->vid); + +	return ret; +} + +static int dsa_slave_port_fdb_dump(struct net_device *dev, +				   struct switchdev_obj *obj)  {  	struct dsa_slave_priv *p = netdev_priv(dev);  	struct dsa_switch *ds = p->parent;  	unsigned char addr[ETH_ALEN] = { 0 }; +	u16 vid = 0;  	int ret; -	if (!ds->drv->fdb_getnext) +	if (!ds->drv->port_fdb_getnext)  		return -EOPNOTSUPP; -	for (; ; idx++) { +	for (;;) {  		bool is_static; -		ret = ds->drv->fdb_getnext(ds, p->port, addr, &is_static); +		ret = ds->drv->port_fdb_getnext(ds, p->port, addr, &vid, +						&is_static);  		if (ret < 0)  			break; -		if (idx < cb->args[0]) -			continue; +		obj->u.fdb.addr = addr; +		obj->u.fdb.vid = vid; +		obj->u.fdb.ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE; -		ret = dsa_slave_fill_info(dev, skb, addr, 0, -					  is_static, -					  NETLINK_CB(cb->skb).portid, -					  cb->nlh->nlmsg_seq, -					  RTM_NEWNEIGH, NLM_F_MULTI); +		ret = obj->cb(dev, obj);  		if (ret < 0)  			break;  	} -	return idx; +	return ret == -ENOENT ? 0 : ret;  }  static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) @@ -363,6 +473,71 @@ static int dsa_slave_port_attr_set(struct net_device *dev,  	return ret;  } +static int dsa_slave_port_obj_add(struct net_device *dev, +				  struct switchdev_obj *obj) +{ +	int err; + +	/* For the prepare phase, ensure the full set of changes is feasable in +	 * one go in order to signal a failure properly. If an operation is not +	 * supported, return -EOPNOTSUPP. +	 */ + +	switch (obj->id) { +	case SWITCHDEV_OBJ_PORT_FDB: +		err = dsa_slave_port_fdb_add(dev, obj); +		break; +	case SWITCHDEV_OBJ_PORT_VLAN: +		err = dsa_slave_port_vlan_add(dev, obj); +		break; +	default: +		err = -EOPNOTSUPP; +		break; +	} + +	return err; +} + +static int dsa_slave_port_obj_del(struct net_device *dev, +				  struct switchdev_obj *obj) +{ +	int err; + +	switch (obj->id) { +	case SWITCHDEV_OBJ_PORT_FDB: +		err = dsa_slave_port_fdb_del(dev, obj); +		break; +	case SWITCHDEV_OBJ_PORT_VLAN: +		err = dsa_slave_port_vlan_del(dev, obj); +		break; +	default: +		err = -EOPNOTSUPP; +		break; +	} + +	return err; +} + +static int dsa_slave_port_obj_dump(struct net_device *dev, +				   struct switchdev_obj *obj) +{ +	int err; + +	switch (obj->id) { +	case SWITCHDEV_OBJ_PORT_FDB: +		err = dsa_slave_port_fdb_dump(dev, obj); +		break; +	case SWITCHDEV_OBJ_PORT_VLAN: +		err = dsa_slave_port_vlan_dump(dev, obj); +		break; +	default: +		err = -EOPNOTSUPP; +		break; +	} + +	return err; +} +  static int dsa_slave_bridge_port_join(struct net_device *dev,  				      struct net_device *br)  { @@ -418,24 +593,53 @@ static int dsa_slave_port_attr_get(struct net_device *dev,  	return 0;  } -static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev) +static inline netdev_tx_t dsa_netpoll_send_skb(struct dsa_slave_priv *p, +					       struct sk_buff *skb)  { -	struct dsa_slave_priv *p = netdev_priv(dev); - -	return p->xmit(skb, dev); +#ifdef CONFIG_NET_POLL_CONTROLLER +	if (p->netpoll) +		netpoll_send_skb(p->netpoll, skb); +#else +	BUG(); +#endif +	return NETDEV_TX_OK;  } -static netdev_tx_t dsa_slave_notag_xmit(struct sk_buff *skb, -					struct net_device *dev) +static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)  {  	struct dsa_slave_priv *p = netdev_priv(dev); +	struct sk_buff *nskb; + +	dev->stats.tx_packets++; +	dev->stats.tx_bytes += skb->len; + +	/* Transmit function may have to reallocate the original SKB */ +	nskb = p->xmit(skb, dev); +	if (!nskb) +		return NETDEV_TX_OK; -	skb->dev = p->parent->dst->master_netdev; -	dev_queue_xmit(skb); +	/* SKB for netpoll still need to be mangled with the protocol-specific +	 * tag to be successfully transmitted +	 */ +	if (unlikely(netpoll_tx_running(dev))) +		return dsa_netpoll_send_skb(p, nskb); + +	/* Queue the SKB for transmission on the parent interface, but +	 * do not modify its EtherType +	 */ +	nskb->dev = p->parent->dst->master_netdev; +	dev_queue_xmit(nskb);  	return NETDEV_TX_OK;  } +static struct sk_buff *dsa_slave_notag_xmit(struct sk_buff *skb, +					    struct net_device *dev) +{ +	/* Just return the original SKB */ +	return skb; +} +  /* ethtool operations *******************************************************/  static int @@ -665,6 +869,49 @@ static int dsa_slave_get_eee(struct net_device *dev, struct ethtool_eee *e)  	return ret;  } +#ifdef CONFIG_NET_POLL_CONTROLLER +static int dsa_slave_netpoll_setup(struct net_device *dev, +				   struct netpoll_info *ni) +{ +	struct dsa_slave_priv *p = netdev_priv(dev); +	struct dsa_switch *ds = p->parent; +	struct net_device *master = ds->dst->master_netdev; +	struct netpoll *netpoll; +	int err = 0; + +	netpoll = kzalloc(sizeof(*netpoll), GFP_KERNEL); +	if (!netpoll) +		return -ENOMEM; + +	err = __netpoll_setup(netpoll, master); +	if (err) { +		kfree(netpoll); +		goto out; +	} + +	p->netpoll = netpoll; +out: +	return err; +} + +static void dsa_slave_netpoll_cleanup(struct net_device *dev) +{ +	struct dsa_slave_priv *p = netdev_priv(dev); +	struct netpoll *netpoll = p->netpoll; + +	if (!netpoll) +		return; + +	p->netpoll = NULL; + +	__netpoll_free_async(netpoll); +} + +static void dsa_slave_poll_controller(struct net_device *dev) +{ +} +#endif +  static const struct ethtool_ops dsa_slave_ethtool_ops = {  	.get_settings		= dsa_slave_get_settings,  	.set_settings		= dsa_slave_set_settings, @@ -692,16 +939,27 @@ static const struct net_device_ops dsa_slave_netdev_ops = {  	.ndo_change_rx_flags	= dsa_slave_change_rx_flags,  	.ndo_set_rx_mode	= dsa_slave_set_rx_mode,  	.ndo_set_mac_address	= dsa_slave_set_mac_address, -	.ndo_fdb_add		= dsa_slave_fdb_add, -	.ndo_fdb_del		= dsa_slave_fdb_del, -	.ndo_fdb_dump		= dsa_slave_fdb_dump, +	.ndo_fdb_add		= switchdev_port_fdb_add, +	.ndo_fdb_del		= switchdev_port_fdb_del, +	.ndo_fdb_dump		= switchdev_port_fdb_dump,  	.ndo_do_ioctl		= dsa_slave_ioctl,  	.ndo_get_iflink		= dsa_slave_get_iflink, +#ifdef CONFIG_NET_POLL_CONTROLLER +	.ndo_netpoll_setup	= dsa_slave_netpoll_setup, +	.ndo_netpoll_cleanup	= dsa_slave_netpoll_cleanup, +	.ndo_poll_controller	= dsa_slave_poll_controller, +#endif +	.ndo_bridge_getlink	= switchdev_port_bridge_getlink, +	.ndo_bridge_setlink	= switchdev_port_bridge_setlink, +	.ndo_bridge_dellink	= switchdev_port_bridge_dellink,  };  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, +	.switchdev_port_obj_add		= dsa_slave_port_obj_add, +	.switchdev_port_obj_del		= dsa_slave_port_obj_del, +	.switchdev_port_obj_dump	= dsa_slave_port_obj_dump,  };  static void dsa_slave_adjust_link(struct net_device *dev) @@ -756,7 +1014,8 @@ static int dsa_slave_phy_connect(struct dsa_slave_priv *p,  		return -ENODEV;  	/* Use already configured phy mode */ -	p->phy_interface = p->phy->interface; +	if (p->phy_interface == PHY_INTERFACE_MODE_NA) +		p->phy_interface = p->phy->interface;  	phy_connect_direct(slave_dev, p->phy, dsa_slave_adjust_link,  			   p->phy_interface); @@ -888,7 +1147,7 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent,  	slave_dev->features = master->vlan_features;  	slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;  	eth_hw_addr_inherit(slave_dev, master); -	slave_dev->tx_queue_len = 0; +	slave_dev->priv_flags |= IFF_NO_QUEUE;  	slave_dev->netdev_ops = &dsa_slave_netdev_ops;  	slave_dev->switchdev_ops = &dsa_slave_switchdev_ops;  |