#include "kvm/devices.h" #include "kvm/fdt.h" #include "kvm/ioeventfd.h" #include "kvm/ioport.h" #include "kvm/kvm.h" #include "kvm/kvm-cpu.h" #include "kvm/irq.h" #include "kvm/util.h" static int aia_fd = -1; static u32 aia_mode = KVM_DEV_RISCV_AIA_MODE_EMUL; static struct kvm_device_attr aia_mode_attr = { .group = KVM_DEV_RISCV_AIA_GRP_CONFIG, .attr = KVM_DEV_RISCV_AIA_CONFIG_MODE, }; static u32 aia_nr_ids = 0; static struct kvm_device_attr aia_nr_ids_attr = { .group = KVM_DEV_RISCV_AIA_GRP_CONFIG, .attr = KVM_DEV_RISCV_AIA_CONFIG_IDS, }; static u32 aia_nr_sources = 0; static struct kvm_device_attr aia_nr_sources_attr = { .group = KVM_DEV_RISCV_AIA_GRP_CONFIG, .attr = KVM_DEV_RISCV_AIA_CONFIG_SRCS, }; static u32 aia_hart_bits = 0; static struct kvm_device_attr aia_hart_bits_attr = { .group = KVM_DEV_RISCV_AIA_GRP_CONFIG, .attr = KVM_DEV_RISCV_AIA_CONFIG_HART_BITS, }; static u32 aia_nr_harts = 0; #define IRQCHIP_AIA_NR 0 #define AIA_IMSIC_BASE RISCV_IRQCHIP #define AIA_IMSIC_ADDR(__hart) \ (AIA_IMSIC_BASE + (__hart) * KVM_DEV_RISCV_IMSIC_SIZE) #define AIA_IMSIC_SIZE \ (aia_nr_harts * KVM_DEV_RISCV_IMSIC_SIZE) #define AIA_APLIC_ADDR \ (AIA_IMSIC_BASE + AIA_IMSIC_SIZE) static void aia__generate_fdt_node(void *fdt, struct kvm *kvm) { u32 i; char name[64]; u32 reg_cells[4], *irq_cells; irq_cells = calloc(aia_nr_harts * 2, sizeof(u32)); if (!irq_cells) die("Failed to alloc irq_cells"); sprintf(name, "imsics@%08x", (u32)AIA_IMSIC_BASE); _FDT(fdt_begin_node(fdt, name)); _FDT(fdt_property_string(fdt, "compatible", "riscv,imsics")); reg_cells[0] = 0; reg_cells[1] = cpu_to_fdt32(AIA_IMSIC_BASE); reg_cells[2] = 0; reg_cells[3] = cpu_to_fdt32(AIA_IMSIC_SIZE); _FDT(fdt_property(fdt, "reg", reg_cells, sizeof(reg_cells))); _FDT(fdt_property_cell(fdt, "#interrupt-cells", 0)); _FDT(fdt_property(fdt, "interrupt-controller", NULL, 0)); _FDT(fdt_property(fdt, "msi-controller", NULL, 0)); _FDT(fdt_property_cell(fdt, "riscv,num-ids", aia_nr_ids)); _FDT(fdt_property_cell(fdt, "phandle", PHANDLE_AIA_IMSIC)); for (i = 0; i < aia_nr_harts; i++) { irq_cells[2*i + 0] = cpu_to_fdt32(PHANDLE_CPU_INTC_BASE + i); irq_cells[2*i + 1] = cpu_to_fdt32(9); } _FDT(fdt_property(fdt, "interrupts-extended", irq_cells, sizeof(u32) * aia_nr_harts * 2)); _FDT(fdt_end_node(fdt)); free(irq_cells); /* Skip APLIC node if we have no interrupt sources */ if (!aia_nr_sources) return; sprintf(name, "aplic@%08x", (u32)AIA_APLIC_ADDR); _FDT(fdt_begin_node(fdt, name)); _FDT(fdt_property_string(fdt, "compatible", "riscv,aplic")); reg_cells[0] = 0; reg_cells[1] = cpu_to_fdt32(AIA_APLIC_ADDR); reg_cells[2] = 0; reg_cells[3] = cpu_to_fdt32(KVM_DEV_RISCV_APLIC_SIZE); _FDT(fdt_property(fdt, "reg", reg_cells, sizeof(reg_cells))); _FDT(fdt_property_cell(fdt, "#interrupt-cells", 2)); _FDT(fdt_property(fdt, "interrupt-controller", NULL, 0)); _FDT(fdt_property_cell(fdt, "riscv,num-sources", aia_nr_sources)); _FDT(fdt_property_cell(fdt, "phandle", PHANDLE_AIA_APLIC)); _FDT(fdt_property_cell(fdt, "msi-parent", PHANDLE_AIA_IMSIC)); _FDT(fdt_end_node(fdt)); } static int aia__irq_routing_init(struct kvm *kvm) { int r; int irqlines = aia_nr_sources + 1; /* Skip this if we have no interrupt sources */ if (!aia_nr_sources) return 0; /* * This describes the default routing that the kernel uses without * any routing explicitly set up via KVM_SET_GSI_ROUTING. So we * don't need to commit these setting right now. The first actual * user (MSI routing) will engage these mappings then. */ for (next_gsi = 0; next_gsi < irqlines; next_gsi++) { r = irq__allocate_routing_entry(); if (r) return r; irq_routing->entries[irq_routing->nr++] = (struct kvm_irq_routing_entry) { .gsi = next_gsi, .type = KVM_IRQ_ROUTING_IRQCHIP, .u.irqchip.irqchip = IRQCHIP_AIA_NR, .u.irqchip.pin = next_gsi, }; } return 0; } static int aia__init(struct kvm *kvm) { int i, ret; u64 aia_addr = 0; struct kvm_device_attr aia_addr_attr = { .group = KVM_DEV_RISCV_AIA_GRP_ADDR, .addr = (u64)(unsigned long)&aia_addr, }; struct kvm_device_attr aia_init_attr = { .group = KVM_DEV_RISCV_AIA_GRP_CTRL, .attr = KVM_DEV_RISCV_AIA_CTRL_INIT, }; /* Setup global device attribute variables */ aia_mode_attr.addr = (u64)(unsigned long)&aia_mode; aia_nr_ids_attr.addr = (u64)(unsigned long)&aia_nr_ids; aia_nr_sources_attr.addr = (u64)(unsigned long)&aia_nr_sources; aia_hart_bits_attr.addr = (u64)(unsigned long)&aia_hart_bits; /* Do nothing if AIA device not created */ if (aia_fd < 0) return 0; /* Set/Get AIA device config parameters */ ret = ioctl(aia_fd, KVM_GET_DEVICE_ATTR, &aia_mode_attr); if (ret) return ret; ret = ioctl(aia_fd, KVM_GET_DEVICE_ATTR, &aia_nr_ids_attr); if (ret) return ret; aia_nr_sources = irq__get_nr_allocated_lines(); ret = ioctl(aia_fd, KVM_SET_DEVICE_ATTR, &aia_nr_sources_attr); if (ret) return ret; aia_hart_bits = fls_long(kvm->nrcpus); ret = ioctl(aia_fd, KVM_SET_DEVICE_ATTR, &aia_hart_bits_attr); if (ret) return ret; /* Save number of HARTs for FDT generation */ aia_nr_harts = kvm->nrcpus; /* Set AIA device addresses */ aia_addr = AIA_APLIC_ADDR; aia_addr_attr.attr = KVM_DEV_RISCV_AIA_ADDR_APLIC; ret = ioctl(aia_fd, KVM_SET_DEVICE_ATTR, &aia_addr_attr); if (ret) return ret; for (i = 0; i < kvm->nrcpus; i++) { aia_addr = AIA_IMSIC_ADDR(i); aia_addr_attr.attr = KVM_DEV_RISCV_AIA_ADDR_IMSIC(i); ret = ioctl(aia_fd, KVM_SET_DEVICE_ATTR, &aia_addr_attr); if (ret) return ret; } /* Setup default IRQ routing */ aia__irq_routing_init(kvm); /* Initialize the AIA device */ ret = ioctl(aia_fd, KVM_SET_DEVICE_ATTR, &aia_init_attr); if (ret) return ret; /* Mark IRQFD as ready */ riscv_irqchip_irqfd_ready = true; return 0; } late_init(aia__init); void aia__create(struct kvm *kvm) { int err; struct kvm_create_device aia_device = { .type = KVM_DEV_TYPE_RISCV_AIA, .flags = 0, }; if (kvm->cfg.arch.ext_disabled[KVM_RISCV_ISA_EXT_SSAIA]) return; err = ioctl(kvm->vm_fd, KVM_CREATE_DEVICE, &aia_device); if (err) return; aia_fd = aia_device.fd; riscv_irqchip = IRQCHIP_AIA; riscv_irqchip_inkernel = true; riscv_irqchip_trigger = NULL; riscv_irqchip_generate_fdt_node = aia__generate_fdt_node; riscv_irqchip_phandle = PHANDLE_AIA_APLIC; riscv_irqchip_msi_phandle = PHANDLE_AIA_IMSIC; riscv_irqchip_line_sensing = true; }