Files
xen/xen/arch/riscv/aplic.c
T
Oleksii Kurochko a8b85fabf6 xen/riscv: add external interrupt handling for hypervisor mode
Implement functions necessary to have working external interrupts in
hypervisor mode. The following changes are done:
  - Add a common function intc_handle_external_irq() to call APLIC specific
    function to handle an interrupt.
  - Update do_trap() function to handle IRQ_S_EXT case; add the check to catch
    case when cause of trap is an interrupt.
  - Add handle_interrrupt() member to intc_hw_operations structure.
  - Enable local interrupt delivery for IMSIC by calling of
    imsic_ids_local_delivery() in imsic_init(); additionally introduce helper
    imsic_csr_write() to update IMSIC_EITHRESHOLD and IMSIC_EITHRESHOLD.
  - Enable hypervisor external interrupts.
  - Implement aplic_handler_interrupt() and use it to init ->handle_interrupt
    member of intc_hw_operations for APLIC.
  - Add implementation of do_IRQ() to dispatch the interrupt.

The current patch is based on the code from [1].

[1] https://gitlab.com/xen-project/people/olkur/xen/-/commit/7390e2365828b83e27ead56b03114a56e3699dd5

Co-developed-by: Romain Caritey <Romain.Caritey@microchip.com>
Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
2025-07-10 13:40:23 +02:00

