netdev: Clean up and refactor packet receive interface.
[sliver-openvswitch.git] / lib / netdev.c
index c7de906..9954929 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
 #include "packets.h"
 #include "poll-loop.h"
 #include "shash.h"
+#include "sset.h"
 #include "svec.h"
 #include "vlog.h"
 
@@ -176,88 +177,26 @@ netdev_lookup_provider(const char *type)
 }
 
 /* Clears 'types' and enumerates the types of all currently registered netdev
- * providers into it.  The caller must first initialize the svec. */
+ * providers into it.  The caller must first initialize the sset. */
 void
-netdev_enumerate_types(struct svec *types)
+netdev_enumerate_types(struct sset *types)
 {
     struct shash_node *node;
 
     netdev_initialize();
-    svec_clear(types);
+    sset_clear(types);
 
     SHASH_FOR_EACH(node, &netdev_classes) {
         const struct netdev_class *netdev_class = node->data;
-        svec_add(types, netdev_class->type);
+        sset_add(types, netdev_class->type);
     }
 }
 
-/* Compares 'args' to those used to those used by 'dev'.  Returns true
- * if the arguments are the same, false otherwise.  Does not update the
- * values stored in 'dev'. */
-static bool
-compare_device_args(const struct netdev_dev *dev, const struct shash *args)
-{
-    const struct shash_node **new_args;
-    bool result = true;
-    int i;
-
-    if (shash_count(args) != dev->n_args) {
-        return false;
-    }
-
-    new_args = shash_sort(args);
-    for (i = 0; i < dev->n_args; i++) {
-        if (strcmp(dev->args[i].key, new_args[i]->name) ||
-            strcmp(dev->args[i].value, new_args[i]->data)) {
-            result = false;
-            goto finish;
-        }
-    }
-
-finish:
-    free(new_args);
-    return result;
-}
-
-static int
-compare_args(const void *a_, const void *b_)
-{
-    const struct arg *a = a_;
-    const struct arg *b = b_;
-    return strcmp(a->key, b->key);
-}
-
 void
 update_device_args(struct netdev_dev *dev, const struct shash *args)
 {
-    struct shash_node *node;
-    int i;
-
-    if (dev->n_args) {
-        for (i = 0; i < dev->n_args; i++) {
-            free(dev->args[i].key);
-            free(dev->args[i].value);
-        }
-
-        free(dev->args);
-        dev->n_args = 0;
-    }
-
-    if (!args || shash_is_empty(args)) {
-        return;
-    }
-
-    dev->n_args = shash_count(args);
-    dev->args = xmalloc(dev->n_args * sizeof *dev->args);
-
-    i = 0;
-    SHASH_FOR_EACH(node, args) {
-        dev->args[i].key = xstrdup(node->name);
-        dev->args[i].value = xstrdup(node->data);
-        i++;
-    }
-
-    qsort(dev->args, dev->n_args, sizeof *dev->args, compare_args);
+    smap_destroy(&dev->args);
+    smap_clone(&dev->args, args);
 }
 
 /* Opens the network device named 'name' (e.g. "eth0") and returns zero if
@@ -265,12 +204,8 @@ update_device_args(struct netdev_dev *dev, const struct shash *args)
  * to the new network device, otherwise to null.
  *
  * If this is the first time the device has been opened, then create is called
- * before opening.  The device is created using the given type and arguments.
- *
- * 'ethertype' may be a 16-bit Ethernet protocol value in host byte order to
- * capture frames of that type received on the device.  It may also be one of
- * the 'enum netdev_pseudo_ethertype' values to receive frames in one of those
- * categories. */
+ * before opening.  The device is created using the given type and
+ * arguments. */
 int
 netdev_open(struct netdev_options *options, struct netdev **netdevp)
 {
@@ -302,18 +237,16 @@ netdev_open(struct netdev_options *options, struct netdev **netdevp)
             return error;
         }
         assert(netdev_dev->netdev_class == class);
