On Mon, Mar 14, 2016 at 03:48:25PM +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 <lpro...@redhat.com> > --- > src/drivers/net/virtio-net.c | 252 > +++++++++++++++++++++++++++++++++++++++++-- > 1 file changed, 241 insertions(+), 11 deletions(-) > > diff --git a/src/drivers/net/virtio-net.c b/src/drivers/net/virtio-net.c > index 1c535f7..27003bc 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
it would be cleaner to check a feature bit. > @@ -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, > @@ -135,7 +141,7 @@ static void virtnet_enqueue_iob ( struct net_device > *netdev, > virtnet, iobuf, vq_idx ); > > vring_add_buf ( vq, list, out, in, iobuf, 0 ); > - vring_kick ( NULL, virtnet->ioaddr, vq, 1 ); > + vring_kick ( &virtnet->vdev, virtnet->ioaddr, vq, 1 ); > } > > /** Try to keep rx virtqueue filled with iobufs > @@ -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,77 @@ 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; > + u8 status; > + > + /* Negotiate features */ > + 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 ); > + > + status = vpm_get_status ( &virtnet->vdev ); > + if ( ! ( status & VIRTIO_CONFIG_S_FEATURES_OK ) ) { > + DBGC ( virtnet, "VIRTIO-NET %p device didn't accept features\n", > + virtnet ); > + vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_FAILED ); > + return -EINVAL; > + } > + > + /* 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 ); > + > + /* 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 +295,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; > > @@ -302,10 +388,14 @@ static void virtnet_poll ( struct net_device *netdev ) { > > /* Acknowledge interrupt. This is necessary for UNDI operation and > * interrupts that are raised despite VRING_AVAIL_F_NO_INTERRUPT being > - * set (that flag is just a hint and the hypervisor not not have to > + * set (that flag is just a hint and the hypervisor does not have to > * 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 +428,12 @@ 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 +484,152 @@ static int virtnet_probe ( struct pci_device *pci ) { > } > > /** > + * Probe PCI device, modern virtio 1.0 > + * > + * @v pci PCI device > + * @v found_dev Set to non-zero if modern device was found (probe may > still fail) > + * @ret rc Return status code > + */ > +static int virtnet_probe_modern ( struct pci_device *pci, int *found_dev ) { > + struct net_device *netdev; > + struct virtnet_nic *virtnet; > + u64 features; > + int rc, common, isr, notify, config, device; > + > + common = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_COMMON_CFG ); > + if ( ! common ) { > + DBG ( "Common virtio capability not found!\n" ); > + return -ENODEV; > + } > + *found_dev = 1; > + > + isr = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_ISR_CFG ); > + notify = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_NOTIFY_CFG ); > + config = virtio_pci_find_capability ( pci, VIRTIO_PCI_CAP_PCI_CFG ); > + if ( ! isr || ! notify || ! config ) { > + DBG ( "Missing virtio capabilities %i/%i/%i/%i\n", > + common, isr, notify, config ); > + 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 modern %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 ); > + > + virtnet->vdev.notify_cap_pos = notify; > + virtnet->vdev.cfg_cap_pos = config; move this chunk to generic code, where it's used? > + > + /* Map the device capability */ > + if ( device ) { > + rc = virtio_pci_map_capability ( pci, device, > + 0, 4, 0, sizeof ( struct virtio_net_config ), > + &virtnet->vdev.device ); > + if ( rc ) > + goto err_map_device; > + } > + > + /* Enable PCI bus master and reset NIC */ > + adjust_pci_device ( pci ); > + > + /* Reset the device and set initial status bits */ > + vpm_reset ( &virtnet->vdev ); > + vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_ACKNOWLEDGE ); > + vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_DRIVER ); > + > + /* Load MAC address */ > + if ( device ) { If device config is not accessible, I think you should not negotiate mac. Correct? > + 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 ); do things work without a MAC? If not - fail early? > + 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.isr ); > +err_map_isr: > + virtio_pci_unmap_capability ( &virtnet->vdev.common ); > +err_map_common: > + return rc; > +} > + > +/** > + * Probe PCI device > + * > + * @v pci PCI device > + * @ret rc Return status code > + */ > +static int virtnet_probe ( struct pci_device *pci ) { > + int found_modern = 0; > + int rc = virtnet_probe_modern ( pci, &found_modern ); > + if ( ! found_modern ) { > + /* fall back to the legacy probe */ You also must not do this for devices with modern device id. > + 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 ); this will unmap even if device was not set. > + virtio_pci_unmap_capability ( &virtnet->vdev.isr ); > + virtio_pci_unmap_capability ( &virtnet->vdev.common ); > > unregister_netdev ( netdev ); > netdev_nullify ( netdev ); > @@ -409,6 +638,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 _______________________________________________ ipxe-devel mailing list ipxe-devel@lists.ipxe.org https://lists.ipxe.org/mailman/listinfo.cgi/ipxe-devel