368 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* SPDX-License-Identifier: MIT */
/*
* xen/arch/riscv/aplic.c
*
* RISC-V Advanced Platform-Level Interrupt Controller support
*
* Copyright (c) 2023-2024 Microchip.
* Copyright (c) 2024-2025 Vates
*/
#include <xen/const.h>
#include <xen/device_tree.h>
#include <xen/errno.h>
#include <xen/init.h>
#include <xen/irq.h>
#include <xen/mm.h>
#include <xen/sections.h>
#include <xen/spinlock.h>
#include <xen/types.h>
#include <xen/vmap.h>
#include "aplic-priv.h"
#include <asm/device.h>
#include <asm/imsic.h>
#include <asm/intc.h>
#include <asm/io.h>
#include <asm/riscv_encoding.h>
#define APLIC_DEFAULT_PRIORITY 1
static struct aplic_priv aplic = {
.lock = SPIN_LOCK_UNLOCKED,
};
static struct intc_info __ro_after_init aplic_info = {
.hw_version = INTC_APLIC,
};
static void __init aplic_init_hw_interrupts(void)
{
unsigned int i;
/* Disable all interrupts */
for ( i = 0; i < ARRAY_SIZE(aplic.regs->clrie); i++)
writel(~0U, &aplic.regs->clrie[i]);
/* Set interrupt type and default priority for all interrupts */
for ( i = 0; i < aplic_info.num_irqs; i++ )
{
writel(0, &aplic.regs->sourcecfg[i]);
/*
* Low bits of target register contains Interrupt Priority bits which
* can't be zero according to AIA spec.
* Thereby they are initialized to APLIC_DEFAULT_PRIORITY.
*/
writel(APLIC_DEFAULT_PRIORITY, &aplic.regs->target[i]);
}
writel(APLIC_DOMAINCFG_IE | APLIC_DOMAINCFG_DM, &aplic.regs->domaincfg);
}
static int __init cf_check aplic_init(void)
{
dt_phandle imsic_phandle;
const __be32 *prop;
uint64_t size, paddr;
const struct dt_device_node *imsic_node;
const struct dt_device_node *node = aplic_info.node;
int rc;
/* Check for associated imsic node */
if ( !dt_property_read_u32(node, "msi-parent", &imsic_phandle) )
panic("%s: IDC mode not supported\n", node->full_name);
imsic_node = dt_find_node_by_phandle(imsic_phandle);
if ( !imsic_node )
panic("%s: unable to find IMSIC node\n", node->full_name);
rc = imsic_init(imsic_node);
if ( rc == IRQ_M_EXT )
/* Machine mode imsic node, ignore this aplic node */
return 0;
if ( rc )
panic("%s: Failed to initialize IMSIC\n", node->full_name);
/* Find out number of interrupt sources */
if ( !dt_property_read_u32(node, "riscv,num-sources",
&aplic_info.num_irqs) )
panic("%s: failed to get number of interrupt sources\n",
node->full_name);
if ( aplic_info.num_irqs > ARRAY_SIZE(aplic.regs->sourcecfg) )
aplic_info.num_irqs = ARRAY_SIZE(aplic.regs->sourcecfg);
prop = dt_get_property(node, "reg", NULL);
dt_get_range(&prop, node, &paddr, &size);
if ( !paddr )
panic("%s: first MMIO resource not found\n", node->full_name);
if ( !IS_ALIGNED(paddr, KB(4)) )
panic("%s: paddr of memory-mapped control region should be 4Kb aligned:%#lx\n",
__func__, paddr);
if ( !IS_ALIGNED(size, KB(4)) || (size < KB(16)) )
panic("%s: control region size must be >= 16KB and 4KB-aligned:%#lx\n",
__func__, size);
aplic.paddr_start = paddr;
aplic.size = size;
aplic.regs = ioremap(paddr, size);
if ( !aplic.regs )
panic("%s: unable to map\n", node->full_name);
/* Setup initial state APLIC interrupts */
aplic_init_hw_interrupts();
return 0;
}
static void cf_check aplic_irq_enable(struct irq_desc *desc)
{
/*
* TODO: Currently, APLIC is supported only with MSI interrupts.
* If APLIC without MSI interrupts is required in the future,
* this function will need to be updated accordingly.
*/
ASSERT(readl(&aplic.regs->domaincfg) & APLIC_DOMAINCFG_DM);
ASSERT(spin_is_locked(&desc->lock));
spin_lock(&aplic.lock);
/* Enable interrupt in IMSIC */
imsic_irq_enable(desc->irq);
/* Enable interrupt in APLIC */
writel(desc->irq, &aplic.regs->setienum);
spin_unlock(&aplic.lock);
}
static void cf_check aplic_irq_disable(struct irq_desc *desc)
{
/*
* TODO: Currently, APLIC is supported only with MSI interrupts.
* If APLIC without MSI interrupts is required in the future,
* this function will need to be updated accordingly.
*/
ASSERT(readl(&aplic.regs->domaincfg) & APLIC_DOMAINCFG_DM);
ASSERT(spin_is_locked(&desc->lock));
spin_lock(&aplic.lock);
/* Disable interrupt in APLIC */
writel(desc->irq, &aplic.regs->clrienum);
/* Disable interrupt in IMSIC */
imsic_irq_disable(desc->irq);
spin_unlock(&aplic.lock);
}
static unsigned int cf_check aplic_irq_startup(struct irq_desc *desc)
{
aplic_irq_enable(desc);
return 0;
}
static unsigned int aplic_get_cpu_from_mask(const cpumask_t *cpumask)
{
cpumask_t mask;
cpumask_and(&mask, cpumask, &cpu_online_map);
return cpumask_any(&mask);
}
static void cf_check aplic_set_irq_affinity(struct irq_desc *desc, const cpumask_t *mask)
{
unsigned int cpu;
uint64_t group_index, base_ppn;
uint32_t hhxw, lhxw, hhxs, value;
const struct imsic_config *imsic = aplic.imsic_cfg;
/*
* TODO: Currently, APLIC is supported only with MSI interrupts.
* If APLIC without MSI interrupts is required in the future,
* this function will need to be updated accordingly.
*/
ASSERT(readl(&aplic.regs->domaincfg) & APLIC_DOMAINCFG_DM);
ASSERT(!cpumask_empty(mask));
ASSERT(spin_is_locked(&desc->lock));
cpu = cpuid_to_hartid(aplic_get_cpu_from_mask(mask));
hhxw = imsic->group_index_bits;
lhxw = imsic->hart_index_bits;
/*
* Although this variable is used only once in the calculation of
* group_index, and it might seem that hhxs could be defined as:
* hhxs = imsic->group_index_shift - IMSIC_MMIO_PAGE_SHIFT;
* and then the addition of IMSIC_MMIO_PAGE_SHIFT could be omitted
* when calculating the group index.
* It was done intentionally this way to follow the formula from
* the AIA specification for calculating the MSI address.
*/
hhxs = imsic->group_index_shift - IMSIC_MMIO_PAGE_SHIFT * 2;
base_ppn = imsic->msi[cpu].base_addr >> IMSIC_MMIO_PAGE_SHIFT;
/* Update hart and EEID in the target register */
group_index = (base_ppn >> (hhxs + IMSIC_MMIO_PAGE_SHIFT)) &
(BIT(hhxw, UL) - 1);
value = desc->irq;
value |= cpu << APLIC_TARGET_HART_IDX_SHIFT;
value |= group_index << (lhxw + APLIC_TARGET_HART_IDX_SHIFT);
spin_lock(&aplic.lock);
writel(value, &aplic.regs->target[desc->irq - 1]);
spin_unlock(&aplic.lock);
}
static void cf_check aplic_handle_interrupt(struct cpu_user_regs *regs)
{
/* Disable to avoid more external interrupts */
csr_clear(CSR_SIE, BIT(IRQ_S_EXT, UL));
/* Clear the pending bit */
csr_clear(CSR_SIP, BIT(IRQ_S_EXT, UL));
/* Dispatch the interrupt */
do_IRQ(regs, csr_swap(CSR_STOPEI, 0) >> TOPI_IID_SHIFT);
/* Enable external interrupts */
csr_set(CSR_SIE, BIT(IRQ_S_EXT, UL));
}
static void cf_check aplic_set_irq_type(struct irq_desc *desc,
unsigned int type)
{
/*
* Interrupt 0 isn't possible based on the spec:
* Each of an APLIC’s interrupt sources has a fixed unique identity
* number in the range 1 to N, where N is the total number of sources at
* the APLIC. The number zero is not a valid interrupt identity number at
* an APLIC. The maximum number of interrupt sources an APLIC may support
* is 1023.
*
* Thereby interrupt 1 will correspond to bit 0 in sourcecfg[] register,
* interrupt 2 ->sourcecfg[1] and so on.
*
* And that is the reason why we need -1.
*/
unsigned int irq_bit = desc->irq - 1;
ASSERT(spin_is_locked(&desc->lock));
spin_lock(&aplic.lock);
switch ( type )
{
case IRQ_TYPE_EDGE_RISING:
writel(APLIC_SOURCECFG_SM_EDGE_RISE, &aplic.regs->sourcecfg[irq_bit]);
break;
case IRQ_TYPE_EDGE_FALLING:
writel(APLIC_SOURCECFG_SM_EDGE_FALL, &aplic.regs->sourcecfg[irq_bit]);
break;
case IRQ_TYPE_LEVEL_HIGH:
writel(APLIC_SOURCECFG_SM_LEVEL_HIGH, &aplic.regs->sourcecfg[irq_bit]);
break;
case IRQ_TYPE_LEVEL_LOW:
writel(APLIC_SOURCECFG_SM_LEVEL_LOW, &aplic.regs->sourcecfg[irq_bit]);
break;
case IRQ_TYPE_NONE:
case IRQ_TYPE_INVALID:
writel(APLIC_SOURCECFG_SM_INACTIVE, &aplic.regs->sourcecfg[irq_bit]);
break;
default:
panic("%s: APLIC doesnt support IRQ type: 0x%x?\n", __func__, type);
}
spin_unlock(&aplic.lock);
}
static const hw_irq_controller aplic_xen_irq_type = {
.typename = "aplic",
.startup = aplic_irq_startup,
.shutdown = aplic_irq_disable,
.enable = aplic_irq_enable,
.disable = aplic_irq_disable,
.set_affinity = aplic_set_irq_affinity,
};
static const struct intc_hw_operations aplic_ops = {
.info = &aplic_info,
.init = aplic_init,
.host_irq_type = &aplic_xen_irq_type,
.handle_interrupt = aplic_handle_interrupt,
.set_irq_type = aplic_set_irq_type,
};
static int cf_check aplic_irq_xlate(const uint32_t *intspec,
unsigned int intsize,
unsigned int *out_hwirq,
unsigned int *out_type)
{
if ( intsize < 2 )
return -EINVAL;
/* Mapping 1:1 */
*out_hwirq = intspec[0];
if ( out_type )
*out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
return 0;
}
static int __init aplic_preinit(struct dt_device_node *node, const void *dat)
{
if ( aplic_info.node )
{
printk("XEN doesn't support more than one S mode APLIC\n");
return -ENODEV;
}
/* don't process if APLIC node is not for S mode */
if ( dt_get_property(node, "riscv,children", NULL) )
return -ENODEV;
aplic_info.node = node;
aplic.imsic_cfg = imsic_get_config();
dt_irq_xlate = aplic_irq_xlate;
register_intc_ops(&aplic_ops);
/* Enable supervisor external interrupt */
csr_set(CSR_SIE, BIT(IRQ_S_EXT, UL));
return 0;
}
static const struct dt_device_match __initconstrel aplic_dt_match[] =
{
DT_MATCH_COMPATIBLE("riscv,aplic"),
{ /* sentinel */ },
};
DT_DEVICE_START(aplic, "APLIC", DEVICE_INTERRUPT_CONTROLLER)
.dt_match = aplic_dt_match,
.init = aplic_preinit,
DT_DEVICE_END