From 386a1df60df041745bca44d4db86e1870df91f08 Mon Sep 17 00:00:00 2001 From: Shiqing Gao Date: Wed, 15 Oct 2025 13:08:44 +0800 Subject: [PATCH] hv: riscv: implement arch-specific IRQ functions Implement the following functions required by the common IRQ framework: - void arch_init_irq_descs(struct irq_desc *descs); - void arch_setup_irqs(void); - void arch_init_interrupt(uint16_t pcpu_id); - void arch_free_irq(uint32_t irq); - bool arch_request_irq(uint32_t irq); - void arch_pre_irq(const struct irq_desc *desc); - void arch_post_irq(const struct irq_desc *desc); Tracked-On: #8845 Signed-off-by: Shiqing Gao Acked-by: Wang Yu1 --- hypervisor/arch/riscv/irq.c | 282 +++++++++++++++++++++++- hypervisor/include/arch/riscv/asm/irq.h | 72 +++++- 2 files changed, 351 insertions(+), 3 deletions(-) diff --git a/hypervisor/arch/riscv/irq.c b/hypervisor/arch/riscv/irq.c index d58a465fe..d63d4b1dd 100644 --- a/hypervisor/arch/riscv/irq.c +++ b/hypervisor/arch/riscv/irq.c @@ -10,9 +10,278 @@ #include #include #include +#include +#include +#include #include -static void arch_init_interrupt(__unused uint16_t pcpu_id) +/** + * RISC-V IRQ integration with the common IRQ framework (simplified flat mapping). + * + * IRQ Domain Mapping Overview + * =========================== + * + * +---------------------------------------+ + * | Common IRQ Framework | + * |---------------------------------------| + * | IRQ table: irq_desc_array[] | + * | Indexed by acrn_irq | + * +---------------------------------------+ + * ^ + * | + * | acrn_irq = domain_base + src_id + * | + * +-------------------+-------------------------------+ + * | | | + * +-----------+ +-----------+ +-----------+ + * | CPU IRQ | | PLIC | | Other | + * | Domain | | Domain | | Domain | + * +-----------+ +-----------+ +-----------+ + * | | | + * | src_id = trap | src_id = PLIC source ID | + * | cause | | + * v v v + * +-----------+ +-----------+ +-----------+ + * | SW IRQ, | | Ext. Dev | | Ext. Dev | + * | Timer, | | Interrupt | | Interrupt | + * | etc. | +-----------+ +-----------+ + * +-----------+ + * + * Flat offset-based model: + * acrn_irq = domain_base + src_id; + * + * - domain_base: the base ACRN IRQ for this domain, allocated by + * riscv_register_irq_domain() during domain initialization. + * - src_id: the IRQ source ID within a specific IRQ domain. + * For example: + * - In the CPU IRQ domain, src_id corresponds to the trap cause. + * - In the PLIC IRQ domain, src_id corresponds to the external source ID + * defined in the device tree. + * + * Example device tree entry for a device whose interrupt is routed through the PLIC: + * i2c0: i2c@10030000 { + * ... + * interrupt-parent = <&plic0>; + * interrupts = <52>; // src_id is 52 for this device. + * ... + * }; + * + * Workflow overview: + * + * By the IRQ chip: + * 1. Domain registration and IRQ reservation: + * riscv_register_irq_domain(domain_name, irq_num); + * - The CPU IRQ domain is registered first with base 0. + * - Other IRQ domains can be registered later during IRQ chip probe. + * + * By the specific device: + * 2. IRQ handler registration: + * // Lookup the ACRN IRQ based on domain name and src_id. + * acrn_irq = riscv_domain_get_acrn_irq(domain_name, src_id); + * // Register the IRQ handler. + * request_irq(acrn_irq, irq_handler, NULL, IRQF_NONE); + * + */ + +static spinlock_t riscv_irq_lock = { .head = 0U, .tail = 0U }; + +static struct riscv_irq_data irq_data[NR_IRQS]; + +/* Maximum number of IRQ domains */ +#define MAX_IRQ_DOMAIN_NUM 2U + +/* Maximum name size of IRQ domain */ +#define MAX_IRQ_DOMAIN_NAME_SIZE 32U + +/* IRQ domain registry */ +struct riscv_irq_domain { + char name[MAX_IRQ_DOMAIN_NAME_SIZE]; /* name of this domain */ + uint32_t base; /* base acrn_irq for this domain */ + uint32_t irq_num; /* number of IRQs in this domain */ +}; + +static struct riscv_irq_domain riscv_domains[MAX_IRQ_DOMAIN_NUM]; +static uint32_t nr_domains; /* number of registered domains */ + +/* + * Find a domain by name. + * Return: pointer to the domain if found, NULL otherwise. + */ +static struct riscv_irq_domain *find_domain_by_name(const char *name) +{ + uint32_t i; + struct riscv_irq_domain *domain = NULL; + + /* Check if name is valid */ + if (name == NULL) { + pr_err("%s: invalid name", __func__); + } else { + for (i = 0U; i < nr_domains; i++) { + if (strncmp(riscv_domains[i].name, name, MAX_IRQ_DOMAIN_NAME_SIZE) == 0) { + domain = &riscv_domains[i]; + break; + } + } + } + + return domain; +} + +/* + * Return the ACRN IRQ for the given domain and source ID (acrn_irq = domain_base + src_id). + * Return IRQ_INVALID if the given domain or source ID is invalid. + */ +uint32_t riscv_domain_get_acrn_irq(const char *name, uint32_t src_id) +{ + uint32_t acrn_irq = IRQ_INVALID; + struct riscv_irq_domain *domain = NULL; + + domain = find_domain_by_name(name); + if ((domain != NULL) && (src_id < domain->irq_num)) { + acrn_irq = domain->base + src_id; + } else { + pr_err("%s: invalid params name=%s src_id=%u", __func__, name, src_id); + } + + return acrn_irq; +} + +/* + * Register an IRQ domain and reserve ACRN IRQs for that domain. + * + * Return: false if domain registration or IRQ reservation fails. + */ +bool riscv_register_irq_domain(const char *name, uint32_t irq_num) +{ + static uint32_t next_acrn_irq_base; /* next free base for auto-allocation */ + bool ret = false; + uint32_t base, end, i, acrn_irq; + uint64_t flags; + struct riscv_irq_domain *domain = NULL; + + /* Check if domain with this name already exists */ + if (find_domain_by_name(name) != NULL) { + pr_err("%s: domain %s already exists", __func__, name); + /* Check if we have space for a new domain */ + } else if (nr_domains >= MAX_IRQ_DOMAIN_NUM) { + pr_err("%s: registered domains (%d) exceeds MAX_IRQ_DOMAIN_NUM (%d)", + __func__, nr_domains, MAX_IRQ_DOMAIN_NUM); + } else { + spinlock_irqsave_obtain(&riscv_irq_lock, &flags); + + base = next_acrn_irq_base; + end = base + irq_num; + + /* Domain registration */ + if ((irq_num > 0U) && (end <= NR_IRQS)) { + domain = &riscv_domains[nr_domains]; + + (void)strncpy_s(domain->name, MAX_IRQ_DOMAIN_NAME_SIZE, name, (MAX_IRQ_DOMAIN_NAME_SIZE - 1U)); + domain->base = base; + domain->irq_num = irq_num; + + nr_domains++; + next_acrn_irq_base = end; + ret = true; + } else { + pr_err("%s: invalid params name=%s base=%u irq_num=%u (NR_IRQS=%u next_base=%u)", + __func__, name, base, irq_num, NR_IRQS, next_acrn_irq_base); + } + + spinlock_irqrestore_release(&riscv_irq_lock, flags); + } + + /* IRQ reservation */ + if (ret) { + for (i = 0U; i < irq_num; i++) { + acrn_irq = riscv_domain_get_acrn_irq(name, i); + + if ((acrn_irq == IRQ_INVALID) || (reserve_irq_num(acrn_irq) == IRQ_INVALID)) { + pr_err("%s: failed to reserve IRQ[%d]", __func__, acrn_irq); + ret = false; + break; + } + } + } + + return ret; +} + +bool arch_request_irq(__unused uint32_t irq) +{ + bool ret = false; + uint64_t flags; + + spinlock_irqsave_obtain(&riscv_irq_lock, &flags); + + if (irq < NR_IRQS) { + /* + * Update irq_data[] during request_irq(). + * The data is later used by riscv_is_valid_acrn_irq() to verify + * whether a given ACRN IRQ is valid when dispatching interrupts. + */ + irq_data[irq].acrn_irq = irq; + ret = true; + } else { + pr_err("invalid irq=%u", irq); + } + + spinlock_irqrestore_release(&riscv_irq_lock, flags); + + return ret; +} + +void arch_free_irq(__unused uint32_t irq) +{ + uint64_t flags; + + spinlock_irqsave_obtain(&riscv_irq_lock, &flags); + + if (irq < NR_IRQS) { + irq_data[irq].acrn_irq = IRQ_INVALID; + } else { + pr_err("invalid irq=%u", irq); + } + + spinlock_irqrestore_release(&riscv_irq_lock, flags); +} + +void arch_pre_irq(__unused const struct irq_desc *desc) +{ + /* No pre-dispatch actions */ +} + +void arch_post_irq(__unused const struct irq_desc *desc) +{ + /* No post-dispatch actions */ +} + +void arch_init_irq_descs(struct irq_desc descs[]) +{ + uint32_t i; + + /* Attach arch_data pointer */ + for (i = 0U; i < NR_IRQS; i++) { + irq_data[i].acrn_irq = IRQ_INVALID; + descs[i].arch_data = &irq_data[i]; + } +} + +void arch_setup_irqs(void) +{ + /* The CPU IRQ domain is mandatory and registered during interrupt initialization. */ + riscv_register_irq_domain(RISCV_IRQD_CPU, IRQ_NUM_CPU_DOMAIN); + + /** + * NOTE: + * Additional domains (PLIC/APLIC) will be registered later by IRQ chip drivers. + * + * Example: + * riscv_register_irq_domain(RISCV_IRQD_PLIC, plic_ndev); + */ +} + +void arch_init_interrupt(__unused uint16_t pcpu_id) { uint64_t addr = (uint64_t)&strap_handler; @@ -48,3 +317,14 @@ void init_interrupt(uint16_t pcpu_id) local_irq_enable(); } + +bool riscv_is_valid_acrn_irq(uint32_t irq) +{ + bool ret = false; + + if (irq < NR_IRQS) { + ret = (irq_data[irq].acrn_irq == irq); + } + + return ret; +} diff --git a/hypervisor/include/arch/riscv/asm/irq.h b/hypervisor/include/arch/riscv/asm/irq.h index c132f2232..0674b0999 100644 --- a/hypervisor/include/arch/riscv/asm/irq.h +++ b/hypervisor/include/arch/riscv/asm/irq.h @@ -14,8 +14,41 @@ #define IPI_NOTIFY_CPU 0U -/* TODO: to be implemented in later patches when integration with the common IRQ framework is done.*/ -#define NR_IRQS 0U +/* CPU-local interrupt count (room for standard causes) */ +#define IRQ_NUM_CPU_DOMAIN 16U + +/* + * TODO: Abstract IRQ_NUM_PLIC_DOMAIN from the device tree. + * + * Example PLIC device tree entry (as emulated by QEMU): + * plic@c000000 { + * ... + * riscv,ndev = <0x0000005f>; + * ... + * }; + * + * Add 1 to 0x0000005f since interrupt ID 0 is reserved to mean "no interrupt", + * per the PLIC specification. + */ +#define IRQ_NUM_PLIC_DOMAIN 0x60U + +/* + * Future: IRQ_NUM_APLIC_DOMAIN + * + * Example APLIC device tree entry: + * aplic1: interrupt-controller@d000000 { + * ... + * riscv,num-sources = <63>; + * ... + * }; + */ + +#define NR_IRQS (IRQ_NUM_CPU_DOMAIN + IRQ_NUM_PLIC_DOMAIN) + +struct riscv_irq_data { + uint32_t acrn_irq; + /* Can be extended when PROFILING_ON is supported, similar to "struct x86_irq_data". */ +}; struct intr_excp_ctx { struct cpu_regs regs; @@ -23,4 +56,39 @@ struct intr_excp_ctx { void init_interrupt(uint16_t pcpu_id); +/* IRQ domain names */ +#define RISCV_IRQD_CPU "cpu-intc" + +/* + * For non-CPU IRQ domains, the domain name can be derived from the device tree + * during the IRQ chip probe. + * + * Example device tree: + * + * IRQ chip: + * plic0: interrupt-controller@c000000 { + * compatible = "sifive,fu540-c000-plic", "sifive,plic-1.0.0"; + * ... + * }; + * + * Device: + * i2c0: i2c@10030000 { + * ... + * interrupt-parent = <&plic0>; + * ... + * }; + * + * The IRQ domain name can be taken from the 'interrupt-parent' label (e.g. "plic0"), + * or derived by combining it with one of the compatible strings when only a phandle + * is provided as the 'interrupt-parent'. This helps uniquely identify each IRQ + * domain instance. + */ + +/* Domain registration & flat mapping helpers */ +bool riscv_register_irq_domain(const char *name, uint32_t irq_num); +uint32_t riscv_domain_get_acrn_irq(const char *name, uint32_t src_id); + +/* Helper to verify that the IRQ is registered and valid during interrupt dispatch. */ +bool riscv_is_valid_acrn_irq(uint32_t irq); + #endif /* RISCV_IRQ_H */