diff options
author | Paul Mundt <lethal@linux-sh.org> | 2010-10-04 21:47:03 +0200 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2010-10-04 21:47:03 +0200 |
commit | c1e30ad98fe210688edca872686db4a715c2fb23 (patch) | |
tree | 1ff151ccc9658d7a2d89da9cfc6a2d6817913a79 /drivers/sh/intc.c | |
parent | sh: intc: Implement reverse mapping for IRQs to per-controller IDs. (diff) | |
download | linux-c1e30ad98fe210688edca872686db4a715c2fb23.tar.xz linux-c1e30ad98fe210688edca872686db4a715c2fb23.zip |
sh: intc: Support virtual mappings for IRQ subgroups.
Many interrupts that share a single mask source but are on different
hardware vectors will have an associated register tied to an INTEVT that
denotes the precise cause for the interrupt exception being triggered.
This introduces the concept of IRQ subgroups in the intc core, where
a virtual IRQ map is constructed for each of the pre-defined cause bits,
and a higher level chained handler takes control of the parent INTEVT.
This enables CPUs with heavily muxed IRQ vectors (especially across
disjoint blocks) to break things out in to a series of managed chained
handlers while being able to dynamically lookup and adopt the IRQs
created for them.
This is largely an opt-in interface, requiring CPUs to manually submit
IRQs for subgroup splitting, in addition to providing identifiers in
their enum maps that can be used for lazy lookup via the radix tree.
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'drivers/sh/intc.c')
-rw-r--r-- | drivers/sh/intc.c | 404 |
1 files changed, 338 insertions, 66 deletions
diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c index a27dcb4254c7..c81fe23db7f7 100644 --- a/drivers/sh/intc.c +++ b/drivers/sh/intc.c @@ -35,6 +35,7 @@ #include <linux/seq_file.h> #include <linux/radix-tree.h> #include <linux/mutex.h> +#include <linux/rcupdate.h> #include <asm/sizes.h> #define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \ @@ -64,11 +65,19 @@ struct intc_map_entry { struct intc_desc_int *desc; }; +struct intc_subgroup_entry { + unsigned int pirq; + intc_enum enum_id; + unsigned long handle; +}; + struct intc_desc_int { struct list_head list; struct sys_device sysdev; struct radix_tree_root tree; pm_message_t state; + spinlock_t lock; + unsigned int index; unsigned long *reg; #ifdef CONFIG_SMP unsigned long *smp; @@ -84,6 +93,7 @@ struct intc_desc_int { }; static LIST_HEAD(intc_list); +static unsigned int nr_intc_controllers; /* * The intc_irq_map provides a global map of bound IRQ vectors for a @@ -99,7 +109,7 @@ static LIST_HEAD(intc_list); static DECLARE_BITMAP(intc_irq_map, NR_IRQS); static struct intc_map_entry intc_irq_xlate[NR_IRQS]; static DEFINE_SPINLOCK(vector_lock); -static DEFINE_MUTEX(irq_xlate_mutex); +static DEFINE_SPINLOCK(xlate_lock); #ifdef CONFIG_SMP #define IS_SMP(x) x.smp @@ -118,12 +128,39 @@ static unsigned long ack_handle[NR_IRQS]; static unsigned long dist_handle[NR_IRQS]; #endif +struct intc_virq_list { + unsigned int irq; + struct intc_virq_list *next; +}; + +#define for_each_virq(entry, head) \ + for (entry = head; entry; entry = entry->next) + static inline struct intc_desc_int *get_intc_desc(unsigned int irq) { struct irq_chip *chip = get_irq_chip(irq); + return container_of(chip, struct intc_desc_int, chip); } +static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc) +{ + generic_handle_irq((unsigned int)get_irq_data(irq)); +} + +static inline void activate_irq(int irq) +{ +#ifdef CONFIG_ARM + /* ARM requires an extra step to clear IRQ_NOREQUEST, which it + * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. + */ + set_irq_flags(irq, IRQF_VALID); +#else + /* same effect on other architectures */ + set_irq_noprobe(irq); +#endif +} + static unsigned long intc_phys_to_virt(struct intc_desc_int *d, unsigned long address) { @@ -177,56 +214,103 @@ static inline unsigned int set_field(unsigned int value, return value; } -static void write_8(unsigned long addr, unsigned long h, unsigned long data) +static inline unsigned long get_field(unsigned int value, unsigned int handle) +{ + unsigned int width = _INTC_WIDTH(handle); + unsigned int shift = _INTC_SHIFT(handle); + unsigned int mask = ((1 << width) - 1) << shift; + + return (value & mask) >> shift; +} + +static unsigned long test_8(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return get_field(__raw_readb(addr), h); +} + +static unsigned long test_16(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return get_field(__raw_readw(addr), h); +} + +static unsigned long test_32(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return get_field(__raw_readl(addr), h); +} + +static unsigned long write_8(unsigned long addr, unsigned long h, + unsigned long data) { __raw_writeb(set_field(0, data, h), addr); (void)__raw_readb(addr); /* Defeat write posting */ + return 0; } -static void write_16(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long write_16(unsigned long addr, unsigned long h, + unsigned long data) { __raw_writew(set_field(0, data, h), addr); (void)__raw_readw(addr); /* Defeat write posting */ + return 0; } -static void write_32(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long write_32(unsigned long addr, unsigned long h, + unsigned long data) { __raw_writel(set_field(0, data, h), addr); (void)__raw_readl(addr); /* Defeat write posting */ + return 0; } -static void modify_8(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long modify_8(unsigned long addr, unsigned long h, + unsigned long data) { unsigned long flags; local_irq_save(flags); __raw_writeb(set_field(__raw_readb(addr), data, h), addr); (void)__raw_readb(addr); /* Defeat write posting */ local_irq_restore(flags); + return 0; } -static void modify_16(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long modify_16(unsigned long addr, unsigned long h, + unsigned long data) { unsigned long flags; local_irq_save(flags); __raw_writew(set_field(__raw_readw(addr), data, h), addr); (void)__raw_readw(addr); /* Defeat write posting */ local_irq_restore(flags); + return 0; } -static void modify_32(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long modify_32(unsigned long addr, unsigned long h, + unsigned long data) { unsigned long flags; local_irq_save(flags); __raw_writel(set_field(__raw_readl(addr), data, h), addr); (void)__raw_readl(addr); /* Defeat write posting */ local_irq_restore(flags); + return 0; } -enum { REG_FN_ERR = 0, REG_FN_WRITE_BASE = 1, REG_FN_MODIFY_BASE = 5 }; +enum { + REG_FN_ERR = 0, + REG_FN_TEST_BASE = 1, + REG_FN_WRITE_BASE = 5, + REG_FN_MODIFY_BASE = 9 +}; -static void (*intc_reg_fns[])(unsigned long addr, - unsigned long h, - unsigned long data) = { +static unsigned long (*intc_reg_fns[])(unsigned long addr, + unsigned long h, + unsigned long data) = { + [REG_FN_TEST_BASE + 0] = test_8, + [REG_FN_TEST_BASE + 1] = test_16, + [REG_FN_TEST_BASE + 3] = test_32, [REG_FN_WRITE_BASE + 0] = write_8, [REG_FN_WRITE_BASE + 1] = write_16, [REG_FN_WRITE_BASE + 3] = write_32, @@ -242,42 +326,42 @@ enum { MODE_ENABLE_REG = 0, /* Bit(s) set -> interrupt enabled */ MODE_PCLR_REG, /* Above plus all bits set to disable interrupt */ }; -static void intc_mode_field(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) +static unsigned long intc_mode_field(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) { - fn(addr, handle, ((1 << _INTC_WIDTH(handle)) - 1)); + return fn(addr, handle, ((1 << _INTC_WIDTH(handle)) - 1)); } -static void intc_mode_zero(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) +static unsigned long intc_mode_zero(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) { - fn(addr, handle, 0); + return fn(addr, handle, 0); } -static void intc_mode_prio(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) +static unsigned long intc_mode_prio(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) { - fn(addr, handle, intc_prio_level[irq]); + return fn(addr, handle, intc_prio_level[irq]); } -static void (*intc_enable_fns[])(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) = { +static unsigned long (*intc_enable_fns[])(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) = { [MODE_ENABLE_REG] = intc_mode_field, [MODE_MASK_REG] = intc_mode_zero, [MODE_DUAL_REG] = intc_mode_field, @@ -285,9 +369,9 @@ static void (*intc_enable_fns[])(unsigned long addr, [MODE_PCLR_REG] = intc_mode_prio, }; -static void (*intc_disable_fns[])(unsigned long addr, +static unsigned long (*intc_disable_fns[])(unsigned long addr, unsigned long handle, - void (*fn)(unsigned long, + unsigned long (*fn)(unsigned long, unsigned long, unsigned long), unsigned int irq) = { @@ -421,12 +505,13 @@ static void intc_disable(unsigned int irq) } } -static void (*intc_enable_noprio_fns[])(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) = { +static unsigned long +(*intc_enable_noprio_fns[])(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) = { [MODE_ENABLE_REG] = intc_mode_field, [MODE_MASK_REG] = intc_mode_zero, [MODE_DUAL_REG] = intc_mode_field, @@ -439,8 +524,9 @@ static void intc_enable_disable(struct intc_desc_int *d, { unsigned long addr; unsigned int cpu; - void (*fn)(unsigned long, unsigned long, - void (*)(unsigned long, unsigned long, unsigned long), + unsigned long (*fn)(unsigned long, unsigned long, + unsigned long (*)(unsigned long, unsigned long, + unsigned long), unsigned int); if (do_enable) { @@ -861,6 +947,186 @@ unsigned int intc_irq_lookup(const char *chipname, intc_enum enum_id) } EXPORT_SYMBOL_GPL(intc_irq_lookup); +static int add_virq_to_pirq(unsigned int irq, unsigned int virq) +{ + struct intc_virq_list **last, *entry; + struct irq_desc *desc = irq_to_desc(irq); + + /* scan for duplicates */ + last = (struct intc_virq_list **)&desc->handler_data; + for_each_virq(entry, desc->handler_data) { + if (entry->irq == virq) + return 0; + last = &entry->next; + } + + entry = kzalloc(sizeof(struct intc_virq_list), GFP_ATOMIC); + if (!entry) { + pr_err("can't allocate VIRQ mapping for %d\n", virq); + return -ENOMEM; + } + + entry->irq = virq; + + *last = entry; + + return 0; +} + +static void intc_virq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct intc_virq_list *entry, *vlist = get_irq_data(irq); + struct intc_desc_int *d = get_intc_desc(irq); + + desc->chip->mask_ack(irq); + + for_each_virq(entry, vlist) { + unsigned long addr, handle; + + handle = (unsigned long)get_irq_data(entry->irq); + addr = INTC_REG(d, _INTC_ADDR_E(handle), 0); + + if (intc_reg_fns[_INTC_FN(handle)](addr, handle, 0)) + generic_handle_irq(entry->irq); + } + + desc->chip->unmask(irq); +} + +static unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup, + struct intc_desc_int *d, + unsigned int index) +{ + unsigned int fn = REG_FN_TEST_BASE + (subgroup->reg_width >> 3) - 1; + + return _INTC_MK(fn, MODE_ENABLE_REG, intc_get_reg(d, subgroup->reg), + 0, 1, (subgroup->reg_width - 1) - index); +} + +#define INTC_TAG_VIRQ_NEEDS_ALLOC 0 + +static void __init intc_subgroup_init_one(struct intc_desc *desc, + struct intc_desc_int *d, + struct intc_subgroup *subgroup) +{ + struct intc_map_entry *mapped; + unsigned int pirq; + unsigned long flags; + int i; + + mapped = radix_tree_lookup(&d->tree, subgroup->parent_id); + if (!mapped) { + WARN_ON(1); + return; + } + + pirq = mapped - intc_irq_xlate; + + spin_lock_irqsave(&d->lock, flags); + + for (i = 0; i < ARRAY_SIZE(subgroup->enum_ids); i++) { + struct intc_subgroup_entry *entry; + int err; + + if (!subgroup->enum_ids[i]) + continue; + + entry = kmalloc(sizeof(*entry), GFP_NOWAIT); + if (!entry) + break; + + entry->pirq = pirq; + entry->enum_id = subgroup->enum_ids[i]; + entry->handle = intc_subgroup_data(subgroup, d, i); + + err = radix_tree_insert(&d->tree, entry->enum_id, entry); + if (unlikely(err < 0)) + break; + + radix_tree_tag_set(&d->tree, entry->enum_id, + INTC_TAG_VIRQ_NEEDS_ALLOC); + } + + spin_unlock_irqrestore(&d->lock, flags); +} + +static void __init intc_subgroup_init(struct intc_desc *desc, + struct intc_desc_int *d) +{ + int i; + + if (!desc->hw.subgroups) + return; + + for (i = 0; i < desc->hw.nr_subgroups; i++) + intc_subgroup_init_one(desc, d, desc->hw.subgroups + i); +} + +static void __init intc_subgroup_map(struct intc_desc_int *d) +{ + struct intc_subgroup_entry *entries[32]; + unsigned long flags; + unsigned int nr_found; + int i; + + spin_lock_irqsave(&d->lock, flags); + +restart: + nr_found = radix_tree_gang_lookup_tag_slot(&d->tree, + (void ***)entries, 0, ARRAY_SIZE(entries), + INTC_TAG_VIRQ_NEEDS_ALLOC); + + for (i = 0; i < nr_found; i++) { + struct intc_subgroup_entry *entry; + int irq; + + entry = radix_tree_deref_slot((void **)entries[i]); + if (unlikely(!entry)) + continue; + if (unlikely(entry == RADIX_TREE_RETRY)) + goto restart; + + irq = create_irq(); + if (unlikely(irq < 0)) { + pr_err("no more free IRQs, bailing..\n"); + break; + } + + pr_info("Setting up a chained VIRQ from %d -> %d\n", + irq, entry->pirq); + + spin_lock(&xlate_lock); + intc_irq_xlate[irq].desc = d; + intc_irq_xlate[irq].enum_id = entry->enum_id; + spin_unlock(&xlate_lock); + + set_irq_chip_and_handler_name(irq, get_irq_chip(entry->pirq), + handle_simple_irq, "virq"); + set_irq_chip_data(irq, get_irq_chip_data(entry->pirq)); + + set_irq_data(irq, (void *)entry->handle); + + set_irq_chained_handler(entry->pirq, intc_virq_handler); + add_virq_to_pirq(entry->pirq, irq); + + radix_tree_tag_clear(&d->tree, entry->enum_id, + INTC_TAG_VIRQ_NEEDS_ALLOC); + radix_tree_replace_slot((void **)entries[i], + &intc_irq_xlate[irq]); + } + + spin_unlock_irqrestore(&d->lock, flags); +} + +void __init intc_finalize(void) +{ + struct intc_desc_int *d; + + list_for_each_entry(d, &intc_list, list) + if (radix_tree_tagged(&d->tree, INTC_TAG_VIRQ_NEEDS_ALLOC)) + intc_subgroup_map(d); +} + static void __init intc_register_irq(struct intc_desc *desc, struct intc_desc_int *d, intc_enum enum_id, @@ -868,6 +1134,7 @@ static void __init intc_register_irq(struct intc_desc *desc, { struct intc_handle_int *hp; unsigned int data[2], primary; + unsigned long flags; /* * Register the IRQ position with the global IRQ map, then insert @@ -875,9 +1142,9 @@ static void __init intc_register_irq(struct intc_desc *desc, */ set_bit(irq, intc_irq_map); - mutex_lock(&irq_xlate_mutex); + spin_lock_irqsave(&xlate_lock, flags); radix_tree_insert(&d->tree, enum_id, &intc_irq_xlate[irq]); - mutex_unlock(&irq_xlate_mutex); + spin_unlock_irqrestore(&xlate_lock, flags); /* * Prefer single interrupt source bitmap over other combinations: @@ -957,9 +1224,7 @@ static void __init intc_register_irq(struct intc_desc *desc, dist_handle[irq] = intc_dist_data(desc, d, enum_id); #endif -#ifdef CONFIG_ARM - set_irq_flags(irq, IRQF_VALID); /* Enable IRQ on ARM systems */ -#endif + activate_irq(irq); } static unsigned int __init save_reg(struct intc_desc_int *d, @@ -980,11 +1245,6 @@ static unsigned int __init save_reg(struct intc_desc_int *d, return 0; } -static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc) -{ - generic_handle_irq((unsigned int)get_irq_data(irq)); -} - int __init register_intc_controller(struct intc_desc *desc) { unsigned int i, k, smp; @@ -1000,7 +1260,11 @@ int __init register_intc_controller(struct intc_desc *desc) goto err0; INIT_LIST_HEAD(&d->list); - list_add(&d->list, &intc_list); + list_add_tail(&d->list, &intc_list); + + spin_lock_init(&d->lock); + + d->index = nr_intc_controllers; if (desc->num_resources) { d->nr_windows = desc->num_resources; @@ -1029,6 +1293,7 @@ int __init register_intc_controller(struct intc_desc *desc) d->nr_reg += hw->prio_regs ? hw->nr_prio_regs * 2 : 0; d->nr_reg += hw->sense_regs ? hw->nr_sense_regs : 0; d->nr_reg += hw->ack_regs ? hw->nr_ack_regs : 0; + d->nr_reg += hw->subgroups ? hw->nr_subgroups : 0; d->reg = kzalloc(d->nr_reg * sizeof(*d->reg), GFP_NOWAIT); if (!d->reg) @@ -1075,6 +1340,11 @@ int __init register_intc_controller(struct intc_desc *desc) k += save_reg(d, k, hw->sense_regs[i].reg, 0); } + if (hw->subgroups) + for (i = 0; i < hw->nr_subgroups; i++) + if (hw->subgroups[i].reg) + k+= save_reg(d, k, hw->subgroups[i].reg, 0); + d->chip.name = desc->name; d->chip.mask = intc_disable; d->chip.unmask = intc_enable; @@ -1109,6 +1379,7 @@ int __init register_intc_controller(struct intc_desc *desc) for (i = 0; i < hw->nr_vectors; i++) { struct intc_vect *vect = hw->vectors + i; unsigned int irq = evt2irq(vect->vect); + unsigned long flags; struct irq_desc *irq_desc; if (!vect->enum_id) @@ -1120,8 +1391,10 @@ int __init register_intc_controller(struct intc_desc *desc) continue; } + spin_lock_irqsave(&xlate_lock, flags); intc_irq_xlate[irq].enum_id = vect->enum_id; intc_irq_xlate[irq].desc = d; + spin_unlock_irqrestore(&xlate_lock, flags); intc_register_irq(desc, d, vect->enum_id, irq); @@ -1152,10 +1425,14 @@ int __init register_intc_controller(struct intc_desc *desc) } } + intc_subgroup_init(desc, d); + /* enable bits matching force_enable after registering irqs */ if (desc->force_enable) intc_enable_disable_enum(desc, d, desc->force_enable, 1); + nr_intc_controllers++; + return 0; err5: kfree(d->prio); @@ -1353,7 +1630,6 @@ static int __init register_intc_sysdevs(void) { struct intc_desc_int *d; int error; - int id = 0; error = sysdev_class_register(&intc_sysdev_class); #ifdef CONFIG_INTC_USERIMASK @@ -1363,7 +1639,7 @@ static int __init register_intc_sysdevs(void) #endif if (!error) { list_for_each_entry(d, &intc_list, list) { - d->sysdev.id = id; + d->sysdev.id = d->index; d->sysdev.cls = &intc_sysdev_class; error = sysdev_register(&d->sysdev); if (error == 0) @@ -1371,8 +1647,6 @@ static int __init register_intc_sysdevs(void) &attr_name); if (error) break; - - id++; } } @@ -1422,9 +1696,7 @@ out_unlock: if (irq > 0) { dynamic_irq_init(irq); -#ifdef CONFIG_ARM - set_irq_flags(irq, IRQF_VALID); /* Enable IRQ on ARM systems */ -#endif + activate_irq(irq); } return irq; |