| /****************************************************************************** |
| * 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 <cpu.h> |
| #include <cache.h> |
| #include <byteorder.h> |
| #include "virtio.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 |
| |
| |
| /** |
| * Calculate ring size according to queue size number |
| */ |
| unsigned long virtio_vring_size(unsigned int qsize) |
| { |
| return VQ_ALIGN(sizeof(struct vring_desc) * qsize + 2 * (2 + qsize)) |
| + VQ_ALIGN(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 |
| */ |
| int virtio_get_qsize(struct virtio_device *dev, int queue) |
| { |
| int size = 0; |
| |
| if (dev->type == VIRTIO_TYPE_PCI) { |
| ci_write_16(dev->base+VIRTIOHDR_QUEUE_SELECT, |
| cpu_to_le16(queue)); |
| eieio(); |
| size = le16_to_cpu(ci_read_16(dev->base+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) |
| { |
| struct vring_desc *desc = 0; |
| |
| if (dev->type == VIRTIO_TYPE_PCI) { |
| ci_write_16(dev->base+VIRTIOHDR_QUEUE_SELECT, |
| cpu_to_le16(queue)); |
| eieio(); |
| desc = (void*)(4096L * |
| le32_to_cpu(ci_read_32(dev->base+VIRTIOHDR_QUEUE_ADDRESS))); |
| } |
| |
| return 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 (void*)((uint64_t)virtio_get_vring_desc(dev, queue) |
| + virtio_get_qsize(dev, queue) * sizeof(struct vring_desc)); |
| } |
| |
| |
| /** |
| * 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 (void*)VQ_ALIGN((uint64_t)virtio_get_vring_avail(dev, queue) |
| + virtio_get_qsize(dev, queue) |
| * sizeof(struct vring_avail)); |
| } |
| |
| |
| /** |
| * 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); |
| } |
| } |
| |
| |
| /** |
| * Notify hypervisor about queue update |
| */ |
| 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)); |
| } |
| } |
| |
| /** |
| * Set queue address |
| */ |
| void virtio_set_qaddr(struct virtio_device *dev, int queue, unsigned int qaddr) |
| { |
| 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)); |
| } |
| } |
| |
| /** |
| * Set device status bits |
| */ |
| 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); |
| } |
| } |
| |
| |
| /** |
| * Set guest feature bits |
| */ |
| void virtio_set_guest_features(struct virtio_device *dev, int features) |
| |
| { |
| if (dev->type == VIRTIO_TYPE_PCI) { |
| ci_write_32(dev->base+VIRTIOHDR_GUEST_FEATURES, bswap_32(features)); |
| } |
| } |
| |
| /** |
| * Get host feature bits |
| */ |
| void virtio_get_host_features(struct virtio_device *dev, int *features) |
| |
| { |
| if (dev->type == VIRTIO_TYPE_PCI && features) { |
| *features = bswap_32(ci_read_32(dev->base+VIRTIOHDR_DEVICE_FEATURES)); |
| } |
| } |
| |
| |
| /** |
| * Get additional config values |
| */ |
| uint64_t virtio_get_config(struct virtio_device *dev, int offset, int size) |
| { |
| uint64_t val = ~0ULL; |
| void *confbase; |
| |
| switch (dev->type) { |
| case VIRTIO_TYPE_PCI: |
| confbase = dev->base+VIRTIOHDR_DEVICE_CONFIG; |
| break; |
| default: |
| return ~0ULL; |
| } |
| switch (size) { |
| case 1: |
| val = ci_read_8(confbase+offset); |
| break; |
| case 2: |
| val = ci_read_16(confbase+offset); |
| break; |
| case 4: |
| val = ci_read_32(confbase+offset); |
| break; |
| 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); |
| 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; |
| |
| switch (dev->type) { |
| case VIRTIO_TYPE_PCI: |
| confbase = dev->base+VIRTIOHDR_DEVICE_CONFIG; |
| break; |
| default: |
| return 0; |
| } |
| for (i = 0; i < len; i++) |
| buf[i] = ci_read_8(confbase + offset + i); |
| return len; |
| } |