diff --git a/xen/arch/riscv/imsic.c b/xen/arch/riscv/imsic.c index 809f29af86..44b8640ea3 100644 --- a/xen/arch/riscv/imsic.c +++ b/xen/arch/riscv/imsic.c @@ -13,8 +13,12 @@ #include #include #include +#include #include +#include +#include #include +#include #include #include #include @@ -34,6 +38,21 @@ static struct imsic_config imsic_cfg = { .lock = SPIN_LOCK_UNLOCKED, }; +/* + * Number of MSIs available to a guest. Determined by the host interrupt + * controller, so it is identical for every domain -- hence a single global + * rather than a per-domain value. + */ +static unsigned int __ro_after_init guest_num_msis; + +#define GUEST_IMSIC_COMPATIBLE "riscv,imsics" + +/* + * Value is inspired by what QEMU is using for riscv,num-ids property for IMSIC + * node. + */ +#define GUEST_IMSIC_MAX_MSIS 255U + #define IMSIC_DISABLE_EIDELIVERY 0 #define IMSIC_ENABLE_EIDELIVERY 1 #define IMSIC_DISABLE_EITHRESHOLD 1 @@ -182,7 +201,7 @@ static int __init imsic_get_parent_hartid(const struct dt_device_node *node, * or IRQ_M_EXT if the IMSIC node corresponds to a machine-mode IMSIC, * which should be ignored by the hypervisor. */ -static int imsic_parse_node(const struct dt_device_node *node, +static int __init imsic_parse_node(const struct dt_device_node *node, unsigned int *nr_parent_irqs, unsigned int *nr_mmios) { @@ -285,6 +304,11 @@ static int imsic_parse_node(const struct dt_device_node *node, return -ENOENT; } + if ( dt_property_read_u32(node, "riscv,num-guest-ids", &tmp) ) + guest_num_msis = min(GUEST_IMSIC_MAX_MSIS, tmp); + else + guest_num_msis = GUEST_IMSIC_MAX_MSIS; + if ( (imsic_cfg.nr_ids < IMSIC_MIN_ID) || (imsic_cfg.nr_ids > IMSIC_MAX_ID) ) { @@ -526,3 +550,120 @@ int __init imsic_init(const struct dt_device_node *node) return rc; } + +static int __init guest_imsic_make_reg_property(struct domain *d, void *fdt) +{ + paddr_t size = IMSIC_MMIO_PAGE_SZ * d->max_vcpus; + __be32 regs[4] = { + cpu_to_be32(GUEST_IMSIC_S_BASE >> 32), + cpu_to_be32(GUEST_IMSIC_S_BASE), + cpu_to_be32(size >> 32), + cpu_to_be32(size), + }; + + return fdt_property(fdt, "reg", regs, sizeof(regs)); +} + +static int __init guest_imsic_set_interrupt_extended_prop(struct domain *d, + void *fdt) +{ + unsigned int cpu, pos = 0; + __be32 *irq_ext; + int res; + + irq_ext = xvzalloc_array(__be32, d->max_vcpus * 2); + if ( !irq_ext ) + return -ENOMEM; + + for ( cpu = 0; cpu < d->max_vcpus; cpu++ ) + { + char buf[64]; + uint32_t phandle; + + snprintf(buf, ARRAY_SIZE(buf), "/cpus/cpu@%u/interrupt-controller", cpu); + phandle = fdt_get_phandle(fdt, fdt_path_offset(fdt, buf)); + + if ( !phandle ) + { + res = -ENODEV; + goto out; + } + + irq_ext[pos++] = cpu_to_be32(phandle); + irq_ext[pos++] = cpu_to_be32(IRQ_S_EXT); + } + + res = fdt_property(fdt, "interrupts-extended", irq_ext, + d->max_vcpus * 2 * sizeof(*irq_ext)); + + out: + xvfree(irq_ext); + + return res; +} + +int __init vimsic_make_domu_dt_node(struct kernel_info *kinfo, + unsigned int *phandle) +{ + int res; + void *fdt = kinfo->fdt; + char vimsic_name[32]; + unsigned int vimsic_phandle; + + res = snprintf(vimsic_name, ARRAY_SIZE(vimsic_name), "/soc/imsic@%lx", + GUEST_IMSIC_S_BASE); + if ( res >= ARRAY_SIZE(vimsic_name) ) + { + dprintk(XENLOG_DEBUG, "vimsic name is truncated\n"); + return -ENOBUFS; + } + + res = fdt_begin_node(fdt, vimsic_name); + if ( res ) + return res; + + res = fdt_property_string(fdt, "compatible", GUEST_IMSIC_COMPATIBLE); + if ( res ) + return res; + + res = guest_imsic_make_reg_property(kinfo->bd.d, fdt); + if ( res ) + return res; + + res = guest_imsic_set_interrupt_extended_prop(kinfo->bd.d, fdt); + if ( res ) + return res; + + res = fdt_property_u32(fdt, "riscv,num-ids", guest_num_msis); + if ( res ) + return res; + + res = fdt_property(fdt, "msi-controller", NULL, 0); + if ( res ) + return res; + + res = fdt_property_u32(fdt, "#msi-cells", 0); + if ( res ) + return res; + + res = fdt_property(fdt, "interrupt-controller", NULL, 0); + if ( res ) + return res; + + res = fdt_property_u32(fdt, "#interrupt-cells", 0); + if ( res ) + return res; + + vimsic_phandle = alloc_phandle(kinfo); + if ( !vimsic_phandle ) + return -EOVERFLOW; + + res = fdt_property_cell(fdt, "phandle", vimsic_phandle); + if ( res ) + return res; + + if ( phandle ) + *phandle = vimsic_phandle; + + return fdt_end_node(fdt); +} diff --git a/xen/arch/riscv/include/asm/guest-layout.h b/xen/arch/riscv/include/asm/guest-layout.h index 68d95a0939..5e566450bd 100644 --- a/xen/arch/riscv/include/asm/guest-layout.h +++ b/xen/arch/riscv/include/asm/guest-layout.h @@ -3,6 +3,12 @@ #include +/* + * Base address of the guest's supervisor-mode IMSIC. The value is the address + * typically used for IMSIC by QEMU. + */ +#define GUEST_IMSIC_S_BASE _UL(0x28000000) + #define GUEST_RAM_BANKS 2 /* diff --git a/xen/arch/riscv/include/asm/imsic.h b/xen/arch/riscv/include/asm/imsic.h index 5ee954bbf3..2425430ed1 100644 --- a/xen/arch/riscv/include/asm/imsic.h +++ b/xen/arch/riscv/include/asm/imsic.h @@ -78,6 +78,7 @@ struct vimsic_state { }; struct dt_device_node; +struct kernel_info; struct vcpu; int imsic_init(const struct dt_device_node *node); @@ -93,4 +94,6 @@ int vcpu_imsic_init(struct vcpu *v); void vcpu_imsic_deinit(struct vcpu *v); unsigned int vcpu_guest_file_id(const struct vcpu *v); +int vimsic_make_domu_dt_node(struct kernel_info *kinfo, unsigned int *phandle); + #endif /* ASM_RISCV_IMSIC_H */