Merge branch 'next/cleanup-samsung' into next/cleanup-samsung-2
[cascardo/linux.git] / kernel / irq / irqdomain.c
index 38c5eb8..4e69e24 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/mutex.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
+#include <linux/topology.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 #include <linux/smp.h>
@@ -45,7 +46,8 @@ static struct irq_domain *irq_domain_alloc(struct device_node *of_node,
 {
        struct irq_domain *domain;
 
-       domain = kzalloc(sizeof(*domain), GFP_KERNEL);
+       domain = kzalloc_node(sizeof(*domain), GFP_KERNEL,
+                             of_node_to_nid(of_node));
        if (WARN_ON(!domain))
                return NULL;
 
@@ -137,6 +139,59 @@ static unsigned int irq_domain_legacy_revmap(struct irq_domain *domain,
        return hwirq - first_hwirq + domain->revmap_data.legacy.first_irq;
 }
 
+/**
+ * irq_domain_add_simple() - Allocate and register a simple irq_domain.
+ * @of_node: pointer to interrupt controller's device tree node.
+ * @size: total number of irqs in mapping
+ * @first_irq: first number of irq block assigned to the domain
+ * @ops: map/unmap domain callbacks
+ * @host_data: Controller private data pointer
+ *
+ * Allocates a legacy irq_domain if irq_base is positive or a linear
+ * domain otherwise. For the legacy domain, IRQ descriptors will also
+ * be allocated.
+ *
+ * This is intended to implement the expected behaviour for most
+ * interrupt controllers which is that a linear mapping should
+ * normally be used unless the system requires a legacy mapping in
+ * order to support supplying interrupt numbers during non-DT
+ * registration of devices.
+ */
+struct irq_domain *irq_domain_add_simple(struct device_node *of_node,
+                                        unsigned int size,
+                                        unsigned int first_irq,
+                                        const struct irq_domain_ops *ops,
+                                        void *host_data)
+{
+       if (first_irq > 0) {
+               int irq_base;
+
+               if (IS_ENABLED(CONFIG_SPARSE_IRQ)) {
+                       /*
+                        * Set the descriptor allocator to search for a
+                        * 1-to-1 mapping, such as irq_alloc_desc_at().
+                        * Use of_node_to_nid() which is defined to
+                        * numa_node_id() on platforms that have no custom
+                        * implementation.
+                        */
+                       irq_base = irq_alloc_descs(first_irq, first_irq, size,
+                                                  of_node_to_nid(of_node));
+                       if (irq_base < 0) {
+                               WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
+                                    first_irq);
+                               irq_base = first_irq;
+                       }
+               } else
+                       irq_base = first_irq;
+
+               return irq_domain_add_legacy(of_node, size, irq_base, 0,
+                                            ops, host_data);
+       }
+
+       /* A linear domain is the default */
+       return irq_domain_add_linear(of_node, size, ops, host_data);
+}
+
 /**
  * irq_domain_add_legacy() - Allocate and register a legacy revmap irq_domain.
  * @of_node: pointer to interrupt controller's device tree node.
@@ -203,7 +258,8 @@ struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
                 * one can then use irq_create_mapping() to
                 * explicitly change them
                 */
-               ops->map(domain, irq, hwirq);
+               if (ops->map)
+                       ops->map(domain, irq, hwirq);
 
                /* Clear norequest flags */
                irq_clear_status_flags(irq, IRQ_NOREQUEST);
@@ -215,7 +271,7 @@ struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
 EXPORT_SYMBOL_GPL(irq_domain_add_legacy);
 
 /**
- * irq_domain_add_linear() - Allocate and register a legacy revmap irq_domain.
+ * irq_domain_add_linear() - Allocate and register a linear revmap irq_domain.
  * @of_node: pointer to interrupt controller's device tree node.
  * @size: Number of interrupts in the domain.
  * @ops: map/unmap domain callbacks
@@ -229,7 +285,8 @@ struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
        struct irq_domain *domain;
        unsigned int *revmap;
 
-       revmap = kzalloc(sizeof(*revmap) * size, GFP_KERNEL);
+       revmap = kzalloc_node(sizeof(*revmap) * size, GFP_KERNEL,
+                             of_node_to_nid(of_node));
        if (WARN_ON(!revmap))
                return NULL;
 
@@ -330,24 +387,112 @@ void irq_set_default_host(struct irq_domain *domain)
 }
 EXPORT_SYMBOL_GPL(irq_set_default_host);
 
-static int irq_setup_virq(struct irq_domain *domain, unsigned int virq,
-                           irq_hw_number_t hwirq)
+static void irq_domain_disassociate_many(struct irq_domain *domain,
+                                        unsigned int irq_base, int count)
 {
-       struct irq_data *irq_data = irq_get_irq_data(virq);
+       /*
+        * disassociate in reverse order;
+        * not strictly necessary, but nice for unwinding
+        */
+       while (count--) {
+               int irq = irq_base + count;
+               struct irq_data *irq_data = irq_get_irq_data(irq);
+               irq_hw_number_t hwirq = irq_data->hwirq;
+
+               if (WARN_ON(!irq_data || irq_data->domain != domain))
+                       continue;
+
+               irq_set_status_flags(irq, IRQ_NOREQUEST);
+
+               /* remove chip and handler */
+               irq_set_chip_and_handler(irq, NULL, NULL);
+
+               /* Make sure it's completed */
+               synchronize_irq(irq);
+
+               /* Tell the PIC about it */
+               if (domain->ops->unmap)
+                       domain->ops->unmap(domain, irq);
+               smp_mb();
 
-       irq_data->hwirq = hwirq;
-       irq_data->domain = domain;
-       if (domain->ops->map(domain, virq, hwirq)) {
-               pr_debug("irq-%i==>hwirq-0x%lx mapping failed\n", virq, hwirq);
                irq_data->domain = NULL;
                irq_data->hwirq = 0;
-               return -1;
+
+               /* Clear reverse map */
+               switch(domain->revmap_type) {
+               case IRQ_DOMAIN_MAP_LINEAR:
+                       if (hwirq < domain->revmap_data.linear.size)
+                               domain->revmap_data.linear.revmap[hwirq] = 0;
+                       break;
+               case IRQ_DOMAIN_MAP_TREE:
+                       mutex_lock(&revmap_trees_mutex);
+                       radix_tree_delete(&domain->revmap_data.tree, hwirq);
+                       mutex_unlock(&revmap_trees_mutex);
+                       break;
+               }
        }
+}
 
-       irq_clear_status_flags(virq, IRQ_NOREQUEST);
+int irq_domain_associate_many(struct irq_domain *domain, unsigned int irq_base,
+                             irq_hw_number_t hwirq_base, int count)
+{
+       unsigned int virq = irq_base;
+       irq_hw_number_t hwirq = hwirq_base;
+       int i, ret;
+
+       pr_debug("%s(%s, irqbase=%i, hwbase=%i, count=%i)\n", __func__,
+               of_node_full_name(domain->of_node), irq_base, (int)hwirq_base, count);
+
+       for (i = 0; i < count; i++) {
+               struct irq_data *irq_data = irq_get_irq_data(virq + i);
+
+               if (WARN(!irq_data, "error: irq_desc not allocated; "
+                        "irq=%i hwirq=0x%x\n", virq + i, (int)hwirq + i))
+                       return -EINVAL;
+               if (WARN(irq_data->domain, "error: irq_desc already associated; "
+                        "irq=%i hwirq=0x%x\n", virq + i, (int)hwirq + i))
+                       return -EINVAL;
+       };
+
+       for (i = 0; i < count; i++, virq++, hwirq++) {
+               struct irq_data *irq_data = irq_get_irq_data(virq);
+
+               irq_data->hwirq = hwirq;
+               irq_data->domain = domain;
+               if (domain->ops->map) {
+                       ret = domain->ops->map(domain, virq, hwirq);
+                       if (ret != 0) {
+                               pr_err("irq-%i==>hwirq-0x%lx mapping failed: %d\n",
+                                      virq, hwirq, ret);
+                               WARN_ON(1);
+                               irq_data->domain = NULL;
+                               irq_data->hwirq = 0;
+                               goto err_unmap;
+                       }
+               }
+
+               switch (domain->revmap_type) {
+               case IRQ_DOMAIN_MAP_LINEAR:
+                       if (hwirq < domain->revmap_data.linear.size)
+                               domain->revmap_data.linear.revmap[hwirq] = virq;
+                       break;
+               case IRQ_DOMAIN_MAP_TREE:
+                       mutex_lock(&revmap_trees_mutex);
+                       radix_tree_insert(&domain->revmap_data.tree, hwirq, irq_data);
+                       mutex_unlock(&revmap_trees_mutex);
+                       break;
+               }
+
+               irq_clear_status_flags(virq, IRQ_NOREQUEST);
+       }
 
        return 0;
+
+ err_unmap:
+       irq_domain_disassociate_many(domain, irq_base, i);
+       return -EINVAL;
 }
+EXPORT_SYMBOL_GPL(irq_domain_associate_many);
 
 /**
  * irq_create_direct_mapping() - Allocate an irq for direct mapping
@@ -364,10 +509,10 @@ unsigned int irq_create_direct_mapping(struct irq_domain *domain)
        if (domain == NULL)
                domain = irq_default_domain;
 
-       BUG_ON(domain == NULL);
-       WARN_ON(domain->revmap_type != IRQ_DOMAIN_MAP_NOMAP);
+       if (WARN_ON(!domain || domain->revmap_type != IRQ_DOMAIN_MAP_NOMAP))
+               return 0;
 
-       virq = irq_alloc_desc_from(1, 0);
+       virq = irq_alloc_desc_from(1, of_node_to_nid(domain->of_node));
        if (!virq) {
                pr_debug("create_direct virq allocation failed\n");
                return 0;
@@ -380,7 +525,7 @@ unsigned int irq_create_direct_mapping(struct irq_domain *domain)
        }
        pr_debug("create_direct obtained virq %d\n", virq);
 
-       if (irq_setup_virq(domain, virq, virq)) {
+       if (irq_domain_associate(domain, virq, virq)) {
                irq_free_desc(virq);
                return 0;
        }
@@ -433,17 +578,16 @@ unsigned int irq_create_mapping(struct irq_domain *domain,
        hint = hwirq % nr_irqs;
        if (hint == 0)
                hint++;
-       virq = irq_alloc_desc_from(hint, 0);
+       virq = irq_alloc_desc_from(hint, of_node_to_nid(domain->of_node));
        if (virq <= 0)
-               virq = irq_alloc_desc_from(1, 0);
+               virq = irq_alloc_desc_from(1, of_node_to_nid(domain->of_node));
        if (virq <= 0) {
                pr_debug("-> virq allocation failed\n");
                return 0;
        }
 
-       if (irq_setup_virq(domain, virq, hwirq)) {
-               if (domain->revmap_type != IRQ_DOMAIN_MAP_LEGACY)
-                       irq_free_desc(virq);
+       if (irq_domain_associate(domain, virq, hwirq)) {
+               irq_free_desc(virq);
                return 0;
        }
 
@@ -454,6 +598,44 @@ unsigned int irq_create_mapping(struct irq_domain *domain,
 }
 EXPORT_SYMBOL_GPL(irq_create_mapping);
 
+/**
+ * irq_create_strict_mappings() - Map a range of hw irqs to fixed linux irqs
+ * @domain: domain owning the interrupt range
+ * @irq_base: beginning of linux IRQ range
+ * @hwirq_base: beginning of hardware IRQ range
+ * @count: Number of interrupts to map
+ *
+ * This routine is used for allocating and mapping a range of hardware
+ * irqs to linux irqs where the linux irq numbers are at pre-defined
+ * locations. For use by controllers that already have static mappings
+ * to insert in to the domain.
+ *
+ * Non-linear users can use irq_create_identity_mapping() for IRQ-at-a-time
+ * domain insertion.
+ *
+ * 0 is returned upon success, while any failure to establish a static
+ * mapping is treated as an error.
+ */
+int irq_create_strict_mappings(struct irq_domain *domain, unsigned int irq_base,
+                              irq_hw_number_t hwirq_base, int count)
+{
+       int ret;
+
+       ret = irq_alloc_descs(irq_base, irq_base, count,
+                             of_node_to_nid(domain->of_node));
+       if (unlikely(ret < 0))
+               return ret;
+
+       ret = irq_domain_associate_many(domain, irq_base, hwirq_base, count);
+       if (unlikely(ret < 0)) {
+               irq_free_descs(irq_base, count);
+               return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(irq_create_strict_mappings);
+
 unsigned int irq_create_of_mapping(struct device_node *controller,
                                   const u32 *intspec, unsigned int intsize)
 {
@@ -511,7 +693,6 @@ void irq_dispose_mapping(unsigned int virq)
 {
        struct irq_data *irq_data = irq_get_irq_data(virq);
        struct irq_domain *domain;
-       irq_hw_number_t hwirq;
 
        if (!virq || !irq_data)
                return;
@@ -524,33 +705,7 @@ void irq_dispose_mapping(unsigned int virq)
        if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY)
                return;
 
-       irq_set_status_flags(virq, IRQ_NOREQUEST);
-
-       /* remove chip and handler */
-       irq_set_chip_and_handler(virq, NULL, NULL);
-
-       /* Make sure it's completed */
-       synchronize_irq(virq);
-
-       /* Tell the PIC about it */
-       if (domain->ops->unmap)
-               domain->ops->unmap(domain, virq);
-       smp_mb();
-
-       /* Clear reverse map */
-       hwirq = irq_data->hwirq;
-       switch(domain->revmap_type) {
-       case IRQ_DOMAIN_MAP_LINEAR:
-               if (hwirq < domain->revmap_data.linear.size)
-                       domain->revmap_data.linear.revmap[hwirq] = 0;
-               break;
-       case IRQ_DOMAIN_MAP_TREE:
-               mutex_lock(&revmap_trees_mutex);
-               radix_tree_delete(&domain->revmap_data.tree, hwirq);
-               mutex_unlock(&revmap_trees_mutex);
-               break;
-       }
-
+       irq_domain_disassociate_many(domain, virq, 1);
        irq_free_desc(virq);
 }
 EXPORT_SYMBOL_GPL(irq_dispose_mapping);
@@ -559,16 +714,11 @@ EXPORT_SYMBOL_GPL(irq_dispose_mapping);
  * irq_find_mapping() - Find a linux irq from an hw irq number.
  * @domain: domain owning this hardware interrupt
  * @hwirq: hardware irq number in that domain space
- *
- * This is a slow path, for use by generic code. It's expected that an
- * irq controller implementation directly calls the appropriate low level
- * mapping function.
  */
 unsigned int irq_find_mapping(struct irq_domain *domain,
                              irq_hw_number_t hwirq)
 {
-       unsigned int i;
-       unsigned int hint = hwirq % nr_irqs;
+       struct irq_data *data;
 
        /* Look for default domain if nececssary */
        if (domain == NULL)
@@ -576,115 +726,47 @@ unsigned int irq_find_mapping(struct irq_domain *domain,
        if (domain == NULL)
                return 0;
 
-       /* legacy -> bail early */
-       if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY)
+       switch (domain->revmap_type) {
+       case IRQ_DOMAIN_MAP_LEGACY:
                return irq_domain_legacy_revmap(domain, hwirq);
-
-       /* Slow path does a linear search of the map */
-       if (hint == 0)
-               hint = 1;
-       i = hint;
-       do {
-               struct irq_data *data = irq_get_irq_data(i);
+       case IRQ_DOMAIN_MAP_LINEAR:
+               return irq_linear_revmap(domain, hwirq);
+       case IRQ_DOMAIN_MAP_TREE:
+               rcu_read_lock();
+               data = radix_tree_lookup(&domain->revmap_data.tree, hwirq);
+               rcu_read_unlock();
+               if (data)
+                       return data->irq;
+               break;
+       case IRQ_DOMAIN_MAP_NOMAP:
+               data = irq_get_irq_data(hwirq);
                if (data && (data->domain == domain) && (data->hwirq == hwirq))
-                       return i;
-               i++;
-               if (i >= nr_irqs)
-                       i = 1;
-       } while(i != hint);
+                       return hwirq;
+               break;
+       }
+
        return 0;
 }
 EXPORT_SYMBOL_GPL(irq_find_mapping);
 
-/**
- * irq_radix_revmap_lookup() - Find a linux irq from a hw irq number.
- * @domain: domain owning this hardware interrupt
- * @hwirq: hardware irq number in that domain space
- *
- * This is a fast path, for use by irq controller code that uses radix tree
- * revmaps
- */
-unsigned int irq_radix_revmap_lookup(struct irq_domain *domain,
-                                    irq_hw_number_t hwirq)
-{
-       struct irq_data *irq_data;
-
-       if (WARN_ON_ONCE(domain->revmap_type != IRQ_DOMAIN_MAP_TREE))
-               return irq_find_mapping(domain, hwirq);
-
-       /*
-        * Freeing an irq can delete nodes along the path to
-        * do the lookup via call_rcu.
-        */
-       rcu_read_lock();
-       irq_data = radix_tree_lookup(&domain->revmap_data.tree, hwirq);
-       rcu_read_unlock();
-
-       /*
-        * If found in radix tree, then fine.
-        * Else fallback to linear lookup - this should not happen in practice
-        * as it means that we failed to insert the node in the radix tree.
-        */
-       return irq_data ? irq_data->irq : irq_find_mapping(domain, hwirq);
-}
-EXPORT_SYMBOL_GPL(irq_radix_revmap_lookup);
-
-/**
- * irq_radix_revmap_insert() - Insert a hw irq to linux irq number mapping.
- * @domain: domain owning this hardware interrupt
- * @virq: linux irq number
- * @hwirq: hardware irq number in that domain space
- *
- * This is for use by irq controllers that use a radix tree reverse
- * mapping for fast lookup.
- */
-void irq_radix_revmap_insert(struct irq_domain *domain, unsigned int virq,
-                            irq_hw_number_t hwirq)
-{
-       struct irq_data *irq_data = irq_get_irq_data(virq);
-
-       if (WARN_ON(domain->revmap_type != IRQ_DOMAIN_MAP_TREE))
-               return;
-
-       if (virq) {
-               mutex_lock(&revmap_trees_mutex);
-               radix_tree_insert(&domain->revmap_data.tree, hwirq, irq_data);
-               mutex_unlock(&revmap_trees_mutex);
-       }
-}
-EXPORT_SYMBOL_GPL(irq_radix_revmap_insert);
-
 /**
  * irq_linear_revmap() - Find a linux irq from a hw irq number.
  * @domain: domain owning this hardware interrupt
  * @hwirq: hardware irq number in that domain space
  *
- * This is a fast path, for use by irq controller code that uses linear
- * revmaps. It does fallback to the slow path if the revmap doesn't exist
- * yet and will create the revmap entry with appropriate locking
+ * This is a fast path that can be called directly by irq controller code to
+ * save a handful of instructions.
  */
 unsigned int irq_linear_revmap(struct irq_domain *domain,
                               irq_hw_number_t hwirq)
 {
-       unsigned int *revmap;
-
-       if (WARN_ON_ONCE(domain->revmap_type != IRQ_DOMAIN_MAP_LINEAR))
-               return irq_find_mapping(domain, hwirq);
+       BUG_ON(domain->revmap_type != IRQ_DOMAIN_MAP_LINEAR);
 
-       /* Check revmap bounds */
-       if (unlikely(hwirq >= domain->revmap_data.linear.size))
-               return irq_find_mapping(domain, hwirq);
-
-       /* Check if revmap was allocated */
-       revmap = domain->revmap_data.linear.revmap;
-       if (unlikely(revmap == NULL))
-               return irq_find_mapping(domain, hwirq);
-
-       /* Fill up revmap with slow path if no mapping found */
-       if (unlikely(!revmap[hwirq]))
-               revmap[hwirq] = irq_find_mapping(domain, hwirq);
+       /* Check revmap bounds; complain if exceeded */
+       if (WARN_ON(hwirq >= domain->revmap_data.linear.size))
+               return 0;
 
-       return revmap[hwirq];
+       return domain->revmap_data.linear.revmap[hwirq];
 }
 EXPORT_SYMBOL_GPL(irq_linear_revmap);
 
@@ -761,12 +843,6 @@ static int __init irq_debugfs_init(void)
 __initcall(irq_debugfs_init);
 #endif /* CONFIG_IRQ_DOMAIN_DEBUG */
 
-static int irq_domain_simple_map(struct irq_domain *d, unsigned int irq,
-                                irq_hw_number_t hwirq)
-{
-       return 0;
-}
-
 /**
  * irq_domain_xlate_onecell() - Generic xlate for direct one cell bindings
  *
@@ -829,7 +905,6 @@ int irq_domain_xlate_onetwocell(struct irq_domain *d,
 EXPORT_SYMBOL_GPL(irq_domain_xlate_onetwocell);
 
 const struct irq_domain_ops irq_domain_simple_ops = {
-       .map = irq_domain_simple_map,
        .xlate = irq_domain_xlate_onetwocell,
 };
 EXPORT_SYMBOL_GPL(irq_domain_simple_ops);