blob: 02f6f7d8830adb756542b27bf22118e5808b90f8 [file] [log] [blame] [edit]
#include "linux/sizes.h"
#include "kvm/irq.h"
#include "kvm/kvm.h"
#include "kvm/kvm-cpu.h"
#include "kvm/vfio.h"
#include <assert.h>
#include <sys/ioctl.h>
#include <sys/eventfd.h>
#include <sys/resource.h>
#include <sys/time.h>
#define DUMMU_BINDING_COMPAT "snps,designware-i2c"
static u32 vfio_mmio_io_space_blocks = KVM_VFIO_MMIO_AREA;
static u32 vfio_mmio_get_io_space_block(u32 size)
{
u32 block = vfio_mmio_io_space_blocks;
vfio_mmio_io_space_blocks += size;
return block;
}
static void generate_qemu_virt_clk(void *fdt) {
_FDT(fdt_begin_node(fdt, "apb-pclk"));
_FDT(fdt_property_string(fdt, "clock-output-names", "clk24mhz"));
_FDT(fdt_property_string(fdt, "compatible", "fixed-clock"));
_FDT(fdt_property_cell(fdt, "clock-frequency", 0x16e3600));
_FDT(fdt_property_cell(fdt, "phandle", 0x8000));
_FDT(fdt_property_cell(fdt, "#clock-cells", 0x00));
_FDT(fdt_end_node(fdt));
}
static void generate_qemu_iommu(void *fdt, u64 id) {
_FDT(fdt_begin_node(fdt, "pviommu"));
_FDT(fdt_property_string(fdt, "compatible", "pkvm,pviommu"));
_FDT(fdt_property_cell(fdt, "id", id));
_FDT(fdt_property_cell(fdt, "phandle", 0x9000));
_FDT(fdt_property_cell(fdt, "#iommu-cells", 1));
_FDT(fdt_end_node(fdt));
}
/*
* Ideally we should have either a user passed configuration or a look up table to generate
* the device tree from certain platform device.
* As there is many information we can't deduce as dma coherency, compatible string, power
* domains...
* For now we just hardcode this value, this is mainly for testing and not intended for production.
*/
static void generate_mmio_fdt_node(void *fdt,
struct device_header *dev_hdr,
void (*generate_irq_prop)(void *fdt,
u8 irq,
enum irq_type))
{
struct vfio_device *vdev = container_of(dev_hdr,
struct vfio_device, dev_hdr);
struct vfio_platform_device *pdev = &vdev->platform;
u32 iommu[] = {
cpu_to_fdt32(0x9000), /*phandle of pvIOMMU */
cpu_to_fdt32(vdev->sid[0]), /* SID */
};
u64 reg_prop[10]; /* Max 5 for now. */
unsigned int i = 0;
generate_qemu_virt_clk(fdt);
generate_qemu_iommu(fdt, vdev->iommu);
for ( ; i < vdev->info.num_regions ; i++) {
reg_prop[2 * i] = cpu_to_fdt64(vdev->regions[i].guest_phys_addr); /* Addr */
reg_prop[2 * i + 1] = cpu_to_fdt64(vdev->regions[i].info.size); /* Size */
}
_FDT(fdt_begin_node(fdt, vdev->params->name));
_FDT(fdt_property_string(fdt, "compatible", DUMMU_BINDING_COMPAT));
_FDT(fdt_property_string(fdt, "clock-names", "apb_pclk"));
_FDT(fdt_property_cell(fdt, "clocks", 0x8000));
_FDT(fdt_property(fdt, "dma-coherent", NULL, 0));
_FDT(fdt_property(fdt, "iommus", iommu, sizeof(iommu)));
_FDT(fdt_property(fdt, "reg", reg_prop, sizeof(u64) * vdev->info.num_regions * 2));
for (i = 0 ; i < vdev->info.num_irqs ; ++i)
generate_irq_prop(fdt, pdev->gsi[i], IRQ_TYPE_LEVEL_HIGH);
_FDT(fdt_end_node(fdt));
}
static int vfio_platform_configure_regs(struct kvm *kvm,
struct vfio_device *vdev)
{
int ret = 0;
unsigned int i = 0;
struct vfio_region *region;
struct vfio_region_info *info;
for (i = 0 ; i < vdev->info.num_regions ; i++) {
info = &vdev->regions[i].info;
*info = (struct vfio_region_info) {
.argsz = sizeof(*info),
.index = i,
};
ioctl(vdev->fd, VFIO_DEVICE_GET_REGION_INFO, info);
vfio_dev_info(vdev, "map vfio[%d] 0x%llx - 0x%llx ", i, info->offset, info->size);
region = &vdev->regions[i];
region->vdev = vdev;
region->is_ioport = false;
region->guest_phys_addr = vfio_mmio_get_io_space_block(region->info.size);
vfio_dev_info(vdev, "map vfio[%d] guest_addr %llx ", i, region->guest_phys_addr);
ret = vfio_map_region(kvm, vdev, region);
if (ret) {
vfio_dev_err(vdev, "Failed to map region[%d] at 0x%llx ", i, info->offset);
break;
}
}
return ret;
}
static int vfio_platform_configure_dev_irqs(struct kvm *kvm, struct vfio_device *vdev)
{
int ret = 0;
unsigned int i = 0, j;
struct vfio_platform_device *pdev = &vdev->platform;
struct vfio_irq_info irq_info;
unsigned int num_irqs = vdev->info.num_irqs;
union vfio_irq_eventfd trigger, unmask;
int trigger_fd, unmask_fd;
pdev->gsi = malloc(num_irqs * sizeof(pdev->gsi));
pdev->irqfd = malloc(num_irqs * sizeof(pdev->irqfd));
pdev->unmaskfd = malloc(num_irqs * sizeof(pdev->unmaskfd));
for ( ; i < num_irqs; ++i) {
irq_info = (struct vfio_irq_info){
.argsz = sizeof(irq_info),
.index = i,
};
ret = ioctl(vdev->fd, VFIO_DEVICE_GET_IRQ_INFO, &irq_info);
if (ret) {
vfio_dev_err(vdev, "Failed to get irq[%d] info, err %d", i, ret);
return -EINVAL;
}
if (!(irq_info.flags & VFIO_IRQ_INFO_EVENTFD)) {
vfio_dev_err(vdev, "irq[%d] not eventfd capable", i);
return -EINVAL;
}
/* Assume level interrupt. */
if (!(irq_info.flags & VFIO_IRQ_INFO_AUTOMASKED)) {
vfio_dev_err(vdev, "irq[%d] interrupt not AUTOMASKED", i);
return -EINVAL;
}
pdev->gsi[i] = irq__alloc_line();
trigger_fd = eventfd(0, 0);
if (trigger_fd < 0) {
vfio_dev_err(vdev, "Failed to create trigger eventfd");
return trigger_fd;
}
unmask_fd = eventfd(0, 0);
if (unmask_fd < 0) {
vfio_dev_err(vdev, "Failed to create unmask eventfd");
close(trigger_fd);
return unmask_fd;
}
ret = irq__add_irqfd(kvm, pdev->gsi[i] - KVM_IRQ_OFFSET, trigger_fd, unmask_fd);
if (ret)
goto err_close;
trigger.irq = (struct vfio_irq_set) {
.argsz = sizeof(trigger),
.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER,
.index = i,
.start = 0,
.count = 1,
};
set_vfio_irq_eventd_payload(&trigger, trigger_fd);
ret = ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, &trigger);
if (ret < 0) {
vfio_dev_err(vdev, "failed to setup VFIO IRQ");
goto err_delete_line;
}
unmask.irq = (struct vfio_irq_set) {
.argsz = sizeof(unmask),
.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_UNMASK,
.index = i,
.start = 0,
.count = 1,
};
set_vfio_irq_eventd_payload(&unmask, unmask_fd);
ret = ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, &unmask);
if (ret < 0) {
vfio_dev_err(vdev, "Failed to setup unmask IRQ");
goto err_remove_event;
}
pdev->irqfd[i] = trigger_fd;
pdev->unmaskfd[i] = unmask_fd;
vfio_dev_info(vdev, "map irq[%d] gsi %d fd %d", i, pdev->gsi[i], pdev->irqfd[i]);
}
return 0;
err_remove_event:
for (j = i - 1 ; j >= 0 && i; --j) {
/* Remove trigger event */
trigger.irq.flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_TRIGGER;
trigger.irq.count = 0;
trigger.irq.index = j;
ioctl(vdev->fd, VFIO_DEVICE_SET_IRQS, &trigger);
}
err_delete_line:
for (j = i - 1 ; j >= 0 && i; --j)
irq__del_irqfd(kvm, pdev->gsi[j] - KVM_IRQ_OFFSET, pdev->irqfd[j]);
err_close:
for (j = i - 1 ; j >= 0 && i; --j) {
close(pdev->irqfd[j]);
close(pdev->unmaskfd[j]);
}
return ret;
}
int vfio_platform_setup_device(struct kvm *kvm, struct vfio_device *vdev)
{
int ret;
vfio_dev_info(vdev, "VFIO Platform device setup");
ret = vfio_platform_configure_regs(kvm, vdev);
if (ret)
return ret;
vdev->dev_hdr = (struct device_header) {
.bus_type = DEVICE_BUS_MMIO,
.data = generate_mmio_fdt_node,
};
ret = device__register(&vdev->dev_hdr);
if (ret) {
vfio_dev_err(vdev, "Failed to register VFIO device");
return ret;
}
ret = vfio_platform_configure_dev_irqs(kvm, vdev);
if (ret) {
vfio_dev_err(vdev, "Failed to configure IRQs");
return ret;
}
return 0;
}
void vfio_platform_teardown_device(struct kvm *kvm, struct vfio_device *vdev)
{
size_t i;
struct vfio_platform_device *pdev = &vdev->platform;
for (i = 0; i < vdev->info.num_regions; ++i)
vfio_unmap_region(kvm, &vdev->regions[i]);
for(i = 0; i < vdev->info.num_irqs; ++i) {
irq__del_irqfd(kvm, pdev->gsi[i] - KVM_IRQ_OFFSET, pdev->irqfd[i]);
close(pdev->irqfd[i]);
close(pdev->unmaskfd[i]);
}
device__unregister(&vdev->dev_hdr);
free(pdev->gsi);
free(pdev->irqfd);
free(pdev->unmaskfd);
}