Use device notifier in Linux kernel switch for detecting port status changes.
authorJustin Pettit <jpettit@nicira.com>
Fri, 19 Sep 2008 23:10:12 +0000 (16:10 -0700)
committerJustin Pettit <jpettit@nicira.com>
Fri, 19 Sep 2008 23:10:28 +0000 (16:10 -0700)
To report on link status and whether a port is administratively enabled or
disabled, the reference switches poll.  This change uses the Linux kernel's
device notification system, so that polling isn't necessary.

datapath/Modules.mk
datapath/datapath.c
datapath/datapath.h
datapath/dp_notify.c [new file with mode: 0644]
datapath/linux-2.4/.gitignore
datapath/linux-2.6/.gitignore

index 23e54da71b0daf280680a17f02d160f5679feea6..f21af16b442b6dad39eb0f6e709ef8276f042556 100644 (file)
@@ -6,6 +6,7 @@ openflow_sources = \
        crc32.c \
        datapath.c \
        dp_dev.c \
+       dp_notify.c \
        flow.c \
        forward.c \
        table-hash.c \
index 0ec226d9dcf061c73428a4bbe2df51d7e69a4b8d..564d303979a64b6546d2537d15d100e0758a2780 100644 (file)
@@ -88,12 +88,10 @@ DEFINE_MUTEX(dp_mutex);
 EXPORT_SYMBOL(dp_mutex);
 
 static int dp_maint_func(void *data);
-static int update_port_status(struct net_bridge_port *p);
-static int send_port_status(struct net_bridge_port *p, uint8_t status);
+static void init_port_status(struct net_bridge_port *p);
 static int dp_genl_openflow_done(struct netlink_callback *);
 static struct net_bridge_port *new_nbp(struct datapath *,
                                       struct net_device *, int port_no);
-static int del_switch_port(struct net_bridge_port *);
 
 /* nla_shrink - reduce amount of space reserved by nla_reserve
  * @skb: socket buffer from which to recover room
@@ -309,7 +307,7 @@ static int new_dp(int dp_idx)
        return 0;
 
 err_destroy_local_port:
-       del_switch_port(dp->local_port);
+       dp_del_switch_port(dp->local_port);
 err_destroy_chain:
        chain_destroy(dp->chain);
 err_destroy_dp_dev:
@@ -378,16 +376,16 @@ int add_switch_port(struct datapath *dp, struct net_device *dev)
        if (IS_ERR(p))
                return PTR_ERR(p);
 
-       update_port_status(p);
+       init_port_status(p);
 
        /* Notify the ctlpath that this port has been added */
-       send_port_status(p, OFPPR_ADD);
+       dp_send_port_status(p, OFPPR_ADD);
 
        return 0;
 }
 
 /* Delete 'p' from switch. */
-static int del_switch_port(struct net_bridge_port *p)
+int dp_del_switch_port(struct net_bridge_port *p)
 {
        /* First drop references to device. */
        cancel_work_sync(&p->port_task);
@@ -403,7 +401,7 @@ static int del_switch_port(struct net_bridge_port *p)
        synchronize_rcu();
 
        /* Notify the ctlpath that this port no longer exists */
-       send_port_status(p, OFPPR_DELETE);
+       dp_send_port_status(p, OFPPR_DELETE);
 
        dev_put(p->dev);
        kfree(p);
@@ -419,7 +417,7 @@ static void del_dp(struct datapath *dp)
 
        /* Drop references to DP. */
        list_for_each_entry_safe (p, n, &dp->port_list, node)
-               del_switch_port(p);
+               dp_del_switch_port(p);
        rcu_assign_pointer(dps[dp->dp_idx], NULL);
 
        /* Kill off local_port dev references from buffered packets that have
@@ -443,15 +441,6 @@ static int dp_maint_func(void *data)
        struct datapath *dp = (struct datapath *) data;
 
        while (!kthread_should_stop()) {
-               struct net_bridge_port *p;
-
-               /* Check if port status has changed */
-               rcu_read_lock();
-               list_for_each_entry_rcu (p, &dp->port_list, node) 
-                       if (update_port_status(p)) 
-                               send_port_status(p, OFPPR_MOD);
-               rcu_read_unlock();
-
                /* Timeout old entries */
                chain_timeout(dp->chain);
                msleep_interruptible(MAINT_SLEEP_MSECS);
@@ -886,20 +875,13 @@ dp_update_port_flags(struct datapath *dp, const struct ofp_port_mod *opm)
        return 0;
 }
 
-/* Update the port status field of the bridge port.  A non-zero return
- * value indicates some field has changed. 
- *
- * NB: Callers of this function may hold the RCU read lock, so any
- * additional checks must not sleep.
- */
-static int
-update_port_status(struct net_bridge_port *p)
+/* Initialize the port status field of the bridge port. */
+static void
+init_port_status(struct net_bridge_port *p)
 {
        unsigned long int flags;
-       uint32_t orig_status;
 
        spin_lock_irqsave(&p->lock, flags);
-       orig_status = p->status;
 
        if (p->dev->flags & IFF_UP) 
                p->status &= ~OFPPFL_PORT_DOWN;
@@ -912,11 +894,10 @@ update_port_status(struct net_bridge_port *p)
                p->status |= OFPPFL_LINK_DOWN;
 
        spin_unlock_irqrestore(&p->lock, flags);
-       return (orig_status != p->status);
 }
 
