diff options
Diffstat (limited to 'drivers/net/virtio_net.c')
| -rw-r--r-- | drivers/net/virtio_net.c | 66 | 
1 files changed, 61 insertions, 5 deletions
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index af8acc85f4b..9ce6995e8d0 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -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; @@ -492,7 +501,9 @@ static void virtnet_napi_enable(struct virtnet_info *vi)  	 * We synchronize against interrupts via NAPI_STATE_SCHED */  	if (napi_schedule_prep(&vi->napi)) {  		virtqueue_disable_cb(vi->rvq); +		local_bh_disable();  		__napi_schedule(&vi->napi); +		local_bh_enable();  	}  } @@ -780,6 +791,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 +972,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 +1007,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 +1109,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 +1147,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 +1206,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 +1226,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 +1244,8 @@ static int virtnet_freeze(struct virtio_device *vdev)  	remove_vq_common(vi); +	flush_work(&vi->config_work); +  	return 0;  } @@ -1215,6 +1266,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 +1287,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 = {  |