Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * Support for virtual IRQ subgroups.
0003  *
0004  * Copyright (C) 2010  Paul Mundt
0005  *
0006  * This file is subject to the terms and conditions of the GNU General Public
0007  * License.  See the file "COPYING" in the main directory of this archive
0008  * for more details.
0009  */
0010 #define pr_fmt(fmt) "intc: " fmt
0011 
0012 #include <linux/slab.h>
0013 #include <linux/irq.h>
0014 #include <linux/list.h>
0015 #include <linux/radix-tree.h>
0016 #include <linux/spinlock.h>
0017 #include <linux/export.h>
0018 #include "internals.h"
0019 
0020 static struct intc_map_entry intc_irq_xlate[INTC_NR_IRQS];
0021 
0022 struct intc_virq_list {
0023     unsigned int irq;
0024     struct intc_virq_list *next;
0025 };
0026 
0027 #define for_each_virq(entry, head) \
0028     for (entry = head; entry; entry = entry->next)
0029 
0030 /*
0031  * Tags for the radix tree
0032  */
0033 #define INTC_TAG_VIRQ_NEEDS_ALLOC   0
0034 
0035 void intc_irq_xlate_set(unsigned int irq, intc_enum id, struct intc_desc_int *d)
0036 {
0037     unsigned long flags;
0038 
0039     raw_spin_lock_irqsave(&intc_big_lock, flags);
0040     intc_irq_xlate[irq].enum_id = id;
0041     intc_irq_xlate[irq].desc = d;
0042     raw_spin_unlock_irqrestore(&intc_big_lock, flags);
0043 }
0044 
0045 struct intc_map_entry *intc_irq_xlate_get(unsigned int irq)
0046 {
0047     return intc_irq_xlate + irq;
0048 }
0049 
0050 int intc_irq_lookup(const char *chipname, intc_enum enum_id)
0051 {
0052     struct intc_map_entry *ptr;
0053     struct intc_desc_int *d;
0054     int irq = -1;
0055 
0056     list_for_each_entry(d, &intc_list, list) {
0057         int tagged;
0058 
0059         if (strcmp(d->chip.name, chipname) != 0)
0060             continue;
0061 
0062         /*
0063          * Catch early lookups for subgroup VIRQs that have not
0064          * yet been allocated an IRQ. This already includes a
0065          * fast-path out if the tree is untagged, so there is no
0066          * need to explicitly test the root tree.
0067          */
0068         tagged = radix_tree_tag_get(&d->tree, enum_id,
0069                         INTC_TAG_VIRQ_NEEDS_ALLOC);
0070         if (unlikely(tagged))
0071             break;
0072 
0073         ptr = radix_tree_lookup(&d->tree, enum_id);
0074         if (ptr) {
0075             irq = ptr - intc_irq_xlate;
0076             break;
0077         }
0078     }
0079 
0080     return irq;
0081 }
0082 EXPORT_SYMBOL_GPL(intc_irq_lookup);
0083 
0084 static int add_virq_to_pirq(unsigned int irq, unsigned int virq)
0085 {
0086     struct intc_virq_list *entry;
0087     struct intc_virq_list **last = NULL;
0088 
0089     /* scan for duplicates */
0090     for_each_virq(entry, irq_get_handler_data(irq)) {
0091         if (entry->irq == virq)
0092             return 0;
0093         last = &entry->next;
0094     }
0095 
0096     entry = kzalloc(sizeof(struct intc_virq_list), GFP_ATOMIC);
0097     if (!entry)
0098         return -ENOMEM;
0099 
0100     entry->irq = virq;
0101 
0102     if (last)
0103         *last = entry;
0104     else
0105         irq_set_handler_data(irq, entry);
0106 
0107     return 0;
0108 }
0109 
0110 static void intc_virq_handler(struct irq_desc *desc)
0111 {
0112     unsigned int irq = irq_desc_get_irq(desc);
0113     struct irq_data *data = irq_desc_get_irq_data(desc);
0114     struct irq_chip *chip = irq_data_get_irq_chip(data);
0115     struct intc_virq_list *entry, *vlist = irq_data_get_irq_handler_data(data);
0116     struct intc_desc_int *d = get_intc_desc(irq);
0117 
0118     chip->irq_mask_ack(data);
0119 
0120     for_each_virq(entry, vlist) {
0121         unsigned long addr, handle;
0122         struct irq_desc *vdesc = irq_to_desc(entry->irq);
0123 
0124         if (vdesc) {
0125             handle = (unsigned long)irq_desc_get_handler_data(vdesc);
0126             addr = INTC_REG(d, _INTC_ADDR_E(handle), 0);
0127             if (intc_reg_fns[_INTC_FN(handle)](addr, handle, 0))
0128                 generic_handle_irq_desc(vdesc);
0129         }
0130     }
0131 
0132     chip->irq_unmask(data);
0133 }
0134 
0135 static unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup,
0136                            struct intc_desc_int *d,
0137                            unsigned int index)
0138 {
0139     unsigned int fn = REG_FN_TEST_BASE + (subgroup->reg_width >> 3) - 1;
0140 
0141     return _INTC_MK(fn, MODE_ENABLE_REG, intc_get_reg(d, subgroup->reg),
0142             0, 1, (subgroup->reg_width - 1) - index);
0143 }
0144 
0145 static void __init intc_subgroup_init_one(struct intc_desc *desc,
0146                       struct intc_desc_int *d,
0147                       struct intc_subgroup *subgroup)
0148 {
0149     struct intc_map_entry *mapped;
0150     unsigned int pirq;
0151     unsigned long flags;
0152     int i;
0153 
0154     mapped = radix_tree_lookup(&d->tree, subgroup->parent_id);
0155     if (!mapped) {
0156         WARN_ON(1);
0157         return;
0158     }
0159 
0160     pirq = mapped - intc_irq_xlate;
0161 
0162     raw_spin_lock_irqsave(&d->lock, flags);
0163 
0164     for (i = 0; i < ARRAY_SIZE(subgroup->enum_ids); i++) {
0165         struct intc_subgroup_entry *entry;
0166         int err;
0167 
0168         if (!subgroup->enum_ids[i])
0169             continue;
0170 
0171         entry = kmalloc(sizeof(*entry), GFP_NOWAIT);
0172         if (!entry)
0173             break;
0174 
0175         entry->pirq = pirq;
0176         entry->enum_id = subgroup->enum_ids[i];
0177         entry->handle = intc_subgroup_data(subgroup, d, i);
0178 
0179         err = radix_tree_insert(&d->tree, entry->enum_id, entry);
0180         if (unlikely(err < 0))
0181             break;
0182 
0183         radix_tree_tag_set(&d->tree, entry->enum_id,
0184                    INTC_TAG_VIRQ_NEEDS_ALLOC);
0185     }
0186 
0187     raw_spin_unlock_irqrestore(&d->lock, flags);
0188 }
0189 
0190 void __init intc_subgroup_init(struct intc_desc *desc, struct intc_desc_int *d)
0191 {
0192     int i;
0193 
0194     if (!desc->hw.subgroups)
0195         return;
0196 
0197     for (i = 0; i < desc->hw.nr_subgroups; i++)
0198         intc_subgroup_init_one(desc, d, desc->hw.subgroups + i);
0199 }
0200 
0201 static void __init intc_subgroup_map(struct intc_desc_int *d)
0202 {
0203     struct intc_subgroup_entry *entries[32];
0204     unsigned long flags;
0205     unsigned int nr_found;
0206     int i;
0207 
0208     raw_spin_lock_irqsave(&d->lock, flags);
0209 
0210 restart:
0211     nr_found = radix_tree_gang_lookup_tag_slot(&d->tree,
0212             (void ***)entries, 0, ARRAY_SIZE(entries),
0213             INTC_TAG_VIRQ_NEEDS_ALLOC);
0214 
0215     for (i = 0; i < nr_found; i++) {
0216         struct intc_subgroup_entry *entry;
0217         int irq;
0218 
0219         entry = radix_tree_deref_slot((void **)entries[i]);
0220         if (unlikely(!entry))
0221             continue;
0222         if (radix_tree_deref_retry(entry))
0223             goto restart;
0224 
0225         irq = irq_alloc_desc(numa_node_id());
0226         if (unlikely(irq < 0)) {
0227             pr_err("no more free IRQs, bailing..\n");
0228             break;
0229         }
0230 
0231         activate_irq(irq);
0232 
0233         pr_info("Setting up a chained VIRQ from %d -> %d\n",
0234             irq, entry->pirq);
0235 
0236         intc_irq_xlate_set(irq, entry->enum_id, d);
0237 
0238         irq_set_chip_and_handler_name(irq, irq_get_chip(entry->pirq),
0239                           handle_simple_irq, "virq");
0240         irq_set_chip_data(irq, irq_get_chip_data(entry->pirq));
0241 
0242         irq_set_handler_data(irq, (void *)entry->handle);
0243 
0244         /*
0245          * Set the virtual IRQ as non-threadable.
0246          */
0247         irq_set_nothread(irq);
0248 
0249         /* Set handler data before installing the handler */
0250         add_virq_to_pirq(entry->pirq, irq);
0251         irq_set_chained_handler(entry->pirq, intc_virq_handler);
0252 
0253         radix_tree_tag_clear(&d->tree, entry->enum_id,
0254                      INTC_TAG_VIRQ_NEEDS_ALLOC);
0255         radix_tree_replace_slot(&d->tree, (void **)entries[i],
0256                     &intc_irq_xlate[irq]);
0257     }
0258 
0259     raw_spin_unlock_irqrestore(&d->lock, flags);
0260 }
0261 
0262 void __init intc_finalize(void)
0263 {
0264     struct intc_desc_int *d;
0265 
0266     list_for_each_entry(d, &intc_list, list)
0267         if (radix_tree_tagged(&d->tree, INTC_TAG_VIRQ_NEEDS_ALLOC))
0268             intc_subgroup_map(d);
0269 }