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

Ladi Prosek lprosek at redhat.com
Thu Mar 10 14:52:01 UTC 2016


On Thu, Mar 10, 2016 at 3:02 PM, Michael S. Tsirkin <mst at redhat.com> wrote:
> 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?

vpm_reset is called in virtnet_probe_modern right after mapping caps
and before any other device access.

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

You're right, I'll fix it.

>> +
>> +     /* 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