Skip to content

Commit

Permalink
RISC-V: Treat IPIs as normal Linux IRQs
Browse files Browse the repository at this point in the history
Currently, the RISC-V kernel provides arch specific hooks (i.e.
struct riscv_ipi_ops) to register IPI handling methods. The stats
gathering of IPIs is also arch specific in the RISC-V kernel.

Other architectures (such as ARM, ARM64, and MIPS) have moved away
from custom arch specific IPI handling methods. Currently, these
architectures have Linux irqchip drivers providing a range of Linux
IRQ numbers to be used as IPIs and IPI triggering is done using
generic IPI APIs. This approach allows architectures to treat IPIs
as normal Linux IRQs and IPI stats gathering is done by the generic
Linux IRQ subsystem.

We extend the RISC-V IPI handling as-per above approach so that arch
specific IPI handling methods (struct riscv_ipi_ops) can be removed
and the IPI handling is totally contained Linux within irqchip drivers.

Signed-off-by: Anup Patel <anup.patel@wdc.com>
  • Loading branch information
avpatel committed Jun 18, 2021
1 parent df8f7f1 commit 3ac8b37
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 172 deletions.
1 change: 1 addition & 0 deletions arch/riscv/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ config RISCV
select GENERIC_EARLY_IOREMAP
select GENERIC_GETTIMEOFDAY if HAVE_GENERIC_VDSO
select GENERIC_IOREMAP
select GENERIC_IRQ_IPI
select GENERIC_IRQ_MULTI_HANDLER
select GENERIC_IRQ_SHOW
select GENERIC_LIB_DEVMEM_IS_ALLOWED
Expand Down
2 changes: 2 additions & 0 deletions arch/riscv/include/asm/sbi.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ struct sbiret {
};

void sbi_init(void);
void sbi_ipi_init(void);
struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0,
unsigned long arg1, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
Expand Down Expand Up @@ -175,6 +176,7 @@ static inline unsigned long sbi_mk_version(unsigned long major,
int sbi_err_map_linux_errno(int err);
#else /* CONFIG_RISCV_SBI */
static inline int sbi_remote_fence_i(const unsigned long *hart_mask) { return -1; }
static inline void sbi_ipi_init(void) { }
static inline void sbi_init(void) {}
#endif /* CONFIG_RISCV_SBI */
#endif /* _ASM_RISCV_SBI_H */
34 changes: 20 additions & 14 deletions arch/riscv/include/asm/smp.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@
struct seq_file;
extern unsigned long boot_cpu_hartid;

struct riscv_ipi_ops {
void (*ipi_inject)(const struct cpumask *target);
void (*ipi_clear)(void);
};

#ifdef CONFIG_SMP
/*
* Mapping between linux logical cpu index and hartid.
Expand All @@ -33,9 +28,6 @@ void show_ipi_stats(struct seq_file *p, int prec);
/* SMP initialization hook for setup_arch */
void __init setup_smp(void);

/* Called from C code, this handles an IPI. */
void handle_IPI(struct pt_regs *regs);

/* Hook for the generic smp_call_function_many() routine. */
void arch_send_call_function_ipi_mask(struct cpumask *mask);

Expand All @@ -45,11 +37,17 @@ void arch_send_call_function_single_ipi(int cpu);
int riscv_hartid_to_cpuid(int hartid);
void riscv_cpuid_to_hartid_mask(const struct cpumask *in, struct cpumask *out);

/* Set custom IPI operations */
void riscv_set_ipi_ops(const struct riscv_ipi_ops *ops);
/* Enable IPI for CPU hotplug */
void riscv_ipi_enable(void);

/* Disable IPI for CPU hotplug */
void riscv_ipi_disable(void);

/* Clear IPI for current CPU */
void riscv_clear_ipi(void);
/* Setup IPI for boot CPU */
void riscv_ipi_setup(void);

/* Set the IPI interrupt numbers for arch (called by irqchip drivers) */
void riscv_ipi_set_virq_range(int virq, int nr_irqs);

/* Secondary hart entry */
asmlinkage void smp_callin(void);
Expand Down Expand Up @@ -92,11 +90,19 @@ static inline void riscv_cpuid_to_hartid_mask(const struct cpumask *in,
cpumask_set_cpu(boot_cpu_hartid, out);
}

static inline void riscv_set_ipi_ops(const struct riscv_ipi_ops *ops)
static inline void riscv_ipi_enable(void)
{
}

static inline void riscv_ipi_disable(void)
{
}

static inline void riscv_ipi_setup(void)
{
}

static inline void riscv_clear_ipi(void)
static inline void riscv_ipi_set_virq_range(int virq, int nr)
{
}

Expand Down
1 change: 1 addition & 0 deletions arch/riscv/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ obj-$(CONFIG_RISCV_BASE_PMU) += perf_event.o
obj-$(CONFIG_PERF_EVENTS) += perf_callchain.o
obj-$(CONFIG_HAVE_PERF_REGS) += perf_regs.o
obj-$(CONFIG_RISCV_SBI) += sbi.o
obj-$(CONFIG_RISCV_SBI) += sbi-ipi.o
ifeq ($(CONFIG_RISCV_SBI), y)
obj-$(CONFIG_SMP) += cpu_ops_sbi.o
endif
Expand Down
2 changes: 2 additions & 0 deletions arch/riscv/kernel/cpu-hotplug.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <asm/irq.h>
#include <asm/cpu_ops.h>
#include <asm/sbi.h>
#include <asm/smp.h>

void cpu_stop(void);
void arch_cpu_idle_dead(void)
Expand Down Expand Up @@ -47,6 +48,7 @@ int __cpu_disable(void)

remove_cpu_topology(cpu);
set_cpu_online(cpu, false);
riscv_ipi_disable();
irq_migrate_all_off_this_cpu();

return ret;
Expand Down
1 change: 1 addition & 0 deletions arch/riscv/kernel/irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ void __init init_IRQ(void)
irqchip_init();
if (!handle_arch_irq)
panic("No interrupt controller found.");
riscv_ipi_setup();
}
223 changes: 223 additions & 0 deletions arch/riscv/kernel/sbi-ipi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SBI based IPI support.
*
* Copyright (c) 2021 Western Digital Corporation or its affiliates.
*/

