diff options
Diffstat (limited to 'net/switchdev/switchdev.c')
| -rw-r--r-- | net/switchdev/switchdev.c | 206 | 
1 files changed, 187 insertions, 19 deletions
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c index 8c1e558db118..c9bfa004abed 100644 --- a/net/switchdev/switchdev.c +++ b/net/switchdev/switchdev.c @@ -1,6 +1,7 @@  /*   * net/switchdev/switchdev.c - Switch device API   * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us> + * Copyright (c) 2014-2015 Scott Feldman <sfeldma@gmail.com>   *   * This program is free software; you can redistribute it and/or modify   * it under the terms of the GNU General Public License as published by @@ -14,6 +15,7 @@  #include <linux/mutex.h>  #include <linux/notifier.h>  #include <linux/netdevice.h> +#include <net/ip_fib.h>  #include <net/switchdev.h>  /** @@ -26,13 +28,13 @@  int netdev_switch_parent_id_get(struct net_device *dev,  				struct netdev_phys_item_id *psid)  { -	const struct net_device_ops *ops = dev->netdev_ops; +	const struct swdev_ops *ops = dev->swdev_ops; -	if (!ops->ndo_switch_parent_id_get) +	if (!ops || !ops->swdev_parent_id_get)  		return -EOPNOTSUPP; -	return ops->ndo_switch_parent_id_get(dev, psid); +	return ops->swdev_parent_id_get(dev, psid);  } -EXPORT_SYMBOL(netdev_switch_parent_id_get); +EXPORT_SYMBOL_GPL(netdev_switch_parent_id_get);  /**   *	netdev_switch_port_stp_update - Notify switch device port of STP @@ -44,20 +46,20 @@ EXPORT_SYMBOL(netdev_switch_parent_id_get);   */  int netdev_switch_port_stp_update(struct net_device *dev, u8 state)  { -	const struct net_device_ops *ops = dev->netdev_ops; +	const struct swdev_ops *ops = dev->swdev_ops; -	if (!ops->ndo_switch_port_stp_update) +	if (!ops || !ops->swdev_port_stp_update)  		return -EOPNOTSUPP; -	WARN_ON(!ops->ndo_switch_parent_id_get); -	return ops->ndo_switch_port_stp_update(dev, state); +	WARN_ON(!ops->swdev_parent_id_get); +	return ops->swdev_port_stp_update(dev, state);  } -EXPORT_SYMBOL(netdev_switch_port_stp_update); +EXPORT_SYMBOL_GPL(netdev_switch_port_stp_update);  static DEFINE_MUTEX(netdev_switch_mutex);  static RAW_NOTIFIER_HEAD(netdev_switch_notif_chain);  /** - *	register_netdev_switch_notifier - Register nofifier + *	register_netdev_switch_notifier - Register notifier   *	@nb: notifier_block   *   *	Register switch device notifier. This should be used by code @@ -73,10 +75,10 @@ int register_netdev_switch_notifier(struct notifier_block *nb)  	mutex_unlock(&netdev_switch_mutex);  	return err;  } -EXPORT_SYMBOL(register_netdev_switch_notifier); +EXPORT_SYMBOL_GPL(register_netdev_switch_notifier);  /** - *	unregister_netdev_switch_notifier - Unregister nofifier + *	unregister_netdev_switch_notifier - Unregister notifier   *	@nb: notifier_block   *   *	Unregister switch device notifier. @@ -91,10 +93,10 @@ int unregister_netdev_switch_notifier(struct notifier_block *nb)  	mutex_unlock(&netdev_switch_mutex);  	return err;  } -EXPORT_SYMBOL(unregister_netdev_switch_notifier); +EXPORT_SYMBOL_GPL(unregister_netdev_switch_notifier);  /** - *	call_netdev_switch_notifiers - Call nofifiers + *	call_netdev_switch_notifiers - Call notifiers   *	@val: value passed unmodified to notifier function   *	@dev: port device   *	@info: notifier information data @@ -114,7 +116,7 @@ int call_netdev_switch_notifiers(unsigned long val, struct net_device *dev,  	mutex_unlock(&netdev_switch_mutex);  	return err;  } -EXPORT_SYMBOL(call_netdev_switch_notifiers); +EXPORT_SYMBOL_GPL(call_netdev_switch_notifiers);  /**   *	netdev_switch_port_bridge_setlink - Notify switch device port of bridge @@ -139,7 +141,7 @@ int netdev_switch_port_bridge_setlink(struct net_device *dev,  	return ops->ndo_bridge_setlink(dev, nlh, flags);  } -EXPORT_SYMBOL(netdev_switch_port_bridge_setlink); +EXPORT_SYMBOL_GPL(netdev_switch_port_bridge_setlink);  /**   *	netdev_switch_port_bridge_dellink - Notify switch device port of bridge @@ -164,7 +166,7 @@ int netdev_switch_port_bridge_dellink(struct net_device *dev,  	return ops->ndo_bridge_dellink(dev, nlh, flags);  } -EXPORT_SYMBOL(netdev_switch_port_bridge_dellink); +EXPORT_SYMBOL_GPL(netdev_switch_port_bridge_dellink);  /**   *	ndo_dflt_netdev_switch_port_bridge_setlink - default ndo bridge setlink @@ -194,7 +196,7 @@ int ndo_dflt_netdev_switch_port_bridge_setlink(struct net_device *dev,  	return ret;  } -EXPORT_SYMBOL(ndo_dflt_netdev_switch_port_bridge_setlink); +EXPORT_SYMBOL_GPL(ndo_dflt_netdev_switch_port_bridge_setlink);  /**   *	ndo_dflt_netdev_switch_port_bridge_dellink - default ndo bridge dellink @@ -224,4 +226,170 @@ int ndo_dflt_netdev_switch_port_bridge_dellink(struct net_device *dev,  	return ret;  } -EXPORT_SYMBOL(ndo_dflt_netdev_switch_port_bridge_dellink); +EXPORT_SYMBOL_GPL(ndo_dflt_netdev_switch_port_bridge_dellink); + +static struct net_device *netdev_switch_get_lowest_dev(struct net_device *dev) +{ +	const struct swdev_ops *ops = dev->swdev_ops; +	struct net_device *lower_dev; +	struct net_device *port_dev; +	struct list_head *iter; + +	/* Recusively search down until we find a sw port dev. +	 * (A sw port dev supports swdev_parent_id_get). +	 */ + +	if (dev->features & NETIF_F_HW_SWITCH_OFFLOAD && +	    ops && ops->swdev_parent_id_get) +		return dev; + +	netdev_for_each_lower_dev(dev, lower_dev, iter) { +		port_dev = netdev_switch_get_lowest_dev(lower_dev); +		if (port_dev) +			return port_dev; +	} + +	return NULL; +} + +static struct net_device *netdev_switch_get_dev_by_nhs(struct fib_info *fi) +{ +	struct netdev_phys_item_id psid; +	struct netdev_phys_item_id prev_psid; +	struct net_device *dev = NULL; +	int nhsel; + +	/* For this route, all nexthop devs must be on the same switch. */ + +	for (nhsel = 0; nhsel < fi->fib_nhs; nhsel++) { +		const struct fib_nh *nh = &fi->fib_nh[nhsel]; + +		if (!nh->nh_dev) +			return NULL; + +		dev = netdev_switch_get_lowest_dev(nh->nh_dev); +		if (!dev) +			return NULL; + +		if (netdev_switch_parent_id_get(dev, &psid)) +			return NULL; + +		if (nhsel > 0) { +			if (prev_psid.id_len != psid.id_len) +				return NULL; +			if (memcmp(prev_psid.id, psid.id, psid.id_len)) +				return NULL; +		} + +		prev_psid = psid; +	} + +	return dev; +} + +/** + *	netdev_switch_fib_ipv4_add - Add IPv4 route entry to switch + * + *	@dst: route's IPv4 destination address + *	@dst_len: destination address length (prefix length) + *	@fi: route FIB info structure + *	@tos: route TOS + *	@type: route type + *	@nlflags: netlink flags passed in (NLM_F_*) + *	@tb_id: route table ID + * + *	Add IPv4 route entry to switch device. + */ +int netdev_switch_fib_ipv4_add(u32 dst, int dst_len, struct fib_info *fi, +			       u8 tos, u8 type, u32 nlflags, u32 tb_id) +{ +	struct net_device *dev; +	const struct swdev_ops *ops; +	int err = 0; + +	/* Don't offload route if using custom ip rules or if +	 * IPv4 FIB offloading has been disabled completely. +	 */ + +#ifdef CONFIG_IP_MULTIPLE_TABLES +	if (fi->fib_net->ipv4.fib_has_custom_rules) +		return 0; +#endif + +	if (fi->fib_net->ipv4.fib_offload_disabled) +		return 0; + +	dev = netdev_switch_get_dev_by_nhs(fi); +	if (!dev) +		return 0; +	ops = dev->swdev_ops; + +	if (ops->swdev_fib_ipv4_add) { +		err = ops->swdev_fib_ipv4_add(dev, htonl(dst), dst_len, +					      fi, tos, type, nlflags, +					      tb_id); +		if (!err) +			fi->fib_flags |= RTNH_F_EXTERNAL; +	} + +	return err; +} +EXPORT_SYMBOL_GPL(netdev_switch_fib_ipv4_add); + +/** + *	netdev_switch_fib_ipv4_del - Delete IPv4 route entry from switch + * + *	@dst: route's IPv4 destination address + *	@dst_len: destination address length (prefix length) + *	@fi: route FIB info structure + *	@tos: route TOS + *	@type: route type + *	@tb_id: route table ID + * + *	Delete IPv4 route entry from switch device. + */ +int netdev_switch_fib_ipv4_del(u32 dst, int dst_len, struct fib_info *fi, +			       u8 tos, u8 type, u32 tb_id) +{ +	struct net_device *dev; +	const struct swdev_ops *ops; +	int err = 0; + +	if (!(fi->fib_flags & RTNH_F_EXTERNAL)) +		return 0; + +	dev = netdev_switch_get_dev_by_nhs(fi); +	if (!dev) +		return 0; +	ops = dev->swdev_ops; + +	if (ops->swdev_fib_ipv4_del) { +		err = ops->swdev_fib_ipv4_del(dev, htonl(dst), dst_len, +					      fi, tos, type, tb_id); +		if (!err) +			fi->fib_flags &= ~RTNH_F_EXTERNAL; +	} + +	return err; +} +EXPORT_SYMBOL_GPL(netdev_switch_fib_ipv4_del); + +/** + *	netdev_switch_fib_ipv4_abort - Abort an IPv4 FIB operation + * + *	@fi: route FIB info structure + */ +void netdev_switch_fib_ipv4_abort(struct fib_info *fi) +{ +	/* There was a problem installing this route to the offload +	 * device.  For now, until we come up with more refined +	 * policy handling, abruptly end IPv4 fib offloading for +	 * for entire net by flushing offload device(s) of all +	 * IPv4 routes, and mark IPv4 fib offloading broken from +	 * this point forward. +	 */ + +	fib_flush_external(fi->fib_net); +	fi->fib_net->ipv4.fib_offload_disabled = true; +} +EXPORT_SYMBOL_GPL(netdev_switch_fib_ipv4_abort);  | 
