diff options
author | 2016-06-03 03:33:22 +0000 | |
---|---|---|
committer | 2016-06-03 03:33:23 +0000 | |
commit | da27230f80795d0028333713f036d44c53cb0e68 (patch) | |
tree | b3d379eaf000adf72b36cb01cdf4d79c3e3f064c /qemu/roms/SLOF/lib/libvirtio/virtio.c | |
parent | 0e68cb048bb8aadb14675f5d4286d8ab2fc35449 (diff) | |
parent | 437fd90c0250dee670290f9b714253671a990160 (diff) |
Merge "These changes are the raw update to qemu-2.6."
Diffstat (limited to 'qemu/roms/SLOF/lib/libvirtio/virtio.c')
-rw-r--r-- | qemu/roms/SLOF/lib/libvirtio/virtio.c | 457 |
1 files changed, 396 insertions, 61 deletions
diff --git a/qemu/roms/SLOF/lib/libvirtio/virtio.c b/qemu/roms/SLOF/lib/libvirtio/virtio.c index f9c00a67a..f189941c7 100644 --- a/qemu/roms/SLOF/lib/libvirtio/virtio.c +++ b/qemu/roms/SLOF/lib/libvirtio/virtio.c @@ -10,10 +10,16 @@ * IBM Corporation - initial implementation *****************************************************************************/ +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> #include <cpu.h> #include <cache.h> #include <byteorder.h> #include "virtio.h" +#include "helpers.h" /* PCI virtio header offsets */ #define VIRTIOHDR_DEVICE_FEATURES 0 @@ -26,6 +32,166 @@ #define VIRTIOHDR_ISR_STATUS 19 #define VIRTIOHDR_DEVICE_CONFIG 20 +/* PCI defines */ +#define PCI_BASE_ADDR_SPACE_IO 0x01 +#define PCI_BASE_ADDR_SPACE_64BIT 0x04 +#define PCI_BASE_ADDR_MEM_MASK (~0x0fUL) +#define PCI_BASE_ADDR_IO_MASK (~0x03UL) + +#define PCI_BASE_ADDR_REG_0 0x10 +#define PCI_CONFIG_CAP_REG 0x34 + +#define PCI_CAP_ID_VNDR 0x9 + +/* Common configuration */ +#define VIRTIO_PCI_CAP_COMMON_CFG 1 +/* Notifications */ +#define VIRTIO_PCI_CAP_NOTIFY_CFG 2 +/* ISR access */ +#define VIRTIO_PCI_CAP_ISR_CFG 3 +/* Device specific configuration */ +#define VIRTIO_PCI_CAP_DEVICE_CFG 4 +/* PCI configuration access */ +#define VIRTIO_PCI_CAP_PCI_CFG 5 + +#define VIRTIO_PCI_CAP_VNDR 0 /* Generic PCI field: PCI_CAP_ID_VNDR */ +#define VIRTIO_PCI_CAP_NEXT 1 /* Generic PCI field: next ptr. */ +#define VIRTIO_PCI_CAP_LEN 2 /* Generic PCI field: capability length */ +#define VIRTIO_PCI_CAP_CFG_TYPE 3 /* Identifies the structure. */ +#define VIRTIO_PCI_CAP_BAR 4 /* Where to find it. */ +#define VIRTIO_PCI_CAP_OFFSET 8 /* Offset within bar. */ +#define VIRTIO_PCI_CAP_LENGTH 12 /* Length of the structure, in bytes. */ + +struct virtio_dev_common { + le32 dev_features_sel; + le32 dev_features; + le32 drv_features_sel; + le32 drv_features; + le16 msix_config; + le16 num_queues; + uint8_t dev_status; + uint8_t cfg_generation; + + le16 q_select; + le16 q_size; + le16 q_msix_vec; + le16 q_enable; + le16 q_notify_off; + le64 q_desc; + le64 q_avail; + le64 q_used; +} __attribute__ ((packed)); + +/* virtio 1.0 Spec: 4.1.3 PCI Device Layout + * + * Fields of different sizes are present in the device configuration regions. + * All 64-bit, 32-bit and 16-bit fields are little-endian. 64-bit fields are to + * be treated as two 32-bit fields, with low 32 bit part followed by the high 32 + * bit part. + */ +static void virtio_pci_write64(void *addr, uint64_t val) +{ + uint32_t hi = (val >> 32) & 0xFFFFFFFF; + uint32_t lo = val & 0xFFFFFFFF; + + ci_write_32(addr, cpu_to_le32(lo)); + ci_write_32(addr + 4, cpu_to_le32(hi)); +} + +static uint64_t virtio_pci_read64(void *addr) +{ + uint64_t hi, lo; + + lo = le32_to_cpu(ci_read_32(addr)); + hi = le32_to_cpu(ci_read_32(addr + 4)); + return (hi << 32) | lo; +} + +static void virtio_cap_set_base_addr(struct virtio_cap *cap, uint32_t offset) +{ + uint64_t addr; + + addr = SLOF_pci_config_read32(PCI_BASE_ADDR_REG_0 + 4 * cap->bar); + if (addr & PCI_BASE_ADDR_SPACE_IO) { + addr = addr & PCI_BASE_ADDR_IO_MASK; + cap->is_io = 1; + } else { + if (addr & PCI_BASE_ADDR_SPACE_64BIT) + addr |= SLOF_pci_config_read32(PCI_BASE_ADDR_REG_0 + 4 * (cap->bar + 1)) << 32; + addr = addr & PCI_BASE_ADDR_MEM_MASK; + cap->is_io = 0; + } + addr = (uint64_t)SLOF_translate_my_address((void *)addr); + cap->addr = (void *)addr + offset; +} + +static void virtio_process_cap(struct virtio_device *dev, uint8_t cap_ptr) +{ + struct virtio_cap *cap; + uint8_t cfg_type, bar; + uint32_t offset; + + cfg_type = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_CFG_TYPE); + bar = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_BAR); + offset = SLOF_pci_config_read32(cap_ptr + VIRTIO_PCI_CAP_OFFSET); + + switch(cfg_type) { + case VIRTIO_PCI_CAP_COMMON_CFG: + cap = &dev->common; + break; + case VIRTIO_PCI_CAP_NOTIFY_CFG: + cap = &dev->notify; + dev->notify_off_mul = SLOF_pci_config_read32(cap_ptr + sizeof(struct virtio_cap)); + break; + case VIRTIO_PCI_CAP_ISR_CFG: + cap = &dev->isr; + break; + case VIRTIO_PCI_CAP_DEVICE_CFG: + cap = &dev->device; + break; + default: + return; + } + + cap->bar = bar; + virtio_cap_set_base_addr(cap, offset); + cap->cap_id = cfg_type; +} + +/** + * Reads the virtio device capabilities, gets called from SLOF routines The + * function determines legacy or modern device and sets up driver registers + */ +struct virtio_device *virtio_setup_vd(void) +{ + uint8_t cap_ptr, cap_vndr; + struct virtio_device *dev; + + dev = SLOF_alloc_mem(sizeof(struct virtio_device)); + if (!dev) { + printf("Failed to allocate memory"); + return NULL; + } + + cap_ptr = SLOF_pci_config_read8(PCI_CONFIG_CAP_REG); + while (cap_ptr != 0) { + cap_vndr = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_VNDR); + if (cap_vndr == PCI_CAP_ID_VNDR) + virtio_process_cap(dev, cap_ptr); + cap_ptr = SLOF_pci_config_read8(cap_ptr+VIRTIO_PCI_CAP_NEXT); + } + + if (dev->common.cap_id && dev->notify.cap_id && + dev->isr.cap_id && dev->device.cap_id) { + dev->is_modern = 1; + } else { + dev->is_modern = 0; + dev->legacy.cap_id = 0; + dev->legacy.bar = 0; + virtio_cap_set_base_addr(&dev->legacy, 0); + } + return dev; +} /** * Calculate ring size according to queue size number @@ -33,9 +199,9 @@ unsigned long virtio_vring_size(unsigned int qsize) { return VQ_ALIGN(sizeof(struct vring_desc) * qsize + - sizeof(struct vring_avail) + sizeof(uint16_t) * qsize) + - VQ_ALIGN(sizeof(struct vring_used) + - sizeof(struct vring_used_elem) * qsize); + sizeof(struct vring_avail) + sizeof(uint16_t) * qsize) + + VQ_ALIGN(sizeof(struct vring_used) + + sizeof(struct vring_used_elem) * qsize); } @@ -45,15 +211,22 @@ unsigned long virtio_vring_size(unsigned int qsize) * @param queue virtio queue number * @return number of elements */ -int virtio_get_qsize(struct virtio_device *dev, int queue) +unsigned int virtio_get_qsize(struct virtio_device *dev, int queue) { - int size = 0; + unsigned int size = 0; - if (dev->type == VIRTIO_TYPE_PCI) { - ci_write_16(dev->base+VIRTIOHDR_QUEUE_SELECT, + if (dev->is_modern) { + void *addr = dev->common.addr + offset_of(struct virtio_dev_common, q_select); + ci_write_16(addr, cpu_to_le16(queue)); + eieio(); + addr = dev->common.addr + offset_of(struct virtio_dev_common, q_size); + size = le16_to_cpu(ci_read_16(addr)); + } + else { + ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SELECT, cpu_to_le16(queue)); eieio(); - size = le16_to_cpu(ci_read_16(dev->base+VIRTIOHDR_QUEUE_SIZE)); + size = le16_to_cpu(ci_read_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SIZE)); } return size; @@ -70,12 +243,19 @@ struct vring_desc *virtio_get_vring_desc(struct virtio_device *dev, int queue) { struct vring_desc *desc = 0; - if (dev->type == VIRTIO_TYPE_PCI) { - ci_write_16(dev->base+VIRTIOHDR_QUEUE_SELECT, + if (dev->is_modern) { + void *q_sel = dev->common.addr + offset_of(struct virtio_dev_common, q_select); + void *q_desc = dev->common.addr + offset_of(struct virtio_dev_common, q_desc); + + ci_write_16(q_sel, cpu_to_le16(queue)); + eieio(); + desc = (void *)(virtio_pci_read64(q_desc)); + } else { + ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SELECT, cpu_to_le16(queue)); eieio(); desc = (void*)(4096L * - le32_to_cpu(ci_read_32(dev->base+VIRTIOHDR_QUEUE_ADDRESS))); + le32_to_cpu(ci_read_32(dev->legacy.addr+VIRTIOHDR_QUEUE_ADDRESS))); } return desc; @@ -90,8 +270,18 @@ struct vring_desc *virtio_get_vring_desc(struct virtio_device *dev, int queue) */ struct vring_avail *virtio_get_vring_avail(struct virtio_device *dev, int queue) { - return (void*)((uint64_t)virtio_get_vring_desc(dev, queue) - + virtio_get_qsize(dev, queue) * sizeof(struct vring_desc)); + if (dev->is_modern) { + void *q_sel = dev->common.addr + offset_of(struct virtio_dev_common, q_select); + void *q_avail = dev->common.addr + offset_of(struct virtio_dev_common, q_avail); + + ci_write_16(q_sel, cpu_to_le16(queue)); + eieio(); + return (void *)(virtio_pci_read64(q_avail)); + } + else { + return (void*)((uint64_t)virtio_get_vring_desc(dev, queue) + + virtio_get_qsize(dev, queue) * sizeof(struct vring_desc)); + } } @@ -103,20 +293,46 @@ struct vring_avail *virtio_get_vring_avail(struct virtio_device *dev, int queue) */ struct vring_used *virtio_get_vring_used(struct virtio_device *dev, int queue) { - return (void*)VQ_ALIGN((uint64_t)virtio_get_vring_avail(dev, queue) - + virtio_get_qsize(dev, queue) - * sizeof(struct vring_avail)); + if (dev->is_modern) { + void *q_sel = dev->common.addr + offset_of(struct virtio_dev_common, q_select); + void *q_used = dev->common.addr + offset_of(struct virtio_dev_common, q_used); + + ci_write_16(q_sel, cpu_to_le16(queue)); + eieio(); + return (void *)(virtio_pci_read64(q_used)); + } else { + return (void*)VQ_ALIGN((uint64_t)virtio_get_vring_avail(dev, queue) + + virtio_get_qsize(dev, queue) + * sizeof(struct vring_avail)); + } } +/** + * Fill the virtio ring descriptor depending on the legacy mode or virtio 1.0 + */ +void virtio_fill_desc(struct vring_desc *desc, bool is_modern, + uint64_t addr, uint32_t len, + uint16_t flags, uint16_t next) +{ + if (is_modern) { + desc->addr = cpu_to_le64(addr); + desc->len = cpu_to_le32(len); + desc->flags = cpu_to_le16(flags); + desc->next = cpu_to_le16(next); + } else { + desc->addr = addr; + desc->len = len; + desc->flags = flags; + desc->next = next; + } +} /** * Reset virtio device */ void virtio_reset_device(struct virtio_device *dev) { - if (dev->type == VIRTIO_TYPE_PCI) { - ci_write_8(dev->base+VIRTIOHDR_DEVICE_STATUS, 0); - } + virtio_set_status(dev, 0); } @@ -125,25 +341,64 @@ void virtio_reset_device(struct virtio_device *dev) */ void virtio_queue_notify(struct virtio_device *dev, int queue) { - if (dev->type == VIRTIO_TYPE_PCI) { - ci_write_16(dev->base+VIRTIOHDR_QUEUE_NOTIFY, cpu_to_le16(queue)); + if (dev->is_modern) { + void *q_sel = dev->common.addr + offset_of(struct virtio_dev_common, q_select); + void *q_ntfy = dev->common.addr + offset_of(struct virtio_dev_common, q_notify_off); + void *addr; + uint16_t q_notify_off; + + ci_write_16(q_sel, cpu_to_le16(queue)); + eieio(); + q_notify_off = le16_to_cpu(ci_read_16(q_ntfy)); + addr = dev->notify.addr + q_notify_off * dev->notify_off_mul; + ci_write_16(addr, cpu_to_le16(queue)); + } else { + ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_NOTIFY, cpu_to_le16(queue)); } } /** * Set queue address */ -void virtio_set_qaddr(struct virtio_device *dev, int queue, unsigned int qaddr) +void virtio_set_qaddr(struct virtio_device *dev, int queue, unsigned long qaddr) +{ + if (dev->is_modern) { + uint64_t q_desc = qaddr; + uint64_t q_avail; + uint64_t q_used; + uint32_t q_size = virtio_get_qsize(dev, queue); + + virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_desc), q_desc); + q_avail = q_desc + q_size * sizeof(struct vring_desc); + virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_avail), q_avail); + q_used = VQ_ALIGN(q_avail + sizeof(struct vring_avail) + sizeof(uint16_t) * q_size); + virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_used), q_used); + ci_write_16(dev->common.addr + offset_of(struct virtio_dev_common, q_enable), cpu_to_le16(1)); + } else { + uint32_t val = qaddr; + val = val >> 12; + ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SELECT, + cpu_to_le16(queue)); + eieio(); + ci_write_32(dev->legacy.addr+VIRTIOHDR_QUEUE_ADDRESS, + cpu_to_le32(val)); + } +} + +int virtio_queue_init_vq(struct virtio_device *dev, struct vqs *vq, unsigned int id) { - if (dev->type == VIRTIO_TYPE_PCI) { - uint32_t val = qaddr; - val = val >> 12; - ci_write_16(dev->base+VIRTIOHDR_QUEUE_SELECT, - cpu_to_le16(queue)); - eieio(); - ci_write_32(dev->base+VIRTIOHDR_QUEUE_ADDRESS, - cpu_to_le32(val)); - } + vq->size = virtio_get_qsize(dev, id); + vq->desc = SLOF_alloc_mem_aligned(virtio_vring_size(vq->size), 4096); + if (!vq->desc) { + printf("memory allocation failed!\n"); + return -1; + } + memset(vq->desc, 0, virtio_vring_size(vq->size)); + virtio_set_qaddr(dev, id, (unsigned long)vq->desc); + vq->avail = virtio_get_vring_avail(dev, id); + vq->used = virtio_get_vring_used(dev, id); + vq->id = id; + return 0; } /** @@ -151,34 +406,109 @@ void virtio_set_qaddr(struct virtio_device *dev, int queue, unsigned int qaddr) */ void virtio_set_status(struct virtio_device *dev, int status) { - if (dev->type == VIRTIO_TYPE_PCI) { - ci_write_8(dev->base+VIRTIOHDR_DEVICE_STATUS, status); + if (dev->is_modern) { + ci_write_8(dev->common.addr + + offset_of(struct virtio_dev_common, dev_status), status); + } else { + ci_write_8(dev->legacy.addr+VIRTIOHDR_DEVICE_STATUS, status); } } +/** + * Get device status bits + */ +void virtio_get_status(struct virtio_device *dev, int *status) +{ + if (dev->is_modern) { + *status = ci_read_8(dev->common.addr + + offset_of(struct virtio_dev_common, dev_status)); + } else { + *status = ci_read_8(dev->legacy.addr+VIRTIOHDR_DEVICE_STATUS); + } +} /** * Set guest feature bits */ -void virtio_set_guest_features(struct virtio_device *dev, int features) +void virtio_set_guest_features(struct virtio_device *dev, uint64_t features) { - if (dev->type == VIRTIO_TYPE_PCI) { - ci_write_32(dev->base+VIRTIOHDR_GUEST_FEATURES, bswap_32(features)); + if (dev->is_modern) { + uint32_t f1 = (features >> 32) & 0xFFFFFFFF; + uint32_t f0 = features & 0xFFFFFFFF; + void *addr = dev->common.addr; + + ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features_sel), + cpu_to_le32(1)); + ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features), + cpu_to_le32(f1)); + + ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features_sel), + cpu_to_le32(0)); + ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features), + cpu_to_le32(f0)); + } else { + ci_write_32(dev->legacy.addr+VIRTIOHDR_GUEST_FEATURES, cpu_to_le32(features)); } } /** * Get host feature bits */ -void virtio_get_host_features(struct virtio_device *dev, int *features) +uint64_t virtio_get_host_features(struct virtio_device *dev) { - if (dev->type == VIRTIO_TYPE_PCI && features) { - *features = bswap_32(ci_read_32(dev->base+VIRTIOHDR_DEVICE_FEATURES)); + uint64_t features = 0; + if (dev->is_modern) { + uint32_t f0 = 0, f1 = 0; + void *addr = dev->common.addr; + + ci_write_32(addr + offset_of(struct virtio_dev_common, dev_features_sel), + cpu_to_le32(1)); + f1 = ci_read_32(addr + + offset_of(struct virtio_dev_common, dev_features)); + ci_write_32(addr + offset_of(struct virtio_dev_common, dev_features_sel), + cpu_to_le32(0)); + f0 = ci_read_32(addr + + offset_of(struct virtio_dev_common, dev_features)); + + features = ((uint64_t)le32_to_cpu(f1) << 32) | le32_to_cpu(f0); + } else { + features = le32_to_cpu(ci_read_32(dev->legacy.addr+VIRTIOHDR_DEVICE_FEATURES)); } + return features; } +int virtio_negotiate_guest_features(struct virtio_device *dev, uint64_t features) +{ + uint64_t host_features = 0; + int status; + + /* Negotiate features */ + host_features = virtio_get_host_features(dev); + if (!(host_features & VIRTIO_F_VERSION_1)) { + fprintf(stderr, "Device does not support virtio 1.0 %llx\n", host_features); + return -1; + } + + virtio_set_guest_features(dev, features); + host_features = virtio_get_host_features(dev); + if ((host_features & features) != features) { + fprintf(stderr, "Features error %llx\n", features); + return -1; + } + + virtio_get_status(dev, &status); + status |= VIRTIO_STAT_FEATURES_OK; + virtio_set_status(dev, status); + + /* Read back to verify the FEATURES_OK bit */ + virtio_get_status(dev, &status); + if ((status & VIRTIO_STAT_FEATURES_OK) != VIRTIO_STAT_FEATURES_OK) + return -1; + + return 0; +} /** * Get additional config values @@ -186,32 +516,38 @@ void virtio_get_host_features(struct virtio_device *dev, int *features) uint64_t virtio_get_config(struct virtio_device *dev, int offset, int size) { uint64_t val = ~0ULL; + uint32_t hi, lo; void *confbase; - switch (dev->type) { - case VIRTIO_TYPE_PCI: - confbase = dev->base+VIRTIOHDR_DEVICE_CONFIG; - break; - default: - return ~0ULL; - } + if (dev->is_modern) + confbase = dev->device.addr; + else + confbase = dev->legacy.addr+VIRTIOHDR_DEVICE_CONFIG; + switch (size) { - case 1: + case 1: val = ci_read_8(confbase+offset); break; - case 2: + case 2: val = ci_read_16(confbase+offset); + if (dev->is_modern) + val = le16_to_cpu(val); break; - case 4: + case 4: val = ci_read_32(confbase+offset); + if (dev->is_modern) + val = le32_to_cpu(val); break; - case 8: + case 8: /* We don't support 8 bytes PIO accesses * in qemu and this is all PIO */ - val = ci_read_32(confbase+offset); - val <<= 32; - val |= ci_read_32(confbase+offset+4); + lo = ci_read_32(confbase+offset); + hi = ci_read_32(confbase+offset+4); + if (dev->is_modern) + val = (uint64_t)le32_to_cpu(hi) << 32 | le32_to_cpu(lo); + else + val = (uint64_t)hi << 32 | lo; break; } @@ -222,20 +558,19 @@ uint64_t virtio_get_config(struct virtio_device *dev, int offset, int size) * Get config blob */ int __virtio_read_config(struct virtio_device *dev, void *dst, - int offset, int len) + int offset, int len) { void *confbase; unsigned char *buf = dst; int i; - switch (dev->type) { - case VIRTIO_TYPE_PCI: - confbase = dev->base+VIRTIOHDR_DEVICE_CONFIG; - break; - default: - return 0; - } + if (dev->is_modern) + confbase = dev->device.addr; + else + confbase = dev->legacy.addr+VIRTIOHDR_DEVICE_CONFIG; + for (i = 0; i < len; i++) buf[i] = ci_read_8(confbase + offset + i); + return len; } |