-        update_device_args(netdev_dev, options->args);
 
     } else if (!shash_is_empty(options->args) &&
-               !compare_device_args(netdev_dev, options->args)) {
+               !netdev_dev_args_equal(netdev_dev, options->args)) {
 
         VLOG_WARN("%s: attempted to open already open netdev with "
                   "different arguments", options->name);
         return EINVAL;
     }
 
-    error = netdev_dev->netdev_class->open(netdev_dev, options->ethertype,
-                netdevp);
+    error = netdev_dev->netdev_class->open(netdev_dev, netdevp);
 
     if (!error) {
         netdev_dev->ref_cnt++;
@@ -333,7 +266,6 @@ netdev_open_default(const char *name, struct netdev **netdevp)
 
     memset(&options, 0, sizeof options);
     options.name = name;
-    options.ethertype = NETDEV_ETH_TYPE_NONE;
 
     return netdev_open(&options, netdevp);
 }
@@ -341,7 +273,7 @@ netdev_open_default(const char *name, struct netdev **netdevp)
 /* Reconfigures the device 'netdev' with 'args'.  'args' may be empty
  * or NULL if none are needed. */
 int
-netdev_reconfigure(struct netdev *netdev, const struct shash *args)
+netdev_set_config(struct netdev *netdev, const struct shash *args)
 {
     struct shash empty_args = SHASH_INITIALIZER(&empty_args);
     struct netdev_dev *netdev_dev = netdev_get_dev(netdev);
@@ -350,19 +282,31 @@ netdev_reconfigure(struct netdev *netdev, const struct shash *args)
         args = &empty_args;
     }
 
-    if (netdev_dev->netdev_class->reconfigure) {
-        if (!compare_device_args(netdev_dev, args)) {
+    if (netdev_dev->netdev_class->set_config) {
+        if (!netdev_dev_args_equal(netdev_dev, args)) {
             update_device_args(netdev_dev, args);
-            return netdev_dev->netdev_class->reconfigure(netdev_dev, args);
+            return netdev_dev->netdev_class->set_config(netdev_dev, args);
         }
     } else if (!shash_is_empty(args)) {
-        VLOG_WARN("%s: arguments provided to device that does not have a "
-                  "reconfigure function", netdev_get_name(netdev));
+        VLOG_WARN("%s: arguments provided to device whose configuration "
+                  "cannot be changed", netdev_get_name(netdev));
     }
 
     return 0;
 }
 
+/* Returns the current configuration for 'netdev'.  This is either the
+ * configuration passed to netdev_open() or netdev_set_config(), or it is a
+ * configuration retrieved from the device itself if no configuration was
+ * passed to those functions.
+ *
+ * 'netdev' retains ownership of the returned configuration. */
+const struct shash *
+netdev_get_config(const struct netdev *netdev)
+{
+    return &netdev_get_dev(netdev)->args;
+}
+
 /* Closes and destroys 'netdev'. */
 void
 netdev_close(struct netdev *netdev)
@@ -410,20 +354,20 @@ netdev_is_open(const char *name)
     return !!shash_find_data(&netdev_dev_shash, name);
 }
 
-/*  Clears 'svec' and enumerates the names of all known network devices. */
+/*  Clears 'sset' and enumerates the names of all known network devices. */
 int
