mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2026-07-27 08:50:58 +00:00
Original interrupt context save/restore routine saves to/restore from per-cpu stack. This commit modifies it to support saving to/restoring from address pointed to by sscratch register. When sscratch is 0, the assembly is functionally equivalent to the old version (save to/restore from per-cpu stack) When sscratch is not 0, the assembly saves to/restores from the place that sscratch points to. hstatus is also added to the trap frame as this affects the virtual mode the trap returns to. Tracked-On: #8841 Signed-off-by: Yifan Liu <yifan1.liu@intel.com> Signed-off-by: Haicheng Li <haicheng.li@intel.com> Acked-by: Wang Yu1 <yu1.wang@intel.com>
317 lines
9.1 KiB
C
317 lines
9.1 KiB
C
/*
|
|
* Copyright (C) 2023-2025 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Authors:
|
|
* Haicheng Li <haicheng.li@intel.com>
|
|
*/
|
|
|
|
#include <asm/trap.h>
|
|
#include <cpu.h>
|
|
#include <asm/csr.h>
|
|
#include <irq.h>
|
|
#include <rtl.h>
|
|
#include <spinlock.h>
|
|
#include <types.h>
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/*
|
|
* According to RISC-V Privileged Architecture
|
|
* 12.1.2. Supervisor Trap Vector Base Address (stvec) Register:
|
|
* The BASE field in stvec is a field that can hold any valid virtual
|
|
* or physical address, subject to the following alignment constraints:
|
|
* the address must be ``4-byte aligned``, and MODE settings other than
|
|
* Direct might impose additional alignment constraints on the
|
|
* value in the BASE field.
|
|
*/
|
|
cpu_csr_write(CSR_STVEC, (addr | TRAP_VECTOR_MODE_DIRECT));
|
|
cpu_csr_write(CSR_SIE, (IP_IE_SSI | IP_IE_STI | IP_IE_SEI));
|
|
|
|
/* sscratch holds the value used by save/restore routine.
|
|
* sscratch == 0: save/restore to per-cpu stack (hs-mode trap handling)
|
|
* sscratch != 0: save/restore to vcpu struct (v-mode trap handling)
|
|
*/
|
|
cpu_csr_write(CSR_SSCRATCH, 0ULL);
|
|
}
|
|
|
|
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;
|
|
}
|