+static int
+netdev_linux_do_miimon(const struct netdev *netdev, 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);
+ memcpy(data, &ifr.ifr_data, sizeof *data);
+
+ return error;
+}
+
+static int
+netdev_linux_get_miimon(const struct netdev *netdev, 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);
+ 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",
+ &data);
+
+ if (!error) {
+ *miimon = !!(data.val_out & BMSR_LSTATUS);
+ } else {
+ VLOG_WARN_RL(&rl, "%s: failed to query MII", name);
+ }
+ } else {
+ struct ethtool_cmd 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) {
+ struct ethtool_value eval;
+
+ memcpy(&eval, &ecmd, sizeof eval);
+ *miimon = !!eval.data;
+ } else {
+ VLOG_WARN_RL(&rl, "%s: ethtool link status failed", name);
+ }
+ }
+
+ return error;
+}
+