blob: 9dfc93cb2876c8a8d2e758ea911a2713474d9e7b [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2011 IBM Corporation
* All rights reserved.
* This program and the accompanying materials
* are made available under the terms of the BSD License
* which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/bsd-license.php
*
* Contributors:
* IBM Corporation - initial implementation
*****************************************************************************/
#include <stdio.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"
#include "virtio-internal.h"
/* PCI virtio header offsets */
#define VIRTIOHDR_DEVICE_FEATURES 0
#define VIRTIOHDR_GUEST_FEATURES 4
#define VIRTIOHDR_QUEUE_ADDRESS 8
#define VIRTIOHDR_QUEUE_SIZE 12
#define VIRTIOHDR_QUEUE_SELECT 14
#define VIRTIOHDR_QUEUE_NOTIFY 16
#define VIRTIOHDR_DEVICE_STATUS 18
#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 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->features = VIRTIO_F_VERSION_1;
} else {
dev->features = 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);
}
/**
* Get number of elements in a vring
* @param dev pointer to virtio device information
* @param queue virtio queue number
* @return number of elements
*/
unsigned int virtio_get_qsize(struct virtio_device *dev, int queue)
{
unsigned int size = 0;
if (dev->features & VIRTIO_F_VERSION_1) {
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->legacy.addr+VIRTIOHDR_QUEUE_SIZE));
}
return size;
}
/**
* Get address of descriptor vring
* @param dev pointer to virtio device information
* @param queue virtio queue number
* @return pointer to the descriptor ring
*/
struct vring_desc *virtio_get_vring_desc(struct virtio_device *dev, int queue)
{
return dev->vq[queue].desc;
}
/**
* Get address of "available" vring
* @param dev pointer to virtio device information
* @param queue virtio queue number
* @return pointer to the "available" ring
*/
struct vring_avail *virtio_get_vring_avail(struct virtio_device *dev, int queue)
{
return dev->vq[queue].avail;
}
/**
* Get address of "used" vring
* @param dev pointer to virtio device information
* @param queue virtio queue number
* @return pointer to the "used" ring
*/
struct vring_used *virtio_get_vring_used(struct virtio_device *dev, int queue)
{
return dev->vq[queue].used;
}
/**
* Fill the virtio ring descriptor depending on the legacy mode or virtio 1.0
*/
void virtio_fill_desc(struct vqs *vq, int id, uint64_t features,
uint64_t addr, uint32_t len,
uint16_t flags, uint16_t next)
{
struct vring_desc *desc;
id %= vq->size;
desc = &vq->desc[id];
next %= vq->size;
if (features & VIRTIO_F_VERSION_1) {
if (features & VIRTIO_F_IOMMU_PLATFORM) {
void *gpa = (void *) addr;
if (!vq->desc_gpas) {
fprintf(stderr, "IOMMU setup has not been done!\n");
return;
}
addr = SLOF_dma_map_in(gpa, len, 0);
vq->desc_gpas[id] = gpa;
}
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;
}
}
void virtio_free_desc(struct vqs *vq, int id, uint64_t features)
{
struct vring_desc *desc;
id %= vq->size;
desc = &vq->desc[id];
if (!(features & VIRTIO_F_VERSION_1) ||
!(features & VIRTIO_F_IOMMU_PLATFORM))
return;
if (!vq->desc_gpas[id])
return;
SLOF_dma_map_out(le64_to_cpu(desc->addr), 0, le32_to_cpu(desc->len));
vq->desc_gpas[id] = NULL;
}
void *virtio_desc_addr(struct virtio_device *vdev, int queue, int id)
{
struct vqs *vq = &vdev->vq[queue];
if (vq->desc_gpas)
return vq->desc_gpas[id];
return (void *) virtio_modern64_to_cpu(vdev, vq->desc[id].addr);
}
/**
* Reset virtio device
*/
void virtio_reset_device(struct virtio_device *dev)
{
virtio_set_status(dev, 0);
}
/**
* Notify hypervisor about queue update
*/
void virtio_queue_notify(struct virtio_device *dev, int queue)
{
if (dev->features & VIRTIO_F_VERSION_1) {
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
*/
static void virtio_set_qaddr(struct virtio_device *dev, int queue, unsigned long qaddr)
{
if (dev->features & VIRTIO_F_VERSION_1) {
uint64_t q_desc = qaddr;
uint64_t q_avail;
uint64_t q_used;
uint32_t q_size = virtio_get_qsize(dev, queue);
if (dev->features & VIRTIO_F_IOMMU_PLATFORM) {
unsigned long cb;
cb = q_size * sizeof(struct vring_desc);
cb += sizeof(struct vring_avail) +
sizeof(uint16_t) * q_size;
cb = VQ_ALIGN(cb);
cb += sizeof(struct vring_used) +
sizeof(struct vring_used_elem) * q_size;
cb = VQ_ALIGN(cb);
q_desc = SLOF_dma_map_in((void *)q_desc, cb, 0);
dev->vq[queue].bus_desc = q_desc;
}
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));
}
}
struct vqs *virtio_queue_init_vq(struct virtio_device *dev, unsigned int id)
{
struct vqs *vq;
if (id >= sizeof(dev->vq)/sizeof(dev->vq[0])) {
printf("Queue index is too big!\n");
return NULL;
}
vq = &dev->vq[id];
memset(vq, 0, sizeof(*vq));
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 NULL;
}
vq->avail = (void *) vq->desc + vq->size * sizeof(struct vring_desc);
vq->used = (void *) VQ_ALIGN((unsigned long) vq->avail +
sizeof(struct vring_avail) +
sizeof(uint16_t) * vq->size);
memset(vq->desc, 0, virtio_vring_size(vq->size));
virtio_set_qaddr(dev, id, (unsigned long)vq->desc);
vq->avail->flags = virtio_cpu_to_modern16(dev, VRING_AVAIL_F_NO_INTERRUPT);
vq->avail->idx = 0;
if (dev->features & VIRTIO_F_IOMMU_PLATFORM)
vq->desc_gpas = SLOF_alloc_mem_aligned(
vq->size * sizeof(vq->desc_gpas[0]), 4096);
return vq;
}
void virtio_queue_term_vq(struct virtio_device *dev, struct vqs *vq, unsigned int id)
{
if (vq->desc_gpas) {
unsigned i;
for (i = 0; i < vq->size; ++i)
virtio_free_desc(vq, i, dev->features);
SLOF_free_mem(vq->desc_gpas,
vq->size * sizeof(vq->desc_gpas[0]));
}
if (vq->desc) {
if (dev->features & VIRTIO_F_IOMMU_PLATFORM) {
unsigned long cb;
uint32_t q_size = virtio_get_qsize(dev, id);
cb = q_size * sizeof(struct vring_desc);
cb += sizeof(struct vring_avail) +
sizeof(uint16_t) * q_size;
cb = VQ_ALIGN(cb);
cb += sizeof(struct vring_used) +
sizeof(struct vring_used_elem) * q_size;
cb = VQ_ALIGN(cb);
SLOF_dma_map_out(vq->bus_desc, 0, cb);
}
SLOF_free_mem(vq->desc, virtio_vring_size(vq->size));
}
memset(vq, 0, sizeof(*vq));
}
/**
* Set device status bits
*/
void virtio_set_status(struct virtio_device *dev, int status)
{
if (dev->features & VIRTIO_F_VERSION_1) {
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->features & VIRTIO_F_VERSION_1) {
*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, uint64_t features)
{
if (dev->features & VIRTIO_F_VERSION_1) {
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
*/
uint64_t virtio_get_host_features(struct virtio_device *dev)
{
uint64_t features = 0;
if (dev->features & VIRTIO_F_VERSION_1) {
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;
}
if (host_features & VIRTIO_F_IOMMU_PLATFORM)
features |= VIRTIO_F_IOMMU_PLATFORM;
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;
dev->features = features;
return 0;
}
/**
* Get additional config values
*/
uint64_t virtio_get_config(struct virtio_device *dev, int offset, int size)
{
uint64_t val = ~0ULL;
uint32_t hi, lo;
void *confbase;
if (dev->features & VIRTIO_F_VERSION_1)
confbase = dev->device.addr;
else
confbase = dev->legacy.addr+VIRTIOHDR_DEVICE_CONFIG;
switch (size) {
case 1:
val = ci_read_8(confbase+offset);
break;
case 2:
val = ci_read_16(confbase+offset);
if (dev->features & VIRTIO_F_VERSION_1)
val = le16_to_cpu(val);
break;
case 4:
val = ci_read_32(confbase+offset);
if (dev->features & VIRTIO_F_VERSION_1)
val = le32_to_cpu(val);
break;
case 8:
/* We don't support 8 bytes PIO accesses
* in qemu and this is all PIO
*/
lo = ci_read_32(confbase+offset);
hi = ci_read_32(confbase+offset+4);
if (dev->features & VIRTIO_F_VERSION_1)
val = (uint64_t)le32_to_cpu(hi) << 32 | le32_to_cpu(lo);
else
val = (uint64_t)hi << 32 | lo;
break;
}
return val;
}
/**
* Get config blob
*/
int __virtio_read_config(struct virtio_device *dev, void *dst,
int offset, int len)
{
void *confbase;
unsigned char *buf = dst;
int i;
if (dev->features & VIRTIO_F_VERSION_1)
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;
}