123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- /* virtio-pci.c - pci interface for virtio interface
- *
- * (c) Copyright 2008 Bull S.A.S.
- *
- * Author: Laurent Vivier <Laurent.Vivier@bull.net>
- *
- * some parts from Linux Virtio PCI driver
- *
- * Copyright IBM Corp. 2007
- * Authors: Anthony Liguori <aliguori@us.ibm.com>
- *
- */
-
- #include "errno.h"
- #include "byteswap.h"
- #include "etherboot.h"
- #include "ipxe/io.h"
- #include "ipxe/iomap.h"
- #include "ipxe/pci.h"
- #include "ipxe/reboot.h"
- #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 */
-
- outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_SEL);
-
- /* check if the queue is available */
-
- num = inw(ioaddr + VIRTIO_PCI_QUEUE_NUM);
- if (!num) {
- DBG("VIRTIO-PCI ERROR: queue size is 0\n");
- return -1;
- }
-
- /* check if the queue is already active */
-
- if (inl(ioaddr + VIRTIO_PCI_QUEUE_PFN)) {
- DBG("VIRTIO-PCI ERROR: queue already active\n");
- return -1;
- }
-
- vq->queue_index = queue_index;
-
- /* initialize the 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
- *
- * NOTE: vr->desc is initialized by vring_init()
- */
-
- outl((unsigned long)virt_to_phys(vr->desc) >> PAGE_SHIFT,
- ioaddr + VIRTIO_PCI_QUEUE_PFN);
-
- return num;
- }
-
- #define CFG_POS(vdev, field) \
- (vdev->cfg_cap_pos + offsetof(struct virtio_pci_cfg_cap, field))
-
- static void prep_pci_cfg_cap(struct virtio_pci_modern_device *vdev,
- struct virtio_pci_region *region,
- size_t offset, u32 length)
- {
- pci_write_config_byte(vdev->pci, CFG_POS(vdev, cap.bar), region->bar);
- pci_write_config_dword(vdev->pci, CFG_POS(vdev, cap.length), length);
- pci_write_config_dword(vdev->pci, CFG_POS(vdev, cap.offset),
- (intptr_t)(region->base + offset));
- }
-
- void vpm_iowrite8(struct virtio_pci_modern_device *vdev,
- struct virtio_pci_region *region, u8 data, size_t offset)
- {
- switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
- case VIRTIO_PCI_REGION_MEMORY:
- writeb(data, region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PORT:
- outb(data, region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PCI_CONFIG:
- prep_pci_cfg_cap(vdev, region, offset, 1);
- pci_write_config_byte(vdev->pci, CFG_POS(vdev, pci_cfg_data), data);
- break;
- default:
- assert(0);
- break;
- }
- }
-
- void vpm_iowrite16(struct virtio_pci_modern_device *vdev,
- struct virtio_pci_region *region, u16 data, size_t offset)
- {
- data = cpu_to_le16(data);
- switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
- case VIRTIO_PCI_REGION_MEMORY:
- writew(data, region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PORT:
- outw(data, region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PCI_CONFIG:
- prep_pci_cfg_cap(vdev, region, offset, 2);
- pci_write_config_word(vdev->pci, CFG_POS(vdev, pci_cfg_data), data);
- break;
- default:
- assert(0);
- break;
- }
- }
-
- void vpm_iowrite32(struct virtio_pci_modern_device *vdev,
- struct virtio_pci_region *region, u32 data, size_t offset)
- {
- data = cpu_to_le32(data);
- switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
- case VIRTIO_PCI_REGION_MEMORY:
- writel(data, region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PORT:
- outl(data, region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PCI_CONFIG:
- prep_pci_cfg_cap(vdev, region, offset, 4);
- pci_write_config_dword(vdev->pci, CFG_POS(vdev, pci_cfg_data), data);
- break;
- default:
- assert(0);
- break;
- }
- }
-
- u8 vpm_ioread8(struct virtio_pci_modern_device *vdev,
- struct virtio_pci_region *region, size_t offset)
- {
- uint8_t data;
- switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
- case VIRTIO_PCI_REGION_MEMORY:
- data = readb(region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PORT:
- data = inb(region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PCI_CONFIG:
- prep_pci_cfg_cap(vdev, region, offset, 1);
- pci_read_config_byte(vdev->pci, CFG_POS(vdev, pci_cfg_data), &data);
- break;
- default:
- assert(0);
- data = 0;
- break;
- }
- return data;
- }
-
- u16 vpm_ioread16(struct virtio_pci_modern_device *vdev,
- struct virtio_pci_region *region, size_t offset)
- {
- uint16_t data;
- switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
- case VIRTIO_PCI_REGION_MEMORY:
- data = readw(region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PORT:
- data = inw(region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PCI_CONFIG:
- prep_pci_cfg_cap(vdev, region, offset, 2);
- pci_read_config_word(vdev->pci, CFG_POS(vdev, pci_cfg_data), &data);
- break;
- default:
- assert(0);
- data = 0;
- break;
- }
- return le16_to_cpu(data);
- }
-
- u32 vpm_ioread32(struct virtio_pci_modern_device *vdev,
- struct virtio_pci_region *region, size_t offset)
- {
- uint32_t data;
- switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
- case VIRTIO_PCI_REGION_MEMORY:
- data = readw(region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PORT:
- data = inw(region->base + offset);
- break;
- case VIRTIO_PCI_REGION_PCI_CONFIG:
- prep_pci_cfg_cap(vdev, region, offset, 4);
- pci_read_config_dword(vdev->pci, CFG_POS(vdev, pci_cfg_data), &data);
- break;
- default:
- assert(0);
- data = 0;
- break;
- }
- return le32_to_cpu(data);
- }
-
- int virtio_pci_find_capability(struct pci_device *pci, uint8_t cfg_type)
- {
- int pos;
- uint8_t type, bar;
-
- for (pos = pci_find_capability(pci, PCI_CAP_ID_VNDR);
- pos > 0;
- pos = pci_find_next_capability(pci, pos, PCI_CAP_ID_VNDR)) {
-
- pci_read_config_byte(pci, pos + offsetof(struct virtio_pci_cap,
- cfg_type), &type);
- pci_read_config_byte(pci, pos + offsetof(struct virtio_pci_cap,
- bar), &bar);
-
- /* Ignore structures with reserved BAR values */
- if (bar > 0x5) {
- continue;
- }
-
- if (type == cfg_type) {
- return pos;
- }
- }
- return 0;
- }
-
- int virtio_pci_map_capability(struct pci_device *pci, int cap, size_t minlen,
- u32 align, u32 start, u32 size,
- struct virtio_pci_region *region)
- {
- u8 bar;
- u32 offset, length, base_raw;
- unsigned long base;
-
- pci_read_config_byte(pci, cap + offsetof(struct virtio_pci_cap, bar), &bar);
- pci_read_config_dword(pci, cap + offsetof(struct virtio_pci_cap, offset),
- &offset);
- pci_read_config_dword(pci, cap + offsetof(struct virtio_pci_cap, length),
- &length);
-
- if (length <= start) {
- DBG("VIRTIO-PCI bad capability len %d (>%d expected)\n", length, start);
- return -EINVAL;
- }
- if (length - start < minlen) {
- DBG("VIRTIO-PCI bad capability len %d (>=%zd expected)\n", length, minlen);
- return -EINVAL;
- }
- length -= start;
- if (start + offset < offset) {
- DBG("VIRTIO-PCI map wrap-around %d+%d\n", start, offset);
- return -EINVAL;
- }
- offset += start;
- if (offset & (align - 1)) {
- DBG("VIRTIO-PCI offset %d not aligned to %d\n", offset, align);
- return -EINVAL;
- }
- if (length > size) {
- length = size;
- }
-
- if (minlen + offset < minlen ||
- minlen + offset > pci_bar_size(pci, PCI_BASE_ADDRESS(bar))) {
- DBG("VIRTIO-PCI map virtio %zd@%d out of range on bar %i length %ld\n",
- minlen, offset,
- bar, pci_bar_size(pci, PCI_BASE_ADDRESS(bar)));
- return -EINVAL;
- }
-
- region->base = NULL;
- region->length = length;
- region->bar = bar;
-
- base = pci_bar_start(pci, PCI_BASE_ADDRESS(bar));
- if (base) {
- pci_read_config_dword(pci, PCI_BASE_ADDRESS(bar), &base_raw);
-
- if (base_raw & PCI_BASE_ADDRESS_SPACE_IO) {
- /* Region accessed using port I/O */
- region->base = (void *)(base + offset);
- region->flags = VIRTIO_PCI_REGION_PORT;
- } else {
- /* Region mapped into memory space */
- region->base = ioremap(base + offset, length);
- region->flags = VIRTIO_PCI_REGION_MEMORY;
- }
- }
- if (!region->base) {
- /* Region accessed via PCI config space window */
- region->base = (void *)(intptr_t)offset;
- region->flags = VIRTIO_PCI_REGION_PCI_CONFIG;
- }
- return 0;
- }
-
- void virtio_pci_unmap_capability(struct virtio_pci_region *region)
- {
- unsigned region_type = region->flags & VIRTIO_PCI_REGION_TYPE_MASK;
- if (region_type == VIRTIO_PCI_REGION_MEMORY) {
- iounmap(region->base);
- }
- }
-
- void vpm_notify(struct virtio_pci_modern_device *vdev,
- struct vring_virtqueue *vq)
- {
- vpm_iowrite16(vdev, &vq->notification, (u16)vq->queue_index, 0);
- }
-
- int vpm_find_vqs(struct virtio_pci_modern_device *vdev,
- unsigned nvqs, struct vring_virtqueue *vqs)
- {
- unsigned i;
- struct vring_virtqueue *vq;
- u16 size, off;
- u32 notify_offset_multiplier;
- int err;
-
- if (nvqs > vpm_ioread16(vdev, &vdev->common, COMMON_OFFSET(num_queues))) {
- return -ENOENT;
- }
-
- /* Read notify_off_multiplier from config space. */
- pci_read_config_dword(vdev->pci,
- vdev->notify_cap_pos + offsetof(struct virtio_pci_notify_cap,
- notify_off_multiplier),
- ¬ify_offset_multiplier);
-
- for (i = 0; i < nvqs; i++) {
- /* Select the queue we're interested in */
- vpm_iowrite16(vdev, &vdev->common, (u16)i, COMMON_OFFSET(queue_select));
-
- /* Check if queue is either not available or already active. */
- size = vpm_ioread16(vdev, &vdev->common, COMMON_OFFSET(queue_size));
- /* QEMU has a bug where queues don't revert to inactive on device
- * reset. Skip checking the queue_enable field until it is fixed.
- */
- if (!size /*|| vpm_ioread16(vdev, &vdev->common.queue_enable)*/)
- return -ENOENT;
-
- if (size & (size - 1)) {
- 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));
-
- 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));
-
- vpm_iowrite64(vdev, &vdev->common, virt_to_phys(vq->vring.desc),
- COMMON_OFFSET(queue_desc_lo),
- COMMON_OFFSET(queue_desc_hi));
- vpm_iowrite64(vdev, &vdev->common, virt_to_phys(vq->vring.avail),
- COMMON_OFFSET(queue_avail_lo),
- COMMON_OFFSET(queue_avail_hi));
- vpm_iowrite64(vdev, &vdev->common, virt_to_phys(vq->vring.used),
- COMMON_OFFSET(queue_used_lo),
- COMMON_OFFSET(queue_used_hi));
-
- err = virtio_pci_map_capability(vdev->pci,
- vdev->notify_cap_pos, 2, 2,
- off * notify_offset_multiplier, 2,
- &vq->notification);
- if (err) {
- return err;
- }
- }
-
- /* Select and activate all queues. Has to be done last: once we do
- * this, there's no way to go back except reset.
- */
- for (i = 0; i < nvqs; i++) {
- vq = &vqs[i];
- vpm_iowrite16(vdev, &vdev->common, (u16)vq->queue_index,
- COMMON_OFFSET(queue_select));
- vpm_iowrite16(vdev, &vdev->common, 1, COMMON_OFFSET(queue_enable));
- }
- return 0;
- }
|