|
@@ -66,12 +66,21 @@ struct virtnet_info {
|
|
|
/* Host will merge rx buffers for big packets (shake it! shake it!) */
|
|
|
bool mergeable_rx_bufs;
|
|
|
|
|
|
+ /* enable config space updates */
|
|
|
+ bool config_enable;
|
|
|
+
|
|
|
/* Active statistics */
|
|
|
struct virtnet_stats __percpu *stats;
|
|
|
|
|
|
/* Work struct for refilling if we run low on memory. */
|
|
|
struct delayed_work refill;
|
|
|
|
|
|
+ /* Work struct for config space updates */
|
|
|
+ struct work_struct config_work;
|
|
|
+
|
|
|
+ /* Lock for config space updates */
|
|
|
+ struct mutex config_lock;
|
|
|
+
|
|
|
/* Chain pages by the private ptr. */
|
|
|
struct page *pages;
|
|
|
|
|
@@ -780,6 +789,16 @@ static bool virtnet_send_command(struct virtnet_info *vi, u8 class, u8 cmd,
|
|
|
return status == VIRTIO_NET_OK;
|
|
|
}
|
|
|
|
|
|
+static void virtnet_ack_link_announce(struct virtnet_info *vi)
|
|
|
+{
|
|
|
+ rtnl_lock();
|
|
|
+ if (!virtnet_send_command(vi, VIRTIO_NET_CTRL_ANNOUNCE,
|
|
|
+ VIRTIO_NET_CTRL_ANNOUNCE_ACK, NULL,
|
|
|
+ 0, 0))
|
|
|
+ dev_warn(&vi->dev->dev, "Failed to ack link announce.\n");
|
|
|
+ rtnl_unlock();
|
|
|
+}
|
|
|
+
|
|
|
static int virtnet_close(struct net_device *dev)
|
|
|
{
|
|
|
struct virtnet_info *vi = netdev_priv(dev);
|
|
@@ -951,20 +970,31 @@ static const struct net_device_ops virtnet_netdev = {
|
|
|
#endif
|
|
|
};
|
|
|
|
|
|
-static void virtnet_update_status(struct virtnet_info *vi)
|
|
|
+static void virtnet_config_changed_work(struct work_struct *work)
|
|
|
{
|
|
|
+ struct virtnet_info *vi =
|
|
|
+ container_of(work, struct virtnet_info, config_work);
|
|
|
u16 v;
|
|
|
|
|
|
+ mutex_lock(&vi->config_lock);
|
|
|
+ if (!vi->config_enable)
|
|
|
+ goto done;
|
|
|
+
|
|
|
if (virtio_config_val(vi->vdev, VIRTIO_NET_F_STATUS,
|
|
|
offsetof(struct virtio_net_config, status),
|
|
|
&v) < 0)
|
|
|
- return;
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ if (v & VIRTIO_NET_S_ANNOUNCE) {
|
|
|
+ netif_notify_peers(vi->dev);
|
|
|
+ virtnet_ack_link_announce(vi);
|
|
|
+ }
|
|
|
|
|
|
/* Ignore unknown (future) status bits */
|
|
|
v &= VIRTIO_NET_S_LINK_UP;
|
|
|
|
|
|
if (vi->status == v)
|
|
|
- return;
|
|
|
+ goto done;
|
|
|
|
|
|
vi->status = v;
|
|
|
|
|
@@ -975,13 +1005,15 @@ static void virtnet_update_status(struct virtnet_info *vi)
|
|
|
netif_carrier_off(vi->dev);
|
|
|
netif_stop_queue(vi->dev);
|
|
|
}
|
|
|
+done:
|
|
|
+ mutex_unlock(&vi->config_lock);
|
|
|
}
|
|
|
|
|
|
static void virtnet_config_changed(struct virtio_device *vdev)
|
|
|
{
|
|
|
struct virtnet_info *vi = vdev->priv;
|
|
|
|
|
|
- virtnet_update_status(vi);
|
|
|
+ queue_work(system_nrt_wq, &vi->config_work);
|
|
|
}
|
|
|
|
|
|
static int init_vqs(struct virtnet_info *vi)
|
|
@@ -1075,6 +1107,9 @@ static int virtnet_probe(struct virtio_device *vdev)
|
|
|
goto free;
|
|
|
|
|
|
INIT_DELAYED_WORK(&vi->refill, refill_work);
|
|
|
+ mutex_init(&vi->config_lock);
|
|
|
+ vi->config_enable = true;
|
|
|
+ INIT_WORK(&vi->config_work, virtnet_config_changed_work);
|
|
|
sg_init_table(vi->rx_sg, ARRAY_SIZE(vi->rx_sg));
|
|
|
sg_init_table(vi->tx_sg, ARRAY_SIZE(vi->tx_sg));
|
|
|
|
|
@@ -1110,7 +1145,7 @@ static int virtnet_probe(struct virtio_device *vdev)
|
|
|
otherwise get link status from config. */
|
|
|
if (virtio_has_feature(vi->vdev, VIRTIO_NET_F_STATUS)) {
|
|
|
netif_carrier_off(dev);
|
|
|
- virtnet_update_status(vi);
|
|
|
+ queue_work(system_nrt_wq, &vi->config_work);
|
|
|
} else {
|
|
|
vi->status = VIRTIO_NET_S_LINK_UP;
|
|
|
netif_carrier_on(dev);
|
|
@@ -1169,10 +1204,17 @@ static void __devexit virtnet_remove(struct virtio_device *vdev)
|
|
|
{
|
|
|
struct virtnet_info *vi = vdev->priv;
|
|
|
|
|
|
+ /* Prevent config work handler from accessing the device. */
|
|
|
+ mutex_lock(&vi->config_lock);
|
|
|
+ vi->config_enable = false;
|
|
|
+ mutex_unlock(&vi->config_lock);
|
|
|
+
|
|
|
unregister_netdev(vi->dev);
|
|
|
|
|
|
remove_vq_common(vi);
|
|
|
|
|
|
+ flush_work(&vi->config_work);
|
|
|
+
|
|
|
free_percpu(vi->stats);
|
|
|
free_netdev(vi->dev);
|
|
|
}
|
|
@@ -1182,6 +1224,11 @@ static int virtnet_freeze(struct virtio_device *vdev)
|
|
|
{
|
|
|
struct virtnet_info *vi = vdev->priv;
|
|
|
|
|
|
+ /* Prevent config work handler from accessing the device */
|
|
|
+ mutex_lock(&vi->config_lock);
|
|
|
+ vi->config_enable = false;
|
|
|
+ mutex_unlock(&vi->config_lock);
|
|
|
+
|
|
|
virtqueue_disable_cb(vi->rvq);
|
|
|
virtqueue_disable_cb(vi->svq);
|
|
|
if (virtio_has_feature(vi->vdev, VIRTIO_NET_F_CTRL_VQ))
|
|
@@ -1195,6 +1242,8 @@ static int virtnet_freeze(struct virtio_device *vdev)
|
|
|
|
|
|
remove_vq_common(vi);
|
|
|
|
|
|
+ flush_work(&vi->config_work);
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1215,6 +1264,10 @@ static int virtnet_restore(struct virtio_device *vdev)
|
|
|
if (!try_fill_recv(vi, GFP_KERNEL))
|
|
|
queue_delayed_work(system_nrt_wq, &vi->refill, 0);
|
|
|
|
|
|
+ mutex_lock(&vi->config_lock);
|
|
|
+ vi->config_enable = true;
|
|
|
+ mutex_unlock(&vi->config_lock);
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
#endif
|
|
@@ -1232,6 +1285,7 @@ static unsigned int features[] = {
|
|
|
VIRTIO_NET_F_GUEST_ECN, VIRTIO_NET_F_GUEST_UFO,
|
|
|
VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_STATUS, VIRTIO_NET_F_CTRL_VQ,
|
|
|
VIRTIO_NET_F_CTRL_RX, VIRTIO_NET_F_CTRL_VLAN,
|
|
|
+ VIRTIO_NET_F_GUEST_ANNOUNCE,
|
|
|
};
|
|
|
|
|
|
static struct virtio_driver virtio_net_driver = {
|