netdev: New Function netdev_change_seq().
[sliver-openvswitch.git] / lib / netdev-linux.c
index cd104b3..eda1276 100644 (file)
@@ -68,6 +68,7 @@
 #include "socket-util.h"
 #include "shash.h"
 #include "sset.h"
+#include "timer.h"
 #include "vlog.h"
 
 VLOG_DEFINE_THIS_MODULE(netdev_linux);
@@ -345,6 +346,11 @@ struct netdev_dev_linux {
 
     struct shash_node *shash_node;
     unsigned int cache_valid;
+    unsigned int change_seq;
+
+    bool miimon;                    /* Link status of last poll. */
+    long long int miimon_interval;  /* Miimon Poll rate. Disabled if <= 0. */
+    struct timer miimon_timer;
 
     /* The following are figured out "on demand" only.  They are only valid
      * when the corresponding VALID_* bit in 'cache_valid' is set. */
@@ -411,6 +417,9 @@ static int set_etheraddr(const char *netdev_name, int hwaddr_family,
 static int get_stats_via_netlink(int ifindex, struct netdev_stats *stats);
 static int get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats);
 static int af_packet_sock(void);
+static void poll_notify(struct list *);
+static void netdev_linux_miimon_run(void);
+static void netdev_linux_miimon_wait(void);
 
 static bool
 is_netdev_linux_class(const struct netdev_class *netdev_class)
@@ -465,12 +474,24 @@ static void
 netdev_linux_run(void)
 {
     rtnetlink_link_notifier_run();
+    netdev_linux_miimon_run();
 }
 
 static void
 netdev_linux_wait(void)
 {
     rtnetlink_link_notifier_wait();
+    netdev_linux_miimon_wait();
+}
+
+static void
+netdev_dev_linux_changed(struct netdev_dev_linux *dev)
+{
+    dev->change_seq++;
+    if (!dev->change_seq) {
+        dev->change_seq++;
+    }
+    dev->cache_valid = 0;
 }
 
 static void
@@ -486,7 +507,7 @@ netdev_linux_cache_cb(const struct rtnetlink_link_change *change,
 
             if (is_netdev_linux_class(netdev_class)) {
                 dev = netdev_dev_linux_cast(base_dev);
-                dev->cache_valid = 0;
+                netdev_dev_linux_changed(dev);
             }
         }
     } else {
@@ -497,7 +518,7 @@ netdev_linux_cache_cb(const struct rtnetlink_link_change *change,
         netdev_dev_get_devices(&netdev_linux_class, &device_shash);
         SHASH_FOR_EACH (node, &device_shash) {
             dev = node->data;
-            dev->cache_valid = 0;
+            netdev_dev_linux_changed(dev);
         }
         shash_destroy(&device_shash);
     }
@@ -527,6 +548,7 @@ netdev_linux_create(const struct netdev_class *class,
     cache_notifier_refcount++;
 
     netdev_dev = xzalloc(sizeof *netdev_dev);
+    netdev_dev->change_seq = 1;
     netdev_dev_init(&netdev_dev->netdev_dev, name, args, class);
 
     *netdev_devp = &netdev_dev->netdev_dev;
@@ -673,7 +695,8 @@ netdev_linux_open(struct netdev_dev *netdev_dev_, int ethertype,
         protocol = (ethertype == NETDEV_ETH_TYPE_ANY ? ETH_P_ALL
                     : ethertype == NETDEV_ETH_TYPE_802_2 ? ETH_P_802_2
                     : ethertype);
-        netdev->fd = socket(PF_PACKET, SOCK_RAW, htons(protocol));
+        netdev->fd = socket(PF_PACKET, SOCK_RAW,
+                            (OVS_FORCE int) htons(protocol));
         if (netdev->fd < 0) {
             error = errno;
             goto error;
@@ -1003,6 +1026,11 @@ netdev_linux_get_carrier(const struct netdev *netdev_, bool *carrier)
     char *fn = NULL;
     int fd = -1;
 
+    if (netdev_dev->miimon_interval > 0) {
+        *carrier = netdev_dev->miimon;
+        return 0;
+    }
+
     if (!(netdev_dev->cache_valid & VALID_CARRIER)) {
         char line[8];
         int retval;
@@ -1053,36 +1081,34 @@ exit:
 }
 
 static int
-netdev_linux_do_miimon(const struct netdev *netdev, int cmd,
-                       const char *cmd_name, struct mii_ioctl_data *data)
+netdev_linux_do_miimon(const char *name, int cmd, const char *cmd_name,
+                       struct mii_ioctl_data *data)
 {
     struct ifreq ifr;
     int error;
 
     memset(&ifr, 0, sizeof ifr);
     memcpy(&ifr.ifr_data, data, sizeof *data);
-    error = netdev_linux_do_ioctl(netdev_get_name(netdev),
-                                  &ifr, cmd, cmd_name);
+    error = netdev_linux_do_ioctl(name, &ifr, cmd, cmd_name);
     memcpy(data, &ifr.ifr_data, sizeof *data);
 
     return error;
 }
 
 static int
-netdev_linux_get_miimon(const struct netdev *netdev, bool *miimon)
+netdev_linux_get_miimon(const char *name, bool *miimon)
 {
-    const char *name = netdev_get_name(netdev);
     struct mii_ioctl_data data;
     int error;
 
     *miimon = false;
 
     memset(&data, 0, sizeof data);
-    error = netdev_linux_do_miimon(netdev, SIOCGMIIPHY, "SIOCGMIIPHY", &data);
+    error = netdev_linux_do_miimon(name, SIOCGMIIPHY, "SIOCGMIIPHY", &data);
     if (!error) {
         /* data.phy_id is filled out by previous SIOCGMIIPHY miimon call. */
         data.reg_num = MII_BMSR;
-        error = netdev_linux_do_miimon(netdev, SIOCGMIIREG, "SIOCGMIIREG",
+        error = netdev_linux_do_miimon(name, SIOCGMIIREG, "SIOCGMIIREG",
                                        &data);
 
         if (!error) {
@@ -1112,6 +1138,76 @@ netdev_linux_get_miimon(const struct netdev *netdev, bool *miimon)
     return error;
 }
 
+static int
+netdev_linux_set_miimon_interval(struct netdev *netdev_,
+                                 long long int interval)
+{
+    struct netdev_dev_linux *netdev_dev;
+
+    netdev_dev = netdev_dev_linux_cast(netdev_get_dev(netdev_));
+
+    interval = interval > 0 ? MAX(interval, 100) : 0;
+    if (netdev_dev->miimon_interval != interval) {
+        netdev_dev->miimon_interval = interval;
+        timer_set_expired(&netdev_dev->miimon_timer);
+    }
+
+    return 0;
+}
+
+static void
+netdev_linux_miimon_run(void)
+{
+    struct shash device_shash;
+    struct shash_node *node;
+
+    shash_init(&device_shash);
+    netdev_dev_get_devices(&netdev_linux_class, &device_shash);
+    SHASH_FOR_EACH (node, &device_shash) {
+        struct netdev_dev_linux *dev = node->data;
+        bool miimon;
+
+        if (dev->miimon_interval <= 0 || !timer_expired(&dev->miimon_timer)) {
+            continue;
+        }
+
+        netdev_linux_get_miimon(dev->netdev_dev.name, &miimon);
+        if (miimon != dev->miimon) {
+            struct list *list;
+
+            dev->miimon = miimon;
+            list = shash_find_data(&netdev_linux_notifiers,
+                                   dev->netdev_dev.name);
+            if (list) {
+                poll_notify(list);
+            }
+            netdev_dev_linux_changed(dev);
+        }
+
+        timer_set_duration(&dev->miimon_timer, dev->miimon_interval);
+    }
+
+    shash_destroy(&device_shash);
+}
+
+static void
+netdev_linux_miimon_wait(void)
+{
+    struct shash device_shash;
+    struct shash_node *node;
+
+    shash_init(&device_shash);
+    netdev_dev_get_devices(&netdev_linux_class, &device_shash);
+    SHASH_FOR_EACH (node, &device_shash) {
+        struct netdev_dev_linux *dev = node->data;
+
+        if (dev->miimon_interval > 0) {
+            timer_wait(&dev->miimon_timer);
+        }
+    }
+    shash_destroy(&device_shash);
+}
+
 /* Check whether we can we use RTM_GETLINK to get network device statistics.
  * In pre-2.6.19 kernels, this was only available if wireless extensions were
  * enabled. */
@@ -2235,6 +2331,12 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
     }
 }
 
+static unsigned int
+netdev_linux_change_seq(const struct netdev *netdev)
+{
+    return netdev_dev_linux_cast(netdev_get_dev(netdev))->change_seq;
+}
+
 #define NETDEV_LINUX_CLASS(NAME, CREATE, ENUMERATE, SET_STATS)  \
 {                                                               \
     NAME,                                                       \
@@ -2264,7 +2366,7 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
     netdev_linux_get_mtu,                                       \
     netdev_linux_get_ifindex,                                   \
     netdev_linux_get_carrier,                                   \
-    netdev_linux_get_miimon,                                    \
+    netdev_linux_set_miimon_interval,                           \
     netdev_linux_get_stats,                                     \
     SET_STATS,                                                  \
                                                                 \
@@ -2295,7 +2397,8 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
     netdev_linux_update_flags,                                  \
                                                                 \
     netdev_linux_poll_add,                                      \
-    netdev_linux_poll_remove                                    \
+    netdev_linux_poll_remove,                                   \
+    netdev_linux_change_seq                                     \
 }
 
 const struct netdev_class netdev_linux_class =
@@ -3973,6 +4076,8 @@ netdev_stats_to_rtnl_link_stats64(struct rtnl_link_stats64 *dst,
                                   const struct netdev_stats *src)
 {
     COPY_NETDEV_STATS;
+    dst->rx_compressed = 0;
+    dst->tx_compressed = 0;
 }
 \f
 /* Utility functions. */