3889 lines · 94.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * net/dsa/user.c - user device handling * Copyright (c) 2008-2009 Marvell Semiconductor */ #include <linux/list.h>#include <linux/etherdevice.h>#include <linux/netdevice.h>#include <linux/phy.h>#include <linux/phy_fixed.h>#include <linux/phylink.h>#include <linux/of_net.h>#include <linux/of_mdio.h>#include <linux/mdio.h>#include <net/netdev_lock.h>#include <net/rtnetlink.h>#include <net/pkt_cls.h>#include <net/selftests.h>#include <net/tc_act/tc_mirred.h>#include <linux/if_bridge.h>#include <linux/if_hsr.h>#include <net/dcbnl.h>#include <linux/netpoll.h>#include <linux/string.h> #include "conduit.h"#include "dsa.h"#include "netlink.h"#include "port.h"#include "switch.h"#include "tag.h"#include "user.h" struct dsa_switchdev_event_work {	struct net_device *dev;	struct net_device *orig_dev;	struct work_struct work;	unsigned long event;	/* Specific for SWITCHDEV_FDB_ADD_TO_DEVICE and	 * SWITCHDEV_FDB_DEL_TO_DEVICE	 */	unsigned char addr[ETH_ALEN];	u16 vid;	bool host_addr;}; enum dsa_standalone_event {	DSA_UC_ADD,	DSA_UC_DEL,	DSA_MC_ADD,	DSA_MC_DEL,}; struct dsa_standalone_event_work {	struct work_struct work;	struct net_device *dev;	enum dsa_standalone_event event;	unsigned char addr[ETH_ALEN];	u16 vid;}; struct dsa_host_vlan_rx_filtering_ctx {	struct net_device *dev;	const unsigned char *addr;	enum dsa_standalone_event event;}; static bool dsa_switch_supports_uc_filtering(struct dsa_switch *ds){	return ds->ops->port_fdb_add && ds->ops->port_fdb_del &&	       ds->fdb_isolation && !ds->vlan_filtering_is_global &&	       !ds->needs_standalone_vlan_filtering;} static bool dsa_switch_supports_mc_filtering(struct dsa_switch *ds){	return ds->ops->port_mdb_add && ds->ops->port_mdb_del &&	       ds->fdb_isolation && !ds->vlan_filtering_is_global &&	       !ds->needs_standalone_vlan_filtering;} static void dsa_user_standalone_event_work(struct work_struct *work){	struct dsa_standalone_event_work *standalone_work =		container_of(work, struct dsa_standalone_event_work, work);	const unsigned char *addr = standalone_work->addr;	struct net_device *dev = standalone_work->dev;	struct dsa_port *dp = dsa_user_to_port(dev);	struct switchdev_obj_port_mdb mdb;	struct dsa_switch *ds = dp->ds;	u16 vid = standalone_work->vid;	int err; 	switch (standalone_work->event) {	case DSA_UC_ADD:		err = dsa_port_standalone_host_fdb_add(dp, addr, vid);		if (err) {			dev_err(ds->dev,				"port %d failed to add %pM vid %d to fdb: %d\n",				dp->index, addr, vid, err);			break;		}		break; 	case DSA_UC_DEL:		err = dsa_port_standalone_host_fdb_del(dp, addr, vid);		if (err) {			dev_err(ds->dev,				"port %d failed to delete %pM vid %d from fdb: %d\n",				dp->index, addr, vid, err);		} 		break;	case DSA_MC_ADD:		ether_addr_copy(mdb.addr, addr);		mdb.vid = vid; 		err = dsa_port_standalone_host_mdb_add(dp, &mdb);		if (err) {			dev_err(ds->dev,				"port %d failed to add %pM vid %d to mdb: %d\n",				dp->index, addr, vid, err);			break;		}		break;	case DSA_MC_DEL:		ether_addr_copy(mdb.addr, addr);		mdb.vid = vid; 		err = dsa_port_standalone_host_mdb_del(dp, &mdb);		if (err) {			dev_err(ds->dev,				"port %d failed to delete %pM vid %d from mdb: %d\n",				dp->index, addr, vid, err);		} 		break;	} 	kfree(standalone_work);} static int dsa_user_schedule_standalone_work(struct net_device *dev,					     enum dsa_standalone_event event,					     const unsigned char *addr,					     u16 vid){	struct dsa_standalone_event_work *standalone_work; 	standalone_work = kzalloc_obj(*standalone_work, GFP_ATOMIC);	if (!standalone_work)		return -ENOMEM; 	INIT_WORK(&standalone_work->work, dsa_user_standalone_event_work);	standalone_work->event = event;	standalone_work->dev = dev; 	ether_addr_copy(standalone_work->addr, addr);	standalone_work->vid = vid; 	dsa_schedule_work(&standalone_work->work); 	return 0;} static int dsa_user_host_vlan_rx_filtering(void *arg, int vid){	struct dsa_host_vlan_rx_filtering_ctx *ctx = arg; 	return dsa_user_schedule_standalone_work(ctx->dev, ctx->event,						  ctx->addr, vid);} static int dsa_user_vlan_for_each(struct net_device *dev,				  int (*cb)(void *arg, int vid), void *arg){	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_vlan *v;	int err; 	lockdep_assert_held(&dev->addr_list_lock); 	err = cb(arg, 0);	if (err)		return err; 	list_for_each_entry(v, &dp->user_vlans, list) {		err = cb(arg, v->vid);		if (err)			return err;	} 	return 0;} static int dsa_user_sync_uc(struct net_device *dev,			    const unsigned char *addr){	struct net_device *conduit = dsa_user_to_conduit(dev);	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_host_vlan_rx_filtering_ctx ctx = {		.dev = dev,		.addr = addr,		.event = DSA_UC_ADD,	}; 	dev_uc_add(conduit, addr); 	if (!dsa_switch_supports_uc_filtering(dp->ds))		return 0; 	return dsa_user_vlan_for_each(dev, dsa_user_host_vlan_rx_filtering,				      &ctx);} static int dsa_user_unsync_uc(struct net_device *dev,			      const unsigned char *addr){	struct net_device *conduit = dsa_user_to_conduit(dev);	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_host_vlan_rx_filtering_ctx ctx = {		.dev = dev,		.addr = addr,		.event = DSA_UC_DEL,	}; 	dev_uc_del(conduit, addr); 	if (!dsa_switch_supports_uc_filtering(dp->ds))		return 0; 	return dsa_user_vlan_for_each(dev, dsa_user_host_vlan_rx_filtering,				      &ctx);} static int dsa_user_sync_mc(struct net_device *dev,			    const unsigned char *addr){	struct net_device *conduit = dsa_user_to_conduit(dev);	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_host_vlan_rx_filtering_ctx ctx = {		.dev = dev,		.addr = addr,		.event = DSA_MC_ADD,	}; 	dev_mc_add(conduit, addr); 	if (!dsa_switch_supports_mc_filtering(dp->ds))		return 0; 	return dsa_user_vlan_for_each(dev, dsa_user_host_vlan_rx_filtering,				      &ctx);} static int dsa_user_unsync_mc(struct net_device *dev,			      const unsigned char *addr){	struct net_device *conduit = dsa_user_to_conduit(dev);	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_host_vlan_rx_filtering_ctx ctx = {		.dev = dev,		.addr = addr,		.event = DSA_MC_DEL,	}; 	dev_mc_del(conduit, addr); 	if (!dsa_switch_supports_mc_filtering(dp->ds))		return 0; 	return dsa_user_vlan_for_each(dev, dsa_user_host_vlan_rx_filtering,				      &ctx);} void dsa_user_sync_ha(struct net_device *dev){	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_switch *ds = dp->ds;	struct netdev_hw_addr *ha; 	netif_addr_lock_bh(dev); 	netdev_for_each_synced_mc_addr(ha, dev)		dsa_user_sync_mc(dev, ha->addr); 	netdev_for_each_synced_uc_addr(ha, dev)		dsa_user_sync_uc(dev, ha->addr); 	netif_addr_unlock_bh(dev); 	if (dsa_switch_supports_uc_filtering(ds) ||	    dsa_switch_supports_mc_filtering(ds))		dsa_flush_workqueue();} void dsa_user_unsync_ha(struct net_device *dev){	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_switch *ds = dp->ds;	struct netdev_hw_addr *ha; 	netif_addr_lock_bh(dev); 	netdev_for_each_synced_uc_addr(ha, dev)		dsa_user_unsync_uc(dev, ha->addr); 	netdev_for_each_synced_mc_addr(ha, dev)		dsa_user_unsync_mc(dev, ha->addr); 	netif_addr_unlock_bh(dev); 	if (dsa_switch_supports_uc_filtering(ds) ||	    dsa_switch_supports_mc_filtering(ds))		dsa_flush_workqueue();} /* user mii_bus handling ***************************************************/static int dsa_user_phy_read(struct mii_bus *bus, int addr, int reg){	struct dsa_switch *ds = bus->priv; 	if (ds->phys_mii_mask & (1 << addr))		return ds->ops->phy_read(ds, addr, reg); 	return 0xffff;} static int dsa_user_phy_write(struct mii_bus *bus, int addr, int reg, u16 val){	struct dsa_switch *ds = bus->priv; 	if (ds->phys_mii_mask & (1 << addr))		return ds->ops->phy_write(ds, addr, reg, val); 	return 0;} void dsa_user_mii_bus_init(struct dsa_switch *ds){	ds->user_mii_bus->priv = (void *)ds;	ds->user_mii_bus->name = "dsa user smi";	ds->user_mii_bus->read = dsa_user_phy_read;	ds->user_mii_bus->write = dsa_user_phy_write;	snprintf(ds->user_mii_bus->id, MII_BUS_ID_SIZE, "dsa-%d.%d",		 ds->dst->index, ds->index);	ds->user_mii_bus->parent = ds->dev;	ds->user_mii_bus->phy_mask = ~ds->phys_mii_mask;}  /* user device handling ****************************************************/static int dsa_user_get_iflink(const struct net_device *dev){	return READ_ONCE(dsa_user_to_conduit(dev)->ifindex);} int dsa_user_host_uc_install(struct net_device *dev, const u8 *addr){	struct net_device *conduit = dsa_user_to_conduit(dev);	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_switch *ds = dp->ds;	int err; 	if (dsa_switch_supports_uc_filtering(ds)) {		err = dsa_port_standalone_host_fdb_add(dp, addr, 0);		if (err)			goto out;	} 	if (!ether_addr_equal(addr, conduit->dev_addr)) {		err = dev_uc_add(conduit, addr);		if (err < 0)			goto del_host_addr;	} 	return 0; del_host_addr:	if (dsa_switch_supports_uc_filtering(ds))		dsa_port_standalone_host_fdb_del(dp, addr, 0);out:	return err;} void dsa_user_host_uc_uninstall(struct net_device *dev){	struct net_device *conduit = dsa_user_to_conduit(dev);	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_switch *ds = dp->ds; 	if (!ether_addr_equal(dev->dev_addr, conduit->dev_addr))		dev_uc_del(conduit, dev->dev_addr); 	if (dsa_switch_supports_uc_filtering(ds))		dsa_port_standalone_host_fdb_del(dp, dev->dev_addr, 0);} static int dsa_user_open(struct net_device *dev){	struct net_device *conduit = dsa_user_to_conduit(dev);	struct dsa_port *dp = dsa_user_to_port(dev);	int err; 	err = dev_open(conduit, NULL);	if (err < 0) {		netdev_err(dev, "failed to open conduit %s\n", conduit->name);		goto out;	} 	err = dsa_user_host_uc_install(dev, dev->dev_addr);	if (err)		goto out; 	err = dsa_port_enable_rt(dp, dev->phydev);	if (err)		goto out_del_host_uc; 	return 0; out_del_host_uc:	dsa_user_host_uc_uninstall(dev);out:	return err;} static int dsa_user_close(struct net_device *dev){	struct dsa_port *dp = dsa_user_to_port(dev); 	dsa_port_disable_rt(dp); 	dsa_user_host_uc_uninstall(dev); 	return 0;} static void dsa_user_manage_host_flood(struct net_device *dev){	bool mc = dev->flags & (IFF_PROMISC | IFF_ALLMULTI);	struct dsa_port *dp = dsa_user_to_port(dev);	bool uc = dev->flags & IFF_PROMISC; 	dsa_port_set_host_flood(dp, uc, mc);} static void dsa_user_change_rx_flags(struct net_device *dev, int change){	struct net_device *conduit = dsa_user_to_conduit(dev);	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_switch *ds = dp->ds; 	if (change & IFF_ALLMULTI)		dev_set_allmulti(conduit,				 dev->flags & IFF_ALLMULTI ? 1 : -1);	if (change & IFF_PROMISC)		dev_set_promiscuity(conduit,				    dev->flags & IFF_PROMISC ? 1 : -1); 	if (dsa_switch_supports_uc_filtering(ds) &&	    dsa_switch_supports_mc_filtering(ds))		dsa_user_manage_host_flood(dev);} static void dsa_user_set_rx_mode(struct net_device *dev){	__dev_mc_sync(dev, dsa_user_sync_mc, dsa_user_unsync_mc);	__dev_uc_sync(dev, dsa_user_sync_uc, dsa_user_unsync_uc);} static int dsa_user_set_mac_address(struct net_device *dev, void *a){	struct dsa_port *dp = dsa_user_to_port(dev);	struct dsa_switch *ds = dp->ds;	struct sockaddr *addr = a;	int err; 	if (!is_valid_ether_addr(addr->sa_data))		return -EADDRNOTAVAIL; 	if (ds->ops->port_set_mac_address) {		err = ds->ops->port_set_mac_address(ds, dp->index,						    addr->sa_data);		if (err)			return err;	} 	/* If the port is down, the address isn't synced yet to hardware or	 * to the DSA conduit, so there is nothing to change.	 */	if (!(dev->flags & IFF_UP))		goto out_change_dev_addr; 	err = dsa_user_host_uc_install(dev, addr->sa_data);	if (err)		return err; 	dsa_user_host_uc_uninstall(dev); 

Lines 1–500 of 3889.