/** 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;
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
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
* header fields get used.
*/
.addr = ( char* ) &virtnet->empty_header,
- .length = sizeof ( virtnet->empty_header ),
+ .length = header_len,
},
{
.addr = ( char* ) iobuf->data,
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
}
}
-/** 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;
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
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;
/* 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 );
};
/**
- * 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;
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
*
*/
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 );
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 = {