powerpc/xics: Rewrite XICS driver

This is a significant rework of the XICS driver, too significant to
conveniently break it up into a series of smaller patches to be honest.

The driver is moved to a more generic location to allow new platforms
to use it, and is broken up into separate ICP and ICS "backends". For
now we have the native and "hypervisor" ICP backends and one common
RTAS ICS backend.

The driver supports one ICP backend instanciation, and many ICS ones,
in order to accomodate future platforms with multiple possibly different
interrupt "sources" mechanisms.
Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
parent f0e615c3
...@@ -142,6 +142,12 @@ extern struct irq_map_entry irq_map[NR_IRQS]; ...@@ -142,6 +142,12 @@ extern struct irq_map_entry irq_map[NR_IRQS];
extern irq_hw_number_t virq_to_hw(unsigned int virq); extern irq_hw_number_t virq_to_hw(unsigned int virq);
/* This will eventually -replace- virq_to_hw if/when we stash the
* HW number in the irq_data itself. We use a macro so we can inline
* it as irq_data isn't defined yet
*/
#define irq_data_to_hw(d) (irq_map[(d)->irq].hwirq)
/** /**
* irq_alloc_host - Allocate a new irq_host data structure * irq_alloc_host - Allocate a new irq_host data structure
* @of_node: optional device-tree node of the interrupt controller * @of_node: optional device-tree node of the interrupt controller
......
/*
* Common definitions accross all variants of ICP and ICS interrupt
* controllers.
*/
#ifndef _XICS_H
#define _XICS_H
#define XICS_IPI 2
#define XICS_IRQ_SPURIOUS 0
/* Want a priority other than 0. Various HW issues require this. */
#define DEFAULT_PRIORITY 5
/*
* Mark IPIs as higher priority so we can take them inside interrupts that
* arent marked IRQF_DISABLED
*/
#define IPI_PRIORITY 4
/* The least favored priority */
#define LOWEST_PRIORITY 0xFF
/* The number of priorities defined above */
#define MAX_NUM_PRIORITIES 3
/* Native ICP */
extern int icp_native_init(void);
/* PAPR ICP */
extern int icp_hv_init(void);
/* ICP ops */
struct icp_ops {
unsigned int (*get_irq)(void);
void (*eoi)(struct irq_data *d);
void (*set_priority)(unsigned char prio);
void (*teardown_cpu)(void);
void (*flush_ipi)(void);
#ifdef CONFIG_SMP
void (*message_pass)(int target, int msg);
irq_handler_t ipi_action;
#endif
};
extern const struct icp_ops *icp_ops;
/* Native ICS */
extern int ics_native_init(void);
/* RTAS ICS */
extern int ics_rtas_init(void);
/* ICS instance, hooked up to chip_data of an irq */
struct ics {
struct list_head link;
int (*map)(struct ics *ics, unsigned int virq);
void (*mask_unknown)(struct ics *ics, unsigned long vec);
long (*get_server)(struct ics *ics, unsigned long vec);
char data[];
};
/* Commons */
extern unsigned int xics_default_server;
extern unsigned int xics_default_distrib_server;
extern unsigned int xics_interrupt_server_size;
extern struct irq_host *xics_host;
struct xics_cppr {
unsigned char stack[MAX_NUM_PRIORITIES];
int index;
};
DECLARE_PER_CPU(struct xics_cppr, xics_cppr);
static inline void xics_push_cppr(unsigned int vec)
{
struct xics_cppr *os_cppr = &__get_cpu_var(xics_cppr);
if (WARN_ON(os_cppr->index >= MAX_NUM_PRIORITIES - 1))
return;
if (vec == XICS_IPI)
os_cppr->stack[++os_cppr->index] = IPI_PRIORITY;
else
os_cppr->stack[++os_cppr->index] = DEFAULT_PRIORITY;
}
static inline unsigned char xics_pop_cppr(void)
{
struct xics_cppr *os_cppr = &__get_cpu_var(xics_cppr);
if (WARN_ON(os_cppr->index < 1))
return LOWEST_PRIORITY;
return os_cppr->stack[--os_cppr->index];
}
static inline void xics_set_base_cppr(unsigned char cppr)
{
struct xics_cppr *os_cppr = &__get_cpu_var(xics_cppr);
/* we only really want to set the priority when there's
* just one cppr value on the stack
*/
WARN_ON(os_cppr->index != 0);
os_cppr->stack[0] = cppr;
}
static inline unsigned char xics_cppr_top(void)
{
struct xics_cppr *os_cppr = &__get_cpu_var(xics_cppr);
return os_cppr->stack[os_cppr->index];
}
DECLARE_PER_CPU_SHARED_ALIGNED(unsigned long, xics_ipi_message);
extern void xics_init(void);
extern void xics_setup_cpu(void);
extern void xics_update_irq_servers(void);
extern void xics_set_cpu_giq(unsigned int gserver, unsigned int join);
extern void xics_mask_unknown_vec(unsigned int vec);
extern irqreturn_t xics_ipi_dispatch(int cpu);
extern int xics_smp_probe(void);
extern void xics_register_ics(struct ics *ics);
extern void xics_teardown_cpu(void);
extern void xics_kexec_teardown_cpu(int secondary);
extern void xics_migrate_irqs_away(void);
#ifdef CONFIG_SMP
extern int xics_get_irq_server(unsigned int virq, const struct cpumask *cpumask,
unsigned int strict_check);
#else
#define xics_get_irq_server(virq, cpumask, strict_check) (xics_default_server)
#endif
#endif /* _XICS_H */
...@@ -3,7 +3,10 @@ config PPC_PSERIES ...@@ -3,7 +3,10 @@ config PPC_PSERIES
bool "IBM pSeries & new (POWER5-based) iSeries" bool "IBM pSeries & new (POWER5-based) iSeries"
select MPIC select MPIC
select PCI_MSI select PCI_MSI
select XICS select PPC_XICS
select PPC_ICP_NATIVE
select PPC_ICP_HV
select PPC_ICS_RTAS
select PPC_I8259 select PPC_I8259
select PPC_RTAS select PPC_RTAS
select PPC_RTAS_DAEMON select PPC_RTAS_DAEMON
......
...@@ -5,7 +5,6 @@ obj-y := lpar.o hvCall.o nvram.o reconfig.o \ ...@@ -5,7 +5,6 @@ obj-y := lpar.o hvCall.o nvram.o reconfig.o \
setup.o iommu.o event_sources.o ras.o \ setup.o iommu.o event_sources.o ras.o \
firmware.o power.o dlpar.o mobility.o firmware.o power.o dlpar.o mobility.o
obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_SMP) += smp.o
obj-$(CONFIG_XICS) += xics.o
obj-$(CONFIG_SCANLOG) += scanlog.o obj-$(CONFIG_SCANLOG) += scanlog.o
obj-$(CONFIG_EEH) += eeh.o eeh_cache.o eeh_driver.o eeh_event.o eeh_sysfs.o obj-$(CONFIG_EEH) += eeh.o eeh_cache.o eeh_driver.o eeh_event.o eeh_sysfs.o
obj-$(CONFIG_KEXEC) += kexec.o obj-$(CONFIG_KEXEC) += kexec.o
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/cpu.h> #include <linux/cpu.h>
#include <asm/system.h> #include <asm/system.h>
...@@ -28,7 +29,7 @@ ...@@ -28,7 +29,7 @@
#include <asm/machdep.h> #include <asm/machdep.h>
#include <asm/vdso_datapage.h> #include <asm/vdso_datapage.h>
#include <asm/pSeries_reconfig.h> #include <asm/pSeries_reconfig.h>
#include "xics.h" #include <asm/xics.h>
#include "plpar_wrappers.h" #include "plpar_wrappers.h"
#include "offline_states.h" #include "offline_states.h"
......
...@@ -7,15 +7,18 @@ ...@@ -7,15 +7,18 @@
* 2 of the License, or (at your option) any later version. * 2 of the License, or (at your option) any later version.
*/ */
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <asm/machdep.h> #include <asm/machdep.h>
#include <asm/page.h> #include <asm/page.h>
#include <asm/firmware.h> #include <asm/firmware.h>
#include <asm/kexec.h> #include <asm/kexec.h>
#include <asm/mpic.h> #include <asm/mpic.h>
#include <asm/xics.h>
#include <asm/smp.h> #include <asm/smp.h>
#include "pseries.h" #include "pseries.h"
#include "xics.h"
#include "plpar_wrappers.h" #include "plpar_wrappers.h"
static void pseries_kexec_cpu_down(int crash_shutdown, int secondary) static void pseries_kexec_cpu_down(int crash_shutdown, int secondary)
......
...@@ -270,31 +270,4 @@ static inline long plpar_put_term_char(unsigned long termno, unsigned long len, ...@@ -270,31 +270,4 @@ static inline long plpar_put_term_char(unsigned long termno, unsigned long len,
lbuf[1]); lbuf[1]);
} }
static inline long plpar_eoi(unsigned long xirr)
{
return plpar_hcall_norets(H_EOI, xirr);
}
static inline long plpar_cppr(unsigned long cppr)
{
return plpar_hcall_norets(H_CPPR, cppr);
}
static inline long plpar_ipi(unsigned long servernum, unsigned long mfrr)
{
return plpar_hcall_norets(H_IPI, servernum, mfrr);
}
static inline long plpar_xirr(unsigned long *xirr_ret, unsigned char cppr)
{
long rc;
unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
rc = plpar_hcall(H_XIRR, retbuf, cppr);
*xirr_ret = retbuf[0];
return rc;
}
#endif /* _PSERIES_PLPAR_WRAPPERS_H */ #endif /* _PSERIES_PLPAR_WRAPPERS_H */
...@@ -53,9 +53,9 @@ ...@@ -53,9 +53,9 @@
#include <asm/irq.h> #include <asm/irq.h>
#include <asm/time.h> #include <asm/time.h>
#include <asm/nvram.h> #include <asm/nvram.h>
#include "xics.h"
#include <asm/pmc.h> #include <asm/pmc.h>
#include <asm/mpic.h> #include <asm/mpic.h>
#include <asm/xics.h>
#include <asm/ppc-pci.h> #include <asm/ppc-pci.h>
#include <asm/i8259.h> #include <asm/i8259.h>
#include <asm/udbg.h> #include <asm/udbg.h>
...@@ -205,6 +205,9 @@ static void __init pseries_mpic_init_IRQ(void) ...@@ -205,6 +205,9 @@ static void __init pseries_mpic_init_IRQ(void)
mpic_assign_isu(mpic, n, isuaddr); mpic_assign_isu(mpic, n, isuaddr);
} }
/* Setup top-level get_irq */
ppc_md.get_irq = mpic_get_irq;
/* All ISUs are setup, complete initialization */ /* All ISUs are setup, complete initialization */
mpic_init(mpic); mpic_init(mpic);
...@@ -214,7 +217,7 @@ static void __init pseries_mpic_init_IRQ(void) ...@@ -214,7 +217,7 @@ static void __init pseries_mpic_init_IRQ(void)
static void __init pseries_xics_init_IRQ(void) static void __init pseries_xics_init_IRQ(void)
{ {
xics_init_IRQ(); xics_init();
pseries_setup_i8259_cascade(); pseries_setup_i8259_cascade();
} }
...@@ -238,7 +241,6 @@ static void __init pseries_discover_pic(void) ...@@ -238,7 +241,6 @@ static void __init pseries_discover_pic(void)
if (strstr(typep, "open-pic")) { if (strstr(typep, "open-pic")) {
pSeries_mpic_node = of_node_get(np); pSeries_mpic_node = of_node_get(np);
ppc_md.init_IRQ = pseries_mpic_init_IRQ; ppc_md.init_IRQ = pseries_mpic_init_IRQ;
ppc_md.get_irq = mpic_get_irq;
setup_kexec_cpu_down_mpic(); setup_kexec_cpu_down_mpic();
smp_init_pseries_mpic(); smp_init_pseries_mpic();
return; return;
......
...@@ -44,10 +44,11 @@ ...@@ -44,10 +44,11 @@
#include <asm/mpic.h> #include <asm/mpic.h>
#include <asm/vdso_datapage.h> #include <asm/vdso_datapage.h>
#include <asm/cputhreads.h> #include <asm/cputhreads.h>
#include <asm/mpic.h>
#include <asm/xics.h>
#include "plpar_wrappers.h" #include "plpar_wrappers.h"
#include "pseries.h" #include "pseries.h"
#include "xics.h"
#include "offline_states.h" #include "offline_states.h"
...@@ -136,7 +137,6 @@ static inline int __devinit smp_startup_cpu(unsigned int lcpu) ...@@ -136,7 +137,6 @@ static inline int __devinit smp_startup_cpu(unsigned int lcpu)
return 1; return 1;
} }
#ifdef CONFIG_XICS
static void __devinit smp_xics_setup_cpu(int cpu) static void __devinit smp_xics_setup_cpu(int cpu)
{ {
if (cpu != boot_cpuid) if (cpu != boot_cpuid)
...@@ -151,7 +151,6 @@ static void __devinit smp_xics_setup_cpu(int cpu) ...@@ -151,7 +151,6 @@ static void __devinit smp_xics_setup_cpu(int cpu)
set_default_offline_state(cpu); set_default_offline_state(cpu);
#endif #endif
} }
#endif /* CONFIG_XICS */
static void __devinit smp_pSeries_kick_cpu(int nr) static void __devinit smp_pSeries_kick_cpu(int nr)
{ {
...@@ -197,23 +196,21 @@ static int smp_pSeries_cpu_bootable(unsigned int nr) ...@@ -197,23 +196,21 @@ static int smp_pSeries_cpu_bootable(unsigned int nr)
return 1; return 1;
} }
#ifdef CONFIG_MPIC
static struct smp_ops_t pSeries_mpic_smp_ops = { static struct smp_ops_t pSeries_mpic_smp_ops = {
.message_pass = smp_mpic_message_pass, .message_pass = smp_mpic_message_pass,
.probe = smp_mpic_probe, .probe = smp_mpic_probe,
.kick_cpu = smp_pSeries_kick_cpu, .kick_cpu = smp_pSeries_kick_cpu,
.setup_cpu = smp_mpic_setup_cpu, .setup_cpu = smp_mpic_setup_cpu,
}; };
#endif
#ifdef CONFIG_XICS
static struct smp_ops_t pSeries_xics_smp_ops = { static struct smp_ops_t pSeries_xics_smp_ops = {
.message_pass = smp_xics_message_pass, .message_pass = NULL, /* Filled at runtime by xics_smp_probe() */
.probe = smp_xics_probe, .probe = xics_smp_probe,
.kick_cpu = smp_pSeries_kick_cpu, .kick_cpu = smp_pSeries_kick_cpu,
.setup_cpu = smp_xics_setup_cpu, .setup_cpu = smp_xics_setup_cpu,
.cpu_bootable = smp_pSeries_cpu_bootable, .cpu_bootable = smp_pSeries_cpu_bootable,
}; };
#endif
/* This is called very early */ /* This is called very early */
static void __init smp_init_pseries(void) static void __init smp_init_pseries(void)
...@@ -245,14 +242,12 @@ static void __init smp_init_pseries(void) ...@@ -245,14 +242,12 @@ static void __init smp_init_pseries(void)
pr_debug(" <- smp_init_pSeries()\n"); pr_debug(" <- smp_init_pSeries()\n");
} }
#ifdef CONFIG_MPIC
void __init smp_init_pseries_mpic(void) void __init smp_init_pseries_mpic(void)
{ {
smp_ops = &pSeries_mpic_smp_ops; smp_ops = &pSeries_mpic_smp_ops;
smp_init_pseries(); smp_init_pseries();
} }
#endif
void __init smp_init_pseries_xics(void) void __init smp_init_pseries_xics(void)
{ {
......
/*
* arch/powerpc/platforms/pseries/xics.h
*
* Copyright 2000 IBM Corporation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _POWERPC_KERNEL_XICS_H
#define _POWERPC_KERNEL_XICS_H
extern void xics_init_IRQ(void);
extern void xics_setup_cpu(void);
extern void xics_teardown_cpu(void);
extern void xics_kexec_teardown_cpu(int secondary);
extern void xics_migrate_irqs_away(void);
extern int smp_xics_probe(void);
extern void smp_xics_message_pass(int target, int msg);
#endif /* _POWERPC_KERNEL_XICS_H */
...@@ -12,3 +12,6 @@ config PPC_MSI_BITMAP ...@@ -12,3 +12,6 @@ config PPC_MSI_BITMAP
depends on PCI_MSI depends on PCI_MSI
default y if MPIC default y if MPIC
default y if FSL_PCI default y if FSL_PCI
source "arch/powerpc/sysdev/xics/Kconfig"
...@@ -57,3 +57,7 @@ obj-$(CONFIG_PPC_MPC52xx) += mpc5xxx_clocks.o ...@@ -57,3 +57,7 @@ obj-$(CONFIG_PPC_MPC52xx) += mpc5xxx_clocks.o
ifeq ($(CONFIG_SUSPEND),y) ifeq ($(CONFIG_SUSPEND),y)
obj-$(CONFIG_6xx) += 6xx-suspend.o obj-$(CONFIG_6xx) += 6xx-suspend.o
endif endif
subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror
obj-$(CONFIG_PPC_XICS) += xics/
config PPC_XICS
def_bool n
config PPC_ICP_NATIVE
def_bool n
config PPC_ICP_HV
def_bool n
config PPC_ICS_RTAS
def_bool n
subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror
obj-y += xics-common.o
obj-$(CONFIG_PPC_ICP_NATIVE) += icp-native.o
obj-$(CONFIG_PPC_ICP_HV) += icp-hv.o
obj-$(CONFIG_PPC_ICS_RTAS) += ics-rtas.o
/*
* Copyright 2011 IBM Corporation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/smp.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/cpu.h>
#include <linux/of.h>
#include <asm/smp.h>
#include <asm/irq.h>
#include <asm/errno.h>
#include <asm/xics.h>
#include <asm/io.h>
#include <asm/hvcall.h>
static inline unsigned int icp_hv_get_xirr(unsigned char cppr)
{
unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
long rc;
rc = plpar_hcall(H_XIRR, retbuf, cppr);
if (rc != H_SUCCESS)
panic(" bad return code xirr - rc = %lx\n", rc);
return (unsigned int)retbuf[0];
}
static inline void icp_hv_set_xirr(unsigned int value)
{
long rc = plpar_hcall_norets(H_EOI, value);
if (rc != H_SUCCESS)
panic("bad return code EOI - rc = %ld, value=%x\n", rc, value);
}
static inline void icp_hv_set_cppr(u8 value)
{
long rc = plpar_hcall_norets(H_CPPR, value);
if (rc != H_SUCCESS)
panic("bad return code cppr - rc = %lx\n", rc);
}
static inline void icp_hv_set_qirr(int n_cpu , u8 value)
{
long rc = plpar_hcall_norets(H_IPI, get_hard_smp_processor_id(n_cpu),
value);
if (rc != H_SUCCESS)
panic("bad return code qirr - rc = %lx\n", rc);
}
static void icp_hv_eoi(struct irq_data *d)
{
unsigned int hw_irq = (unsigned int)irq_data_to_hw(d);
iosync();
icp_hv_set_xirr((xics_pop_cppr() << 24) | hw_irq);
}
static void icp_hv_teardown_cpu(void)
{
int cpu = smp_processor_id();
/* Clear any pending IPI */
icp_hv_set_qirr(cpu, 0xff);
}
static void icp_hv_flush_ipi(void)
{
/* We take the ipi irq but and never return so we
* need to EOI the IPI, but want to leave our priority 0
*
* should we check all the other interrupts too?
* should we be flagging idle loop instead?
* or creating some task to be scheduled?
*/
icp_hv_set_xirr((0x00 << 24) | XICS_IPI);
}
static unsigned int icp_hv_get_irq(void)
{
unsigned int xirr = icp_hv_get_xirr(xics_cppr_top());
unsigned int vec = xirr & 0x00ffffff;
unsigned int irq;
if (vec == XICS_IRQ_SPURIOUS)
return NO_IRQ;
irq = irq_radix_revmap_lookup(xics_host, vec);
if (likely(irq != NO_IRQ)) {
xics_push_cppr(vec);
return irq;
}
/* We don't have a linux mapping, so have rtas mask it. */
xics_mask_unknown_vec(vec);
/* We might learn about it later, so EOI it */
icp_hv_set_xirr(xirr);
return NO_IRQ;
}
static void icp_hv_set_cpu_priority(unsigned char cppr)
{
xics_set_base_cppr(cppr);
icp_hv_set_cppr(cppr);
iosync();
}
#ifdef CONFIG_SMP
static inline void icp_hv_do_message(int cpu, int msg)
{
unsigned long *tgt = &per_cpu(xics_ipi_message, cpu);
set_bit(msg, tgt);
mb();
icp_hv_set_qirr(cpu, IPI_PRIORITY);
}
static void icp_hv_message_pass(int target, int msg)
{
unsigned int i;
if (target < NR_CPUS) {
icp_hv_do_message(target, msg);
} else {
for_each_online_cpu(i) {
if (target == MSG_ALL_BUT_SELF
&& i == smp_processor_id())
continue;
icp_hv_do_message(i, msg);
}
}
}
static irqreturn_t icp_hv_ipi_action(int irq, void *dev_id)
{
int cpu = smp_processor_id();
icp_hv_set_qirr(cpu, 0xff);
return xics_ipi_dispatch(cpu);
}
#endif /* CONFIG_SMP */
static const struct icp_ops icp_hv_ops = {
.get_irq = icp_hv_get_irq,
.eoi = icp_hv_eoi,
.set_priority = icp_hv_set_cpu_priority,
.teardown_cpu = icp_hv_teardown_cpu,
.flush_ipi = icp_hv_flush_ipi,
#ifdef CONFIG_SMP
.ipi_action = icp_hv_ipi_action,
.message_pass = icp_hv_message_pass,
#endif
};
int icp_hv_init(void)
{
struct device_node *np;
np = of_find_compatible_node(NULL, NULL, "ibm,ppc-xicp");
if (!np)
np = of_find_node_by_type(NULL,
"PowerPC-External-Interrupt-Presentation");
if (!np)
return -ENODEV;
icp_ops = &icp_hv_ops;
return 0;
}
/*
* Copyright 2011 IBM Corporation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/smp.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/cpu.h>
#include <linux/of.h>
#include <linux/spinlock.h>
#include <asm/prom.h>
#include <asm/io.h>
#include <asm/smp.h>
#include <asm/irq.h>
#include <asm/errno.h>
#include <asm/xics.h>
struct icp_ipl {
union {
u32 word;
u8 bytes[4];
} xirr_poll;
union {
u32 word;
u8 bytes[4];
} xirr;
u32 dummy;
union {
u32 word;
u8 bytes[4];
} qirr;
u32 link_a;
u32 link_b;
u32 link_c;
};
static struct icp_ipl __iomem *icp_native_regs[NR_CPUS];
static inline unsigned int icp_native_get_xirr(void)
{
int cpu = smp_processor_id();
return in_be32(&icp_native_regs[cpu]->xirr.word);
}
static inline void icp_native_set_xirr(unsigned int value)
{
int cpu = smp_processor_id();
out_be32(&icp_native_regs[cpu]->xirr.word, value);
}
static inline void icp_native_set_cppr(u8 value)
{
int cpu = smp_processor_id();
out_8(&icp_native_regs[cpu]->xirr.bytes[0], value);
}
static inline void icp_native_set_qirr(int n_cpu, u8 value)
{
out_8(&icp_native_regs[n_cpu]->qirr.bytes[0], value);
}
static void icp_native_set_cpu_priority(unsigned char cppr)
{
xics_set_base_cppr(cppr);
icp_native_set_cppr(cppr);
iosync();
}
static void icp_native_eoi(struct irq_data *d)
{
unsigned int hw_irq = (unsigned int)irq_data_to_hw(d);
iosync();
icp_native_set_xirr((xics_pop_cppr() << 24) | hw_irq);
}
static void icp_native_teardown_cpu(void)
{
int cpu = smp_processor_id();
/* Clear any pending IPI */
icp_native_set_qirr(cpu, 0xff);
}
static void icp_native_flush_ipi(void)
{
/* We take the ipi irq but and never return so we
* need to EOI the IPI, but want to leave our priority 0
*
* should we check all the other interrupts too?
* should we be flagging idle loop instead?
* or creating some task to be scheduled?
*/
icp_native_set_xirr((0x00 << 24) | XICS_IPI);
}
static unsigned int icp_native_get_irq(void)
{
unsigned int xirr = icp_native_get_xirr();
unsigned int vec = xirr & 0x00ffffff;
unsigned int irq;
if (vec == XICS_IRQ_SPURIOUS)
return NO_IRQ;
irq = irq_radix_revmap_lookup(xics_host, vec);
if (likely(irq != NO_IRQ)) {
xics_push_cppr(vec);
return irq;
}
/* We don't have a linux mapping, so have rtas mask it. */
xics_mask_unknown_vec(vec);
/* We might learn about it later, so EOI it */
icp_native_set_xirr(xirr);
return NO_IRQ;
}
#ifdef CONFIG_SMP
static inline void icp_native_do_message(int cpu, int msg)
{
unsigned long *tgt = &per_cpu(xics_ipi_message, cpu);
set_bit(msg, tgt);
mb();
icp_native_set_qirr(cpu, IPI_PRIORITY);
}
static void icp_native_message_pass(int target, int msg)
{
unsigned int i;
if (target < NR_CPUS) {
icp_native_do_message(target, msg);
} else {
for_each_online_cpu(i) {
if (target == MSG_ALL_BUT_SELF
&& i == smp_processor_id())
continue;
icp_native_do_message(i, msg);
}
}
}
static irqreturn_t icp_native_ipi_action(int irq, void *dev_id)
{
int cpu = smp_processor_id();
icp_native_set_qirr(cpu, 0xff);
return xics_ipi_dispatch(cpu);
}
#endif /* CONFIG_SMP */
static int __init icp_native_map_one_cpu(int hw_id, unsigned long addr,
unsigned long size)
{
char *rname;
int i, cpu = -1;
/* This may look gross but it's good enough for now, we don't quite
* have a hard -> linux processor id matching.
*/
for_each_possible_cpu(i) {
if (!cpu_present(i))
continue;
if (hw_id == get_hard_smp_processor_id(i)) {
cpu = i;
break;
}
}
/* Fail, skip that CPU. Don't print, it's normal, some XICS come up
* with way more entries in there than you have CPUs
*/
if (cpu == -1)
return 0;
rname = kasprintf(GFP_KERNEL, "CPU %d [0x%x] Interrupt Presentation",
cpu, hw_id);
if (!request_mem_region(addr, size, rname)) {
pr_warning("icp_native: Could not reserve ICP MMIO"
" for CPU %d, interrupt server #0x%x\n",
cpu, hw_id);
return -EBUSY;
}
icp_native_regs[cpu] = ioremap(addr, size);
if (!icp_native_regs[cpu]) {
pr_warning("icp_native: Failed ioremap for CPU %d, "
"interrupt server #0x%x, addr %#lx\n",
cpu, hw_id, addr);
release_mem_region(addr, size);
return -ENOMEM;
}
return 0;
}
static int __init icp_native_init_one_node(struct device_node *np,
unsigned int *indx)
{
unsigned int ilen;
const u32 *ireg;
int i;
int reg_tuple_size;
int num_servers = 0;
/* This code does the theorically broken assumption that the interrupt
* server numbers are the same as the hard CPU numbers.
* This happens to be the case so far but we are playing with fire...
* should be fixed one of these days. -BenH.
*/
ireg = of_get_property(np, "ibm,interrupt-server-ranges", &ilen);
/* Do that ever happen ? we'll know soon enough... but even good'old
* f80 does have that property ..
*/
WARN_ON((ireg == NULL) || (ilen != 2*sizeof(u32)));
if (ireg) {
*indx = of_read_number(ireg, 1);
if (ilen >= 2*sizeof(u32))
num_servers = of_read_number(ireg + 1, 1);
}
ireg = of_get_property(np, "reg", &ilen);
if (!ireg) {
pr_err("icp_native: Can't find interrupt reg property");
return -1;
}
reg_tuple_size = (of_n_addr_cells(np) + of_n_size_cells(np)) * 4;
if (((ilen % reg_tuple_size) != 0)
|| (num_servers && (num_servers != (ilen / reg_tuple_size)))) {
pr_err("icp_native: ICP reg len (%d) != num servers (%d)",
ilen / reg_tuple_size, num_servers);
return -1;
}
for (i = 0; i < (ilen / reg_tuple_size); i++) {
struct resource r;
int err;
err = of_address_to_resource(np, i, &r);
if (err) {
pr_err("icp_native: Could not translate ICP MMIO"
" for interrupt server 0x%x (%d)\n", *indx, err);
return -1;
}
if (icp_native_map_one_cpu(*indx, r.start, r.end - r.start))
return -1;
(*indx)++;
}
return 0;
}
static const struct icp_ops icp_native_ops = {
.get_irq = icp_native_get_irq,
.eoi = icp_native_eoi,
.set_priority = icp_native_set_cpu_priority,
.teardown_cpu = icp_native_teardown_cpu,
.flush_ipi = icp_native_flush_ipi,
#ifdef CONFIG_SMP
.ipi_action = icp_native_ipi_action,
.message_pass = icp_native_message_pass,
#endif
};
int icp_native_init(void)
{
struct device_node *np;
u32 indx = 0;
int found = 0;
for_each_compatible_node(np, NULL, "ibm,ppc-xicp")
if (icp_native_init_one_node(np, &indx) == 0)
found = 1;
if (!found) {
for_each_node_by_type(np,
"PowerPC-External-Interrupt-Presentation") {
if (icp_native_init_one_node(np, &indx) == 0)
found = 1;
}
}
if (found == 0)
return -ENODEV;
icp_ops = &icp_native_ops;
return 0;
}
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/smp.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/cpu.h>
#include <linux/of.h>
#include <linux/spinlock.h>
#include <linux/msi.h>
#include <asm/prom.h>
#include <asm/smp.h>
#include <asm/machdep.h>
#include <asm/irq.h>
#include <asm/errno.h>
#include <asm/xics.h>
#include <asm/rtas.h>
/* RTAS service tokens */
static int ibm_get_xive;
static int ibm_set_xive;
static int ibm_int_on;
static int ibm_int_off;
static int ics_rtas_map(struct ics *ics, unsigned int virq);
static void ics_rtas_mask_unknown(struct ics *ics, unsigned long vec);
static long ics_rtas_get_server(struct ics *ics, unsigned long vec);
/* Only one global & state struct ics */
static struct ics ics_rtas = {
.map = ics_rtas_map,
.mask_unknown = ics_rtas_mask_unknown,
.get_server = ics_rtas_get_server,
};
static void ics_rtas_unmask_irq(struct irq_data *d)
{
unsigned int hw_irq = (unsigned int)irq_data_to_hw(d);
int call_status;
int server;
pr_devel("xics: unmask virq %d [hw 0x%x]\n", d->irq, hw_irq);
if (hw_irq == XICS_IPI || hw_irq == XICS_IRQ_SPURIOUS)
return;
server = xics_get_irq_server(d->irq, d->affinity, 0);
call_status = rtas_call(ibm_set_xive, 3, 1, NULL, hw_irq, server,
DEFAULT_PRIORITY);
if (call_status != 0) {
printk(KERN_ERR
"%s: ibm_set_xive irq %u server %x returned %d\n",
__func__, hw_irq, server, call_status);
return;
}
/* Now unmask the interrupt (often a no-op) */
call_status = rtas_call(ibm_int_on, 1, 1, NULL, hw_irq);
if (call_status != 0) {
printk(KERN_ERR "%s: ibm_int_on irq=%u returned %d\n",
__func__, hw_irq, call_status);
return;
}
}
static unsigned int ics_rtas_startup(struct irq_data *d)
{
#ifdef CONFIG_PCI_MSI
/*
* The generic MSI code returns with the interrupt disabled on the
* card, using the MSI mask bits. Firmware doesn't appear to unmask
* at that level, so we do it here by hand.
*/
if (d->msi_desc)
unmask_msi_irq(d);
#endif
/* unmask it */
ics_rtas_unmask_irq(d);
return 0;
}
static void ics_rtas_mask_real_irq(unsigned int hw_irq)
{
int call_status;
if (hw_irq == XICS_IPI)
return;
call_status = rtas_call(ibm_int_off, 1, 1, NULL, hw_irq);
if (call_status != 0) {
printk(KERN_ERR "%s: ibm_int_off irq=%u returned %d\n",
__func__, hw_irq, call_status);
return;
}
/* Have to set XIVE to 0xff to be able to remove a slot */
call_status = rtas_call(ibm_set_xive, 3, 1, NULL, hw_irq,
xics_default_server, 0xff);
if (call_status != 0) {
printk(KERN_ERR "%s: ibm_set_xive(0xff) irq=%u returned %d\n",
__func__, hw_irq, call_status);
return;
}
}
static void ics_rtas_mask_irq(struct irq_data *d)
{
unsigned int hw_irq = (unsigned int)irq_data_to_hw(d);
pr_devel("xics: mask virq %d [hw 0x%x]\n", d->irq, hw_irq);
if (hw_irq == XICS_IPI || hw_irq == XICS_IRQ_SPURIOUS)
return;
ics_rtas_mask_real_irq(hw_irq);
}
static int ics_rtas_set_affinity(struct irq_data *d,
const struct cpumask *cpumask,
bool force)
{
unsigned int hw_irq = (unsigned int)irq_data_to_hw(d);
int status;
int xics_status[2];
int irq_server;
if (hw_irq == XICS_IPI || hw_irq == XICS_IRQ_SPURIOUS)
return -1;
status = rtas_call(ibm_get_xive, 1, 3, xics_status, hw_irq);
if (status) {
printk(KERN_ERR "%s: ibm,get-xive irq=%u returns %d\n",
__func__, hw_irq, status);
return -1;
}
irq_server = xics_get_irq_server(d->irq, cpumask, 1);
if (irq_server == -1) {
char cpulist[128];
cpumask_scnprintf(cpulist, sizeof(cpulist), cpumask);
printk(KERN_WARNING
"%s: No online cpus in the mask %s for irq %d\n",
__func__, cpulist, d->irq);
return -1;
}
status = rtas_call(ibm_set_xive, 3, 1, NULL,
hw_irq, irq_server, xics_status[1]);
if (status) {
printk(KERN_ERR "%s: ibm,set-xive irq=%u returns %d\n",
__func__, hw_irq, status);
return -1;
}
return IRQ_SET_MASK_OK;
}
static struct irq_chip ics_rtas_irq_chip = {
.name = "XICS",
.irq_startup = ics_rtas_startup,
.irq_mask = ics_rtas_mask_irq,
.irq_unmask = ics_rtas_unmask_irq,
.irq_eoi = NULL, /* Patched at init time */
.irq_set_affinity = ics_rtas_set_affinity
};
static int ics_rtas_map(struct ics *ics, unsigned int virq)
{
unsigned int hw_irq = (unsigned int)irq_map[virq].hwirq;
int status[2];
int rc;
if (WARN_ON(hw_irq == XICS_IPI || hw_irq == XICS_IRQ_SPURIOUS))
return -EINVAL;
/* Check if RTAS knows about this interrupt */
rc = rtas_call(ibm_get_xive, 1, 3, status, hw_irq);
if (rc)
return -ENXIO;
irq_set_chip_and_handler(virq, &ics_rtas_irq_chip, handle_fasteoi_irq);
irq_set_chip_data(virq, &ics_rtas);
return 0;
}
static void ics_rtas_mask_unknown(struct ics *ics, unsigned long vec)
{
ics_rtas_mask_real_irq(vec);
}
static long ics_rtas_get_server(struct ics *ics, unsigned long vec)
{
int rc, status[2];
rc = rtas_call(ibm_get_xive, 1, 3, status, vec);
if (rc)
return -1;
return status[0];
}
int ics_rtas_init(void)
{
ibm_get_xive = rtas_token("ibm,get-xive");
ibm_set_xive = rtas_token("ibm,set-xive");
ibm_int_on = rtas_token("ibm,int-on");
ibm_int_off = rtas_token("ibm,int-off");
/* We enable the RTAS "ICS" if RTAS is present with the
* appropriate tokens
*/
if (ibm_get_xive == RTAS_UNKNOWN_SERVICE ||
ibm_set_xive == RTAS_UNKNOWN_SERVICE)
return -ENODEV;
/* We need to patch our irq chip's EOI to point to the
* right ICP
*/
ics_rtas_irq_chip.irq_eoi = icp_ops->eoi;
/* Register ourselves */
xics_register_ics(&ics_rtas);
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment