Commit ddaf144c authored by Grant Likely's avatar Grant Likely

irqdomain: Refactor irq_domain_associate_many()

Originally, irq_domain_associate_many() was designed to unwind the
mapped irqs on a failure of any individual association. However, that
proved to be a problem with certain IRQ controllers. Some of them only
support a subset of irqs, and will fail when attempting to map a
reserved IRQ. In those cases we want to map as many IRQs as possible, so
instead it is better for irq_domain_associate_many() to make a
best-effort attempt to map irqs, but not fail if any or all of them
don't succeed. If a caller really cares about how many irqs got
associated, then it should instead go back and check that all of the
irqs is cares about were mapped.

The original design open-coded the individual association code into the
body of irq_domain_associate_many(), but with no longer needing to
unwind associations, the code becomes simpler to split out
irq_domain_associate() to contain the bulk of the logic, and
irq_domain_associate_many() to be a simple loop wrapper.

This patch also adds a new error check to the associate path to make
sure it isn't called for an irq larger than the controller can handle,
and adds locking so that the irq_domain_mutex is held while setting up a
new association.

v3: Fixup missing change to irq_domain_add_tree()
v2: Fixup x86 warning. irq_domain_associate_many() no longer returns an
    error code, but reports errors to the printk log directly. In the
    majority of cases we don't actually want to fail if there is a
    problem, but rather log it and still try to boot the system.
Signed-off-by: default avatarGrant Likely <grant.likely@linaro.org>

irqdomain: Fix flubbed irq_domain_associate_many refactoring

