This patch builds a virtio-pci layer which can be used by virtio devices as a layer to interact with virtio-pci. The purpose of the patch is to seperate the common virtio-pci layer from being replicated in all virtio devices. The new layer provides a callback interface to receive information about virtio events. This allows us to share the entire functionality of virtio-pci throughout all virtio devices, for example - we don't need to implement MSI-X for each device and can just do it once for virtio-pci. Signed-off-by: Sasha Levin <levinsasha928@xxxxxxxxx> --- tools/kvm/Makefile | 1 + tools/kvm/include/kvm/ioport.h | 1 + tools/kvm/include/kvm/virtio-pci.h | 51 ++++++++ tools/kvm/virtio/pci.c | 231 ++++++++++++++++++++++++++++++++++++ 4 files changed, 284 insertions(+), 0 deletions(-) create mode 100644 tools/kvm/include/kvm/virtio-pci.h create mode 100644 tools/kvm/virtio/pci.c diff --git a/tools/kvm/Makefile b/tools/kvm/Makefile index 316c2c9..669386f 100644 --- a/tools/kvm/Makefile +++ b/tools/kvm/Makefile @@ -52,6 +52,7 @@ OBJS += virtio/core.o OBJS += virtio/net.o OBJS += virtio/rng.o OBJS += virtio/balloon.o +OBJS += virtio/pci.o OBJS += disk/blk.o OBJS += disk/qcow.o OBJS += disk/raw.o diff --git a/tools/kvm/include/kvm/ioport.h b/tools/kvm/include/kvm/ioport.h index 45c3856..5b857dd 100644 --- a/tools/kvm/include/kvm/ioport.h +++ b/tools/kvm/include/kvm/ioport.h @@ -4,6 +4,7 @@ #include "kvm/rbtree-interval.h" #include <stdbool.h> +#include <limits.h> #include <asm/types.h> #include <linux/types.h> diff --git a/tools/kvm/include/kvm/virtio-pci.h b/tools/kvm/include/kvm/virtio-pci.h new file mode 100644 index 0000000..4524a7f --- /dev/null +++ b/tools/kvm/include/kvm/virtio-pci.h @@ -0,0 +1,51 @@ +#ifndef KVM__VIRTIO_PCI_H +#define KVM__VIRTIO_PCI_H + +#include "kvm/pci.h" + +#include <linux/types.h> + +#define VIRTIO_PCI_MAX_VQ 3 + +struct kvm; + +struct virtio_pci_ops { + void (*set_config)(struct kvm *kvm, void *dev, u8 data, u32 offset); + u8 (*get_config)(struct kvm *kvm, void *dev, u32 offset); + + u32 (*get_host_features)(struct kvm *kvm, void *dev); + void (*set_guest_features)(struct kvm *kvm, void *dev, u32 features); + + int (*init_vq)(struct kvm *kvm, void *dev, u32 vq, u32 pfn); + int (*notify_vq)(struct kvm *kvm, void *dev, u32 vq); + int (*get_pfn_vq)(struct kvm *kvm, void *dev, u32 vq); + int (*get_size_vq)(struct kvm *kvm, void *dev, u32 vq); +}; + +struct virtio_pci { + struct pci_device_header pci_hdr; + struct virtio_pci_ops ops; + void *dev; + + u16 base_addr; + u8 status; + u8 isr; + + /* MSI-X */ + u16 config_vector; + u32 config_gsi; + u32 vq_vector[VIRTIO_PCI_MAX_VQ]; + u32 gsis[VIRTIO_PCI_MAX_VQ]; + u32 msix_io_block; + int msix_enabled; + + /* virtio queue */ + u16 queue_selector; +}; + +int virtio_pci__init(struct kvm *kvm, struct virtio_pci *vpci, void *dev, + int device_id, int subsys_id); +int virtio_pci__signal_vq(struct kvm *kvm, struct virtio_pci *vpci, u32 vq); +int virtio_pci__signal_config(struct kvm *kvm, struct virtio_pci *vpci); + +#endif diff --git a/tools/kvm/virtio/pci.c b/tools/kvm/virtio/pci.c new file mode 100644 index 0000000..6d086fa --- /dev/null +++ b/tools/kvm/virtio/pci.c @@ -0,0 +1,231 @@ +#include "kvm/virtio-pci.h" + +#include "kvm/ioport.h" +#include "kvm/kvm.h" +#include "kvm/virtio-pci-dev.h" +#include "kvm/irq.h" +#include "kvm/virtio.h" + +#include <linux/virtio_pci.h> +#include <string.h> + +static bool virtio_pci__specific_io_in(struct kvm *kvm, struct virtio_pci *vpci, u16 port, + void *data, int size, int offset) +{ + u32 config_offset; + int type = virtio__get_dev_specific_field(offset - 20, vpci->msix_enabled, + 0, &config_offset); + if (type == VIRTIO_PCI_O_MSIX) { + switch (offset) { + case VIRTIO_MSI_CONFIG_VECTOR: + ioport__write16(data, vpci->config_vector); + break; + case VIRTIO_MSI_QUEUE_VECTOR: + ioport__write16(data, vpci->vq_vector[vpci->queue_selector]); + break; + }; + + return true; + } else if (type == VIRTIO_PCI_O_CONFIG) { + u8 cfg; + + cfg = vpci->ops.get_config(kvm, vpci->dev, config_offset); + ioport__write8(data, cfg); + return true; + } + + return false; +} + +static bool virtio_pci__io_in(struct ioport *ioport, struct kvm *kvm, u16 port, void *data, int size) +{ + unsigned long offset; + bool ret = true; + struct virtio_pci *vpci; + u32 val; + + vpci = ioport->priv; + offset = port - vpci->base_addr; + + switch (offset) { + case VIRTIO_PCI_HOST_FEATURES: + val = vpci->ops.get_host_features(kvm, vpci->dev); + ioport__write32(data, val); + break; + case VIRTIO_PCI_QUEUE_PFN: + val = vpci->ops.get_pfn_vq(kvm, vpci->dev, vpci->queue_selector); + ioport__write32(data, val); + break; + case VIRTIO_PCI_QUEUE_NUM: + val = vpci->ops.get_size_vq(kvm, vpci->dev, vpci->queue_selector); + ioport__write32(data, val); + break; + break; + case VIRTIO_PCI_STATUS: + ioport__write8(data, vpci->status); + break; + case VIRTIO_PCI_ISR: + ioport__write8(data, vpci->isr); + kvm__irq_line(kvm, vpci->pci_hdr.irq_line, VIRTIO_IRQ_LOW); + vpci->isr = VIRTIO_IRQ_LOW; + break; + default: + ret = virtio_pci__specific_io_in(kvm, vpci, port, data, size, offset); + break; + }; + + return ret; +} + +static bool virtio_pci__specific_io_out(struct kvm *kvm, struct virtio_pci *vpci, u16 port, + void *data, int size, int offset) +{ + u32 config_offset, gsi, vec; + int type = virtio__get_dev_specific_field(offset - 20, vpci->msix_enabled, + 0, &config_offset); + if (type == VIRTIO_PCI_O_MSIX) { + switch (offset) { + case VIRTIO_MSI_CONFIG_VECTOR: + vec = vpci->config_vector = ioport__read16(data); + + gsi = irq__add_msix_route(kvm, + vpci->pci_hdr.msix.table[vec].low, + vpci->pci_hdr.msix.table[vec].high, + vpci->pci_hdr.msix.table[vec].data); + + vpci->config_gsi = gsi; + break; + case VIRTIO_MSI_QUEUE_VECTOR: { + vec = vpci->vq_vector[vpci->queue_selector] = ioport__read16(data); + + gsi = irq__add_msix_route(kvm, + vpci->pci_hdr.msix.table[vec].low, + vpci->pci_hdr.msix.table[vec].high, + vpci->pci_hdr.msix.table[vec].data); + vpci->gsis[vpci->queue_selector] = gsi; + break; + } + }; + + return true; + } else if (type == VIRTIO_PCI_O_CONFIG) { + vpci->ops.set_config(kvm, vpci->dev, *(u8 *)data, config_offset); + + return true; + } + + return false; +} + +static bool virtio_pci__io_out(struct ioport *ioport, struct kvm *kvm, u16 port, void *data, int size) +{ + unsigned long offset; + bool ret = true; + struct virtio_pci *vpci; + u32 val; + + vpci = ioport->priv; + offset = port - vpci->base_addr; + + switch (offset) { + case VIRTIO_PCI_GUEST_FEATURES: + val = ioport__read32(data); + vpci->ops.set_guest_features(kvm, vpci, val); + break; + case VIRTIO_PCI_QUEUE_PFN: + val = ioport__read32(data); + vpci->ops.init_vq(kvm, vpci->dev, vpci->queue_selector, val); + break; + case VIRTIO_PCI_QUEUE_SEL: + vpci->queue_selector = ioport__read16(data); + break; + case VIRTIO_PCI_QUEUE_NOTIFY: + val = ioport__read16(data); + vpci->ops.notify_vq(kvm, vpci->dev, val); + break; + case VIRTIO_PCI_STATUS: + vpci->status = ioport__read8(data); + break; + default: + ret = virtio_pci__specific_io_out(kvm, vpci, port, data, size, offset); + break; + }; + + return ret; +} + +static struct ioport_operations virtio_pci__io_ops = { + .io_in = virtio_pci__io_in, + .io_out = virtio_pci__io_out, +}; + +static void callback_mmio(u64 addr, u8 *data, u32 len, u8 is_write, void *ptr) +{ + struct virtio_pci *vpci = ptr; + void *table = &vpci->pci_hdr.msix.table; + + vpci->msix_enabled = 1; + if (is_write) + memcpy(table + addr - vpci->msix_io_block, data, len); + else + memcpy(data, table + addr - vpci->msix_io_block, len); +} + +int virtio_pci__signal_vq(struct kvm *kvm, struct virtio_pci *vpci, u32 vq) +{ + kvm__irq_line(kvm, vpci->gsis[vq], VIRTIO_IRQ_HIGH); + + return 0; +} + +int virtio_pci__signal_config(struct kvm *kvm, struct virtio_pci *vpci) +{ + kvm__irq_line(kvm, vpci->config_gsi, VIRTIO_IRQ_HIGH); + + return 0; +} + +int virtio_pci__init(struct kvm *kvm, struct virtio_pci *vpci, void *dev, + int device_id, int subsys_id) +{ + u8 pin, line, ndev; + + vpci->dev = dev; + vpci->msix_io_block = pci_get_io_space_block(); + + vpci->base_addr = ioport__register(IOPORT_EMPTY, &virtio_pci__io_ops, IOPORT_SIZE, vpci); + kvm__register_mmio(kvm, vpci->msix_io_block, 0x100, callback_mmio, vpci); + + vpci->pci_hdr = (struct pci_device_header) { + .vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET, + .device_id = device_id, + .header_type = PCI_HEADER_TYPE_NORMAL, + .revision_id = 0, + .class = 0x010000, + .subsys_vendor_id = PCI_SUBSYSTEM_VENDOR_ID_REDHAT_QUMRANET, + .subsys_id = subsys_id, + .bar[0] = vpci->base_addr | PCI_BASE_ADDRESS_SPACE_IO, + .bar[1] = vpci->msix_io_block | + PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_TYPE_64, + /* bar[2] is the continuation of bar[1] for 64bit addressing */ + .bar[2] = 0, + .status = PCI_STATUS_CAP_LIST, + .capabilities = (void *)&vpci->pci_hdr.msix - (void *)&vpci->pci_hdr, + }; + + vpci->pci_hdr.msix.cap = PCI_CAP_ID_MSIX; + vpci->pci_hdr.msix.next = 0; + vpci->pci_hdr.msix.table_size = (VIRTIO_PCI_MAX_VQ + 1) | PCI_MSIX_FLAGS_ENABLE; + vpci->pci_hdr.msix.table_offset = 1; /* Use BAR 1 */ + vpci->config_vector = 0; + + if (irq__register_device(VIRTIO_ID_RNG, &ndev, &pin, &line) < 0) + return -1; + + vpci->pci_hdr.irq_pin = pin; + vpci->pci_hdr.irq_line = line; + pci__register(&vpci->pci_hdr, ndev); + + return 0; +} -- 1.7.6 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html