/* virtio-pci.c - pci interface for virtio interface * * (c) Copyright 2008 Bull S.A.S. * * Author: Laurent Vivier * * some parts from Linux Virtio PCI driver * * Copyright IBM Corp. 2007 * Authors: Anthony Liguori * */ #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; }