diff --git a/src/drivers/bus/virtio-pci.c b/src/drivers/bus/virtio-pci.c index 3311595fb2..402bf4f12a 100644 --- a/src/drivers/bus/virtio-pci.c +++ b/src/drivers/bus/virtio-pci.c @@ -21,11 +21,37 @@ #include "ipxe/virtio-pci.h" #include "ipxe/virtio-ring.h" +static int vp_alloc_vq(struct vring_virtqueue *vq, u16 num) +{ + size_t queue_size = PAGE_MASK + vring_size(num); + size_t vdata_size = num * sizeof(void *); + + vq->queue = zalloc(queue_size + vdata_size); + if (!vq->queue) { + return -ENOMEM; + } + + /* vdata immediately follows the ring */ + vq->vdata = (void **)(vq->queue + queue_size); + + return 0; +} + +void vp_free_vq(struct vring_virtqueue *vq) +{ + if (vq->queue) { + free(vq->queue); + vq->queue = NULL; + vq->vdata = NULL; + } +} + int vp_find_vq(unsigned int ioaddr, int queue_index, struct vring_virtqueue *vq) { struct vring * vr = &vq->vring; u16 num; + int rc; /* select the queue */ @@ -39,11 +65,6 @@ int vp_find_vq(unsigned int ioaddr, int queue_index, return -1; } - if (num > MAX_QUEUE_NUM) { - DBG("VIRTIO-PCI ERROR: queue size %d > %d\n", num, MAX_QUEUE_NUM); - return -1; - } - /* check if the queue is already active */ if (inl(ioaddr + VIRTIO_PCI_QUEUE_PFN)) { @@ -54,8 +75,12 @@ int vp_find_vq(unsigned int ioaddr, int queue_index, vq->queue_index = queue_index; /* initialize the queue */ - - vring_init(vr, num, (unsigned char*)&vq->queue); + rc = vp_alloc_vq(vq, num); + if (rc) { + DBG("VIRTIO-PCI ERROR: failed to allocate queue memory\n"); + return rc; + } + vring_init(vr, num, vq->queue); /* activate the queue * @@ -354,18 +379,29 @@ int vpm_find_vqs(struct virtio_pci_modern_device *vdev, return -ENOENT; if (size & (size - 1)) { - DBG("VIRTIO-PCI %p: bad queue size %d", vdev, size); + DBG("VIRTIO-PCI %p: bad queue size %d\n", vdev, size); return -EINVAL; } + if (size > MAX_QUEUE_NUM) { + /* iPXE networking tends to be not perf critical so there's no + * need to accept large queue sizes. + */ + size = MAX_QUEUE_NUM; + } + vq = &vqs[i]; vq->queue_index = i; /* get offset of notification word for this vq */ off = vpm_ioread16(vdev, &vdev->common, COMMON_OFFSET(queue_notify_off)); - vq->vring.num = size; - vring_init(&vq->vring, size, (unsigned char *)vq->queue); + err = vp_alloc_vq(vq, size); + if (err) { + DBG("VIRTIO-PCI %p: failed to allocate queue memory\n", vdev); + return err; + } + vring_init(&vq->vring, size, vq->queue); /* activate the queue */ vpm_iowrite16(vdev, &vdev->common, size, COMMON_OFFSET(queue_size)); @@ -385,7 +421,7 @@ int vpm_find_vqs(struct virtio_pci_modern_device *vdev, off * notify_offset_multiplier, 2, &vq->notification); if (err) { - goto err_map_notify; + return err; } } @@ -399,11 +435,4 @@ int vpm_find_vqs(struct virtio_pci_modern_device *vdev, vpm_iowrite16(vdev, &vdev->common, 1, COMMON_OFFSET(queue_enable)); } return 0; - -err_map_notify: - /* Undo the virtio_pci_map_capability calls. */ - while (i-- > 0) { - virtio_pci_unmap_capability(&vqs[i].notification); - } - return err; } diff --git a/src/drivers/net/virtio-net.c b/src/drivers/net/virtio-net.c index fe0fd4b860..eaf6ed3822 100644 --- a/src/drivers/net/virtio-net.c +++ b/src/drivers/net/virtio-net.c @@ -175,6 +175,23 @@ static void virtnet_refill_rx_virtqueue ( struct net_device *netdev ) { } } +/** Helper to free all virtqueue memory + * + * @v netdev Network device + */ +static void virtnet_free_virtqueues ( struct net_device *netdev ) { + struct virtnet_nic *virtnet = netdev->priv; + int i; + + for ( i = 0; i < QUEUE_NB; i++ ) { + virtio_pci_unmap_capability ( &virtnet->virtqueue[i].notification ); + vp_free_vq ( &virtnet->virtqueue[i] ); + } + + free ( virtnet->virtqueue ); + virtnet->virtqueue = NULL; +} + /** Open network device, legacy virtio 0.9.5 * * @v netdev Network device @@ -200,8 +217,7 @@ static int virtnet_open_legacy ( struct net_device *netdev ) { if ( vp_find_vq ( ioaddr, i, &virtnet->virtqueue[i] ) == -1 ) { DBGC ( virtnet, "VIRTIO-NET %p cannot register queue %d\n", virtnet, i ); - free ( virtnet->virtqueue ); - virtnet->virtqueue = NULL; + virtnet_free_virtqueues ( netdev ); return -ENOENT; } } @@ -263,8 +279,7 @@ static int virtnet_open_modern ( struct net_device *netdev ) { 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; + virtnet_free_virtqueues ( netdev ); vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_FAILED ); return -ENOENT; } @@ -304,7 +319,6 @@ 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; if ( virtnet->virtio_version ) { vpm_reset ( &virtnet->vdev ); @@ -313,12 +327,7 @@ static void virtnet_close ( struct net_device *netdev ) { } /* 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; + virtnet_free_virtqueues ( netdev ); /* Free rx iobufs */ list_for_each_entry_safe ( iobuf, next_iobuf, &virtnet->rx_iobufs, list ) { diff --git a/src/include/ipxe/virtio-pci.h b/src/include/ipxe/virtio-pci.h index f3c9b17ca0..f3074f14d3 100644 --- a/src/include/ipxe/virtio-pci.h +++ b/src/include/ipxe/virtio-pci.h @@ -196,9 +196,11 @@ static inline void vp_del_vq(unsigned int ioaddr, int queue_index) struct vring_virtqueue; +void vp_free_vq(struct vring_virtqueue *vq); int vp_find_vq(unsigned int ioaddr, int queue_index, struct vring_virtqueue *vq); + /* Virtio 1.0 I/O routines abstract away the three possible HW access * mechanisms - memory, port I/O, and PCI cfg space access. Also built-in * are endianness conversions - to LE on write and from LE on read. */ diff --git a/src/include/ipxe/virtio-ring.h b/src/include/ipxe/virtio-ring.h index 6ba550b5a2..e608e624fa 100644 --- a/src/include/ipxe/virtio-ring.h +++ b/src/include/ipxe/virtio-ring.h @@ -71,14 +71,12 @@ struct vring { + PAGE_MASK) & ~PAGE_MASK) + \ (sizeof(struct vring_used) + sizeof(struct vring_used_elem) * num)) -typedef unsigned char virtio_queue_t[PAGE_MASK + vring_size(MAX_QUEUE_NUM)]; - struct vring_virtqueue { - virtio_queue_t queue; + unsigned char *queue; struct vring vring; u16 free_head; u16 last_used_idx; - void *vdata[MAX_QUEUE_NUM]; + void **vdata; /* PCI */ int queue_index; struct virtio_pci_region notification; @@ -95,7 +93,7 @@ static inline void vring_init(struct vring *vr, unsigned int i; unsigned long pa; - vr->num = num; + vr->num = num; /* physical address of desc must be page aligned */ @@ -103,13 +101,13 @@ static inline void vring_init(struct vring *vr, pa = (pa + PAGE_MASK) & ~PAGE_MASK; vr->desc = phys_to_virt(pa); - vr->avail = (struct vring_avail *)&vr->desc[num]; + vr->avail = (struct vring_avail *)&vr->desc[num]; /* physical address of used must be page aligned */ pa = virt_to_phys(&vr->avail->ring[num]); pa = (pa + PAGE_MASK) & ~PAGE_MASK; - vr->used = phys_to_virt(pa); + vr->used = phys_to_virt(pa); for (i = 0; i < num - 1; i++) vr->desc[i].next = i + 1;