Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[virtio] Add virtio-net 1.0 support
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@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
  • Loading branch information
ladipro authored and mcb30 committed Apr 15, 2016
1 parent 8a055a2 commit 988243c
Showing 1 changed file with 251 additions and 11 deletions.
262 changes: 251 additions & 11 deletions src/drivers/net/virtio-net.c
Expand Up @@ -89,6 +89,12 @@ struct virtnet_nic {
/** Base pio register address */
unsigned long ioaddr;

/** 0 for legacy, 1 for virtio 1.0 */
int virtio_version;

/** Virtio 1.0 device data */
struct virtio_pci_modern_device vdev;

/** RX/TX virtqueues */
struct vring_virtqueue *virtqueue;

Expand All @@ -99,7 +105,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
Expand All @@ -116,6 +122,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->virtio_version
? sizeof ( virtnet->empty_header )
: sizeof ( virtnet->empty_header.legacy );
struct vring_list list[] = {
{
/* Share a single zeroed virtio net header between all
Expand All @@ -124,7 +133,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,
Expand All @@ -136,7 +145,8 @@ 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->virtio_version ? &virtnet->vdev : NULL,
virtnet->ioaddr, vq, 1 );
}

/** Try to keep rx virtqueue filled with iobufs
Expand Down Expand Up @@ -165,12 +175,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;
Expand Down Expand Up @@ -211,6 +221,81 @@ 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 );
if ( ! ( features & VIRTIO_F_VERSION_1 ) ) {
vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_FAILED );
return -EINVAL;
}
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->virtio_version ) {
return virtnet_open_modern ( netdev );
} else {
return virtnet_open_legacy ( netdev );
}
}

/** Close network device
*
* @v netdev Network device
Expand All @@ -219,10 +304,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->virtio_version ) {
vpm_reset ( &virtnet->vdev );
} else {
vp_reset ( virtnet->ioaddr );
}

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

Expand Down Expand Up @@ -303,10 +397,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->virtio_version ) {
vpm_get_isr ( &virtnet->vdev );
} else {
vp_get_isr ( virtnet->ioaddr );
}

virtnet_process_tx_packets ( netdev );
virtnet_process_rx_packets ( netdev );
Expand Down Expand Up @@ -339,13 +437,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;
Expand Down Expand Up @@ -395,13 +492,155 @@ static int virtnet_probe ( struct pci_device *pci ) {
return rc;
}

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

virtnet->vdev.notify_cap_pos = notify;
virtnet->vdev.cfg_cap_pos = config;

/* 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 the PCI device */
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 ) {
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 ) );
}
}

/* We need a valid MAC address */
if ( ! is_valid_ether_addr ( netdev->hw_addr ) ) {
rc = -EADDRNOTAVAIL;
goto err_mac_address;
}

/* 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 );

virtnet->virtio_version = 1;
return 0;

unregister_netdev ( netdev );
err_register_netdev:
err_mac_address:
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 && pci->device < 0x1040 ) {
/* 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.isr );
virtio_pci_unmap_capability ( &virtnet->vdev.common );

unregister_netdev ( netdev );
netdev_nullify ( netdev );
Expand All @@ -410,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 = {
Expand Down

0 comments on commit 988243c

Please sign in to comment.