[ipxe-devel] [PATCH 4/4] [virtio] Add virtio-net 1.0 support

Michael S. Tsirkin mst at redhat.com
Thu Mar 10 14:02:47 UTC 2016


On Wed, Mar 09, 2016 at 07:20:03PM +0100, Ladi Prosek wrote:
> This commit makes virtio-net support devices with VEN 0x1af4
> and DEV 0x1041, which is how non-transitional (modern-only)
> virtio-net devices are exposed on the PCI bus.
> 
> Transitional devices supporting both the old 0.9.5 and new 1.0
> version of the virtio spec are driven using the new protocol.
> Legacy devices are driven using the old protocol, same as before
> this commit.
> 
> Signed-off-by: Ladi Prosek <lprosek at redhat.com>
> ---
>  src/drivers/net/virtio-net.c | 257 +++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 249 insertions(+), 8 deletions(-)
> 
> diff --git a/src/drivers/net/virtio-net.c b/src/drivers/net/virtio-net.c
> index 10a9f71..f9ac7ee 100644
> --- a/src/drivers/net/virtio-net.c
> +++ b/src/drivers/net/virtio-net.c
> @@ -88,6 +88,9 @@ struct virtnet_nic {
>  	/** Base pio register address */
>  	unsigned long ioaddr;
>  
> +	/** Virtio 1.0 device data */
> +	struct virtio_pci_modern_device vdev;
> +
>  	/** RX/TX virtqueues */
>  	struct vring_virtqueue *virtqueue;
>  
> @@ -98,7 +101,7 @@ struct virtnet_nic {
>  	unsigned int rx_num_iobufs;
>  
>  	/** Virtio net packet header, we only need one */
> -	struct virtio_net_hdr empty_header;
> +	struct virtio_net_hdr_modern empty_header;
>  };
>  
>  /** Add an iobuf to a virtqueue
> @@ -115,6 +118,9 @@ static void virtnet_enqueue_iob ( struct net_device *netdev,
>  	struct vring_virtqueue *vq = &virtnet->virtqueue[vq_idx];
>  	unsigned int out = ( vq_idx == TX_INDEX ) ? 2 : 0;
>  	unsigned int in = ( vq_idx == TX_INDEX ) ? 0 : 2;
> +	size_t header_len = virtnet->ioaddr
> +		? sizeof ( virtnet->empty_header.legacy )
> +		: sizeof ( virtnet->empty_header );
>  	struct vring_list list[] = {
>  		{
>  			/* Share a single zeroed virtio net header between all
> @@ -123,7 +129,7 @@ static void virtnet_enqueue_iob ( struct net_device *netdev,
>  			 * header fields get used.
>  			 */
>  			.addr = ( char* ) &virtnet->empty_header,
> -			.length = sizeof ( virtnet->empty_header ),
> +			.length = header_len,
>  		},
>  		{
>  			.addr = ( char* ) iobuf->data,
> @@ -164,12 +170,12 @@ static void virtnet_refill_rx_virtqueue ( struct net_device *netdev ) {
>  	}
>  }
>  
> -/** Open network device
> +/** Open network device, legacy virtio 0.9.5
>   *
>   * @v netdev	Network device
>   * @ret rc	Return status code
>   */
> -static int virtnet_open ( struct net_device *netdev ) {
> +static int virtnet_open_legacy ( struct net_device *netdev ) {
>  	struct virtnet_nic *virtnet = netdev->priv;
>  	unsigned long ioaddr = virtnet->ioaddr;
>  	u32 features;
> @@ -210,6 +216,70 @@ static int virtnet_open ( struct net_device *netdev ) {
>  	return 0;
>  }
>  
> +/** Open network device, modern virtio 1.0
> + *
> + * @v netdev	Network device
> + * @ret rc	Return status code
> + */
> +static int virtnet_open_modern ( struct net_device *netdev ) {
> +	struct virtnet_nic *virtnet = netdev->priv;
> +	u64 features;
> +
> +	vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_ACKNOWLEDGE );
> +
> +	/* Driver is ready */
> +	features = vpm_get_features ( &virtnet->vdev );
> +	vpm_set_features ( &virtnet->vdev, features & (
> +		( 1ULL << VIRTIO_NET_F_MAC ) |
> +		( 1ULL << VIRTIO_F_VERSION_1 ) |
> +		( 1ULL << VIRTIO_F_ANY_LAYOUT ) ) );
> +	vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_FEATURES_OK );
> +
> +	/* Allocate virtqueues */
> +	virtnet->virtqueue = zalloc ( QUEUE_NB *
> +				      sizeof ( *virtnet->virtqueue ) );
> +	if ( ! virtnet->virtqueue ) {
> +		vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_FAILED );
> +		return -ENOMEM;
> +	}
> +
> +	/* Initialize rx/tx virtqueues */
> +	if ( vpm_find_vqs( &virtnet->vdev, QUEUE_NB, virtnet->virtqueue ) ) {
> +		DBGC ( virtnet, "VIRTIO-NET %p cannot register queues\n",
> +		       virtnet );
> +		free ( virtnet->virtqueue );
> +		virtnet->virtqueue = NULL;
> +		vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_FAILED );
> +		return -ENOENT;
> +	}
> +
> +	/* Disable interrupts before starting */
> +	netdev_irq ( netdev, 0 );
> +
> +	vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_DRIVER_OK );