-netdev_enumerate(struct svec *svec)
+netdev_enumerate(struct sset *sset)
 {
     struct shash_node *node;
     int error = 0;
 
     netdev_initialize();
-    svec_clear(svec);
+    sset_clear(sset);
 
     SHASH_FOR_EACH(node, &netdev_classes) {
         const struct netdev_class *netdev_class = node->data;
         if (netdev_class->enumerate) {
-            int retval = netdev_class->enumerate(svec);
+            int retval = netdev_class->enumerate(sset);
             if (retval) {
                 VLOG_WARN("failed to enumerate %s network devices: %s",
                           netdev_class->type, strerror(retval));
@@ -437,6 +381,19 @@ netdev_enumerate(struct svec *svec)
     return error;
 }
 
+/* Attempts to set up 'netdev' for receiving packets with netdev_recv().
+ * Returns 0 if successful, otherwise a positive errno value.  EOPNOTSUPP
+ * indicates that the network device does not implement packet reception
+ * through this interface. */
+int
+netdev_listen(struct netdev *netdev)
+{
+    int (*listen)(struct netdev *);
+
+    listen = netdev_get_dev(netdev)->netdev_class->listen;
+    return listen ? (listen)(netdev) : EOPNOTSUPP;
+}
+
 /* Attempts to receive a packet from 'netdev' into 'buffer', which the caller
  * must have initialized with sufficient room for the packet.  The space
  * required to receive any packet is ETH_HEADER_LEN bytes, plus VLAN_HEADER_LEN
@@ -444,6 +401,9 @@ netdev_enumerate(struct svec *svec)
  * (Some devices do not allow for a VLAN header, in which case VLAN_HEADER_LEN
  * need not be included.)
  *
+ * This function can only be expected to return a packet if ->listen() has
+ * been called successfully.
+ *
  * If a packet is successfully retrieved, returns 0.  In this case 'buffer' is
  * guaranteed to contain at least ETH_TOTAL_MIN bytes.  Otherwise, returns a
  * positive errno value.  Returns EAGAIN immediately if no packet is ready to
@@ -572,8 +532,9 @@ netdev_get_name(const struct netdev *netdev)
  * (and received) packets, in bytes, not including the hardware header; thus,
  * this is typically 1500 bytes for Ethernet devices.
  *
- * If successful, returns 0 and stores the MTU size in '*mtup'.  On failure,
- * returns a positive errno value and stores ETH_PAYLOAD_MAX (1500) in
+ * If successful, returns 0 and stores the MTU size in '*mtup'.  Stores INT_MAX
+ * in '*mtup' if 'netdev' does not have an MTU (as e.g. some tunnels do not).On
+ * failure, returns a positive errno value and stores ETH_PAYLOAD_MAX (1500) in
  * '*mtup'. */
 int
 netdev_get_mtu(const struct netdev *netdev, int *mtup)
@@ -618,11 +579,11 @@ netdev_get_ifindex(const struct netdev *netdev)
  * Some network devices may not implement support for this function.  In such
  * cases this function will always return EOPNOTSUPP. */
 int
-netdev_get_features(struct netdev *netdev,
+netdev_get_features(const struct netdev *netdev,
                     uint32_t *current, uint32_t *advertised,
                     uint32_t *supported, uint32_t *peer)
 {
-    int (*get_features)(struct netdev *netdev,
+    int (*get_features)(const struct netdev *netdev,
                         uint32_t *current, uint32_t *advertised,
                         uint32_t *supported, uint32_t *peer);
     uint32_t dummy[4];
@@ -769,6 +730,21 @@ netdev_get_next_hop(const struct netdev *netdev,
     return error;
 }
 
+/* Populates 'sh' with status information.
+ *
+ * Populates 'sh' with 'netdev' specific status information.  This information
+ * may be used to populate the status column of the Interface table as defined
+ * in ovs-vswitchd.conf.db(5). */
+int
+netdev_get_status(const struct netdev *netdev, struct shash *sh)
+{
+    struct netdev_dev *dev = netdev_get_dev(netdev);
+
+    return (dev->netdev_class->get_status
+            ? dev->netdev_class->get_status(netdev, sh)
+            : EOPNOTSUPP);
+}
+
 /* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address and
  * returns 0.  Otherwise, returns a positive errno value and sets '*in6' to
  * all-zero-bits (in6addr_any).
@@ -880,7 +856,7 @@ netdev_turn_flags_off(struct netdev *netdev, enum netdev_flags flags,
  * ENXIO indicates that there is no ARP table entry for 'ip' on 'netdev'. */
 int
 netdev_arp_lookup(const struct netdev *netdev,
-                  uint32_t ip, uint8_t mac[ETH_ADDR_LEN])
+                  ovs_be32 ip, uint8_t mac[ETH_ADDR_LEN])
 {
     int error = (netdev_get_dev(netdev)->netdev_class->arp_lookup
                  ? netdev_get_dev(netdev)->netdev_class->arp_lookup(netdev,
@@ -920,6 +896,23 @@ netdev_get_carrier(const struct netdev *netdev)
     return carrier;
 }
 
+/* Attempts to force netdev_get_carrier() to poll 'netdev''s MII registers for
+ * link status instead of checking 'netdev''s carrier.  'netdev''s MII
+ * registers will be polled once ever 'interval' milliseconds.  If 'netdev'
+ * does not support MII, another method may be used as a fallback.  If
+ * 'interval' is less than or equal to zero, reverts netdev_get_carrier() to
+ * its normal behavior.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+int
+netdev_set_miimon_interval(struct netdev *netdev, long long int interval)
+{
+    struct netdev_dev *netdev_dev = netdev_get_dev(netdev);
+    return (netdev_dev->netdev_class->set_miimon_interval
+            ? netdev_dev->netdev_class->set_miimon_interval(netdev, interval)
+            : EOPNOTSUPP);
+}
+
 /* Retrieves current device stats for 'netdev'. */
 int
 netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
@@ -970,13 +963,13 @@ netdev_set_policing(struct netdev *netdev, uint32_t kbits_rate,
  * Every network device supports disabling QoS with a type of "", but this type
  * will not be added to 'types'.
  *
- * The caller must initialize 'types' (e.g. with svec_init()) before calling
+ * The caller must initialize 'types' (e.g. with sset_init()) before calling
  * this function.  The caller is responsible for destroying 'types' (e.g. with
- * svec_destroy()) when it is no longer needed.
+ * sset_destroy()) when it is no longer needed.
  *
  * Returns 0 if successful, otherwise a positive errno value. */
 int
-netdev_get_qos_types(const struct netdev *netdev, struct svec *types)
+netdev_get_qos_types(const struct netdev *netdev, struct sset *types)
 {
     const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
     return (class->get_qos_types
@@ -1240,6 +1233,19 @@ netdev_dump_queue_stats(const struct netdev *netdev,
             : EOPNOTSUPP);
 }
 
+/* Returns a sequence number which indicates changes in one of 'netdev''s
+ * properties.  The returned sequence will be nonzero so that callers have a
+ * value which they may use as a reset when tracking 'netdev'.
+ *
+ * The returned sequence number will change whenever 'netdev''s flags,
+ * features, ethernet address, or carrier changes.  It may change for other
+ * reasons as well, or no reason at all. */
+unsigned int
+netdev_change_seq(const struct netdev *netdev)
+{
+    return netdev_get_dev(netdev)->netdev_class->change_seq(netdev);
+}
+
 /* If 'netdev' is a VLAN network device (e.g. one created with vconfig(8)),
  * sets '*vlan_vid' to the VLAN VID associated with that device and returns 0.
  * Otherwise returns a errno value (specifically ENOENT if 'netdev_name' is the
@@ -1264,12 +1270,11 @@ struct netdev *
 netdev_find_dev_by_in4(const struct in_addr *in4)
 {
     struct netdev *netdev;
-    struct svec dev_list = SVEC_EMPTY_INITIALIZER;
-    size_t i;
+    struct sset dev_list = SSET_INITIALIZER(&dev_list);
+    const char *name;
 
     netdev_enumerate(&dev_list);
-    for (i = 0; i < dev_list.n; i++) {
-        const char *name = dev_list.names[i];
+    SSET_FOR_EACH (name, &dev_list) {
         struct in_addr dev_in4;
 
         if (!netdev_open_default(name, &netdev)
@@ -1282,18 +1287,25 @@ netdev_find_dev_by_in4(const struct in_addr *in4)
     netdev = NULL;
 
 exit:
-    svec_destroy(&dev_list);
+    sset_destroy(&dev_list);
     return netdev;
 }
 \f
-/* Initializes 'netdev_dev' as a netdev device named 'name' of the
- * specified 'netdev_class'.
+/* Initializes 'netdev_dev' as a netdev device named 'name' of the specified
+ * 'netdev_class'.  This function is ordinarily called from a netdev provider's
+ * 'create' function.
+ *
+ * 'args' should be the arguments that were passed to the netdev provider's
+ * 'create'.  If an empty set of arguments was passed, and 'name' is the name
+ * of a network device that existed before the 'create' call, then 'args' may
+ * instead be the configuration for that existing device.
  *
  * This function adds 'netdev_dev' to a netdev-owned shash, so it is
  * very important that 'netdev_dev' only be freed after calling
  * the refcount drops to zero.  */
 void
 netdev_dev_init(struct netdev_dev *netdev_dev, const char *name,
+                const struct shash *args,
                 const struct netdev_class *netdev_class)
 {
     assert(!shash_find(&netdev_dev_shash, name));
@@ -1302,6 +1314,7 @@ netdev_dev_init(struct netdev_dev *netdev_dev, const char *name,
     netdev_dev->netdev_class = netdev_class;
     netdev_dev->name = xstrdup(name);
     netdev_dev->node = shash_add(&netdev_dev_shash, name, netdev_dev);
+    smap_clone(&netdev_dev->args, args);
 }
 
 /* Undoes the results of initialization.
@@ -1319,7 +1332,7 @@ netdev_dev_uninit(struct netdev_dev *netdev_dev, bool destroy)
     assert(!netdev_dev->ref_cnt);
 
     shash_delete(&netdev_dev_shash, netdev_dev->node);
-    update_device_args(netdev_dev, NULL);
+    smap_destroy(&netdev_dev->args);
 
     if (destroy) {
         netdev_dev->netdev_class->destroy(netdev_dev);
@@ -1379,6 +1392,19 @@ netdev_dev_get_devices(const struct netdev_class *netdev_class,
     }
 }
 
+/* Returns true if 'args' is equivalent to the "args" field in
+ * 'netdev_dev', otherwise false. */
+bool
+netdev_dev_args_equal(const struct netdev_dev *netdev_dev,
+                      const struct shash *args)
+{
+    if (netdev_dev->netdev_class->config_equal) {
+        return netdev_dev->netdev_class->config_equal(netdev_dev, args);
+    } else {
+        return smap_equal(&netdev_dev->args, args);
+    }
+}
+
 /* Initializes 'netdev' as a instance of the netdev_dev.
  *
  * This function adds 'netdev' to a netdev-owned linked list, so it is very
@@ -1428,143 +1454,6 @@ netdev_get_dev(const struct netdev *netdev)
 {
     return netdev->netdev_dev;
 }
-
-/* Initializes 'notifier' as a netdev notifier for 'netdev', for which
- * notification will consist of calling 'cb', with auxiliary data 'aux'. */
-void
-netdev_notifier_init(struct netdev_notifier *notifier, struct netdev *netdev,
-                     void (*cb)(struct netdev_notifier *), void *aux)
-{
-    notifier->netdev = netdev;
-    notifier->cb = cb;
-    notifier->aux = aux;
-}
-\f
-/* Tracks changes in the status of a set of network devices. */
-struct netdev_monitor {
-    struct shash polled_netdevs;
-    struct shash changed_netdevs;
-};
-
-/* Creates and returns a new structure for monitor changes in the status of
- * network devices. */
-struct netdev_monitor *
-netdev_monitor_create(void)
-{
-    struct netdev_monitor *monitor = xmalloc(sizeof *monitor);
-    shash_init(&monitor->polled_netdevs);
-    shash_init(&monitor->changed_netdevs);
-    return monitor;
-}
-
-/* Destroys 'monitor'. */
-void
-netdev_monitor_destroy(struct netdev_monitor *monitor)
-{
-    if (monitor) {
-        struct shash_node *node;
-
-        SHASH_FOR_EACH (node, &monitor->polled_netdevs) {
-            struct netdev_notifier *notifier = node->data;
-            netdev_get_dev(notifier->netdev)->netdev_class->poll_remove(
-                    notifier);
-        }
-
-        shash_destroy(&monitor->polled_netdevs);
-        shash_destroy(&monitor->changed_netdevs);
-        free(monitor);
-    }
-}
-
-static void
-netdev_monitor_cb(struct netdev_notifier *notifier)
-{
-    struct netdev_monitor *monitor = notifier->aux;
-    const char *name = netdev_get_name(notifier->netdev);
-    shash_add_once(&monitor->changed_netdevs, name, NULL);
-}
-
-/* Attempts to add 'netdev' as a netdev monitored by 'monitor'.  Returns 0 if
- * successful, otherwise a positive errno value.
- *
- * Adding a given 'netdev' to a monitor multiple times is equivalent to adding
- * it once. */
-int
-netdev_monitor_add(struct netdev_monitor *monitor, struct netdev *netdev)
-{
-    const char *netdev_name = netdev_get_name(netdev);
-    int error = 0;
-    if (!shash_find(&monitor->polled_netdevs, netdev_name)
-            && netdev_get_dev(netdev)->netdev_class->poll_add)
-    {
-        struct netdev_notifier *notifier;
-        error = netdev_get_dev(netdev)->netdev_class->poll_add(netdev,
-                    netdev_monitor_cb, monitor, &notifier);
-        if (!error) {
-            assert(notifier->netdev == netdev);
-            shash_add(&monitor->polled_netdevs, netdev_name, notifier);
-        }
-    }
-    return error;
-}
-
-/* Removes 'netdev' from the set of netdevs monitored by 'monitor'.  (This has
- * no effect if 'netdev' is not in the set of devices monitored by
- * 'monitor'.) */
-void
-netdev_monitor_remove(struct netdev_monitor *monitor, struct netdev *netdev)
-{
-    const char *netdev_name = netdev_get_name(netdev);
-    struct shash_node *node;
-
-    node = shash_find(&monitor->polled_netdevs, netdev_name);
-    if (node) {
-        /* Cancel future notifications. */
-        struct netdev_notifier *notifier = node->data;
-        netdev_get_dev(netdev)->netdev_class->poll_remove(notifier);
-        shash_delete(&monitor->polled_netdevs, node);
-
-        /* Drop any pending notification. */
-        node = shash_find(&monitor->changed_netdevs, netdev_name);
-        if (node) {
-            shash_delete(&monitor->changed_netdevs, node);
-        }
-    }
-}
-
-/* Checks for changes to netdevs in the set monitored by 'monitor'.  If any of
- * the attributes (Ethernet address, carrier status, speed or peer-advertised
- * speed, flags, etc.) of a network device monitored by 'monitor' has changed,
- * sets '*devnamep' to the name of a device that has changed and returns 0.
- * The caller is responsible for freeing '*devnamep' (with free()).
- *
- * If no devices have changed, sets '*devnamep' to NULL and returns EAGAIN. */
-int
-netdev_monitor_poll(struct netdev_monitor *monitor, char **devnamep)
-{
-    struct shash_node *node = shash_first(&monitor->changed_netdevs);
-    if (!node) {
-        *devnamep = NULL;
-        return EAGAIN;
-    } else {
-        *devnamep = shash_steal(&monitor->changed_netdevs, node);
-        return 0;
-    }
-}
-
-/* Registers with the poll loop to wake up from the next call to poll_block()
- * when netdev_monitor_poll(monitor) would indicate that a device has
- * changed. */
-void
-netdev_monitor_poll_wait(const struct netdev_monitor *monitor)
-{
-    if (!shash_is_empty(&monitor->changed_netdevs)) {
-        poll_immediate_wake();
-    } else {
-        /* XXX Nothing needed here for netdev_linux, but maybe other netdev
-         * classes need help. */
-    }
-}
 \f
 /* Restore the network device flags on 'netdev' to those that were active
  * before we changed them.  Returns 0 if successful, otherwise a positive