netdev: Make netdev access thread-safe.
[sliver-openvswitch.git] / lib / netdev-linux.c
index 5c36094..e569750 100644 (file)
@@ -352,6 +352,9 @@ static int tc_calc_buffer(unsigned int Bps, int mtu, uint64_t burst_bytes);
 struct netdev_linux {
     struct netdev up;
 
+    /* Protects all members below. */
+    struct ovs_mutex mutex;
+
     unsigned int cache_valid;
     unsigned int change_seq;
 
@@ -448,9 +451,11 @@ netdev_rx_linux_cast(const struct netdev_rx *rx)
 }
 \f
 static void netdev_linux_update(struct netdev_linux *netdev,
-                                const struct rtnetlink_link_change *);
+                                const struct rtnetlink_link_change *)
+    OVS_REQUIRES(netdev->mutex);
 static void netdev_linux_changed(struct netdev_linux *netdev,
-                                 unsigned int ifi_flags, unsigned int mask);
+                                 unsigned int ifi_flags, unsigned int mask)
+    OVS_REQUIRES(netdev->mutex);
 
 /* Returns a NETLINK_ROUTE socket listening for RTNLGRP_LINK changes, or NULL
  * if no such socket could be created. */
@@ -504,7 +509,11 @@ netdev_linux_run(void)
                 struct netdev *netdev_ = netdev_from_name(change.ifname);
                 if (netdev_ && is_netdev_linux_class(netdev_->netdev_class)) {
                     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+
+                    ovs_mutex_lock(&netdev->mutex);
                     netdev_linux_update(netdev, &change);
+                    ovs_mutex_unlock(&netdev->mutex);
+
                     netdev_close(netdev_);
                 }
             }
@@ -521,8 +530,11 @@ netdev_linux_run(void)
                 struct netdev_linux *netdev = netdev_linux_cast(netdev_);
                 unsigned int flags;
 
+                ovs_mutex_lock(&netdev->mutex);
                 get_flags(netdev_, &flags);
                 netdev_linux_changed(netdev, flags, 0);
+                ovs_mutex_unlock(&netdev->mutex);
+
                 netdev_close(netdev_);
             }
             shash_destroy(&device_shash);
@@ -549,6 +561,7 @@ netdev_linux_wait(void)
 static void
 netdev_linux_changed(struct netdev_linux *dev,
                      unsigned int ifi_flags, unsigned int mask)
