blob: 58047967a0200e189c5fe3349781cc000fdc79a0 [file] [log] [blame] [edit]
#include "kvm/virtio-pci.h"
#include "kvm/ioport.h"
#include "kvm/virtio.h"
static bool virtio_pci__specific_data_in(struct kvm *kvm, struct virtio_device *vdev,
void *data, u32 size, unsigned long offset)
{
u32 config_offset;
struct virtio_pci *vpci = vdev->virtio;
int type = virtio__get_dev_specific_field(offset - 20,
virtio_pci__msix_enabled(vpci),
&config_offset);
if (type == VIRTIO_PCI_O_MSIX) {
switch (offset) {
case VIRTIO_MSI_CONFIG_VECTOR:
ioport__write16(data, vpci->config_vector);
break;
case VIRTIO_MSI_QUEUE_VECTOR:
ioport__write16(data, vpci->vq_vector[vpci->queue_selector]);
break;
};
return true;
} else if (type == VIRTIO_PCI_O_CONFIG) {
return virtio_access_config(kvm, vdev, vpci->dev, config_offset,
data, size, false);
}
return false;
}
static bool virtio_pci__data_in(struct kvm_cpu *vcpu, struct virtio_device *vdev,
unsigned long offset, void *data, u32 size)
{
bool ret = true;
struct virtio_pci *vpci;
struct virt_queue *vq;
struct kvm *kvm;
u32 val;
kvm = vcpu->kvm;
vpci = vdev->virtio;
switch (offset) {
case VIRTIO_PCI_HOST_FEATURES:
val = vdev->ops->get_host_features(kvm, vpci->dev);
ioport__write32(data, val);
break;
case VIRTIO_PCI_QUEUE_PFN:
vq = vdev->ops->get_vq(kvm, vpci->dev, vpci->queue_selector);
ioport__write32(data, vq->vring_addr.pfn);
break;
case VIRTIO_PCI_QUEUE_NUM:
val = vdev->ops->get_size_vq(kvm, vpci->dev, vpci->queue_selector);
ioport__write16(data, val);
break;
case VIRTIO_PCI_STATUS:
ioport__write8(data, vpci->status);
break;
case VIRTIO_PCI_ISR:
ioport__write8(data, vpci->isr);
kvm__irq_line(kvm, vpci->legacy_irq_line, VIRTIO_IRQ_LOW);
vpci->isr = VIRTIO_IRQ_LOW;
break;
default:
ret = virtio_pci__specific_data_in(kvm, vdev, data, size, offset);
break;
};
return ret;
}
static bool virtio_pci__specific_data_out(struct kvm *kvm, struct virtio_device *vdev,
void *data, u32 size, unsigned long offset)
{
struct virtio_pci *vpci = vdev->virtio;
u32 config_offset, vec;
int gsi;
int type = virtio__get_dev_specific_field(offset - 20, virtio_pci__msix_enabled(vpci),
&config_offset);
if (type == VIRTIO_PCI_O_MSIX) {
switch (offset) {
case VIRTIO_MSI_CONFIG_VECTOR:
vec = vpci->config_vector = ioport__read16(data);
gsi = virtio_pci__add_msix_route(vpci, vec);
if (gsi < 0)
break;
vpci->config_gsi = gsi;
break;
case VIRTIO_MSI_QUEUE_VECTOR:
vec = ioport__read16(data);
vpci->vq_vector[vpci->queue_selector] = vec;
gsi = virtio_pci__add_msix_route(vpci, vec);
if (gsi < 0)
break;
vpci->gsis[vpci->queue_selector] = gsi;
if (vdev->ops->notify_vq_gsi)
vdev->ops->notify_vq_gsi(kvm, vpci->dev,
vpci->queue_selector,
gsi);
break;
};
return true;
} else if (type == VIRTIO_PCI_O_CONFIG) {
return virtio_access_config(kvm, vdev, vpci->dev, config_offset,
data, size, true);
}
return false;
}
static bool virtio_pci__data_out(struct kvm_cpu *vcpu, struct virtio_device *vdev,
unsigned long offset, void *data, u32 size)
{
bool ret = true;
struct virtio_pci *vpci;
struct virt_queue *vq;
struct kvm *kvm;
u32 val;
unsigned int vq_count;
kvm = vcpu->kvm;
vpci = vdev->virtio;
vq_count = vdev->ops->get_vq_count(kvm, vpci->dev);
switch (offset) {
case VIRTIO_PCI_GUEST_FEATURES:
val = ioport__read32(data);
virtio_set_guest_features(kvm, vdev, vpci->dev, val);
break;
case VIRTIO_PCI_QUEUE_PFN:
val = ioport__read32(data);
if (val) {
vq = vdev->ops->get_vq(kvm, vpci->dev,
vpci->queue_selector);
vq->vring_addr = (struct vring_addr) {
.legacy = true,
.pfn = val,
.align = VIRTIO_PCI_VRING_ALIGN,
.pgsize = 1 << VIRTIO_PCI_QUEUE_ADDR_SHIFT,
};
virtio_pci_init_vq(kvm, vdev, vpci->queue_selector);
} else {
virtio_pci_exit_vq(kvm, vdev, vpci->queue_selector);
}
break;
case VIRTIO_PCI_QUEUE_SEL:
val = ioport__read16(data);
if (val >= vq_count) {
WARN_ONCE(1, "QUEUE_SEL value (%u) is larger than VQ count (%u)\n",
val, vq_count);
return false;
}
vpci->queue_selector = val;
break;
case VIRTIO_PCI_QUEUE_NOTIFY:
val = ioport__read16(data);
if (val >= vq_count) {
WARN_ONCE(1, "QUEUE_SEL value (%u) is larger than VQ count (%u)\n",
val, vq_count);
return false;
}
vdev->ops->notify_vq(kvm, vpci->dev, val);
break;
case VIRTIO_PCI_STATUS:
vpci->status = ioport__read8(data);
if (!vpci->status) /* Sample endianness on reset */
vdev->endian = kvm_cpu__get_endianness(vcpu);
virtio_notify_status(kvm, vdev, vpci->dev, vpci->status);
break;
default:
ret = virtio_pci__specific_data_out(kvm, vdev, data, size, offset);
break;
};
return ret;
}
void virtio_pci_legacy__io_mmio_callback(struct kvm_cpu *vcpu, u64 addr,
u8 *data, u32 len, u8 is_write,
void *ptr)
{
struct virtio_device *vdev = ptr;
struct virtio_pci *vpci = vdev->virtio;
u32 ioport_addr = virtio_pci__port_addr(vpci);
u32 base_addr;
if (addr >= ioport_addr &&
addr < ioport_addr + pci__bar_size(&vpci->pci_hdr, 0))
base_addr = ioport_addr;
else
base_addr = virtio_pci__mmio_addr(vpci);
if (!is_write)
virtio_pci__data_in(vcpu, vdev, addr - base_addr, data, len);
else
virtio_pci__data_out(vcpu, vdev, addr - base_addr, data, len);
}