| #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); |
| } |