#define pr_fmt(fmt) "riscv-sbi-ipi: " fmt
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/smp.h>
#include <asm/sbi.h>

static int intc_parent_irq __ro_after_init;
static struct irq_domain *sbi_ipi_domain __ro_after_init;
static DEFINE_PER_CPU(unsigned long, sbi_ipi_bits);

static void sbi_ipi_dummy_mask_unmask(struct irq_data *d)
{
}

static void sbi_ipi_send_mask(struct irq_data *d, const struct cpumask *mask)
{
int cpu;
struct cpumask hartid_mask;

/* Barrier before doing atomic bit update to IPI bits */
smp_mb__before_atomic();
for_each_cpu(cpu, mask)
set_bit(d->hwirq, per_cpu_ptr(&sbi_ipi_bits, cpu));
/* Barrier after doing atomic bit update to IPI bits */
smp_mb__after_atomic();

riscv_cpuid_to_hartid_mask(mask, &hartid_mask);

sbi_send_ipi(cpumask_bits(&hartid_mask));
}

static struct irq_chip sbi_ipi_chip = {
.name = "RISC-V SBI IPI",
.irq_mask = sbi_ipi_dummy_mask_unmask,
.irq_unmask = sbi_ipi_dummy_mask_unmask,
.ipi_send_mask = sbi_ipi_send_mask,
};

static int sbi_ipi_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hwirq, &sbi_ipi_chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);

return 0;
}

static int sbi_ipi_domain_alloc(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
int i, ret;
irq_hw_number_t hwirq;
unsigned int type = IRQ_TYPE_NONE;
struct irq_fwspec *fwspec = arg;

ret = irq_domain_translate_onecell(d, fwspec, &hwirq, &type);
if (ret)
return ret;

for (i = 0; i < nr_irqs; i++) {
ret = sbi_ipi_domain_map(d, virq + i, hwirq + i);
if (ret)
return ret;
}

return 0;
}

static const struct irq_domain_ops sbi_ipi_domain_ops = {
.translate = irq_domain_translate_onecell,
.alloc = sbi_ipi_domain_alloc,
.free = irq_domain_free_irqs_top,
};

static void sbi_ipi_handle_irq(struct irq_desc *desc)
{
int irq;
struct irq_chip *chip = irq_desc_get_chip(desc);
unsigned long irqs, *bits = this_cpu_ptr(&sbi_ipi_bits);
irq_hw_number_t hwirq;

chained_irq_enter(chip, desc);

csr_clear(CSR_IP, IE_SIE);

while (true) {
/* Order bit clearing and data access. */
mb();

irqs = xchg(bits, 0);
if (!irqs)
goto done;

for (hwirq = 0; hwirq < BITS_PER_LONG; hwirq++) {
if (!(BIT(hwirq) & irqs))
continue;

irq = irq_find_mapping(sbi_ipi_domain, hwirq);
if (unlikely(irq <= 0))
pr_warn_ratelimited(
"can't find mapping for hwirq %lu\n",
hwirq);
else
generic_handle_irq(irq);
}
}

done:
chained_irq_exit(chip, desc);
}

