1134 lines · 26.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Handling of a single switch chip, part of a switch fabric * * Copyright (c) 2017 Savoir-faire Linux Inc. *	Vivien Didelot <vivien.didelot@savoirfairelinux.com> */ #include <linux/if_bridge.h>#include <linux/netdevice.h>#include <linux/notifier.h>#include <linux/if_vlan.h>#include <net/switchdev.h> #include "dsa.h"#include "netlink.h"#include "port.h"#include "switch.h"#include "tag_8021q.h"#include "trace.h"#include "user.h" static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds,						   unsigned int ageing_time){	struct dsa_port *dp; 	dsa_switch_for_each_port(dp, ds)		if (dp->ageing_time && dp->ageing_time < ageing_time)			ageing_time = dp->ageing_time; 	return ageing_time;} static int dsa_switch_ageing_time(struct dsa_switch *ds,				  struct dsa_notifier_ageing_time_info *info){	unsigned int ageing_time = info->ageing_time; 	if (ds->ageing_time_min && ageing_time < ds->ageing_time_min)		return -ERANGE; 	if (ds->ageing_time_max && ageing_time > ds->ageing_time_max)		return -ERANGE; 	/* Program the fastest ageing time in case of multiple bridges */	ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time); 	if (ds->ops->set_ageing_time)		return ds->ops->set_ageing_time(ds, ageing_time); 	return 0;} static bool dsa_port_mtu_match(struct dsa_port *dp,			       struct dsa_notifier_mtu_info *info){	return dp == info->dp || dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp);} static int dsa_switch_mtu(struct dsa_switch *ds,			  struct dsa_notifier_mtu_info *info){	struct dsa_port *dp;	int ret; 	if (!ds->ops->port_change_mtu)		return -EOPNOTSUPP; 	dsa_switch_for_each_port(dp, ds) {		if (dsa_port_mtu_match(dp, info)) {			ret = ds->ops->port_change_mtu(ds, dp->index,						       info->mtu);			if (ret)				return ret;		}	} 	return 0;} static int dsa_switch_bridge_join(struct dsa_switch *ds,				  struct dsa_notifier_bridge_info *info){	int err; 	if (info->dp->ds == ds) {		if (!ds->ops->port_bridge_join)			return -EOPNOTSUPP; 		err = ds->ops->port_bridge_join(ds, info->dp->index,						info->bridge,						&info->tx_fwd_offload,						info->extack);		if (err)			return err;	} 	if (info->dp->ds != ds && ds->ops->crosschip_bridge_join) {		err = ds->ops->crosschip_bridge_join(ds,						     info->dp->ds->dst->index,						     info->dp->ds->index,						     info->dp->index,						     info->bridge,						     info->extack);		if (err)			return err;	} 	return 0;} static int dsa_switch_bridge_leave(struct dsa_switch *ds,				   struct dsa_notifier_bridge_info *info){	if (info->dp->ds == ds && ds->ops->port_bridge_leave)		ds->ops->port_bridge_leave(ds, info->dp->index, info->bridge); 	if (info->dp->ds != ds && ds->ops->crosschip_bridge_leave)		ds->ops->crosschip_bridge_leave(ds, info->dp->ds->dst->index,						info->dp->ds->index,						info->dp->index,						info->bridge); 	return 0;} /* Matches for all upstream-facing ports (the CPU port and all upstream-facing * DSA links) that sit between the targeted port on which the notifier was * emitted and its dedicated CPU port. */static bool dsa_port_host_address_match(struct dsa_port *dp,					const struct dsa_port *targeted_dp){	struct dsa_port *cpu_dp = targeted_dp->cpu_dp; 	if (dsa_switch_is_upstream_of(dp->ds, targeted_dp->ds))		return dp->index == dsa_towards_port(dp->ds, cpu_dp->ds->index,						     cpu_dp->index); 	return false;} static struct dsa_mac_addr *dsa_mac_addr_find(struct list_head *addr_list,					      const unsigned char *addr, u16 vid,					      struct dsa_db db){	struct dsa_mac_addr *a; 	list_for_each_entry(a, addr_list, list)		if (ether_addr_equal(a->addr, addr) && a->vid == vid &&		    dsa_db_equal(&a->db, &db))			return a; 	return NULL;} static int dsa_port_do_mdb_add(struct dsa_port *dp,			       const struct switchdev_obj_port_mdb *mdb,			       struct dsa_db db){	struct dsa_switch *ds = dp->ds;	struct dsa_mac_addr *a;	int port = dp->index;	int err = 0; 	/* No need to bother with refcounting for user ports */	if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) {		err = ds->ops->port_mdb_add(ds, port, mdb, db);		trace_dsa_mdb_add_hw(dp, mdb->addr, mdb->vid, &db, err); 		return err;	} 	mutex_lock(&dp->addr_lists_lock); 	a = dsa_mac_addr_find(&dp->mdbs, mdb->addr, mdb->vid, db);	if (a) {		refcount_inc(&a->refcount);		trace_dsa_mdb_add_bump(dp, mdb->addr, mdb->vid, &db,				       &a->refcount);		goto out;	} 	a = kzalloc_obj(*a);	if (!a) {		err = -ENOMEM;		goto out;	} 	err = ds->ops->port_mdb_add(ds, port, mdb, db);	trace_dsa_mdb_add_hw(dp, mdb->addr, mdb->vid, &db, err);	if (err) {		kfree(a);		goto out;	} 	ether_addr_copy(a->addr, mdb->addr);	a->vid = mdb->vid;	a->db = db;	refcount_set(&a->refcount, 1);	list_add_tail(&a->list, &dp->mdbs); out:	mutex_unlock(&dp->addr_lists_lock); 	return err;} static int dsa_port_do_mdb_del(struct dsa_port *dp,			       const struct switchdev_obj_port_mdb *mdb,			       struct dsa_db db){	struct dsa_switch *ds = dp->ds;	struct dsa_mac_addr *a;	int port = dp->index;	int err = 0; 	/* No need to bother with refcounting for user ports */	if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) {		err = ds->ops->port_mdb_del(ds, port, mdb, db);		trace_dsa_mdb_del_hw(dp, mdb->addr, mdb->vid, &db, err); 		return err;	} 	mutex_lock(&dp->addr_lists_lock); 	a = dsa_mac_addr_find(&dp->mdbs, mdb->addr, mdb->vid, db);	if (!a) {		trace_dsa_mdb_del_not_found(dp, mdb->addr, mdb->vid, &db);		err = -ENOENT;		goto out;	} 	if (!refcount_dec_and_test(&a->refcount)) {		trace_dsa_mdb_del_drop(dp, mdb->addr, mdb->vid, &db,				       &a->refcount);		goto out;	} 	err = ds->ops->port_mdb_del(ds, port, mdb, db);	trace_dsa_mdb_del_hw(dp, mdb->addr, mdb->vid, &db, err);	if (err) {		refcount_set(&a->refcount, 1);		goto out;	} 	list_del(&a->list);	kfree(a); out:	mutex_unlock(&dp->addr_lists_lock); 	return err;} static int dsa_port_do_fdb_add(struct dsa_port *dp, const unsigned char *addr,			       u16 vid, struct dsa_db db){	struct dsa_switch *ds = dp->ds;	struct dsa_mac_addr *a;	int port = dp->index;	int err = 0; 	/* No need to bother with refcounting for user ports */	if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) {		err = ds->ops->port_fdb_add(ds, port, addr, vid, db);		trace_dsa_fdb_add_hw(dp, addr, vid, &db, err); 		return err;	} 	mutex_lock(&dp->addr_lists_lock); 	a = dsa_mac_addr_find(&dp->fdbs, addr, vid, db);	if (a) {		refcount_inc(&a->refcount);		trace_dsa_fdb_add_bump(dp, addr, vid, &db, &a->refcount);		goto out;	} 	a = kzalloc_obj(*a);	if (!a) {		err = -ENOMEM;		goto out;	} 	err = ds->ops->port_fdb_add(ds, port, addr, vid, db);	trace_dsa_fdb_add_hw(dp, addr, vid, &db, err);	if (err) {		kfree(a);		goto out;	} 	ether_addr_copy(a->addr, addr);	a->vid = vid;	a->db = db;	refcount_set(&a->refcount, 1);	list_add_tail(&a->list, &dp->fdbs); out:	mutex_unlock(&dp->addr_lists_lock); 	return err;} static int dsa_port_do_fdb_del(struct dsa_port *dp, const unsigned char *addr,			       u16 vid, struct dsa_db db){	struct dsa_switch *ds = dp->ds;	struct dsa_mac_addr *a;	int port = dp->index;	int err = 0; 	/* No need to bother with refcounting for user ports */	if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) {		err = ds->ops->port_fdb_del(ds, port, addr, vid, db);		trace_dsa_fdb_del_hw(dp, addr, vid, &db, err); 		return err;	} 	mutex_lock(&dp->addr_lists_lock); 	a = dsa_mac_addr_find(&dp->fdbs, addr, vid, db);	if (!a) {		trace_dsa_fdb_del_not_found(dp, addr, vid, &db);		err = -ENOENT;		goto out;	} 	if (!refcount_dec_and_test(&a->refcount)) {		trace_dsa_fdb_del_drop(dp, addr, vid, &db, &a->refcount);		goto out;	} 	err = ds->ops->port_fdb_del(ds, port, addr, vid, db);	trace_dsa_fdb_del_hw(dp, addr, vid, &db, err);	if (err) {		refcount_set(&a->refcount, 1);		goto out;	} 	list_del(&a->list);	kfree(a); out:	mutex_unlock(&dp->addr_lists_lock); 	return err;} static int dsa_switch_do_lag_fdb_add(struct dsa_switch *ds, struct dsa_lag *lag,				     const unsigned char *addr, u16 vid,				     struct dsa_db db){	struct dsa_mac_addr *a;	int err = 0; 	mutex_lock(&lag->fdb_lock); 	a = dsa_mac_addr_find(&lag->fdbs, addr, vid, db);	if (a) {		refcount_inc(&a->refcount);		trace_dsa_lag_fdb_add_bump(lag->dev, addr, vid, &db,					   &a->refcount);		goto out;	} 	a = kzalloc_obj(*a);	if (!a) {		err = -ENOMEM;		goto out;	} 	err = ds->ops->lag_fdb_add(ds, *lag, addr, vid, db);	trace_dsa_lag_fdb_add_hw(lag->dev, addr, vid, &db, err);	if (err) {		kfree(a);		goto out;	} 	ether_addr_copy(a->addr, addr);	a->vid = vid;	a->db = db;	refcount_set(&a->refcount, 1);	list_add_tail(&a->list, &lag->fdbs); out:	mutex_unlock(&lag->fdb_lock); 	return err;} static int dsa_switch_do_lag_fdb_del(struct dsa_switch *ds, struct dsa_lag *lag,				     const unsigned char *addr, u16 vid,				     struct dsa_db db){	struct dsa_mac_addr *a;	int err = 0; 	mutex_lock(&lag->fdb_lock); 	a = dsa_mac_addr_find(&lag->fdbs, addr, vid, db);	if (!a) {		trace_dsa_lag_fdb_del_not_found(lag->dev, addr, vid, &db);		err = -ENOENT;		goto out;	} 	if (!refcount_dec_and_test(&a->refcount)) {		trace_dsa_lag_fdb_del_drop(lag->dev, addr, vid, &db,					   &a->refcount);		goto out;	} 	err = ds->ops->lag_fdb_del(ds, *lag, addr, vid, db);	trace_dsa_lag_fdb_del_hw(lag->dev, addr, vid, &db, err);	if (err) {		refcount_set(&a->refcount, 1);		goto out;	} 	list_del(&a->list);	kfree(a); out:	mutex_unlock(&lag->fdb_lock); 	return err;} static int dsa_switch_host_fdb_add(struct dsa_switch *ds,				   struct dsa_notifier_fdb_info *info){	struct dsa_port *dp;	int err = 0; 	if (!ds->ops->port_fdb_add)		return -EOPNOTSUPP; 	dsa_switch_for_each_port(dp, ds) {		if (dsa_port_host_address_match(dp, info->dp)) {			if (dsa_port_is_cpu(dp) && info->dp->cpu_port_in_lag) {				err = dsa_switch_do_lag_fdb_add(ds, dp->lag,								info->addr,								info->vid,								info->db);			} else {				err = dsa_port_do_fdb_add(dp, info->addr,							  info->vid, info->db);			}			if (err)				break;		}	} 	return err;} static int dsa_switch_host_fdb_del(struct dsa_switch *ds,				   struct dsa_notifier_fdb_info *info){	struct dsa_port *dp;	int err = 0; 	if (!ds->ops->port_fdb_del)		return -EOPNOTSUPP; 	dsa_switch_for_each_port(dp, ds) {		if (dsa_port_host_address_match(dp, info->dp)) {			if (dsa_port_is_cpu(dp) && info->dp->cpu_port_in_lag) {				err = dsa_switch_do_lag_fdb_del(ds, dp->lag,								info->addr,								info->vid,								info->db);			} else {				err = dsa_port_do_fdb_del(dp, info->addr,							  info->vid, info->db);			}			if (err)				break;		}	} 	return err;} static int dsa_switch_fdb_add(struct dsa_switch *ds,			      struct dsa_notifier_fdb_info *info){	int port = dsa_towards_port(ds, info->dp->ds->index, info->dp->index);	struct dsa_port *dp = dsa_to_port(ds, port); 	if (!ds->ops->port_fdb_add)		return -EOPNOTSUPP; 	return dsa_port_do_fdb_add(dp, info->addr, info->vid, info->db);}

Lines 1–500 of 1134.