/* 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 * * Adopted for Seabios: Gleb Natapov * * This work is licensed under the terms of the GNU LGPLv3 * See the COPYING file in the top-level directory. */ #include "config.h" // CONFIG_DEBUG_LEVEL #include "malloc.h" // free #include "output.h" // dprintf #include "pci.h" // pci_config_readl #include "pcidevice.h" // pci_find_capability #include "pci_regs.h" // PCI_BASE_ADDRESS_0 #include "string.h" // memset #include "virtio-pci.h" #include "virtio-ring.h" u64 _vp_read(struct vp_cap *cap, u32 offset, u8 size) { u64 var = 0; switch (cap->mode) { case VP_ACCESS_IO: { u32 addr = cap->ioaddr + offset; switch (size) { case 8: var = inl(addr); var |= (u64)inl(addr+4) << 32; break; case 4: var = inl(addr); break; case 2: var = inw(addr); break; case 1: var = inb(addr); break; } break; } case VP_ACCESS_MMIO: { void *addr = cap->memaddr + offset; switch (size) { case 8: var = readl(addr); var |= (u64)readl(addr+4) << 32; break; case 4: var = readl(addr); break; case 2: var = readw(addr); break; case 1: var = readb(addr); break; } break; } case VP_ACCESS_PCICFG: { u32 addr = cap->baroff + offset; pci_config_writeb(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, cap.bar), cap->bar); pci_config_writel(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, cap.offset), addr); pci_config_writel(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, cap.length), (size > 4) ? 4 : size); switch (size) { case 8: var = pci_config_readl(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data)); pci_config_writel(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, cap.offset), addr + 4); var |= (u64)pci_config_readl(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data)) << 32; break; case 4: var = pci_config_readl(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data)); break; case 2: var = pci_config_readw(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data)); break; case 1: var = pci_config_readb(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data)); break; } } } dprintf(9, "vp read %x (%d) -> 0x%llx\n", cap->ioaddr + offset, size, var); return var; } void _vp_write(struct vp_cap *cap, u32 offset, u8 size, u64 var) { dprintf(9, "vp write %x (%d) <- 0x%llx\n", cap->ioaddr + offset, size, var); switch (cap->mode) { case VP_ACCESS_IO: { u32 addr = cap->ioaddr + offset; switch (size) { case 4: outl(var, addr); break; case 2: outw(var, addr); break; case 1: outb(var, addr); break; } break; } case VP_ACCESS_MMIO: { void *addr = cap->memaddr + offset; switch (size) { case 4: writel(addr, var); break; case 2: writew(addr, var); break; case 1: writeb(addr, var); break; } break; } case VP_ACCESS_PCICFG: { u32 addr = cap->baroff + offset; pci_config_writeb(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, cap.bar), cap->bar); pci_config_writel(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, cap.offset), addr); pci_config_writel(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, cap.length), size); switch (size) { case 4: pci_config_writel(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data), var); break; case 2: pci_config_writew(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data), var); break; case 1: pci_config_writeb(cap->bdf, cap->cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data), var); break; } } } } u64 vp_get_features(struct vp_device *vp) { u32 f0, f1; if (vp->use_modern) { vp_write(&vp->common, virtio_pci_common_cfg, device_feature_select, 0); f0 = vp_read(&vp->common, virtio_pci_common_cfg, device_feature); vp_write(&vp->common, virtio_pci_common_cfg, device_feature_select, 1); f1 = vp_read(&vp->common, virtio_pci_common_cfg, device_feature); } else { f0 = vp_read(&vp->legacy, virtio_pci_legacy, host_features); f1 = 0; } return ((u64)f1 << 32) | f0; } void vp_set_features(struct vp_device *vp, u64 features) { u32 f0, f1; f0 = features; f1 = features >> 32; if (vp->use_modern) { vp_write(&vp->common, virtio_pci_common_cfg, guest_feature_select, 0); vp_write(&vp->common, virtio_pci_common_cfg, guest_feature, f0); vp_write(&vp->common, virtio_pci_common_cfg, guest_feature_select, 1); vp_write(&vp->common, virtio_pci_common_cfg, guest_feature, f1); } else { vp_write(&vp->legacy, virtio_pci_legacy, guest_features, f0); } } u8 vp_get_status(struct vp_device *vp) { if (vp->use_modern) { return vp_read(&vp->common, virtio_pci_common_cfg, device_status); } else { return vp_read(&vp->legacy, virtio_pci_legacy, status); } } void vp_set_status(struct vp_device *vp, u8 status) { if (status == 0) /* reset */ return; if (vp->use_modern) { vp_write(&vp->common, virtio_pci_common_cfg, device_status, status); } else { vp_write(&vp->legacy, virtio_pci_legacy, status, status); } } u8 vp_get_isr(struct vp_device *vp) { if (vp->use_modern) { return vp_read(&vp->isr, virtio_pci_isr, isr); } else { return vp_read(&vp->legacy, virtio_pci_legacy, isr); } } void vp_reset(struct vp_device *vp) { if (vp->use_modern) { vp_write(&vp->common, virtio_pci_common_cfg, device_status, 0); vp_read(&vp->isr, virtio_pci_isr, isr); } else { vp_write(&vp->legacy, virtio_pci_legacy, status, 0); vp_read(&vp->legacy, virtio_pci_legacy, isr); } } void vp_notify(struct vp_device *vp, struct vring_virtqueue *vq) { if (vp->use_modern) { u32 offset = vq->queue_notify_off * vp->notify_off_multiplier; switch (vp->notify.mode) { case VP_ACCESS_IO: outw(vq->queue_index, vp->notify.ioaddr + offset); break; case VP_ACCESS_MMIO: writew(vp->notify.memaddr + offset, vq->queue_index); break; case VP_ACCESS_PCICFG: pci_config_writeb(vp->notify.bdf, vp->notify.cfg + offsetof(struct virtio_pci_cfg_cap, cap.bar), vp->notify.bar); pci_config_writel(vp->notify.bdf, vp->notify.cfg + offsetof(struct virtio_pci_cfg_cap, cap.offset), vp->notify.baroff + offset); pci_config_writel(vp->notify.bdf, vp->notify.cfg + offsetof(struct virtio_pci_cfg_cap, cap.length), 2); pci_config_writew(vp->notify.bdf, vp->notify.cfg + offsetof(struct virtio_pci_cfg_cap, pci_cfg_data), vq->queue_index); } dprintf(9, "vp notify %x (%d) -- 0x%x\n", vp->notify.ioaddr, 2, vq->queue_index); } else { vp_write(&vp->legacy, virtio_pci_legacy, queue_notify, vq->queue_index); } } int vp_find_vq(struct vp_device *vp, int queue_index, struct vring_virtqueue **p_vq) { u16 num; ASSERT32FLAT(); struct vring_virtqueue *vq = *p_vq = memalign_high(PAGE_SIZE, sizeof(*vq)); if (!vq) { warn_noalloc(); goto fail; } memset(vq, 0, sizeof(*vq)); /* select the queue */ if (vp->use_modern) { vp_write(&vp->common, virtio_pci_common_cfg, queue_select, queue_index); } else { vp_write(&vp->legacy, virtio_pci_legacy, queue_sel, queue_index); } /* check if the queue is available */ if (vp->use_modern) { num = vp_read(&vp->common, virtio_pci_common_cfg, queue_size); if (num > MAX_QUEUE_NUM) { vp_write(&vp->common, virtio_pci_common_cfg, queue_size, MAX_QUEUE_NUM); num = vp_read(&vp->common, virtio_pci_common_cfg, queue_size); } } else { num = vp_read(&vp->legacy, virtio_pci_legacy, queue_num); } if (!num) { dprintf(1, "ERROR: queue size is 0\n"); goto fail; } if (num > MAX_QUEUE_NUM) { dprintf(1, "ERROR: queue size %d > %d\n", num, MAX_QUEUE_NUM); goto fail; } /* check if the queue is already active */ if (vp->use_modern) { if (vp_read(&vp->common, virtio_pci_common_cfg, queue_enable)) { dprintf(1, "ERROR: queue already active\n"); goto fail; } } else { if (vp_read(&vp->legacy, virtio_pci_legacy, queue_pfn)) { dprintf(1, "ERROR: queue already active\n"); goto fail; } } vq->queue_index = queue_index; /* initialize the queue */ struct vring * vr = &vq->vring; vring_init(vr, num, (unsigned char*)&vq->queue); /* activate the queue * * NOTE: vr->desc is initialized by vring_init() */ if (vp->use_modern) { vp_write(&vp->common, virtio_pci_common_cfg, queue_desc_lo, (unsigned long)virt_to_phys(vr->desc)); vp_write(&vp->common, virtio_pci_common_cfg, queue_desc_hi, 0); vp_write(&vp->common, virtio_pci_common_cfg, queue_avail_lo, (unsigned long)virt_to_phys(vr->avail)); vp_write(&vp->common, virtio_pci_common_cfg, queue_avail_hi, 0); vp_write(&vp->common, virtio_pci_common_cfg, queue_used_lo, (unsigned long)virt_to_phys(vr->used)); vp_write(&vp->common, virtio_pci_common_cfg, queue_used_hi, 0); vp_write(&vp->common, virtio_pci_common_cfg, queue_enable, 1); vq->queue_notify_off = vp_read(&vp->common, virtio_pci_common_cfg, queue_notify_off); } else { vp_write(&vp->legacy, virtio_pci_legacy, queue_pfn, (unsigned long)virt_to_phys(vr->desc) >> PAGE_SHIFT); } return num; fail: free(vq); *p_vq = NULL; return -1; } void vp_init_simple(struct vp_device *vp, struct pci_device *pci) { u8 cap = pci_find_capability(pci, PCI_CAP_ID_VNDR, 0); struct vp_cap *vp_cap; const char *mode; u32 offset, base, mul; u64 addr; u8 type; memset(vp, 0, sizeof(*vp)); while (cap != 0) { type = pci_config_readb(pci->bdf, cap + offsetof(struct virtio_pci_cap, cfg_type)); switch (type) { case VIRTIO_PCI_CAP_COMMON_CFG: vp_cap = &vp->common; break; case VIRTIO_PCI_CAP_NOTIFY_CFG: vp_cap = &vp->notify; mul = offsetof(struct virtio_pci_notify_cap, notify_off_multiplier); vp->notify_off_multiplier = pci_config_readl(pci->bdf, cap + mul); break; case VIRTIO_PCI_CAP_ISR_CFG: vp_cap = &vp->isr; break; case VIRTIO_PCI_CAP_DEVICE_CFG: vp_cap = &vp->device; break; case VIRTIO_PCI_CAP_PCI_CFG: vp->common.cfg = cap; vp->common.bdf = pci->bdf; vp->notify.cfg = cap; vp->notify.bdf = pci->bdf; vp->isr.cfg = cap; vp->isr.bdf = pci->bdf; vp->device.cfg = cap; vp->device.bdf = pci->bdf; vp_cap = NULL; dprintf(1, "pci dev %x:%x virtio cap at 0x%x type %d [pci cfg access]\n", pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf), cap, type); break; default: vp_cap = NULL; break; } if (vp_cap && !vp_cap->cap) { vp_cap->cap = cap; vp_cap->bar = pci_config_readb(pci->bdf, cap + offsetof(struct virtio_pci_cap, bar)); offset = pci_config_readl(pci->bdf, cap + offsetof(struct virtio_pci_cap, offset)); base = PCI_BASE_ADDRESS_0 + 4 * vp_cap->bar; addr = pci_config_readl(pci->bdf, base); if (addr & PCI_BASE_ADDRESS_SPACE_IO) { addr &= PCI_BASE_ADDRESS_IO_MASK; vp_cap->mode = VP_ACCESS_IO; } else if ((addr & PCI_BASE_ADDRESS_MEM_TYPE_MASK) == PCI_BASE_ADDRESS_MEM_TYPE_64) { addr &= PCI_BASE_ADDRESS_MEM_MASK; addr |= (u64)pci_config_readl(pci->bdf, base + 4) << 32; vp_cap->mode = (addr > 0xffffffffll) ? VP_ACCESS_PCICFG : VP_ACCESS_MMIO; } else { addr &= PCI_BASE_ADDRESS_MEM_MASK; vp_cap->mode = VP_ACCESS_MMIO; } switch (vp_cap->mode) { case VP_ACCESS_IO: { u32 addr = pci_enable_iobar(pci, base); if (!addr) return; vp_cap->ioaddr = addr + offset; mode = "io"; break; } case VP_ACCESS_MMIO: { void *addr = pci_enable_membar(pci, base); if (!addr) return; vp_cap->memaddr = addr + offset; mode = "mmio"; break; } case VP_ACCESS_PCICFG: mode = "pcicfg"; vp_cap->baroff = offset; break; default: mode = "Huh?"; break; } dprintf(1, "pci dev %x:%x virtio cap at 0x%x type %d " "bar %d at 0x%08llx off +0x%04x [%s]\n", pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf), vp_cap->cap, type, vp_cap->bar, addr, offset, mode); } cap = pci_find_capability(pci, PCI_CAP_ID_VNDR, cap); } if (vp->common.cap && vp->notify.cap && vp->isr.cap && vp->device.cap) { dprintf(1, "pci dev %pP using modern (1.0) virtio mode\n", pci); vp->use_modern = 1; } else { dprintf(1, "pci dev %pP using legacy (0.9.5) virtio mode\n", pci); vp->legacy.bar = 0; vp->legacy.ioaddr = pci_enable_iobar(pci, PCI_BASE_ADDRESS_0); if (!vp->legacy.ioaddr) return; vp->legacy.mode = VP_ACCESS_IO; } vp_reset(vp); pci_enable_busmaster(pci); vp_set_status(vp, VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER ); }