static int sbi_ipi_dying_cpu(unsigned int cpu)
{
disable_percpu_irq(intc_parent_irq);
return 0;
}

static int sbi_ipi_starting_cpu(unsigned int cpu)
{
enable_percpu_irq(intc_parent_irq,
irq_get_trigger_type(intc_parent_irq));
return 0;
}

static int __init sbi_ipi_set_virq(void)
{
int virq;
struct irq_fwspec ipi = {
.fwnode = sbi_ipi_domain->fwnode,
.param_count = 1,
.param[0] = 0,
};

virq = __irq_domain_alloc_irqs(sbi_ipi_domain, -1, BITS_PER_LONG,
NUMA_NO_NODE, &ipi,
false, NULL);
if (virq <= 0) {
pr_err("unable to alloc IRQs from SBI IPI IRQ domain\n");
return -ENOMEM;
}

riscv_ipi_set_virq_range(virq, BITS_PER_LONG);

return 0;
}

static int __init sbi_ipi_domain_init(struct device_node *node,
struct irq_domain *domain)
{
struct irq_fwspec swi = {
.fwnode = domain->fwnode,
.param_count = 1,
.param[0] = RV_IRQ_SOFT,
};

intc_parent_irq = __irq_domain_alloc_irqs(domain, -1, 1,
NUMA_NO_NODE, &swi,
false, NULL);
if (intc_parent_irq <= 0) {
pr_err("unable to alloc IRQ from INTC IRQ domain\n");
return -ENOMEM;
}

irq_set_chained_handler(intc_parent_irq, sbi_ipi_handle_irq);

sbi_ipi_domain = irq_domain_add_linear(node, BITS_PER_LONG,
&sbi_ipi_domain_ops, NULL);
if (!sbi_ipi_domain) {
pr_err("unable to add SBI IPI IRQ domain\n");
return -ENOMEM;
}

cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
"irqchip/riscv/sbi-ipi:starting",
sbi_ipi_starting_cpu, sbi_ipi_dying_cpu);

return sbi_ipi_set_virq();
}

void __init sbi_ipi_init(void)
{
struct irq_domain *domain = NULL;
struct device_node *cpu, *child, *node = NULL;

for_each_of_cpu_node(cpu) {
child = of_get_compatible_child(cpu, "riscv,cpu-intc");
if (!child) {
pr_err("failed to find INTC node [%pOF]\n", cpu);
return;
}

domain = irq_find_host(child);
if (domain) {
node = cpu;
break;
}

of_node_put(child);
}
if (!domain || !node) {
pr_err("can't find INTC IRQ domain\n");
return;
}

if (sbi_ipi_domain_init(node, domain))
pr_err("failed to register IPI domain\n");
else
pr_info("registered IPI domain\n");
}
21 changes: 0 additions & 21 deletions arch/riscv/kernel/sbi.c
Original file line number Diff line number Diff line change
Expand Up @@ -589,25 +589,6 @@ long sbi_get_mimpid(void)
return __sbi_base_ecall(SBI_EXT_BASE_GET_MIMPID);
}

static void sbi_send_cpumask_ipi(const struct cpumask *target)
{
struct cpumask hartid_mask;

riscv_cpuid_to_hartid_mask(target, &hartid_mask);

sbi_send_ipi(cpumask_bits(&hartid_mask));
}

static void sbi_ipi_clear(void)
{
csr_clear(CSR_IP, IE_SIE);
}

static const struct riscv_ipi_ops sbi_ipi_ops = {
.ipi_inject = sbi_send_cpumask_ipi,
.ipi_clear = sbi_ipi_clear
};

void __init sbi_init(void)
{
int ret;
Expand Down Expand Up @@ -654,6 +635,4 @@ void __init sbi_init(void)
__sbi_send_ipi = __sbi_send_ipi_v01;
__sbi_rfence = __sbi_rfence_v01;
}

riscv_set_ipi_ops(&sbi_ipi_ops);
}

0 comments on commit 3ac8b37

Please sign in to comment.