Subject: [PATCH RFC] openvswitch: add support for netpoll

From: Konstantin Khlebnikov <[email-masked]>
Date: Thu, 23 Apr 2015 18:01:27 +0300

This patch simply forwards unicast netpoll packets via one of physical
interface in datapath depending on source mac address from the skb.

It seems possible to use common net flow classification for netpoll but
there is no way to guarantee presence of right flow in kernel cache.

Signed-off-by: Konstantin Khlebnikov <[email-masked]>
---
 net/openvswitch/vport-internal_dev.c |   74 ++++++++++++++++++++++++++++++++++
 net/openvswitch/vport-netdev.c       |   63 ++++++++++++++++++++++++++++-
 net/openvswitch/vport-netdev.h       |   15 +++++++
 3 files changed, 148 insertions(+), 4 deletions(-)

diff --git a/net/openvswitch/vport-internal_dev.c b/net/openvswitch/vport-internal_dev.c
index 6a55f7105505..d1eb09ac09e8 100644
--- a/net/openvswitch/vport-internal_dev.c
+++ b/net/openvswitch/vport-internal_dev.c
@@ -23,6 +23,7 @@
 #include <linux/etherdevice.h>
 #include <linux/ethtool.h>
 #include <linux/skbuff.h>
+#include <linux/netpoll.h>
 
 #include <net/dst.h>
 #include <net/xfrm.h>
@@ -66,11 +67,77 @@ static struct rtnl_link_stats64 *internal_dev_get_stats(struct net_device *netde
 	return stats;
 }
 
+#ifdef CONFIG_NET_POLL_CONTROLLER
+
+static void internal_dev_poll_controller(struct net_device *dev)
+{
+}
+
+static struct netdev_vport *get_local_netdev_vport(struct net_device *dev)
+{
+	struct datapath *dp = internal_dev_priv(dev)->vport->dp;
+
+	return netdev_vport_priv(ovs_lookup_vport(dp, OVSP_LOCAL));
+}
+
+static int internal_dev_netpoll_setup(struct net_device *internal_dev,
+				      struct netpoll_info *info)
+{
+	struct netdev_vport *local = get_local_netdev_vport(internal_dev);
+	struct netdev_vport *lower;
+	struct list_head *iter;
+	int ret = -EOPNOTSUPP;
+
+	ASSERT_RTNL();
+	/* succeed if at least one lower device can handle netpoll */
+	netdev_for_each_lower_private(local->dev, lower, iter)
+		if (!ovs_netdev_netpoll_enable(lower))
+			ret = 0;
+	/* enable netpoll on local device as mark for new devices */
+	if (!ret && local->dev != internal_dev)
+		ovs_netdev_netpoll_enable(local);
+	return ret;
+}
+
+static void internal_dev_netpoll_cleanup(struct net_device *internal_dev)
+{
+	struct netdev_vport *local = get_local_netdev_vport(internal_dev);
+	struct netdev_vport *lower;
+	struct list_head *iter;
+
+	/* FIXME needs reference counting for more than one netpoll in dp */
+
+	ASSERT_RTNL();
+	netdev_for_each_lower_private(local->dev, lower, iter)
+		ovs_netdev_netpoll_disable(lower);
+	ovs_netdev_netpoll_disable(local);
+}
+
+static void internal_dev_netpoll_xmit(struct sk_buff *skb,
+				      struct net_device *internal_dev)
+{
+	struct netdev_vport *local = get_local_netdev_vport(internal_dev);
+	struct netdev_vport *lower;
+	struct list_head *iter;
+
+	netdev_for_each_lower_private_rcu(local->dev, lower, iter)
+		if (!ovs_netdev_netpoll_send(lower, skb))
+			return; /* Unicast only, first gets all. */
+	dev_kfree_skb_irq(skb);
+}
+
+#endif /* CONFIG_NET_POLL_CONTROLLER */
+
 /* Called with rcu_read_lock_bh. */
 static int internal_dev_xmit(struct sk_buff *skb, struct net_device *netdev)
 {
 	rcu_read_lock();
-	ovs_vport_receive(internal_dev_priv(netdev)->vport, skb, NULL);
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	if (unlikely(netpoll_tx_running(netdev)))
+		internal_dev_netpoll_xmit(skb, netdev);
+	else
+#endif
+		ovs_vport_receive(internal_dev_priv(netdev)->vport, skb, NULL);
 	rcu_read_unlock();
 	return 0;
 }
@@ -122,6 +189,11 @@ static const struct net_device_ops internal_dev_netdev_ops = {
 	.ndo_set_mac_address = eth_mac_addr,
 	.ndo_change_mtu = internal_dev_change_mtu,
 	.ndo_get_stats64 = internal_dev_get_stats,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	.ndo_poll_controller = internal_dev_poll_controller,
+	.ndo_netpoll_setup = internal_dev_netpoll_setup,
+	.ndo_netpoll_cleanup = internal_dev_netpoll_cleanup,
+#endif
 };
 
 static struct rtnl_link_ops internal_dev_link_ops __read_mostly = {
diff --git a/net/openvswitch/vport-netdev.c b/net/openvswitch/vport-netdev.c
index 4776282c6417..324fb078d32a 100644
--- a/net/openvswitch/vport-netdev.c
+++ b/net/openvswitch/vport-netdev.c
@@ -26,6 +26,7 @@
 #include <linux/rtnetlink.h>
 #include <linux/skbuff.h>
 #include <linux/openvswitch.h>
+#include <linux/netpoll.h>
 
 #include <net/llc.h>
 
@@ -86,10 +87,59 @@ static struct net_device *get_dpdev(const struct datapath *dp)
 	return netdev_vport_priv(local)->dev;
 }
 
