diff options
| -rw-r--r-- | drivers/net/virtio_net.c | 64 | ||||
| -rw-r--r-- | include/linux/virtio_net.h | 14 | 
2 files changed, 73 insertions, 5 deletions
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index af8acc85f4b..fa58c786995 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; @@ -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 = { diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index 970d5a2a904..2470f541af5 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -49,8 +49,11 @@  #define VIRTIO_NET_F_CTRL_RX	18	/* Control channel RX mode support */  #define VIRTIO_NET_F_CTRL_VLAN	19	/* Control channel VLAN filtering */  #define VIRTIO_NET_F_CTRL_RX_EXTRA 20	/* Extra RX mode control support */ +#define VIRTIO_NET_F_GUEST_ANNOUNCE 21	/* Guest can announce device on the +					 * network */  #define VIRTIO_NET_S_LINK_UP	1	/* Link is up */ +#define VIRTIO_NET_S_ANNOUNCE	2	/* Announcement is needed */  struct virtio_net_config {  	/* The config defining mac address (if VIRTIO_NET_F_MAC) */ @@ -152,4 +155,15 @@ struct virtio_net_ctrl_mac {   #define VIRTIO_NET_CTRL_VLAN_ADD             0   #define VIRTIO_NET_CTRL_VLAN_DEL             1 +/* + * Control link announce acknowledgement + * + * The command VIRTIO_NET_CTRL_ANNOUNCE_ACK is used to indicate that + * driver has recevied the notification; device would clear the + * VIRTIO_NET_S_ANNOUNCE bit in the status field after it receives + * this command. + */ +#define VIRTIO_NET_CTRL_ANNOUNCE       3 + #define VIRTIO_NET_CTRL_ANNOUNCE_ACK         0 +  #endif /* _LINUX_VIRTIO_NET_H */  |