-static int
-send_port_status(struct net_bridge_port *p, uint8_t status)
+int
+dp_send_port_status(struct net_bridge_port *p, uint8_t status)
 {
        struct sk_buff *skb;
        struct ofp_port_status *ops;
@@ -1162,7 +1143,7 @@ static int dp_genl_add_del_port(struct sk_buff *skb, struct genl_info *info)
                        err = -ENOENT;
                        goto out_put;
                }
-               err = del_switch_port(port->br_port);
+               err = dp_del_switch_port(port->br_port);
        }
 
 out_put:
@@ -1740,10 +1721,14 @@ static int __init dp_init(void)
        if (err)
                goto error;
 
-       err = dp_init_netlink();
+       err = register_netdevice_notifier(&dp_device_notifier);
        if (err)
                goto error_flow_exit;
 
+       err = dp_init_netlink();
+       if (err)
+               goto error_unreg_notifier;
+
        /* Hook into callback used by the bridge to intercept packets.
         * Parasites we are. */
        if (br_handle_frame_hook)
@@ -1752,6 +1737,8 @@ static int __init dp_init(void)
 
        return 0;
 
+error_unreg_notifier:
+       unregister_netdevice_notifier(&dp_device_notifier);
 error_flow_exit:
        flow_exit();
 error:
@@ -1763,6 +1750,7 @@ static void dp_cleanup(void)
 {
        fwd_exit();
        dp_uninit_netlink();
+       unregister_netdevice_notifier(&dp_device_notifier);
        flow_exit();
        br_handle_frame_hook = NULL;
 }
index 01fbaae1cd5d951d5242bcbaa69fc433cc3e5448..4339539a02d5e90b6cffd6c1918b2cd7f56349eb 100644 (file)
@@ -84,7 +84,9 @@ struct net_bridge_port {
 };
 
 extern struct mutex dp_mutex;
+extern struct notifier_block dp_device_notifier;
 
+int dp_del_switch_port(struct net_bridge_port *);
 int dp_output_port(struct datapath *, struct sk_buff *, int out_port,
                   int ignore_no_fwd);
 int dp_output_control(struct datapath *, struct sk_buff *, uint32_t, 
@@ -92,6 +94,7 @@ int dp_output_control(struct datapath *, struct sk_buff *, uint32_t,
 int dp_set_origin(struct datapath *, uint16_t, struct sk_buff *);
 int dp_send_features_reply(struct datapath *, const struct sender *);
 int dp_send_config_reply(struct datapath *, const struct sender *);
+int dp_send_port_status(struct net_bridge_port *p, uint8_t status);
 int dp_send_flow_expired(struct datapath *, struct sw_flow *,
                         enum ofp_flow_expired_reason);
 int dp_send_error_msg(struct datapath *, const struct sender *, 
diff --git a/datapath/dp_notify.c b/datapath/dp_notify.c
new file mode 100644 (file)
index 0000000..2885e7f
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Distributed under the terms of the GNU GPL version 2.
+ * Copyright (c) 2007, 2008 The Board of Trustees of The Leland 
+ * Stanford Junior University
+ */
+
+/* Handle changes to managed devices */
+
+#include <linux/netdevice.h>
+
+#include "datapath.h"
+
+
+static int dp_device_event(struct notifier_block *unused, unsigned long event, 
+               void *ptr) 
+{
+       struct net_device *dev = ptr;
+       struct net_bridge_port *p = dev->br_port;
+       unsigned long int flags;
+       uint32_t orig_status;
+
+
+       /* Check if monitored port */
+       if (!p)
+               return NOTIFY_DONE;
+
+       spin_lock_irqsave(&p->lock, flags);
+       orig_status = p->status;
+
+       switch (event) {
+               case NETDEV_CHANGE:
+                       if (netif_carrier_ok(p->dev))
+                               p->status &= ~OFPPFL_LINK_DOWN;
+                       else
+                               p->status |= OFPPFL_LINK_DOWN;
+                       break;
+
+               case NETDEV_DOWN:
+                       p->status |= OFPPFL_PORT_DOWN;
+                       break;
+
+               case NETDEV_UP:
+                       p->status &= ~OFPPFL_PORT_DOWN;
+                       break;
+
+               case NETDEV_UNREGISTER:
+                       /* xxx Make sure this is correct */
+                       spin_unlock_irqrestore(&p->lock, flags);
+                       dp_del_switch_port(p);
+                       return NOTIFY_DONE;
+                       break;
+       }
+       spin_unlock_irqrestore(&p->lock, flags);
+
+       if (orig_status != p->status) 
+               dp_send_port_status(p, OFPPR_MOD);
+
+       return NOTIFY_DONE;
+}
+
+struct notifier_block dp_device_notifier = {
+       .notifier_call = dp_device_event
+};
index 0b857d8d042f0824a862458ef74d9ea4bfc8763b..739e15716060e49fe68d6d0f3d1ac9b1a2f8c1ef 100644 (file)
@@ -9,6 +9,7 @@
 /crc32.c
 /crc_t.c
 /dp_dev.c
+/dp_notify.c
 /flow.c
 /forward.c
 /forward_t.c
index 634a8d99ed8361a5324d5ec5ba48168cb0334b3d..56f38adabb80952aec1055d496e2636cde3d03a4 100644 (file)
@@ -6,6 +6,7 @@
 /crc_t.c
 /datapath.c
 /dp_dev.c
+/dp_notify.c
 /flow.c
 /forward.c
 /forward_t.c