These changes are the raw update to qemu-2.6.
[kvmfornfv.git] / qemu / roms / SLOF / lib / libvirtio / virtio.c
index f9c00a6..f189941 100644 (file)
  *     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
 #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
 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;
 }