blob: 7ca7e69ffa49c5a434ad2cfcd39c8f97541a5992 [file] [log] [blame]
#include "kvm/ioport.h"
#include "kvm/virtio.h"
#include "kvm/virtio-mmio.h"
#include <linux/virtio_mmio.h>
#define vmmio_selected_vq(vdev, vmmio) \
(vdev)->ops->get_vq((vmmio)->kvm, (vmmio)->dev, (vmmio)->hdr.queue_sel)
static void virtio_mmio_config_in(struct kvm_cpu *vcpu,
u64 addr, void *data, u32 len,
struct virtio_device *vdev)
{
struct virtio_mmio *vmmio = vdev->virtio;
struct virt_queue *vq;
u32 val = 0;
switch (addr) {
case VIRTIO_MMIO_MAGIC_VALUE:
case VIRTIO_MMIO_VERSION:
case VIRTIO_MMIO_DEVICE_ID:
case VIRTIO_MMIO_VENDOR_ID:
case VIRTIO_MMIO_STATUS:
case VIRTIO_MMIO_INTERRUPT_STATUS:
ioport__write32(data, *(u32 *)(((void *)&vmmio->hdr) + addr));
break;
case VIRTIO_MMIO_DEVICE_FEATURES:
if (vmmio->hdr.host_features_sel == 0)
val = vdev->ops->get_host_features(vmmio->kvm,
vmmio->dev);
ioport__write32(data, val);
break;
case VIRTIO_MMIO_QUEUE_PFN:
vq = vmmio_selected_vq(vdev, vmmio);
ioport__write32(data, vq->vring_addr.pfn);
break;
case VIRTIO_MMIO_QUEUE_NUM_MAX:
val = vdev->ops->get_size_vq(vmmio->kvm, vmmio->dev,
vmmio->hdr.queue_sel);
ioport__write32(data, val);
break;
default:
break;
}
}
static void virtio_mmio_config_out(struct kvm_cpu *vcpu,
u64 addr, void *data, u32 len,
struct virtio_device *vdev)
{
struct virtio_mmio *vmmio = vdev->virtio;
struct kvm *kvm = vmmio->kvm;
unsigned int vq_count = vdev->ops->get_vq_count(kvm, vmmio->dev);
struct virt_queue *vq;
u32 val = 0;
switch (addr) {
case VIRTIO_MMIO_DEVICE_FEATURES_SEL:
case VIRTIO_MMIO_DRIVER_FEATURES_SEL:
val = ioport__read32(data);
*(u32 *)(((void *)&vmmio->hdr) + addr) = val;
break;
case VIRTIO_MMIO_QUEUE_SEL:
val = ioport__read32(data);
if (val >= vq_count) {
WARN_ONCE(1, "QUEUE_SEL value (%u) is larger than VQ count (%u)\n",
val, vq_count);
break;
}
*(u32 *)(((void *)&vmmio->hdr) + addr) = val;
break;
case VIRTIO_MMIO_STATUS:
vmmio->hdr.status = ioport__read32(data);
if (!vmmio->hdr.status) /* Sample endianness on reset */
vdev->endian = kvm_cpu__get_endianness(vcpu);
virtio_notify_status(kvm, vdev, vmmio->dev, vmmio->hdr.status);
break;
case VIRTIO_MMIO_DRIVER_FEATURES:
if (vmmio->hdr.guest_features_sel == 0) {
val = ioport__read32(data);
virtio_set_guest_features(vmmio->kvm, vdev,
vmmio->dev, val);
}
break;
case VIRTIO_MMIO_GUEST_PAGE_SIZE:
val = ioport__read32(data);
vmmio->hdr.guest_page_size = val;
break;
case VIRTIO_MMIO_QUEUE_NUM:
val = ioport__read32(data);
vmmio->hdr.queue_num = val;
vdev->ops->set_size_vq(vmmio->kvm, vmmio->dev,
vmmio->hdr.queue_sel, val);
break;
case VIRTIO_MMIO_QUEUE_ALIGN:
val = ioport__read32(data);
vmmio->hdr.queue_align = val;
break;
case VIRTIO_MMIO_QUEUE_PFN:
val = ioport__read32(data);
if (val) {
vq = vmmio_selected_vq(vdev, vmmio);
vq->vring_addr = (struct vring_addr) {
.legacy = true,
.pfn = val,
.align = vmmio->hdr.queue_align,
.pgsize = vmmio->hdr.guest_page_size,
};
virtio_mmio_init_vq(kvm, vdev, vmmio->hdr.queue_sel);
} else {
virtio_mmio_exit_vq(kvm, vdev, vmmio->hdr.queue_sel);
}
break;
case VIRTIO_MMIO_QUEUE_NOTIFY:
val = ioport__read32(data);
if (val >= vq_count) {
WARN_ONCE(1, "QUEUE_NOTIFY value (%u) is larger than VQ count (%u)\n",
val, vq_count);
break;
}
vdev->ops->notify_vq(vmmio->kvm, vmmio->dev, val);
break;
case VIRTIO_MMIO_INTERRUPT_ACK:
val = ioport__read32(data);
vmmio->hdr.interrupt_state &= ~val;
break;
default:
break;
};
}
void virtio_mmio_legacy_callback(struct kvm_cpu *vcpu, u64 addr, u8 *data,
u32 len, u8 is_write, void *ptr)
{
struct virtio_device *vdev = ptr;
struct virtio_mmio *vmmio = vdev->virtio;
u32 offset = addr - vmmio->addr;
if (offset >= VIRTIO_MMIO_CONFIG) {
offset -= VIRTIO_MMIO_CONFIG;
virtio_access_config(vmmio->kvm, vdev, vmmio->dev, offset, data,
len, is_write);
return;
}
if (is_write)
virtio_mmio_config_out(vcpu, offset, data, len, ptr);
else
virtio_mmio_config_in(vcpu, offset, data, len, ptr);
}