+#ifdef CONFIG_NET_POLL_CONTROLLER
+int ovs_netdev_netpoll_enable(struct netdev_vport *netdev_vport)
+{
+	struct netpoll *np;
+	int err;
+
+	if (netdev_vport->np)
+		return 0;
+
+	np = kzalloc(sizeof(*np), GFP_KERNEL);
+	if (!np)
+		return -ENOMEM;
+
+	err = __netpoll_setup(np, netdev_vport->dev);
+	if (err)
+		kfree(np);
+	else
+		netdev_vport->np = np;
+	return err;
+}
+
+void ovs_netdev_netpoll_disable(struct netdev_vport *netdev_vport)
+{
+	struct netpoll *np = netdev_vport->np;
+
+	if (np) {
+		netdev_vport->np = NULL;
+		__netpoll_free_async(np);
+	}
+}
+
+int ovs_netdev_netpoll_send(struct netdev_vport *netdev_vport,
+			    struct sk_buff *skb)
+{
+	struct ethhdr *eth = (struct ethhdr *)skb->data;
+
+	if (!netdev_vport->np)
+		return -EOPNOTSUPP;
+
+	if (!ether_addr_equal(eth->h_source, netdev_vport->dev->dev_addr))
+		return -EXDEV;  /* Not ours */
+
+	skb->dev = netdev_vport->dev;
+	netpoll_send_skb_on_dev(netdev_vport->np, skb, netdev_vport->dev);
+	return 0;
+}
+#endif /* CONFIG_NET_POLL_CONTROLLER */
+
 static struct vport *netdev_create(const struct vport_parms *parms)
 {
 	struct vport *vport;
 	struct netdev_vport *netdev_vport;
+	struct net_device *dpdev;
 	int err;
 
 	vport = ovs_vport_alloc(sizeof(struct netdev_vport),
@@ -115,11 +165,16 @@ static struct vport *netdev_create(const struct vport_parms *parms)
 	}
 
 	rtnl_lock();
-	err = netdev_master_upper_dev_link(netdev_vport->dev,
-					   get_dpdev(vport->dp));
+	call_netdevice_notifiers(NETDEV_JOIN, netdev_vport->dev);
+	dpdev = get_dpdev(vport->dp);
+	err = netdev_master_upper_dev_link_private(netdev_vport->dev,
+						   dpdev, netdev_vport);
 	if (err)
 		goto error_unlock;
 
+	if (dpdev->npinfo)
+		ovs_netdev_netpoll_enable(netdev_vport);
+
 	err = netdev_rx_handler_register(netdev_vport->dev, netdev_frame_hook,
 					 vport);
 	if (err)
@@ -132,7 +187,8 @@ static struct vport *netdev_create(const struct vport_parms *parms)
 	return vport;
 
 error_master_upper_dev_unlink:
-	netdev_upper_dev_unlink(netdev_vport->dev, get_dpdev(vport->dp));
+	ovs_netdev_netpoll_disable(netdev_vport);
+	netdev_upper_dev_unlink(netdev_vport->dev, dpdev);
 error_unlock:
 	rtnl_unlock();
 error_put:
@@ -159,6 +215,7 @@ void ovs_netdev_detach_dev(struct vport *vport)
 	ASSERT_RTNL();
 	netdev_vport->dev->priv_flags &= ~IFF_OVS_DATAPATH;
 	netdev_rx_handler_unregister(netdev_vport->dev);
+	ovs_netdev_netpoll_disable(netdev_vport);
 	netdev_upper_dev_unlink(netdev_vport->dev,
 				netdev_master_upper_dev_get(netdev_vport->dev));
 	dev_set_promiscuity(netdev_vport->dev, -1);
diff --git a/net/openvswitch/vport-netdev.h b/net/openvswitch/vport-netdev.h
index 6f7038e79c52..a9085522d73c 100644
--- a/net/openvswitch/vport-netdev.h
+++ b/net/openvswitch/vport-netdev.h
@@ -30,6 +30,9 @@ struct netdev_vport {
 	struct rcu_head rcu;
 
 	struct net_device *dev;
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	struct netpoll *np;
+#endif
 };
 
 static inline struct netdev_vport *
@@ -44,4 +47,16 @@ void ovs_netdev_detach_dev(struct vport *);
 int __init ovs_netdev_init(void);
 void ovs_netdev_exit(void);
 
+#ifdef CONFIG_NET_POLL_CONTROLLER
+int ovs_netdev_netpoll_enable(struct netdev_vport *);
+void ovs_netdev_netpoll_disable(struct netdev_vport *);
+int ovs_netdev_netpoll_send(struct netdev_vport *, struct sk_buff *);
+#else
+static inline int ovs_netdev_netpoll_enable(struct netdev_vport *nv)
+{
+	return -EOPNOTSUPP;
+}
+static inline void ovs_netdev_netpoll_disable(struct netdev_vport *nv) { }
+#endif
+
 #endif /* vport_netdev.h */

_______________________________________________
dev mailing list
[email-masked]
http://openvswitch.org/mailman/listinfo/dev