vswitchd: Add miimon support.
[sliver-openvswitch.git] / lib / netdev-linux.c
index 1efbfd8..f953cfc 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/ip.h>
 #include <linux/types.h>
 #include <linux/ethtool.h>
+#include <linux/mii.h>
 #include <linux/pkt_sched.h>
 #include <linux/rtnetlink.h>
 #include <linux/sockios.h>
@@ -59,6 +60,7 @@
 #include "packets.h"
 #include "poll-loop.h"
 #include "rtnetlink.h"
+#include "rtnetlink-link.h"
 #include "socket-util.h"
 #include "shash.h"
 #include "svec.h"
@@ -457,17 +459,17 @@ netdev_linux_init(void)
 static void
 netdev_linux_run(void)
 {
-    rtnetlink_notifier_run();
+    rtnetlink_link_notifier_run();
 }
 
 static void
 netdev_linux_wait(void)
 {
-    rtnetlink_notifier_wait();
+    rtnetlink_link_notifier_wait();
 }
 
 static void
-netdev_linux_cache_cb(const struct rtnetlink_change *change,
+netdev_linux_cache_cb(const struct rtnetlink_link_change *change,
                       void *aux OVS_UNUSED)
 {
     struct netdev_dev_linux *dev;
@@ -511,8 +513,8 @@ netdev_linux_create(const struct netdev_class *class,
     }
 
     if (!cache_notifier_refcount) {
-        error = rtnetlink_notifier_register(&netdev_linux_cache_notifier,
-                                            netdev_linux_cache_cb, NULL);
+        error = rtnetlink_link_notifier_register(&netdev_linux_cache_notifier,
+                                                 netdev_linux_cache_cb, NULL);
         if (error) {
             return error;
         }
@@ -608,7 +610,7 @@ netdev_linux_destroy(struct netdev_dev *netdev_dev_)
         cache_notifier_refcount--;
 
         if (!cache_notifier_refcount) {
-            rtnetlink_notifier_unregister(&netdev_linux_cache_notifier);
+            rtnetlink_link_notifier_unregister(&netdev_linux_cache_notifier);
         }
     } else if (class == &netdev_tap_class) {
         destroy_tap(netdev_dev);
@@ -1006,6 +1008,49 @@ exit:
     return error;
 }
 
+static int
+netdev_linux_get_miimon(const struct netdev *netdev_, bool *miimon)
+{
+    int error;
+    struct ifreq ifr;
+    const char *name = netdev_get_name(netdev_);
+
+    *miimon = false;
+    memset(&ifr, 0, sizeof ifr);
+
+    error = netdev_linux_do_ioctl(name, &ifr, SIOCGMIIPHY, "SIOCGMIIPHY");
+    if (!error) {
+        struct mii_ioctl_data *data = (struct mii_ioctl_data *)&ifr.ifr_data;
+
+        /* data->phy_id is filled out by previous SIOCGMIIPHY ioctl call. */
+        data->reg_num = MII_BMSR;
+        error = netdev_linux_do_ioctl(name, &ifr, SIOCGMIIREG, "SIOCGMIIREG");
+
+        if (!error) {
+            *miimon = !!(data->val_out & BMSR_LSTATUS);
+        } else {
+            VLOG_WARN_RL(&rl, "%s: failed to query MII", name);
+        }
+    } else {
+        struct ethtool_cmd ecmd;
+        struct ethtool_value *eval = (struct ethtool_value *) &ecmd;
+
+        VLOG_DBG_RL(&rl, "%s: failed to query MII, falling back to ethtool",
+                    name);
+
+        memset(&ecmd, 0, sizeof ecmd);
+        error = netdev_linux_do_ethtool(name, &ecmd, ETHTOOL_GLINK,
+                                        "ETHTOOL_GLINK");
+        if (!error) {
+            *miimon = !!eval->data;
+        } else {
+            VLOG_WARN_RL(&rl, "%s: ethtool link status failed", name);
+        }
+    }
+
+    return error;
+}
+
 /* 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. */
@@ -2049,7 +2094,7 @@ poll_notify(struct list *list)
 }
 
 static void
-netdev_linux_poll_cb(const struct rtnetlink_change *change,
+netdev_linux_poll_cb(const struct rtnetlink_link_change *change,
                      void *aux OVS_UNUSED)
 {
     if (change) {
@@ -2076,8 +2121,9 @@ netdev_linux_poll_add(struct netdev *netdev,
     struct list *list;
 
     if (shash_is_empty(&netdev_linux_notifiers)) {
-        int error = rtnetlink_notifier_register(&netdev_linux_poll_notifier,
-                                                   netdev_linux_poll_cb, NULL);
+        int error;
+        error = rtnetlink_link_notifier_register(&netdev_linux_poll_notifier,
+                                                 netdev_linux_poll_cb, NULL);
         if (error) {
             return error;
         }
@@ -2117,7 +2163,7 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
 
     /* If that was the last notifier, unregister. */
     if (shash_is_empty(&netdev_linux_notifiers)) {
-        rtnetlink_notifier_unregister(&netdev_linux_poll_notifier);
+        rtnetlink_link_notifier_unregister(&netdev_linux_poll_notifier);
     }
 }
 
@@ -2150,6 +2196,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_get_stats,                                     \
     SET_STATS,                                                  \
                                                                 \
@@ -2174,6 +2221,7 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
     netdev_linux_get_in6,                                       \
     netdev_linux_add_router,                                    \
     netdev_linux_get_next_hop,                                  \
+    NULL,                       /* get_status */                \
     netdev_linux_arp_lookup,                                    \
                                                                 \
     netdev_linux_update_flags,                                  \