commit d39046ec72, "irqdomain: Refactor irq_domain_associate_many()" was
missing the following hunk which causes a boot failure on anything using
irq_domain_add_tree() to allocate an irq domain.
Signed-off-by: default avatarGrant Likely <grant.likely@linaro.org>
Cc: Michael Neuling <mikey@neuling.org>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>,
Cc: Thomas Gleixner <tglx@linutronix.de>,
Cc: Stephen Rothwell <sfr@canb.auug.org.au>
parent 1400ea86
...@@ -364,9 +364,7 @@ static void dt_add_ioapic_domain(unsigned int ioapic_num, ...@@ -364,9 +364,7 @@ static void dt_add_ioapic_domain(unsigned int ioapic_num,
* and assigned so we can keep the 1:1 mapping which the ioapic * and assigned so we can keep the 1:1 mapping which the ioapic
* is having. * is having.
*/ */
ret = irq_domain_associate_many(id, 0, 0, NR_IRQS_LEGACY); irq_domain_associate_many(id, 0, 0, NR_IRQS_LEGACY);
if (ret)
pr_err("Error mapping legacy IRQs: %d\n", ret);
if (num > NR_IRQS_LEGACY) { if (num > NR_IRQS_LEGACY) {
ret = irq_create_strict_mappings(id, NR_IRQS_LEGACY, ret = irq_create_strict_mappings(id, NR_IRQS_LEGACY,
......
...@@ -103,6 +103,7 @@ struct irq_domain { ...@@ -103,6 +103,7 @@ struct irq_domain {
struct irq_domain_chip_generic *gc; struct irq_domain_chip_generic *gc;
/* reverse map data. The linear map gets appended to the irq_domain */ /* reverse map data. The linear map gets appended to the irq_domain */
irq_hw_number_t hwirq_max;
unsigned int revmap_direct_max_irq; unsigned int revmap_direct_max_irq;
unsigned int revmap_size; unsigned int revmap_size;
struct radix_tree_root revmap_tree; struct radix_tree_root revmap_tree;
...@@ -110,8 +111,8 @@ struct irq_domain { ...@@ -110,8 +111,8 @@ struct irq_domain {
}; };
#ifdef CONFIG_IRQ_DOMAIN #ifdef CONFIG_IRQ_DOMAIN
struct irq_domain *__irq_domain_add(struct device_node *of_node, struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,
int size, int direct_max, irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops, const struct irq_domain_ops *ops,
void *host_data); void *host_data);
struct irq_domain *irq_domain_add_simple(struct device_node *of_node, struct irq_domain *irq_domain_add_simple(struct device_node *of_node,
...@@ -140,14 +141,14 @@ static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_no ...@@ -140,14 +141,14 @@ static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_no
const struct irq_domain_ops *ops, const struct irq_domain_ops *ops,
void *host_data) void *host_data)
{ {
return __irq_domain_add(of_node, size, 0, ops, host_data); return __irq_domain_add(of_node, size, size, 0, ops, host_data);
} }
static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node, static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
unsigned int max_irq, unsigned int max_irq,
const struct irq_domain_ops *ops, const struct irq_domain_ops *ops,
void *host_data) void *host_data)
{ {
return __irq_domain_add(of_node, 0, max_irq, ops, host_data); return __irq_domain_add(of_node, 0, max_irq, max_irq, ops, host_data);
} }
static inline struct irq_domain *irq_domain_add_legacy_isa( static inline struct irq_domain *irq_domain_add_legacy_isa(
struct device_node *of_node, struct device_node *of_node,
...@@ -161,19 +162,16 @@ static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node ...@@ -161,19 +162,16 @@ static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node
const struct irq_domain_ops *ops, const struct irq_domain_ops *ops,
void *host_data) void *host_data)
{ {
return irq_domain_add_linear(of_node, 0, ops, host_data); return __irq_domain_add(of_node, 0, ~0, 0, ops, host_data);
} }
extern void irq_domain_remove(struct irq_domain *host); extern void irq_domain_remove(struct irq_domain *host);
extern int irq_domain_associate_many(struct irq_domain *domain, extern int irq_domain_associate(struct irq_domain *domain, unsigned int irq,
unsigned int irq_base, irq_hw_number_t hwirq);
irq_hw_number_t hwirq_base, int count); extern void irq_domain_associate_many(struct irq_domain *domain,
static inline int irq_domain_associate(struct irq_domain *domain, unsigned int irq, unsigned int irq_base,
irq_hw_number_t hwirq) irq_hw_number_t hwirq_base, int count);
{
return irq_domain_associate_many(domain, irq, hwirq, 1);
}
extern unsigned int irq_create_mapping(struct irq_domain *host, extern unsigned int irq_create_mapping(struct irq_domain *host,
irq_hw_number_t hwirq); irq_hw_number_t hwirq);
......
...@@ -35,8 +35,8 @@ static struct irq_domain *irq_default_domain; ...@@ -35,8 +35,8 @@ static struct irq_domain *irq_default_domain;
* register allocated irq_domain with irq_domain_register(). Returns pointer * register allocated irq_domain with irq_domain_register(). Returns pointer
* to IRQ domain, or NULL on failure. * to IRQ domain, or NULL on failure.
*/ */
struct irq_domain *__irq_domain_add(struct device_node *of_node, struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,
int size, int direct_max, irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops, const struct irq_domain_ops *ops,
void *host_data) void *host_data)
{ {
...@@ -52,6 +52,7 @@ struct irq_domain *__irq_domain_add(struct device_node *of_node, ...@@ -52,6 +52,7 @@ struct irq_domain *__irq_domain_add(struct device_node *of_node,
domain->ops = ops; domain->ops = ops;
domain->host_data = host_data; domain->host_data = host_data;
domain->of_node = of_node_get(of_node); domain->of_node = of_node_get(of_node);
domain->hwirq_max = hwirq_max;
domain->revmap_size = size; domain->revmap_size = size;
domain->revmap_direct_max_irq = direct_max; domain->revmap_direct_max_irq = direct_max;
...@@ -126,7 +127,7 @@ struct irq_domain *irq_domain_add_simple(struct device_node *of_node, ...@@ -126,7 +127,7 @@ struct irq_domain *irq_domain_add_simple(struct device_node *of_node,
{ {
struct irq_domain *domain; struct irq_domain *domain;
domain = __irq_domain_add(of_node, size, 0, ops, host_data); domain = __irq_domain_add(of_node, size, size, 0, ops, host_data);
if (!domain) if (!domain)
return NULL; return NULL;
...@@ -139,7 +140,7 @@ struct irq_domain *irq_domain_add_simple(struct device_node *of_node, ...@@ -139,7 +140,7 @@ struct irq_domain *irq_domain_add_simple(struct device_node *of_node,
pr_info("Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n", pr_info("Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
first_irq); first_irq);
} }
WARN_ON(irq_domain_associate_many(domain, first_irq, 0, size)); irq_domain_associate_many(domain, first_irq, 0, size);
} }
return domain; return domain;
...@@ -170,11 +171,12 @@ struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, ...@@ -170,11 +171,12 @@ struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
{ {
struct irq_domain *domain; struct irq_domain *domain;
domain = __irq_domain_add(of_node, first_hwirq + size, 0, ops, host_data); domain = __irq_domain_add(of_node, first_hwirq + size,
first_hwirq + size, 0, ops, host_data);
if (!domain) if (!domain)
return NULL; return NULL;
WARN_ON(irq_domain_associate_many(domain, first_irq, first_hwirq, size)); irq_domain_associate_many(domain, first_irq, first_hwirq, size);
return domain; return domain;
} }
...@@ -228,109 +230,109 @@ void irq_set_default_host(struct irq_domain *domain) ...@@ -228,109 +230,109 @@ void irq_set_default_host(struct irq_domain *domain)
} }
EXPORT_SYMBOL_GPL(irq_set_default_host); EXPORT_SYMBOL_GPL(irq_set_default_host);
static void irq_domain_disassociate_many(struct irq_domain *domain, static void irq_domain_disassociate(struct irq_domain *domain, unsigned int irq)
unsigned int irq_base, int count)
{ {
/* struct irq_data *irq_data = irq_get_irq_data(irq);
* disassociate in reverse order; irq_hw_number_t hwirq;
* 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;
if (WARN_ON(!irq_data || irq_data->domain != domain)) if (WARN(!irq_data || irq_data->domain != domain,
continue; "virq%i doesn't exist; cannot disassociate\n", irq))
return;
hwirq = irq_data->hwirq; hwirq = irq_data->hwirq;
irq_set_status_flags(irq, IRQ_NOREQUEST); irq_set_status_flags(irq, IRQ_NOREQUEST);
/* remove chip and handler */ /* remove chip and handler */
irq_set_chip_and_handler(irq, NULL, NULL); irq_set_chip_and_handler(irq, NULL, NULL);
/* Make sure it's completed */ /* Make sure it's completed */
synchronize_irq(irq); synchronize_irq(irq);
/* Tell the PIC about it */ /* Tell the PIC about it */
if (domain->ops->unmap) if (domain->ops->unmap)
domain->ops->unmap(domain, irq); domain->ops->unmap(domain, irq);
smp_mb(); smp_mb();
irq_data->domain = NULL; irq_data->domain = NULL;
irq_data->hwirq = 0; irq_data->hwirq = 0;
/* Clear reverse map for this hwirq */ /* Clear reverse map for this hwirq */
if (hwirq < domain->revmap_size) { if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = 0; domain->linear_revmap[hwirq] = 0;
} else { } else {
mutex_lock(&revmap_trees_mutex); mutex_lock(&revmap_trees_mutex);
radix_tree_delete(&domain->revmap_tree, hwirq); radix_tree_delete(&domain->revmap_tree, hwirq);
mutex_unlock(&revmap_trees_mutex); mutex_unlock(&revmap_trees_mutex);
}
} }
} }
int irq_domain_associate_many(struct irq_domain *domain, unsigned int irq_base, int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
irq_hw_number_t hwirq_base, int count) irq_hw_number_t hwirq)
{ {
unsigned int virq = irq_base; struct irq_data *irq_data = irq_get_irq_data(virq);
irq_hw_number_t hwirq = hwirq_base; int ret;
int i, ret;
pr_debug("%s(%s, irqbase=%i, hwbase=%i, count=%i)\n", __func__, if (WARN(hwirq >= domain->hwirq_max,
of_node_full_name(domain->of_node), irq_base, (int)hwirq_base, count); "error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
return -EINVAL;
if (WARN(!irq_data, "error: virq%i is not allocated", virq))
return -EINVAL;
if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
return -EINVAL;
for (i = 0; i < count; i++) { mutex_lock(&irq_domain_mutex);
struct irq_data *irq_data = irq_get_irq_data(virq + i); irq_data->hwirq = hwirq;
irq_data->domain = domain;
if (WARN(!irq_data, "error: irq_desc not allocated; " if (domain->ops->map) {
"irq=%i hwirq=0x%x\n", virq + i, (int)hwirq + i)) ret = domain->ops->map(domain, virq, hwirq);
return -EINVAL; if (ret != 0) {
if (WARN(irq_data->domain, "error: irq_desc already associated; " /*
"irq=%i hwirq=0x%x\n", virq + i, (int)hwirq + i)) * If map() returns -EPERM, this interrupt is protected
return -EINVAL; * by the firmware or some other service and shall not
}; * be mapped. Don't bother telling the user about it.
*/
for (i = 0; i < count; i++, virq++, hwirq++) { if (ret != -EPERM) {
struct irq_data *irq_data = irq_get_irq_data(virq); pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
domain->name, hwirq, virq, ret);
irq_data->hwirq = hwirq;
irq_data->domain = domain;
if (domain->ops->map) {
ret = domain->ops->map(domain, virq, hwirq);
if (ret != 0) {
/*
* If map() returns -EPERM, this interrupt is protected
* by the firmware or some other service and shall not
* be mapped. Don't bother telling the user about it.
*/
if (ret != -EPERM) {
pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
domain->name, hwirq, virq, ret);
}
irq_data->domain = NULL;
irq_data->hwirq = 0;
continue;
} }
/* If not already assigned, give the domain the chip's name */ irq_data->domain = NULL;
if (!domain->name && irq_data->chip) irq_data->hwirq = 0;
domain->name = irq_data->chip->name; mutex_unlock(&irq_domain_mutex);
return ret;
} }
if (hwirq < domain->revmap_size) { /* If not already assigned, give the domain the chip's name */
domain->linear_revmap[hwirq] = virq; if (!domain->name && irq_data->chip)
} else { domain->name = irq_data->chip->name;
mutex_lock(&revmap_trees_mutex); }
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
mutex_unlock(&revmap_trees_mutex);
}
irq_clear_status_flags(virq, IRQ_NOREQUEST); if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = virq;
} else {
mutex_lock(&revmap_trees_mutex);
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
mutex_unlock(&revmap_trees_mutex);
} }
mutex_unlock(&irq_domain_mutex);
irq_clear_status_flags(virq, IRQ_NOREQUEST);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(irq_domain_associate);
void irq_domain_associate_many(struct irq_domain *domain, unsigned int irq_base,
irq_hw_number_t hwirq_base, int count)
{
int i;
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++) {
irq_domain_associate(domain, irq_base + i, hwirq_base + i);
}
}
EXPORT_SYMBOL_GPL(irq_domain_associate_many); EXPORT_SYMBOL_GPL(irq_domain_associate_many);
/** /**
...@@ -460,12 +462,7 @@ int irq_create_strict_mappings(struct irq_domain *domain, unsigned int irq_base, ...@@ -460,12 +462,7 @@ int irq_create_strict_mappings(struct irq_domain *domain, unsigned int irq_base,
if (unlikely(ret < 0)) if (unlikely(ret < 0))
return ret; return ret;
ret = irq_domain_associate_many(domain, irq_base, hwirq_base, count); irq_domain_associate_many(domain, irq_base, hwirq_base, count);
if (unlikely(ret < 0)) {
irq_free_descs(irq_base, count);
return ret;
}
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(irq_create_strict_mappings); EXPORT_SYMBOL_GPL(irq_create_strict_mappings);
...@@ -535,7 +532,7 @@ void irq_dispose_mapping(unsigned int virq) ...@@ -535,7 +532,7 @@ void irq_dispose_mapping(unsigned int virq)
if (WARN_ON(domain == NULL)) if (WARN_ON(domain == NULL))
return; return;
irq_domain_disassociate_many(domain, virq, 1); irq_domain_disassociate(domain, virq);
irq_free_desc(virq); irq_free_desc(virq);
} }
EXPORT_SYMBOL_GPL(irq_dispose_mapping); EXPORT_SYMBOL_GPL(irq_dispose_mapping);
......
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