>From virtio spec:

The driver MUST follow this sequence to initialize a device:
1. Reset the device.
2. Set the ACKNOWLEDGE status bit: the guest OS has notice the device.
3. Set the DRIVER status bit: the guest OS knows how to drive the device.
4. Read device feature bits, and write the subset of feature bits understood by the OS and driver to the
device. During this step the driver MAY read (but MUST NOT write) the device-specific configuration
fields to check that it can support the device before accepting it.
5. Set the FEATURES_OK status bit. The driver MUST NOT accept new feature bits after this step.
6. Re-read device status to ensure the FEATURES_OK bit is still set: otherwise, the device does not
support our subset of features and the device is unusable.
7. Perform device-specific setup, including discovery of virtqueues for the device, optional per-bus setup,
reading and possibly writing the device’s virtio configuration space, and population of virtqueues.
8. Set the DRIVER_OK status bit. At this point the device is “live”.

I don't see a reset - are we sure this is right before
system boot, so there was a full reset before any other
driver used the device?

And I see several other violation of the init order here.

> +
> +	/* Initialize rx packets */
> +	INIT_LIST_HEAD ( &virtnet->rx_iobufs );
> +	virtnet->rx_num_iobufs = 0;
> +	virtnet_refill_rx_virtqueue ( netdev );
> +	return 0;
> +}
> +
> +/** Open network device
> + *
> + * @v netdev	Network device
> + * @ret rc	Return status code
> + */
> +static int virtnet_open ( struct net_device *netdev ) {
> +	struct virtnet_nic *virtnet = netdev->priv;
> +
> +	if ( virtnet->ioaddr ) {
> +		return virtnet_open_legacy ( netdev );
> +	} else {
> +		return virtnet_open_modern ( netdev );
> +	}
> +}
> +
>  /** Close network device
>   *
>   * @v netdev	Network device
> @@ -218,10 +288,19 @@ static void virtnet_close ( struct net_device *netdev ) {
>  	struct virtnet_nic *virtnet = netdev->priv;
>  	struct io_buffer *iobuf;
>  	struct io_buffer *next_iobuf;
> +	int i;
>  
> -	vp_reset ( virtnet->ioaddr );
> +	if ( virtnet->ioaddr ) {
> +		vp_reset ( virtnet->ioaddr );
> +	} else {
> +		vpm_reset ( &virtnet->vdev );
> +	}
>  
>  	/* Virtqueues can be freed now that NIC is reset */
> +	for ( i = 0 ; i < QUEUE_NB ; i++ ) {
> +		virtio_pci_unmap_capability ( &virtnet->virtqueue[i].notification );
> +	}
> +
>  	free ( virtnet->virtqueue );
>  	virtnet->virtqueue = NULL;
>  
> @@ -305,7 +384,11 @@ static void virtnet_poll ( struct net_device *netdev ) {
>  	 * set (that flag is just a hint and the hypervisor not not have to

typo but your patch is not to blame

>  	 * honor it).
>  	 */
> -	vp_get_isr ( virtnet->ioaddr );
> +	if ( virtnet->ioaddr ) {
> +		vp_get_isr ( virtnet->ioaddr );
> +	} else {
> +		vpm_get_isr ( &virtnet->vdev );
> +	}
>  
>  	virtnet_process_tx_packets ( netdev );
>  	virtnet_process_rx_packets ( netdev );
> @@ -338,13 +421,13 @@ static struct net_device_operations virtnet_operations = {
>  };
>  
>  /**
> - * Probe PCI device
> + * Probe PCI device, legacy virtio 0.9.5
>   *
>   * @v pci	PCI device
>   * @v id	PCI ID
>   * @ret rc	Return status code
>   */
> -static int virtnet_probe ( struct pci_device *pci ) {
> +static int virtnet_probe_legacy ( struct pci_device *pci ) {
>  	unsigned long ioaddr = pci->ioaddr;
>  	struct net_device *netdev;
>  	struct virtnet_nic *virtnet;
> @@ -395,12 +478,169 @@ static int virtnet_probe ( struct pci_device *pci ) {
>  }
>  
>  /**
> + * Probe PCI device, modern virtio 1.0
> + *
> + * @v pci	PCI device
> + * @v id	PCI ID
> + * @ret rc	Return status code
> + */
> +static int virtnet_probe_modern ( struct pci_device *pci ) {
> +	struct net_device *netdev;
> +	struct virtnet_nic *virtnet;
> +	u32 notify_length, notify_offset;
> +	u64 features;
> +	int rc, common, isr, notify, device;
> +
> +	common = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_COMMON_CFG );
> +	if ( ! common ) {
> +		DBG ( "Common virtio capability not found!\n" );
> +		return -ENODEV;
> +	}
> +	isr = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_ISR_CFG );
> +	notify = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_NOTIFY_CFG );
> +	if ( ! isr || ! notify ) {
> +		DBG ( "Missing virtio capabilities %i/%i/%i\n", common, isr, notify );
> +		return -EINVAL;
> +	}
> +	device = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_DEVICE_CFG );
> +
> +	/* Allocate and hook up net device */
> +	netdev = alloc_etherdev ( sizeof ( *virtnet ) );
> +	if ( ! netdev )
> +		return -ENOMEM;
> +	netdev_init ( netdev, &virtnet_operations );
> +	virtnet = netdev->priv;
> +
> +	pci_set_drvdata ( pci, netdev );
> +	netdev->dev = &pci->dev;
> +
> +	DBGC ( virtnet, "VIRTIO-NET %p busaddr=%s irq=%d\n",
> +	       virtnet, pci->dev.name, pci->irq );
> +
> +	virtnet->vdev.pci = pci;
> +	rc = virtio_pci_map_capability ( pci, common,
> +		sizeof ( struct virtio_pci_common_cfg ), 4,
> +		0, sizeof ( struct virtio_pci_common_cfg ),
> +		&virtnet->vdev.common );
> +	if ( rc )
> +		goto err_map_common;
> +
> +	rc = virtio_pci_map_capability ( pci, isr, sizeof ( u8 ), 1,
> +		0, 1,
> +		&virtnet->vdev.isr );
> +	if ( rc )
> +		goto err_map_isr;
> +
> +	/* Read notify_off_multiplier from config space. */
> +	pci_read_config_dword ( pci,
> +		notify + offsetof ( struct virtio_pci_notify_cap,
> +		notify_off_multiplier ),
> +		&virtnet->vdev.notify_offset_multiplier );
> +	/* Read notify length and offset from config space. */
> +	pci_read_config_dword ( pci,
> +		notify + offsetof ( struct virtio_pci_notify_cap,
> +		cap.length ),
> +		&notify_length );
> +	pci_read_config_dword ( pci,
> +		notify + offsetof ( struct virtio_pci_notify_cap,
> +		cap.offset ),
> +		&notify_offset );
> +
> +	/* We don't know how many VQs we'll map, ahead of the time.
> +	 * If notify length is small, map it all now.
> +	 * Otherwise, map each VQ individually later.
> +	 */
> +	if ( ( u64 ) notify_length + ( notify_offset % PAGE_SIZE ) <= PAGE_SIZE ) {
> +		rc = virtio_pci_map_capability ( pci,
> +			notify, 2, 2,
> +			0, notify_length, &virtnet->vdev.notify_base );
> +		if ( rc )
> +			goto err_map_notify;
> +	}
> +	else {
> +		virtnet->vdev.notify_map_cap = notify;
> +	}
> +
> +	/* Again, we don't know how much we should map, but PAGE_SIZE
> +	 * is more than enough for all existing devices.
> +	 */
> +	if ( device ) {
> +		rc = virtio_pci_map_capability ( pci, device,
> +			0, 4, 0, PAGE_SIZE, &virtnet->vdev.device );
> +		if ( rc )
> +			goto err_map_device;
> +	}
> +
> +	/* Enable PCI bus master and reset NIC */
> +	adjust_pci_device ( pci );
> +
> +	vpm_reset ( &virtnet->vdev );
> +	vpm_add_status( &virtnet->vdev, VIRTIO_CONFIG_S_DRIVER );
> +
> +	/* Load MAC address */
> +	features = vpm_get_features ( &virtnet->vdev );
> +	if ( features & ( 1ULL << VIRTIO_NET_F_MAC ) ) {
> +		vpm_get ( &virtnet->vdev, offsetof ( struct virtio_net_config, mac ),
> +			 netdev->hw_addr, ETH_ALEN );
> +		DBGC ( virtnet, "VIRTIO-NET %p mac=%s\n", virtnet,
> +		       eth_ntoa ( netdev->hw_addr ) );
> +	}
> +
> +	/* Register network device */
> +	if ( ( rc = register_netdev ( netdev ) ) != 0 )
> +		goto err_register_netdev;
> +
> +	/* Mark link as up, control virtqueue is not used */
> +	netdev_link_up ( netdev );
> +
> +	return 0;
> +
> +	unregister_netdev ( netdev );
> + err_register_netdev:
> +	vpm_reset ( &virtnet->vdev );
> +	netdev_nullify ( netdev );
> +	netdev_put ( netdev );
> +
> +	virtio_pci_unmap_capability ( &virtnet->vdev.device );
> +err_map_device:
> +	virtio_pci_unmap_capability ( &virtnet->vdev.notify_base );
> +err_map_notify:
> +	virtio_pci_unmap_capability ( &virtnet->vdev.isr );
> +err_map_isr:
> +	virtio_pci_unmap_capability ( &virtnet->vdev.common );
> +err_map_common:
> +	return rc;
> +}
> +
> +/**
> + * Probe PCI device
> + *
> + * @v pci	PCI device
> + * @v id	PCI ID
> + * @ret rc	Return status code
> + */
> +static int virtnet_probe ( struct pci_device *pci ) {
> +	int rc = virtnet_probe_modern ( pci );
> +	if ( rc == -ENODEV ) {
> +		/* fall back to the legacy probe */
> +		rc = virtnet_probe_legacy ( pci );
> +	}
> +	return rc;
> +}
> +
> +/**
>   * Remove device
>   *
>   * @v pci	PCI device
>   */
>  static void virtnet_remove ( struct pci_device *pci ) {
>  	struct net_device *netdev = pci_get_drvdata ( pci );
> +	struct virtnet_nic *virtnet = netdev->priv;
> +
> +	virtio_pci_unmap_capability ( &virtnet->vdev.device );
> +	virtio_pci_unmap_capability ( &virtnet->vdev.notify_base );
> +	virtio_pci_unmap_capability ( &virtnet->vdev.isr );
> +	virtio_pci_unmap_capability ( &virtnet->vdev.common );
>  
>  	unregister_netdev ( netdev );
>  	netdev_nullify ( netdev );
> @@ -409,6 +649,7 @@ static void virtnet_remove ( struct pci_device *pci ) {
>  
>  static struct pci_device_id virtnet_nics[] = {
>  PCI_ROM(0x1af4, 0x1000, "virtio-net", "Virtio Network Interface", 0),
> +PCI_ROM(0x1af4, 0x1041, "virtio-net", "Virtio Network Interface 1.0", 0),
>  };
>  
>  struct pci_driver virtnet_driver __pci_driver = {
> -- 
> 2.5.0



More information about the ipxe-devel mailing list