Commit 1f44a225 authored by Martin Schwidefsky's avatar Martin Schwidefsky

s390: convert interrupt handling to use generic hardirq

With the introduction of PCI it became apparent that s390 should
convert to generic hardirqs as too many drivers do not have the
correct dependency for GENERIC_HARDIRQS. On the architecture
level s390 does not have irq lines. It has external interrupts,
I/O interrupts and adapter interrupts. This patch hard-codes all
external interrupts as irq #1, all I/O interrupts as irq #2 and
all adapter interrupts as irq #3. The additional information from
the lowcore associated with the interrupt is stored in the
pt_regs of the interrupt frame, where the interrupt handler can
pick it up. For PCI/MSI interrupts the adapter interrupt handler
scans the relevant bit fields and calls generic_handle_irq with
the virtual irq number for the MSI interrupt.
Reviewed-by: default avatarSebastian Ott <sebott@linux.vnet.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent 5d0d8f43
...@@ -116,6 +116,7 @@ config S390 ...@@ -116,6 +116,7 @@ config S390
select HAVE_FUNCTION_GRAPH_TRACER select HAVE_FUNCTION_GRAPH_TRACER
select HAVE_FUNCTION_TRACER select HAVE_FUNCTION_TRACER
select HAVE_FUNCTION_TRACE_MCOUNT_TEST select HAVE_FUNCTION_TRACE_MCOUNT_TEST
select HAVE_GENERIC_HARDIRQS
select HAVE_KERNEL_BZIP2 select HAVE_KERNEL_BZIP2
select HAVE_KERNEL_GZIP select HAVE_KERNEL_GZIP
select HAVE_KERNEL_LZ4 select HAVE_KERNEL_LZ4
...@@ -445,6 +446,16 @@ config PCI_NR_FUNCTIONS ...@@ -445,6 +446,16 @@ config PCI_NR_FUNCTIONS
This allows you to specify the maximum number of PCI functions which This allows you to specify the maximum number of PCI functions which
this kernel will support. this kernel will support.
config PCI_NR_MSI
int "Maximum number of MSI interrupts (64-32768)"
range 64 32768
default "256"
help
This defines the number of virtual interrupts the kernel will
provide for MSI interrupts. If you configure your system to have
too few drivers will fail to allocate MSI interrupts for all
PCI devices.
source "drivers/pci/Kconfig" source "drivers/pci/Kconfig"
source "drivers/pci/pcie/Kconfig" source "drivers/pci/pcie/Kconfig"
source "drivers/pci/hotplug/Kconfig" source "drivers/pci/hotplug/Kconfig"
......
...@@ -20,4 +20,9 @@ ...@@ -20,4 +20,9 @@
#define HARDIRQ_BITS 8 #define HARDIRQ_BITS 8
static inline void ack_bad_irq(unsigned int irq)
{
printk(KERN_CRIT "unexpected IRQ trap at vector %02x\n", irq);
}
#endif /* __ASM_HARDIRQ_H */ #endif /* __ASM_HARDIRQ_H */
...@@ -4,19 +4,8 @@ ...@@ -4,19 +4,8 @@
#include <linux/msi.h> #include <linux/msi.h>
#include <linux/pci.h> #include <linux/pci.h>
static inline struct msi_desc *irq_get_msi_desc(unsigned int irq) void __init init_airq_interrupts(void);
{ void __init init_cio_interrupts(void);
return __irq_get_msi_desc(irq); void __init init_ext_interrupts(void);
}
/* Must be called with msi map lock held */
static inline int irq_set_msi_desc(unsigned int irq, struct msi_desc *msi)
{
if (!msi)
return -EINVAL;
msi->irq = irq;
return 0;
}
#endif #endif
#ifndef _ASM_IRQ_H #ifndef _ASM_IRQ_H
#define _ASM_IRQ_H #define _ASM_IRQ_H
#define EXT_INTERRUPT 1
#define IO_INTERRUPT 2
#define THIN_INTERRUPT 3
#define NR_IRQS_BASE 4
#ifdef CONFIG_PCI_NR_MSI
# define NR_IRQS (NR_IRQS_BASE + CONFIG_PCI_NR_MSI)
#else
# define NR_IRQS NR_IRQS_BASE
#endif
/* This number is used when no interrupt has been assigned */
#define NO_IRQ 0
#ifndef __ASSEMBLY__
#include <linux/hardirq.h> #include <linux/hardirq.h>
#include <linux/percpu.h> #include <linux/percpu.h>
#include <linux/cache.h> #include <linux/cache.h>
#include <linux/types.h> #include <linux/types.h>
enum interruption_main_class {
EXTERNAL_INTERRUPT,
IO_INTERRUPT,
NR_IRQS
};
enum interruption_class { enum interruption_class {
IRQEXT_CLK, IRQEXT_CLK,
IRQEXT_EXC, IRQEXT_EXC,
...@@ -72,14 +83,8 @@ void service_subclass_irq_unregister(void); ...@@ -72,14 +83,8 @@ void service_subclass_irq_unregister(void);
void measurement_alert_subclass_register(void); void measurement_alert_subclass_register(void);
void measurement_alert_subclass_unregister(void); void measurement_alert_subclass_unregister(void);
#ifdef CONFIG_LOCKDEP #define irq_canonicalize(irq) (irq)
# define disable_irq_nosync_lockdep(irq) disable_irq_nosync(irq)
# define disable_irq_nosync_lockdep_irqsave(irq, flags) \ #endif /* __ASSEMBLY__ */
disable_irq_nosync(irq)
# define disable_irq_lockdep(irq) disable_irq(irq)
# define enable_irq_lockdep(irq) enable_irq(irq)
# define enable_irq_lockdep_irqrestore(irq, flags) \
enable_irq(irq)
#endif
#endif /* _ASM_IRQ_H */ #endif /* _ASM_IRQ_H */
...@@ -53,12 +53,6 @@ struct zpci_fmb { ...@@ -53,12 +53,6 @@ struct zpci_fmb {
atomic64_t unmapped_pages; atomic64_t unmapped_pages;
} __packed __aligned(16); } __packed __aligned(16);
struct msi_map {
unsigned long irq;
struct msi_desc *msi;
struct hlist_node msi_chain;
};
#define ZPCI_MSI_VEC_BITS 11 #define ZPCI_MSI_VEC_BITS 11
#define ZPCI_MSI_VEC_MAX (1 << ZPCI_MSI_VEC_BITS) #define ZPCI_MSI_VEC_MAX (1 << ZPCI_MSI_VEC_BITS)
#define ZPCI_MSI_VEC_MASK (ZPCI_MSI_VEC_MAX - 1) #define ZPCI_MSI_VEC_MASK (ZPCI_MSI_VEC_MAX - 1)
...@@ -92,8 +86,6 @@ struct zpci_dev { ...@@ -92,8 +86,6 @@ struct zpci_dev {
/* IRQ stuff */ /* IRQ stuff */
u64 msi_addr; /* MSI address */ u64 msi_addr; /* MSI address */
struct zdev_irq_map *irq_map;
struct msi_map *msi_map;
struct airq_iv *aibv; /* adapter interrupt bit vector */ struct airq_iv *aibv; /* adapter interrupt bit vector */
unsigned int aisb; /* number of the summary bit */ unsigned int aisb; /* number of the summary bit */
...@@ -153,14 +145,6 @@ int clp_add_pci_device(u32, u32, int); ...@@ -153,14 +145,6 @@ int clp_add_pci_device(u32, u32, int);
int clp_enable_fh(struct zpci_dev *, u8); int clp_enable_fh(struct zpci_dev *, u8);
int clp_disable_fh(struct zpci_dev *); int clp_disable_fh(struct zpci_dev *);
/* MSI */
struct msi_desc *__irq_get_msi_desc(unsigned int);
int zpci_msi_set_mask_bits(struct msi_desc *, u32, u32);
int zpci_setup_msi_irq(struct zpci_dev *, struct msi_desc *, unsigned int, int);
void zpci_teardown_msi_irq(struct zpci_dev *, struct msi_desc *);
int zpci_msihash_init(void);
void zpci_msihash_exit(void);
#ifdef CONFIG_PCI #ifdef CONFIG_PCI
/* Error handling and recovery */ /* Error handling and recovery */
void zpci_event_error(void *); void zpci_event_error(void *);
......
#ifndef _ASM_S390_SERIAL_H
#define _ASM_S390_SERIAL_H
#define BASE_BAUD 0
#endif /* _ASM_S390_SERIAL_H */
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <asm/unistd.h> #include <asm/unistd.h>
#include <asm/page.h> #include <asm/page.h>
#include <asm/sigp.h> #include <asm/sigp.h>
#include <asm/irq.h>
__PT_R0 = __PT_GPRS __PT_R0 = __PT_GPRS
__PT_R1 = __PT_GPRS + 4 __PT_R1 = __PT_GPRS + 4
...@@ -435,6 +436,11 @@ io_skip: ...@@ -435,6 +436,11 @@ io_skip:
io_loop: io_loop:
l %r1,BASED(.Ldo_IRQ) l %r1,BASED(.Ldo_IRQ)
lr %r2,%r11 # pass pointer to pt_regs lr %r2,%r11 # pass pointer to pt_regs
lhi %r3,IO_INTERRUPT
tm __PT_INT_CODE+8(%r11),0x80 # adapter interrupt ?
jz io_call
lhi %r3,THIN_INTERRUPT
io_call:
basr %r14,%r1 # call do_IRQ basr %r14,%r1 # call do_IRQ
tm __LC_MACHINE_FLAGS+2,0x10 # MACHINE_FLAG_LPAR tm __LC_MACHINE_FLAGS+2,0x10 # MACHINE_FLAG_LPAR
jz io_return jz io_return
...@@ -584,9 +590,10 @@ ext_skip: ...@@ -584,9 +590,10 @@ ext_skip:
mvc __PT_INT_CODE(4,%r11),__LC_EXT_CPU_ADDR mvc __PT_INT_CODE(4,%r11),__LC_EXT_CPU_ADDR
mvc __PT_INT_PARM(4,%r11),__LC_EXT_PARAMS mvc __PT_INT_PARM(4,%r11),__LC_EXT_PARAMS
TRACE_IRQS_OFF TRACE_IRQS_OFF
l %r1,BASED(.Ldo_IRQ)
lr %r2,%r11 # pass pointer to pt_regs lr %r2,%r11 # pass pointer to pt_regs
l %r1,BASED(.Ldo_extint) lhi %r3,EXT_INTERRUPT
basr %r14,%r1 # call do_extint basr %r14,%r1 # call do_IRQ
j io_return j io_return
/* /*
...@@ -902,7 +909,6 @@ cleanup_idle_wait: ...@@ -902,7 +909,6 @@ cleanup_idle_wait:
.Ldo_machine_check: .long s390_do_machine_check .Ldo_machine_check: .long s390_do_machine_check
.Lhandle_mcck: .long s390_handle_mcck .Lhandle_mcck: .long s390_handle_mcck
.Ldo_IRQ: .long do_IRQ .Ldo_IRQ: .long do_IRQ
.Ldo_extint: .long do_extint
.Ldo_signal: .long do_signal .Ldo_signal: .long do_signal
.Ldo_notify_resume: .long do_notify_resume .Ldo_notify_resume: .long do_notify_resume
.Ldo_per_trap: .long do_per_trap .Ldo_per_trap: .long do_per_trap
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <asm/unistd.h> #include <asm/unistd.h>
#include <asm/page.h> #include <asm/page.h>
#include <asm/sigp.h> #include <asm/sigp.h>
#include <asm/irq.h>
__PT_R0 = __PT_GPRS __PT_R0 = __PT_GPRS
__PT_R1 = __PT_GPRS + 8 __PT_R1 = __PT_GPRS + 8
...@@ -468,6 +469,11 @@ io_skip: ...@@ -468,6 +469,11 @@ io_skip:
xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15) xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15)
io_loop: io_loop:
lgr %r2,%r11 # pass pointer to pt_regs lgr %r2,%r11 # pass pointer to pt_regs
lghi %r3,IO_INTERRUPT
tm __PT_INT_CODE+8(%r11),0x80 # adapter interrupt ?
jz io_call
lghi %r3,THIN_INTERRUPT
io_call:
brasl %r14,do_IRQ brasl %r14,do_IRQ
tm __LC_MACHINE_FLAGS+6,0x10 # MACHINE_FLAG_LPAR tm __LC_MACHINE_FLAGS+6,0x10 # MACHINE_FLAG_LPAR
jz io_return jz io_return
...@@ -623,7 +629,8 @@ ext_skip: ...@@ -623,7 +629,8 @@ ext_skip:
TRACE_IRQS_OFF TRACE_IRQS_OFF
xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15) xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15)
lgr %r2,%r11 # pass pointer to pt_regs lgr %r2,%r11 # pass pointer to pt_regs
brasl %r14,do_extint lghi %r3,EXT_INTERRUPT
brasl %r14,do_IRQ
j io_return j io_return
/* /*
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <asm/cputime.h> #include <asm/cputime.h>
#include <asm/lowcore.h> #include <asm/lowcore.h>
#include <asm/irq.h> #include <asm/irq.h>
#include <asm/hw_irq.h>
#include "entry.h" #include "entry.h"
DEFINE_PER_CPU_SHARED_ALIGNED(struct irq_stat, irq_stat); DEFINE_PER_CPU_SHARED_ALIGNED(struct irq_stat, irq_stat);
...@@ -42,9 +43,10 @@ struct irq_class { ...@@ -42,9 +43,10 @@ struct irq_class {
* Since the external and I/O interrupt fields are already sums we would end * Since the external and I/O interrupt fields are already sums we would end
* up with having a sum which accounts each interrupt twice. * up with having a sum which accounts each interrupt twice.
*/ */
static const struct irq_class irqclass_main_desc[NR_IRQS] = { static const struct irq_class irqclass_main_desc[NR_IRQS_BASE] = {
[EXTERNAL_INTERRUPT] = {.name = "EXT"}, [EXT_INTERRUPT] = {.name = "EXT"},
[IO_INTERRUPT] = {.name = "I/O"} [IO_INTERRUPT] = {.name = "I/O"},
[THIN_INTERRUPT] = {.name = "AIO"},
}; };
/* /*
...@@ -86,6 +88,28 @@ static const struct irq_class irqclass_sub_desc[NR_ARCH_IRQS] = { ...@@ -86,6 +88,28 @@ static const struct irq_class irqclass_sub_desc[NR_ARCH_IRQS] = {
[CPU_RST] = {.name = "RST", .desc = "[CPU] CPU Restart"}, [CPU_RST] = {.name = "RST", .desc = "[CPU] CPU Restart"},
}; };
void __init init_IRQ(void)
{
irq_reserve_irqs(0, THIN_INTERRUPT);
init_cio_interrupts();
init_airq_interrupts();
init_ext_interrupts();
}
void do_IRQ(struct pt_regs *regs, int irq)
{
struct pt_regs *old_regs;
old_regs = set_irq_regs(regs);
irq_enter();
if (S390_lowcore.int_clock >= S390_lowcore.clock_comparator)
/* Serve timer interrupts first. */
clock_comparator_work();
generic_handle_irq(irq);
irq_exit();
set_irq_regs(old_regs);
}
/* /*
* show_interrupts is needed by /proc/interrupts. * show_interrupts is needed by /proc/interrupts.
*/ */
...@@ -100,27 +124,36 @@ int show_interrupts(struct seq_file *p, void *v) ...@@ -100,27 +124,36 @@ int show_interrupts(struct seq_file *p, void *v)
for_each_online_cpu(cpu) for_each_online_cpu(cpu)
seq_printf(p, "CPU%d ", cpu); seq_printf(p, "CPU%d ", cpu);
seq_putc(p, '\n'); seq_putc(p, '\n');
goto out;
} }
if (irq < NR_IRQS) { if (irq < NR_IRQS) {
if (irq >= NR_IRQS_BASE)
goto out;
seq_printf(p, "%s: ", irqclass_main_desc[irq].name); seq_printf(p, "%s: ", irqclass_main_desc[irq].name);
for_each_online_cpu(cpu) for_each_online_cpu(cpu)
seq_printf(p, "%10u ", kstat_cpu(cpu).irqs[irq]); seq_printf(p, "%10u ", kstat_irqs_cpu(irq, cpu));
seq_putc(p, '\n'); seq_putc(p, '\n');
goto skip_arch_irqs; goto out;
} }
for (irq = 0; irq < NR_ARCH_IRQS; irq++) { for (irq = 0; irq < NR_ARCH_IRQS; irq++) {
seq_printf(p, "%s: ", irqclass_sub_desc[irq].name); seq_printf(p, "%s: ", irqclass_sub_desc[irq].name);
for_each_online_cpu(cpu) for_each_online_cpu(cpu)
seq_printf(p, "%10u ", per_cpu(irq_stat, cpu).irqs[irq]); seq_printf(p, "%10u ",
per_cpu(irq_stat, cpu).irqs[irq]);
if (irqclass_sub_desc[irq].desc) if (irqclass_sub_desc[irq].desc)
seq_printf(p, " %s", irqclass_sub_desc[irq].desc); seq_printf(p, " %s", irqclass_sub_desc[irq].desc);
seq_putc(p, '\n'); seq_putc(p, '\n');
} }
skip_arch_irqs: out:
put_online_cpus(); put_online_cpus();
return 0; return 0;
} }
int arch_show_interrupts(struct seq_file *p, int prec)
{
return 0;
}
/* /*
* Switch to the asynchronous interrupt stack for softirq execution. * Switch to the asynchronous interrupt stack for softirq execution.
*/ */
...@@ -159,14 +192,6 @@ asmlinkage void do_softirq(void) ...@@ -159,14 +192,6 @@ asmlinkage void do_softirq(void)
local_irq_restore(flags); local_irq_restore(flags);
} }
#ifdef CONFIG_PROC_FS
void init_irq_proc(void)
{
if (proc_mkdir("irq", NULL))
create_prof_cpu_mask();
}
#endif
/* /*
* ext_int_hash[index] is the list head for all external interrupts that hash * ext_int_hash[index] is the list head for all external interrupts that hash
* to this index. * to this index.
...@@ -183,14 +208,6 @@ struct ext_int_info { ...@@ -183,14 +208,6 @@ struct ext_int_info {
/* ext_int_hash_lock protects the handler lists for external interrupts */ /* ext_int_hash_lock protects the handler lists for external interrupts */
DEFINE_SPINLOCK(ext_int_hash_lock); DEFINE_SPINLOCK(ext_int_hash_lock);
static void __init init_external_interrupts(void)
{
int idx;
for (idx = 0; idx < ARRAY_SIZE(ext_int_hash); idx++)
INIT_LIST_HEAD(&ext_int_hash[idx]);
}
static inline int ext_hash(u16 code) static inline int ext_hash(u16 code)
{ {
return (code + (code >> 9)) & 0xff; return (code + (code >> 9)) & 0xff;
...@@ -234,20 +251,13 @@ int unregister_external_interrupt(u16 code, ext_int_handler_t handler) ...@@ -234,20 +251,13 @@ int unregister_external_interrupt(u16 code, ext_int_handler_t handler)
} }
EXPORT_SYMBOL(unregister_external_interrupt); EXPORT_SYMBOL(unregister_external_interrupt);
void __irq_entry do_extint(struct pt_regs *regs) static irqreturn_t do_ext_interrupt(int irq, void *dummy)
{ {
struct pt_regs *regs = get_irq_regs();
struct ext_code ext_code; struct ext_code ext_code;
struct pt_regs *old_regs;
struct ext_int_info *p; struct ext_int_info *p;
int index; int index;
old_regs = set_irq_regs(regs);
irq_enter();
if (S390_lowcore.int_clock >= S390_lowcore.clock_comparator) {
/* Serve timer interrupts first. */
clock_comparator_work();
}
kstat_incr_irqs_this_cpu(EXTERNAL_INTERRUPT, NULL);
ext_code = *(struct ext_code *) &regs->int_code; ext_code = *(struct ext_code *) &regs->int_code;
if (ext_code.code != 0x1004) if (ext_code.code != 0x1004)
__get_cpu_var(s390_idle).nohz_delay = 1; __get_cpu_var(s390_idle).nohz_delay = 1;
...@@ -259,13 +269,25 @@ void __irq_entry do_extint(struct pt_regs *regs) ...@@ -259,13 +269,25 @@ void __irq_entry do_extint(struct pt_regs *regs)
p->handler(ext_code, regs->int_parm, p->handler(ext_code, regs->int_parm,
regs->int_parm_long); regs->int_parm_long);
rcu_read_unlock(); rcu_read_unlock();
irq_exit();
set_irq_regs(old_regs); return IRQ_HANDLED;
} }
void __init init_IRQ(void) static struct irqaction external_interrupt = {
.name = "EXT",
.handler = do_ext_interrupt,
};
void __init init_ext_interrupts(void)
{ {
init_external_interrupts(); int idx;
for (idx = 0; idx < ARRAY_SIZE(ext_int_hash); idx++)
INIT_LIST_HEAD(&ext_int_hash[idx]);
irq_set_chip_and_handler(EXT_INTERRUPT,
&dummy_irq_chip, handle_percpu_irq);
setup_irq(EXT_INTERRUPT, &external_interrupt);
} }
static DEFINE_SPINLOCK(sc_irq_lock); static DEFINE_SPINLOCK(sc_irq_lock);
...@@ -313,69 +335,3 @@ void measurement_alert_subclass_unregister(void) ...@@ -313,69 +335,3 @@ void measurement_alert_subclass_unregister(void)
spin_unlock(&ma_subclass_lock); spin_unlock(&ma_subclass_lock);
} }
EXPORT_SYMBOL(measurement_alert_subclass_unregister); EXPORT_SYMBOL(measurement_alert_subclass_unregister);
#ifdef CONFIG_SMP
void synchronize_irq(unsigned int irq)
{
/*
* Not needed, the handler is protected by a lock and IRQs that occur
* after the handler is deleted are just NOPs.
*/
}
EXPORT_SYMBOL_GPL(synchronize_irq);
#endif
#ifndef CONFIG_PCI
/* Only PCI devices have dynamically-defined IRQ handlers */
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
return -EINVAL;
}
EXPORT_SYMBOL_GPL(request_irq);
void free_irq(unsigned int irq, void *dev_id)
{
WARN_ON(1);
}
EXPORT_SYMBOL_GPL(free_irq);
void enable_irq(unsigned int irq)
{
WARN_ON(1);
}
EXPORT_SYMBOL_GPL(enable_irq);
void disable_irq(unsigned int irq)
{
WARN_ON(1);
}
EXPORT_SYMBOL_GPL(disable_irq);
#endif /* !CONFIG_PCI */
void disable_irq_nosync(unsigned int irq)
{
disable_irq(irq);
}
EXPORT_SYMBOL_GPL(disable_irq_nosync);
unsigned long probe_irq_on(void)
{
return 0;
}
EXPORT_SYMBOL_GPL(probe_irq_on);
int probe_irq_off(unsigned long val)
{
return 0;
}
EXPORT_SYMBOL_GPL(probe_irq_off);
unsigned int probe_irq_mask(unsigned long val)
{
return val;
}
EXPORT_SYMBOL_GPL(probe_irq_mask);
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
# Makefile for the s390 PCI subsystem. # Makefile for the s390 PCI subsystem.
# #
obj-$(CONFIG_PCI) += pci.o pci_dma.o pci_clp.o pci_msi.o pci_sysfs.o \ obj-$(CONFIG_PCI) += pci.o pci_dma.o pci_clp.o pci_sysfs.o \
pci_event.o pci_debug.o pci_insn.o pci_event.o pci_debug.o pci_insn.o
This diff is collapsed.
/*
* Copyright IBM Corp. 2012
*
* Author(s):
* Jan Glauber <jang@linux.vnet.ibm.com>
*/
#define COMPONENT "zPCI"
#define pr_fmt(fmt) COMPONENT ": " fmt
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/rculist.h>
#include <linux/hash.h>
#include <linux/pci.h>
#include <linux/msi.h>
#include <asm/hw_irq.h>
/* mapping of irq numbers to msi_desc */
static struct hlist_head *msi_hash;
static const unsigned int msi_hash_bits = 8;
#define MSI_HASH_BUCKETS (1U << msi_hash_bits)
#define msi_hashfn(nr) hash_long(nr, msi_hash_bits)
static DEFINE_SPINLOCK(msi_map_lock);
struct msi_desc *__irq_get_msi_desc(unsigned int irq)
{
struct msi_map *map;
hlist_for_each_entry_rcu(map,
&msi_hash[msi_hashfn(irq)], msi_chain)
if (map->irq == irq)
return map->msi;
return NULL;
}
int zpci_msi_set_mask_bits(struct msi_desc *msi, u32 mask, u32 flag)
{
if (msi->msi_attrib.is_msix) {
int offset = msi->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE +
PCI_MSIX_ENTRY_VECTOR_CTRL;
msi->masked = readl(msi->mask_base + offset);
writel(flag, msi->mask_base + offset);
} else {
if (msi->msi_attrib.maskbit) {
int pos;
u32 mask_bits;
pos = (long) msi->mask_base;
pci_read_config_dword(msi->dev, pos, &mask_bits);
mask_bits &= ~(mask);
mask_bits |= flag & mask;
pci_write_config_dword(msi->dev, pos, mask_bits);
} else {
return 0;
}
}
msi->msi_attrib.maskbit = !!flag;
return 1;
}
int zpci_setup_msi_irq(struct zpci_dev *zdev, struct msi_desc *msi,
unsigned int nr, int offset)
{
struct msi_map *map;
struct msi_msg msg;
int rc;
map = zdev->msi_map + (nr & ZPCI_MSI_VEC_MASK);
map->irq = nr;
map->msi = msi;
INIT_HLIST_NODE(&map->msi_chain);
pr_debug("%s hashing irq: %u to bucket nr: %llu\n",
__func__, nr, msi_hashfn(nr));
hlist_add_head_rcu(&map->msi_chain, &msi_hash[msi_hashfn(nr)]);
spin_lock(&msi_map_lock);
rc = irq_set_msi_desc(nr, msi);
if (rc) {
spin_unlock(&msi_map_lock);
hlist_del_rcu(&map->msi_chain);
return rc;
}
spin_unlock(&msi_map_lock);
msg.data = nr - offset;
msg.address_lo = zdev->msi_addr & 0xffffffff;
msg.address_hi = zdev->msi_addr >> 32;
write_msi_msg(nr, &msg);
return 0;
}
void zpci_teardown_msi_irq(struct zpci_dev *zdev, struct msi_desc *msi)
{
int nr = msi->irq & ZPCI_MSI_VEC_MASK;
struct msi_map *map;
zpci_msi_set_mask_bits(msi, 1, 1);
msi->msg.address_lo = 0;
msi->msg.address_hi = 0;
msi->msg.data = 0;
msi->irq = 0;
spin_lock(&msi_map_lock);
map = zdev->msi_map + nr;
hlist_del_rcu(&map->msi_chain);
spin_unlock(&msi_map_lock);
}
/*
* The msi hash table has 256 entries which is good for 4..20
* devices (a typical device allocates 10 + CPUs MSI's). Maybe make
* the hash table size adjustable later.
*/
int __init zpci_msihash_init(void)
{
unsigned int i;
msi_hash = kmalloc(MSI_HASH_BUCKETS * sizeof(*msi_hash), GFP_KERNEL);
if (!msi_hash)
return -ENOMEM;
for (i = 0; i < MSI_HASH_BUCKETS; i++)
INIT_HLIST_HEAD(&msi_hash[i]);
return 0;
}
void __init zpci_msihash_exit(void)
{
kfree(msi_hash);
}
...@@ -81,17 +81,34 @@ void unregister_adapter_interrupt(struct airq_struct *airq) ...@@ -81,17 +81,34 @@ void unregister_adapter_interrupt(struct airq_struct *airq)
} }
EXPORT_SYMBOL(unregister_adapter_interrupt); EXPORT_SYMBOL(unregister_adapter_interrupt);
void do_adapter_IO(u8 isc) static irqreturn_t do_airq_interrupt(int irq, void *dummy)
{ {
struct tpi_info *tpi_info;
struct airq_struct *airq; struct airq_struct *airq;
struct hlist_head *head; struct hlist_head *head;
head = &airq_lists[isc]; __this_cpu_write(s390_idle.nohz_delay, 1);
tpi_info = (struct tpi_info *) &get_irq_regs()->int_code;
head = &airq_lists[tpi_info->isc];
rcu_read_lock(); rcu_read_lock();
hlist_for_each_entry_rcu(airq, head, list) hlist_for_each_entry_rcu(airq, head, list)
if ((*airq->lsi_ptr & airq->lsi_mask) != 0) if ((*airq->lsi_ptr & airq->lsi_mask) != 0)
airq->handler(airq); airq->handler(airq);
rcu_read_unlock(); rcu_read_unlock();
return IRQ_HANDLED;
}
static struct irqaction airq_interrupt = {
.name = "AIO",
.handler = do_airq_interrupt,
};
void __init init_airq_interrupts(void)
{
irq_set_chip_and_handler(THIN_INTERRUPT,
&dummy_irq_chip, handle_percpu_irq);
setup_irq(THIN_INTERRUPT, &airq_interrupt);
} }
/** /**
......
...@@ -561,37 +561,23 @@ int cio_validate_subchannel(struct subchannel *sch, struct subchannel_id schid) ...@@ -561,37 +561,23 @@ int cio_validate_subchannel(struct subchannel *sch, struct subchannel_id schid)
} }
/* /*
* do_IRQ() handles all normal I/O device IRQ's (the special * do_cio_interrupt() handles all normal I/O device IRQ's
* SMP cross-CPU interrupts have their own specific
* handlers).
*
*/ */
void __irq_entry do_IRQ(struct pt_regs *regs) static irqreturn_t do_cio_interrupt(int irq, void *dummy)
{ {
struct tpi_info *tpi_info = (struct tpi_info *) &regs->int_code; struct tpi_info *tpi_info;
struct subchannel *sch; struct subchannel *sch;
struct irb *irb; struct irb *irb;
struct pt_regs *old_regs;
old_regs = set_irq_regs(regs);
irq_enter();
__this_cpu_write(s390_idle.nohz_delay, 1); __this_cpu_write(s390_idle.nohz_delay, 1);
if (S390_lowcore.int_clock >= S390_lowcore.clock_comparator) tpi_info = (struct tpi_info *) &get_irq_regs()->int_code;
/* Serve timer interrupts first. */
clock_comparator_work();
kstat_incr_irqs_this_cpu(IO_INTERRUPT, NULL);
irb = (struct irb *) &S390_lowcore.irb; irb = (struct irb *) &S390_lowcore.irb;
if (tpi_info->adapter_IO) {
do_adapter_IO(tpi_info->isc);
goto out;
}
sch = (struct subchannel *)(unsigned long) tpi_info->intparm; sch = (struct subchannel *)(unsigned long) tpi_info->intparm;
if (!sch) { if (!sch) {
/* Clear pending interrupt condition. */ /* Clear pending interrupt condition. */
inc_irq_stat(IRQIO_CIO); inc_irq_stat(IRQIO_CIO);
tsch(tpi_info->schid, irb); tsch(tpi_info->schid, irb);
goto out; return IRQ_HANDLED;
} }
spin_lock(sch->lock); spin_lock(sch->lock);
/* Store interrupt response block to lowcore. */ /* Store interrupt response block to lowcore. */
...@@ -606,9 +592,23 @@ void __irq_entry do_IRQ(struct pt_regs *regs) ...@@ -606,9 +592,23 @@ void __irq_entry do_IRQ(struct pt_regs *regs)
} else } else
inc_irq_stat(IRQIO_CIO); inc_irq_stat(IRQIO_CIO);
spin_unlock(sch->lock); spin_unlock(sch->lock);
out:
irq_exit(); return IRQ_HANDLED;
set_irq_regs(old_regs); }
static struct irq_desc *irq_desc_io;
static struct irqaction io_interrupt = {
.name = "IO",
.handler = do_cio_interrupt,
};
void __init init_cio_interrupts(void)
{
irq_set_chip_and_handler(IO_INTERRUPT,
&dummy_irq_chip, handle_percpu_irq);
setup_irq(IO_INTERRUPT, &io_interrupt);
irq_desc_io = irq_to_desc(IO_INTERRUPT);
} }
#ifdef CONFIG_CCW_CONSOLE #ifdef CONFIG_CCW_CONSOLE
...@@ -635,7 +635,7 @@ void cio_tsch(struct subchannel *sch) ...@@ -635,7 +635,7 @@ void cio_tsch(struct subchannel *sch)
local_bh_disable(); local_bh_disable();
irq_enter(); irq_enter();
} }
kstat_incr_irqs_this_cpu(IO_INTERRUPT, NULL); kstat_incr_irqs_this_cpu(IO_INTERRUPT, irq_desc_io);
if (sch->driver && sch->driver->irq) if (sch->driver && sch->driver->irq)
sch->driver->irq(sch); sch->driver->irq(sch);
else else
......
...@@ -121,9 +121,6 @@ extern int cio_commit_config(struct subchannel *sch); ...@@ -121,9 +121,6 @@ extern int cio_commit_config(struct subchannel *sch);
int cio_tm_start_key(struct subchannel *sch, struct tcw *tcw, u8 lpm, u8 key); int cio_tm_start_key(struct subchannel *sch, struct tcw *tcw, u8 lpm, u8 key);
int cio_tm_intrg(struct subchannel *sch); int cio_tm_intrg(struct subchannel *sch);
void do_adapter_IO(u8 isc);
void do_IRQ(struct pt_regs *);
/* Use with care. */ /* Use with care. */
#ifdef CONFIG_CCW_CONSOLE #ifdef CONFIG_CCW_CONSOLE
extern struct subchannel *cio_probe_console(void); extern struct subchannel *cio_probe_console(void);
......
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