+    OVS_REQUIRES(dev->mutex)
 {
     dev->change_seq++;
     if (!dev->change_seq) {
@@ -566,6 +579,7 @@ netdev_linux_changed(struct netdev_linux *dev,
 static void
 netdev_linux_update(struct netdev_linux *dev,
                     const struct rtnetlink_link_change *change)
+    OVS_REQUIRES(dev->mutex)
 {
     if (change->nlmsg_type == RTM_NEWLINK) {
         /* Keep drv-info */
@@ -603,6 +617,7 @@ netdev_linux_alloc(void)
 static void
 netdev_linux_common_construct(struct netdev_linux *netdev)
 {
+    ovs_mutex_init(&netdev->mutex, PTHREAD_MUTEX_NORMAL);
     netdev->change_seq = 1;
 }
 
@@ -693,6 +708,8 @@ netdev_linux_destruct(struct netdev *netdev_)
     {
         close(netdev->tap_fd);
     }
+
+    ovs_mutex_destroy(&netdev->mutex);
 }
 
 static void
@@ -717,6 +734,7 @@ netdev_linux_rx_construct(struct netdev_rx *rx_)
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     rx->is_tap = is_tap_netdev(netdev_);
     if (rx->is_tap) {
         rx->fd = netdev->tap_fd;
@@ -776,6 +794,7 @@ netdev_linux_rx_construct(struct netdev_rx *rx_)
             goto error;
         }
     }
+    ovs_mutex_unlock(&netdev->mutex);
 
     return 0;
 
@@ -783,6 +802,7 @@ error:
     if (rx->fd >= 0) {
         close(rx->fd);
     }
+    ovs_mutex_unlock(&netdev->mutex);
     return error;
 }
 
@@ -873,7 +893,6 @@ netdev_linux_send(struct netdev *netdev_, const void *data, size_t size)
             struct msghdr msg;
             struct iovec iov;
             int ifindex;
-            int error;
             int sock;
 
             sock = af_packet_sock();
@@ -881,9 +900,9 @@ netdev_linux_send(struct netdev *netdev_, const void *data, size_t size)
                 return -sock;
             }
 
-            error = get_ifindex(netdev_, &ifindex);
-            if (error) {
-                return error;
+            ifindex = netdev_get_ifindex(netdev_);
+            if (ifindex < 0) {
+                return -ifindex;
             }
 
             /* We don't bother setting most fields in sockaddr_ll because the
@@ -964,12 +983,12 @@ netdev_linux_set_etheraddr(struct netdev *netdev_,
     struct netdev_saved_flags *sf = NULL;
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
+
     if (netdev->cache_valid & VALID_ETHERADDR) {
-        if (netdev->ether_addr_error) {
-            return netdev->ether_addr_error;
-        }
-        if (eth_addr_equals(netdev->etheraddr, mac)) {
-            return 0;
+        error = netdev->ether_addr_error;
+        if (error || eth_addr_equals(netdev->etheraddr, mac)) {
+            goto exit;
         }
         netdev->cache_valid &= ~VALID_ETHERADDR;
     }
@@ -989,6 +1008,8 @@ netdev_linux_set_etheraddr(struct netdev *netdev_,
 
     netdev_restore_flags(sf);
 
+exit:
+    ovs_mutex_unlock(&netdev->mutex);
     return error;
 }
 
@@ -998,20 +1019,22 @@ netdev_linux_get_etheraddr(const struct netdev *netdev_,
                            uint8_t mac[ETH_ADDR_LEN])
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     if (!(netdev->cache_valid & VALID_ETHERADDR)) {
-        int error = get_etheraddr(netdev_get_name(netdev_),
-                                  netdev->etheraddr);
-
-        netdev->ether_addr_error = error;
+        netdev->ether_addr_error = get_etheraddr(netdev_get_name(netdev_),
+                                                 netdev->etheraddr);
         netdev->cache_valid |= VALID_ETHERADDR;
     }
 
-    if (!netdev->ether_addr_error) {
+    error = netdev->ether_addr_error;
+    if (!error) {
         memcpy(mac, netdev->etheraddr, ETH_ADDR_LEN);
     }
+    ovs_mutex_unlock(&netdev->mutex);
 
-    return netdev->ether_addr_error;
+    return error;
 }
 
 /* Returns the maximum size of transmitted (and received) packets on 'netdev',
@@ -1021,22 +1044,25 @@ static int
 netdev_linux_get_mtu(const struct netdev *netdev_, int *mtup)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    int error;
+
+    ovs_mutex_lock(&netdev->mutex);
     if (!(netdev->cache_valid & VALID_MTU)) {
         struct ifreq ifr;
-        int error;
-
-        error = af_inet_ifreq_ioctl(netdev_get_name(netdev_), &ifr,
-                                    SIOCGIFMTU, "SIOCGIFMTU");
 
-        netdev->netdev_mtu_error = error;
+        netdev->netdev_mtu_error = af_inet_ifreq_ioctl(
+            netdev_get_name(netdev_), &ifr, SIOCGIFMTU, "SIOCGIFMTU");
         netdev->mtu = ifr.ifr_mtu;
         netdev->cache_valid |= VALID_MTU;
     }
 
-    if (!netdev->netdev_mtu_error) {
+    error = netdev->netdev_mtu_error;
+    if (!error) {
         *mtup = netdev->mtu;
     }
-    return netdev->netdev_mtu_error;
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 /* Sets the maximum size of transmitted (MTU) for given device using linux
@@ -1049,12 +1075,11 @@ netdev_linux_set_mtu(const struct netdev *netdev_, int mtu)
     struct ifreq ifr;
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     if (netdev->cache_valid & VALID_MTU) {
-        if (netdev->netdev_mtu_error) {
-            return netdev->netdev_mtu_error;
-        }
-        if (netdev->mtu == mtu) {
-            return 0;
+        error = netdev->netdev_mtu_error;
+        if (error || netdev->mtu == mtu) {
+            goto exit;
         }
         netdev->cache_valid &= ~VALID_MTU;
     }
@@ -1066,17 +1091,23 @@ netdev_linux_set_mtu(const struct netdev *netdev_, int mtu)
         netdev->mtu = ifr.ifr_mtu;
         netdev->cache_valid |= VALID_MTU;
     }
+exit:
+    ovs_mutex_unlock(&netdev->mutex);
     return error;
 }
 
 /* Returns the ifindex of 'netdev', if successful, as a positive number.
  * On failure, returns a negative errno value. */
 static int
-netdev_linux_get_ifindex(const struct netdev *netdev)
+netdev_linux_get_ifindex(const struct netdev *netdev_)
 {
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int ifindex, error;
 
-    error = get_ifindex(netdev, &ifindex);
+    ovs_mutex_lock(&netdev->mutex);
+    error = get_ifindex(netdev_, &ifindex);
+    ovs_mutex_unlock(&netdev->mutex);
+
     return error ? -error : ifindex;
 }
 
@@ -1085,19 +1116,28 @@ netdev_linux_get_carrier(const struct netdev *netdev_, bool *carrier)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
 
+    ovs_mutex_lock(&netdev->mutex);
     if (netdev->miimon_interval > 0) {
         *carrier = netdev->miimon;
     } else {
         *carrier = (netdev->ifi_flags & IFF_RUNNING) != 0;
     }
+    ovs_mutex_unlock(&netdev->mutex);
 
     return 0;
 }
 
 static long long int
-netdev_linux_get_carrier_resets(const struct netdev *netdev)
+netdev_linux_get_carrier_resets(const struct netdev *netdev_)
 {
-    return netdev_linux_cast(netdev)->carrier_resets;
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    long long int carrier_resets;
+
+    ovs_mutex_lock(&netdev->mutex);
+    carrier_resets = netdev->carrier_resets;
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return carrier_resets;
 }
 
 static int
@@ -1165,11 +1205,13 @@ netdev_linux_set_miimon_interval(struct netdev *netdev_,
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
 
+    ovs_mutex_lock(&netdev->mutex);
     interval = interval > 0 ? MAX(interval, 100) : 0;
     if (netdev->miimon_interval != interval) {
         netdev->miimon_interval = interval;
         timer_set_expired(&netdev->miimon_timer);
     }
+    ovs_mutex_unlock(&netdev->mutex);
 
     return 0;
 }
@@ -1187,18 +1229,17 @@ netdev_linux_miimon_run(void)
         struct netdev_linux *dev = netdev_linux_cast(netdev);
         bool miimon;
 
-        if (dev->miimon_interval <= 0 || !timer_expired(&dev->miimon_timer)) {
-            netdev_close(netdev);
-            continue;
-        }
+        ovs_mutex_lock(&dev->mutex);
+        if (dev->miimon_interval > 0 && timer_expired(&dev->miimon_timer)) {
+            netdev_linux_get_miimon(dev->up.name, &miimon);
+            if (miimon != dev->miimon) {
+                dev->miimon = miimon;
+                netdev_linux_changed(dev, dev->ifi_flags, 0);
+            }
 
-        netdev_linux_get_miimon(dev->up.name, &miimon);
-        if (miimon != dev->miimon) {
-            dev->miimon = miimon;
-            netdev_linux_changed(dev, dev->ifi_flags, 0);
+            timer_set_duration(&dev->miimon_timer, dev->miimon_interval);
         }
-
-        timer_set_duration(&dev->miimon_timer, dev->miimon_interval);
+        ovs_mutex_unlock(&dev->mutex);
         netdev_close(netdev);
     }
 
@@ -1217,9 +1258,11 @@ netdev_linux_miimon_wait(void)
         struct netdev *netdev = node->data;
         struct netdev_linux *dev = netdev_linux_cast(netdev);
 
+        ovs_mutex_lock(&dev->mutex);
         if (dev->miimon_interval > 0) {
             timer_wait(&dev->miimon_timer);
         }
+        ovs_mutex_unlock(&dev->mutex);
         netdev_close(netdev);
     }
     shash_destroy(&device_shash);
@@ -1336,7 +1379,7 @@ get_stats_via_vport(const struct netdev *netdev_,
 
 static int
 netdev_linux_sys_get_stats(const struct netdev *netdev_,
-                         struct netdev_stats *stats)
+                           struct netdev_stats *stats)
 {
     static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
     static int use_netlink_stats;
@@ -1375,19 +1418,14 @@ netdev_linux_get_stats(const struct netdev *netdev_,
     struct netdev_stats dev_stats;
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     get_stats_via_vport(netdev_, stats);
-
     error = netdev_linux_sys_get_stats(netdev_, &dev_stats);
-
     if (error) {
-        if (netdev->vport_stats_error) {
-            return error;
-        } else {
-            return 0;
+        if (!netdev->vport_stats_error) {
+            error = 0;
         }
-    }
-
-    if (netdev->vport_stats_error) {
+    } else if (netdev->vport_stats_error) {
         /* stats not available from OVS then use ioctl stats. */
         *stats = dev_stats;
     } else {
@@ -1409,7 +1447,9 @@ netdev_linux_get_stats(const struct netdev *netdev_,
         stats->tx_heartbeat_errors += dev_stats.tx_heartbeat_errors;
         stats->tx_window_errors    += dev_stats.tx_window_errors;
     }
-    return 0;
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 /* Retrieves current device stats for 'netdev-tap' netdev or
@@ -1421,24 +1461,20 @@ netdev_tap_get_stats(const struct netdev *netdev_, struct netdev_stats *stats)
     struct netdev_stats dev_stats;
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     get_stats_via_vport(netdev_, stats);
-
     error = netdev_linux_sys_get_stats(netdev_, &dev_stats);
     if (error) {
-        if (netdev->vport_stats_error) {
-            return error;
-        } else {
-            return 0;
+        if (!netdev->vport_stats_error) {
+            error = 0;
         }
-    }
+    } else if (netdev->vport_stats_error) {
+        /* Transmit and receive stats will appear to be swapped relative to the
+         * other ports since we are the one sending the data, not a remote
+         * computer.  For consistency, we swap them back here. This does not
+         * apply if we are getting stats from the vport layer because it always
+         * tracks stats from the perspective of the switch. */
 
-    /* If this port is an internal port then the transmit and receive stats
-     * will appear to be swapped relative to the other ports since we are the
-     * one sending the data, not a remote computer.  For consistency, we swap
-     * them back here. This does not apply if we are getting stats from the
-     * vport layer because it always tracks stats from the perspective of the
-     * switch. */
-    if (netdev->vport_stats_error) {
         *stats = dev_stats;
         swap_uint64(&stats->rx_packets, &stats->tx_packets);
         swap_uint64(&stats->rx_bytes, &stats->tx_bytes);
@@ -1465,7 +1501,9 @@ netdev_tap_get_stats(const struct netdev *netdev_, struct netdev_stats *stats)
         stats->multicast           += dev_stats.multicast;
         stats->collisions          += dev_stats.collisions;
     }
-    return 0;
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 static int
@@ -1473,9 +1511,14 @@ netdev_internal_get_stats(const struct netdev *netdev_,
                           struct netdev_stats *stats)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     get_stats_via_vport(netdev_, stats);
-    return netdev->vport_stats_error;
+    error = netdev->vport_stats_error;
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 static int
@@ -1515,6 +1558,7 @@ netdev_internal_set_stats(struct netdev *netdev,
 
 static void
 netdev_linux_read_features(struct netdev_linux *netdev)
+    OVS_REQUIRES(netdev->mutex)
 {
     struct ethtool_cmd ecmd;
     uint32_t speed;
@@ -1656,32 +1700,39 @@ netdev_linux_get_features(const struct netdev *netdev_,
                           enum netdev_features *peer)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     netdev_linux_read_features(netdev);
-
     if (!netdev->get_features_error) {
         *current = netdev->current;
         *advertised = netdev->advertised;
         *supported = netdev->supported;
         *peer = 0;              /* XXX */
     }
-    return netdev->get_features_error;
+    error = netdev->get_features_error;
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 /* Set the features advertised by 'netdev' to 'advertise'. */
 static int
-netdev_linux_set_advertisements(struct netdev *netdev,
+netdev_linux_set_advertisements(struct netdev *netdev_,
                                 enum netdev_features advertise)
 {
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     struct ethtool_cmd ecmd;
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
+
     COVERAGE_INC(netdev_get_ethtool);
     memset(&ecmd, 0, sizeof ecmd);
-    error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
+    error = netdev_linux_do_ethtool(netdev_get_name(netdev_), &ecmd,
                                     ETHTOOL_GSET, "ETHTOOL_GSET");
     if (error) {
-        return error;
+        goto exit;
     }
 
     ecmd.advertising = 0;
@@ -1722,8 +1773,12 @@ netdev_linux_set_advertisements(struct netdev *netdev,
         ecmd.advertising |= ADVERTISED_Asym_Pause;
     }
     COVERAGE_INC(netdev_set_ethtool);
-    return netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
-                                   ETHTOOL_SSET, "ETHTOOL_SSET");
+    error = netdev_linux_do_ethtool(netdev_get_name(netdev_), &ecmd,
+                                    ETHTOOL_SSET, "ETHTOOL_SSET");
+
+exit:
+    ovs_mutex_unlock(&netdev->mutex);
+    return error;
 }
 
 /* Attempts to set input rate limiting (policing) policy.  Returns 0 if
@@ -1736,20 +1791,17 @@ netdev_linux_set_policing(struct netdev *netdev_,
     const char *netdev_name = netdev_get_name(netdev_);
     int error;
 
-
     kbits_burst = (!kbits_rate ? 0       /* Force to 0 if no rate specified. */
                    : !kbits_burst ? 1000 /* Default to 1000 kbits if 0. */
                    : kbits_burst);       /* Stick with user-specified value. */
 
+    ovs_mutex_lock(&netdev->mutex);
     if (netdev->cache_valid & VALID_POLICING) {
-        if (netdev->netdev_policing_error) {
-            return netdev->netdev_policing_error;
-        }
-
-        if (netdev->kbits_rate == kbits_rate &&
-            netdev->kbits_burst == kbits_burst) {
+        error = netdev->netdev_policing_error;
+        if (error || (netdev->kbits_rate == kbits_rate &&
+                      netdev->kbits_burst == kbits_burst)) {
             /* Assume that settings haven't changed since we last set them. */
-            return 0;
+            goto out;
         }
         netdev->cache_valid &= ~VALID_POLICING;
     }
@@ -1787,6 +1839,7 @@ out:
         netdev->netdev_policing_error = error;
         netdev->cache_valid |= VALID_POLICING;
     }
+    ovs_mutex_unlock(&netdev->mutex);
     return error;
 }
 
@@ -1874,15 +1927,17 @@ netdev_linux_get_qos(const struct netdev *netdev_,
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     error = tc_query_qdisc(netdev_);
-    if (error) {
-        return error;
+    if (!error) {
+        *typep = netdev->tc->ops->ovs_name;
+        error = (netdev->tc->ops->qdisc_get
+                 ? netdev->tc->ops->qdisc_get(netdev_, details)
+                 : 0);
     }
+    ovs_mutex_unlock(&netdev->mutex);
 
-    *typep = netdev->tc->ops->ovs_name;
-    return (netdev->tc->ops->qdisc_get
-            ? netdev->tc->ops->qdisc_get(netdev_, details)
-            : 0);
+    return error;
 }
 
 static int
@@ -1898,27 +1953,30 @@ netdev_linux_set_qos(struct netdev *netdev_,
         return EOPNOTSUPP;
     }
 
+    ovs_mutex_lock(&netdev->mutex);
     error = tc_query_qdisc(netdev_);
     if (error) {
-        return error;
+        goto exit;
     }
 
     if (new_ops == netdev->tc->ops) {
-        return new_ops->qdisc_set ? new_ops->qdisc_set(netdev_, details) : 0;
+        error = new_ops->qdisc_set ? new_ops->qdisc_set(netdev_, details) : 0;
     } else {
         /* Delete existing qdisc. */
         error = tc_del_qdisc(netdev_);
         if (error) {
-            return error;
+            goto exit;
         }
         ovs_assert(netdev->tc == NULL);
 
         /* Install new qdisc. */
         error = new_ops->tc_install(netdev_, details);
         ovs_assert((error == 0) == (netdev->tc != NULL));
-
-        return error;
     }
+
+exit:
+    ovs_mutex_unlock(&netdev->mutex);
+    return error;
 }
 
 static int
@@ -1928,15 +1986,17 @@ netdev_linux_get_queue(const struct netdev *netdev_,
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     error = tc_query_qdisc(netdev_);
-    if (error) {
-        return error;
-    } else {
+    if (!error) {
         struct tc_queue *queue = tc_find_queue(netdev_, queue_id);
-        return (queue
+        error = (queue
                 ? netdev->tc->ops->class_get(netdev_, queue, details)
                 : ENOENT);
     }
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 static int
@@ -1946,15 +2006,17 @@ netdev_linux_set_queue(struct netdev *netdev_,
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     error = tc_query_qdisc(netdev_);
-    if (error) {
-        return error;
-    } else if (queue_id >= netdev->tc->ops->n_queues
-               || !netdev->tc->ops->class_set) {
-        return EINVAL;
+    if (!error) {
+        error = (queue_id < netdev->tc->ops->n_queues
+                 && netdev->tc->ops->class_set
+                 ? netdev->tc->ops->class_set(netdev_, queue_id, details)
+                 : EINVAL);
     }
+    ovs_mutex_unlock(&netdev->mutex);
 
-    return netdev->tc->ops->class_set(netdev_, queue_id, details);
+    return error;
 }
 
 static int
@@ -1963,17 +2025,21 @@ netdev_linux_delete_queue(struct netdev *netdev_, unsigned int queue_id)
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     error = tc_query_qdisc(netdev_);
-    if (error) {
-        return error;
-    } else if (!netdev->tc->ops->class_delete) {
-        return EINVAL;
-    } else {
-        struct tc_queue *queue = tc_find_queue(netdev_, queue_id);
-        return (queue
-                ? netdev->tc->ops->class_delete(netdev_, queue)
-                : ENOENT);
+    if (!error) {
+        if (netdev->tc->ops->class_delete) {
+            struct tc_queue *queue = tc_find_queue(netdev_, queue_id);
+            error = (queue
+                     ? netdev->tc->ops->class_delete(netdev_, queue)
+                     : ENOENT);
+        } else {
+            error = EINVAL;
+        }
     }
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 static int
@@ -1984,19 +2050,25 @@ netdev_linux_get_queue_stats(const struct netdev *netdev_,
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     error = tc_query_qdisc(netdev_);
-    if (error) {
-        return error;
-    } else if (!netdev->tc->ops->class_get_stats) {
-        return EOPNOTSUPP;
-    } else {
-        const struct tc_queue *queue = tc_find_queue(netdev_, queue_id);
-        if (!queue) {
-            return ENOENT;
+    if (!error) {
+        if (netdev->tc->ops->class_get_stats) {
+            const struct tc_queue *queue = tc_find_queue(netdev_, queue_id);
+            if (queue) {
+                stats->created = queue->created;
+                error = netdev->tc->ops->class_get_stats(netdev_, queue,
+                                                         stats);
+            } else {
+                error = ENOENT;
+            }
+        } else {
+            error = EOPNOTSUPP;
         }
-        stats->created = queue->created;
-        return netdev->tc->ops->class_get_stats(netdev_, queue, stats);
     }
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 static bool
@@ -2020,34 +2092,37 @@ netdev_linux_dump_queues(const struct netdev *netdev_,
                          netdev_dump_queues_cb *cb, void *aux)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    struct tc_queue *queue, *next_queue;
-    struct smap details;
-    int last_error;
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     error = tc_query_qdisc(netdev_);
-    if (error) {
-        return error;
-    } else if (!netdev->tc->ops->class_get) {
-        return EOPNOTSUPP;
-    }
+    if (!error) {
+        if (netdev->tc->ops->class_get) {
+            struct tc_queue *queue, *next_queue;
+            struct smap details;
 
-    last_error = 0;
-    smap_init(&details);
-    HMAP_FOR_EACH_SAFE (queue, next_queue, hmap_node,
-                        &netdev->tc->queues) {
-        smap_clear(&details);
+            smap_init(&details);
+            HMAP_FOR_EACH_SAFE (queue, next_queue, hmap_node,
+                                &netdev->tc->queues) {
+                int retval;
 
-        error = netdev->tc->ops->class_get(netdev_, queue, &details);
-        if (!error) {
-            (*cb)(queue->queue_id, &details, aux);
+                smap_clear(&details);
+
+                retval = netdev->tc->ops->class_get(netdev_, queue, &details);
+                if (!retval) {
+                    (*cb)(queue->queue_id, &details, aux);
+                } else {
+                    error = retval;
+                }
+            }
+            smap_destroy(&details);
         } else {
-            last_error = error;
+            error = EOPNOTSUPP;
         }
     }
-    smap_destroy(&details);
+    ovs_mutex_unlock(&netdev->mutex);
 
-    return last_error;
+    return error;
 }
 
 static int
@@ -2055,31 +2130,38 @@ netdev_linux_dump_queue_stats(const struct netdev *netdev_,
                               netdev_dump_queue_stats_cb *cb, void *aux)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    struct nl_dump dump;
-    struct ofpbuf msg;
-    int last_error;
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     error = tc_query_qdisc(netdev_);
-    if (error) {
-        return error;
-    } else if (!netdev->tc->ops->class_dump_stats) {
-        return EOPNOTSUPP;
-    }
+    if (!error) {
+        struct nl_dump dump;
 
-    last_error = 0;
-    if (!start_queue_dump(netdev_, &dump)) {
-        return ENODEV;
-    }
-    while (nl_dump_next(&dump, &msg)) {
-        error = netdev->tc->ops->class_dump_stats(netdev_, &msg, cb, aux);
-        if (error) {
-            last_error = error;
+        if (!netdev->tc->ops->class_dump_stats) {
+            error = EOPNOTSUPP;
+        } else if (!start_queue_dump(netdev_, &dump)) {
+            error = ENODEV;
+        } else {
+            struct ofpbuf msg;
+            int retval;
+
+            while (nl_dump_next(&dump, &msg)) {
+                retval = netdev->tc->ops->class_dump_stats(netdev_, &msg,
+                                                           cb, aux);
+                if (retval) {
+                    error = retval;
+                }
+            }
+
+            retval = nl_dump_done(&dump);
+            if (retval) {
+                error = retval;
+            }
         }
     }
+    ovs_mutex_unlock(&netdev->mutex);
 
-    error = nl_dump_done(&dump);
-    return error ? error : last_error;
+    return error;
 }
 
 static int
@@ -2087,27 +2169,34 @@ netdev_linux_get_in4(const struct netdev *netdev_,
                      struct in_addr *address, struct in_addr *netmask)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     if (!(netdev->cache_valid & VALID_IN4)) {
-        int error;
-
         error = netdev_linux_get_ipv4(netdev_, &netdev->address,
                                       SIOCGIFADDR, "SIOCGIFADDR");
-        if (error) {
-            return error;
+        if (!error) {
+            error = netdev_linux_get_ipv4(netdev_, &netdev->netmask,
+                                          SIOCGIFNETMASK, "SIOCGIFNETMASK");
+            if (!error) {
+                netdev->cache_valid |= VALID_IN4;
+            }
         }
+    } else {
+        error = 0;
+    }
 
-        error = netdev_linux_get_ipv4(netdev_, &netdev->netmask,
-                                      SIOCGIFNETMASK, "SIOCGIFNETMASK");
-        if (error) {
-            return error;
+    if (!error) {
+        if (netdev->address.s_addr != INADDR_ANY) {
+            *address = netdev->address;
+            *netmask = netdev->netmask;
+        } else {
+            error = EADDRNOTAVAIL;
         }
-
-        netdev->cache_valid |= VALID_IN4;
     }
-    *address = netdev->address;
-    *netmask = netdev->netmask;
-    return address->s_addr == INADDR_ANY ? EADDRNOTAVAIL : 0;
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return error;
 }
 
 static int
@@ -2117,6 +2206,7 @@ netdev_linux_set_in4(struct netdev *netdev_, struct in_addr address,
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error;
 
+    ovs_mutex_lock(&netdev->mutex);
     error = do_set_addr(netdev_, SIOCSIFADDR, "SIOCSIFADDR", address);
     if (!error) {
         netdev->cache_valid |= VALID_IN4;
@@ -2127,6 +2217,8 @@ netdev_linux_set_in4(struct netdev *netdev_, struct in_addr address,
                                 "SIOCSIFNETMASK", netmask);
         }
     }
+    ovs_mutex_unlock(&netdev->mutex);
+
     return error;
 }
 
@@ -2152,6 +2244,8 @@ static int
 netdev_linux_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+
+    ovs_mutex_lock(&netdev->mutex);
     if (!(netdev->cache_valid & VALID_IN6)) {
         FILE *file;
         char line[128];
@@ -2176,6 +2270,8 @@ netdev_linux_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
         netdev->cache_valid |= VALID_IN6;
     }
     *in6 = netdev->in6;
+    ovs_mutex_unlock(&netdev->mutex);
+
     return 0;
 }
 
@@ -2290,6 +2386,7 @@ netdev_linux_get_status(const struct netdev *netdev_, struct smap *smap)
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
     int error = 0;
 
+    ovs_mutex_lock(&netdev->mutex);
     if (!(netdev->cache_valid & VALID_DRVINFO)) {
         struct ethtool_cmd *cmd = (struct ethtool_cmd *) &netdev->drvinfo;
 
@@ -2309,6 +2406,8 @@ netdev_linux_get_status(const struct netdev *netdev_, struct smap *smap)
         smap_add(smap, "driver_version", netdev->drvinfo.version);
         smap_add(smap, "firmware_version", netdev->drvinfo.fw_version);
     }
+    ovs_mutex_unlock(&netdev->mutex);
+
     return error;
 }
 
@@ -2387,6 +2486,7 @@ netdev_linux_update_flags(struct netdev *netdev_, enum netdev_flags off,
     int old_flags, new_flags;
     int error = 0;
 
+    ovs_mutex_lock(&netdev->mutex);
     old_flags = netdev->ifi_flags;
     *old_flagsp = iff_to_nd_flags(old_flags);
     new_flags = (old_flags & ~nd_to_iff_flags(off)) | nd_to_iff_flags(on);
@@ -2394,13 +2494,22 @@ netdev_linux_update_flags(struct netdev *netdev_, enum netdev_flags off,
         error = set_flags(netdev_get_name(netdev_), new_flags);
         get_flags(netdev_, &netdev->ifi_flags);
     }
+    ovs_mutex_unlock(&netdev->mutex);
+
     return error;
 }
 
 static unsigned int
-netdev_linux_change_seq(const struct netdev *netdev)
+netdev_linux_change_seq(const struct netdev *netdev_)
 {
-    return netdev_linux_cast(netdev)->change_seq;
+    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    unsigned int change_seq;
+
+    ovs_mutex_lock(&netdev->mutex);
+    change_seq = netdev->change_seq;
+    ovs_mutex_unlock(&netdev->mutex);
+
+    return change_seq;
 }
 
 #define NETDEV_LINUX_CLASS(NAME, CONSTRUCT, GET_STATS, SET_STATS,  \