diff options
Diffstat (limited to 'drivers/of/irq.c')
| -rw-r--r-- | drivers/of/irq.c | 714 |
1 files changed, 515 insertions, 199 deletions
diff --git a/drivers/of/irq.c b/drivers/of/irq.c index a3c1c5aae6a9..e3816819dbfe 100644 --- a/drivers/of/irq.c +++ b/drivers/of/irq.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * Derived from arch/i386/kernel/irq.c * Copyright (C) 1992 Linus Torvalds @@ -8,16 +9,15 @@ * Adapted for Power Macintosh by Paul Mackerras * Copyright (C) 1996 Paul Mackerras (paulus@cs.anu.edu.au) * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * * This file contains the code used to make IRQ descriptions in the * device tree to actual irq numbers on an interrupt controller * driver. */ +#define pr_fmt(fmt) "OF: " fmt + +#include <linux/cleanup.h> +#include <linux/device.h> #include <linux/errno.h> #include <linux/list.h> #include <linux/module.h> @@ -26,23 +26,28 @@ #include <linux/string.h> #include <linux/slab.h> +#include "of_private.h" + /** * irq_of_parse_and_map - Parse and map an interrupt into linux virq space - * @device: Device node of the device whose interrupt is to be mapped + * @dev: Device node of the device whose interrupt is to be mapped * @index: Index of the interrupt to map * - * This function is a wrapper that chains of_irq_map_one() and + * This function is a wrapper that chains of_irq_parse_one() and * irq_create_of_mapping() to make things easier to callers */ unsigned int irq_of_parse_and_map(struct device_node *dev, int index) { - struct of_irq oirq; + struct of_phandle_args oirq; + unsigned int ret; - if (of_irq_map_one(dev, index, &oirq)) + if (of_irq_parse_one(dev, index, &oirq)) return 0; - return irq_create_of_mapping(oirq.controller, oirq.specifier, - oirq.size); + ret = irq_create_of_mapping(&oirq); + of_node_put(oirq.np); + + return ret; } EXPORT_SYMBOL_GPL(irq_of_parse_and_map); @@ -50,26 +55,25 @@ EXPORT_SYMBOL_GPL(irq_of_parse_and_map); * of_irq_find_parent - Given a device node, find its interrupt parent node * @child: pointer to device node * - * Returns a pointer to the interrupt parent node, or NULL if the interrupt - * parent could not be determined. + * Return: A pointer to the interrupt parent node with refcount increased + * or NULL if the interrupt parent could not be determined. */ struct device_node *of_irq_find_parent(struct device_node *child) { struct device_node *p; - const __be32 *parp; + phandle parent; if (!of_node_get(child)) return NULL; do { - parp = of_get_property(child, "interrupt-parent", NULL); - if (parp == NULL) + if (of_property_read_u32(child, "interrupt-parent", &parent)) { p = of_get_parent(child); - else { + } else { if (of_irq_workarounds & OF_IMAP_NO_PHANDLE) p = of_node_get(of_irq_dflt_pic); else - p = of_find_node_by_phandle(be32_to_cpup(parp)); + p = of_find_node_by_phandle(parent); } of_node_put(child); child = p; @@ -77,46 +81,120 @@ struct device_node *of_irq_find_parent(struct device_node *child) return p; } +EXPORT_SYMBOL_GPL(of_irq_find_parent); -/** - * of_irq_map_raw - Low level interrupt tree parsing - * @parent: the device interrupt parent - * @intspec: interrupt specifier ("interrupts" property of the device) - * @ointsize: size of the passed in interrupt specifier - * @addr: address specifier (start of "reg" property of the device) - * @out_irq: structure of_irq filled by this function +/* + * These interrupt controllers abuse interrupt-map for unspeakable + * reasons and rely on the core code to *ignore* it (the drivers do + * their own parsing of the property). The PAsemi entry covers a + * non-sensical interrupt-map that is better left ignored. * - * Returns 0 on success and a negative number on error + * If you think of adding to the list for something *new*, think + * again. There is a high chance that you will be sent back to the + * drawing board. + */ +static const char * const of_irq_imap_abusers[] = { + "CBEA,platform-spider-pic", + "sti,platform-spider-pic", + "realtek,rtl-intc", + "fsl,ls1021a-extirq", + "fsl,ls1043a-extirq", + "fsl,ls1088a-extirq", + "renesas,rza1-irqc", + "pasemi,rootbus", + NULL, +}; + +const __be32 *of_irq_parse_imap_parent(const __be32 *imap, int len, struct of_phandle_args *out_irq) +{ + u32 intsize, addrsize; + struct device_node *np; + + /* Get the interrupt parent */ + if (of_irq_workarounds & OF_IMAP_NO_PHANDLE) + np = of_node_get(of_irq_dflt_pic); + else + np = of_find_node_by_phandle(be32_to_cpup(imap)); + imap++; + len--; + + /* Check if not found */ + if (!np) { + pr_debug(" -> imap parent not found !\n"); + return NULL; + } + + /* Get #interrupt-cells and #address-cells of new parent */ + if (of_property_read_u32(np, "#interrupt-cells", + &intsize)) { + pr_debug(" -> parent lacks #interrupt-cells!\n"); + of_node_put(np); + return NULL; + } + if (of_property_read_u32(np, "#address-cells", + &addrsize)) + addrsize = 0; + + pr_debug(" -> intsize=%d, addrsize=%d\n", + intsize, addrsize); + + /* Check for malformed properties */ + if (WARN_ON(addrsize + intsize > MAX_PHANDLE_ARGS) + || (len < (addrsize + intsize))) { + of_node_put(np); + return NULL; + } + + pr_debug(" -> imaplen=%d\n", len); + + imap += addrsize + intsize; + + out_irq->np = np; + for (int i = 0; i < intsize; i++) + out_irq->args[i] = be32_to_cpup(imap - intsize + i); + out_irq->args_count = intsize; + + return imap; +} + +/** + * of_irq_parse_raw - Low level interrupt tree parsing + * @addr: address specifier (start of "reg" property of the device) in be32 format + * @out_irq: structure of_phandle_args updated by this function * * This function is a low-level interrupt tree walking function. It - * can be used to do a partial walk with synthetized reg and interrupts + * can be used to do a partial walk with synthesized reg and interrupts * properties, for example when resolving PCI interrupts when no device - * node exist for the parent. + * node exist for the parent. It takes an interrupt specifier structure as + * input, walks the tree looking for any interrupt-map properties, translates + * the specifier for each map, and then returns the translated map. + * + * Return: 0 on success and a negative number on error + * + * Note: refcount of node @out_irq->np is increased by 1 on success. */ -int of_irq_map_raw(struct device_node *parent, const __be32 *intspec, - u32 ointsize, const __be32 *addr, struct of_irq *out_irq) +int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq) { - struct device_node *ipar, *tnode, *old = NULL, *newpar = NULL; - const __be32 *tmp, *imap, *imask; - u32 intsize = 1, addrsize, newintsize = 0, newaddrsize = 0; - int imaplen, match, i; + struct device_node *ipar, *tnode, *old = NULL; + __be32 initial_match_array[MAX_PHANDLE_ARGS]; + const __be32 *match_array = initial_match_array; + const __be32 *tmp, dummy_imask[] = { [0 ... (MAX_PHANDLE_ARGS - 1)] = cpu_to_be32(~0) }; + u32 intsize = 1, addrsize; + int i, rc = -EINVAL; - pr_debug("of_irq_map_raw: par=%s,intspec=[0x%08x 0x%08x...],ointsize=%d\n", - parent->full_name, be32_to_cpup(intspec), - be32_to_cpup(intspec + 1), ointsize); +#ifdef DEBUG + of_print_phandle_args("of_irq_parse_raw: ", out_irq); +#endif - ipar = of_node_get(parent); + ipar = of_node_get(out_irq->np); /* First get the #interrupt-cells property of the current cursor * that tells us how to interpret the passed-in intspec. If there * is none, we are nice and just walk up the tree */ do { - tmp = of_get_property(ipar, "#interrupt-cells", NULL); - if (tmp != NULL) { - intsize = be32_to_cpu(*tmp); + if (!of_property_read_u32(ipar, "#interrupt-cells", &intsize)) break; - } tnode = ipar; ipar = of_irq_find_parent(ipar); of_node_put(tnode); @@ -126,10 +204,10 @@ int of_irq_map_raw(struct device_node *parent, const __be32 *intspec, goto fail; } - pr_debug("of_irq_map_raw: ipar=%s, size=%d\n", ipar->full_name, intsize); + pr_debug("of_irq_parse_raw: ipar=%pOF, size=%d\n", ipar, intsize); - if (ointsize != intsize) - return -EINVAL; + if (out_irq->args_count != intsize) + goto fail; /* Look for this #address-cells. We have to implement the old linux * trick of looking for the parent here as some device-trees rely on it @@ -147,25 +225,48 @@ int of_irq_map_raw(struct device_node *parent, const __be32 *intspec, pr_debug(" -> addrsize=%d\n", addrsize); + /* Range check so that the temporary buffer doesn't overflow */ + if (WARN_ON(addrsize + intsize > MAX_PHANDLE_ARGS)) { + rc = -EFAULT; + goto fail; + } + + /* Precalculate the match array - this simplifies match loop */ + for (i = 0; i < addrsize; i++) + initial_match_array[i] = addr ? addr[i] : 0; + for (i = 0; i < intsize; i++) + initial_match_array[addrsize + i] = cpu_to_be32(out_irq->args[i]); + /* Now start the actual "proper" walk of the interrupt tree */ while (ipar != NULL) { - /* Now check if cursor is an interrupt-controller and if it is - * then we are done + int imaplen, match; + const __be32 *imap, *oldimap, *imask; + struct device_node *newpar; + /* + * Now check if cursor is an interrupt-controller and + * if it is then we are done, unless there is an + * interrupt-map which takes precedence except on one + * of these broken platforms that want to parse + * interrupt-map themselves for $reason. */ - if (of_get_property(ipar, "interrupt-controller", NULL) != - NULL) { + bool intc = of_property_read_bool(ipar, "interrupt-controller"); + + imap = of_get_property(ipar, "interrupt-map", &imaplen); + if (intc && + (!imap || of_device_compatible_match(ipar, of_irq_imap_abusers))) { pr_debug(" -> got it !\n"); - for (i = 0; i < intsize; i++) - out_irq->specifier[i] = - of_read_number(intspec +i, 1); - out_irq->size = intsize; - out_irq->controller = ipar; - of_node_put(old); return 0; } - /* Now look for an interrupt-map */ - imap = of_get_property(ipar, "interrupt-map", &imaplen); + /* + * interrupt-map parsing does not work without a reg + * property when #address-cells != 0 + */ + if (addrsize && !addr) { + pr_debug(" -> no reg passed in when needed !\n"); + goto fail; + } + /* No interrupt map, check for an interrupt parent */ if (imap == NULL) { pr_debug(" -> no map, getting parent\n"); @@ -176,159 +277,140 @@ int of_irq_map_raw(struct device_node *parent, const __be32 *intspec, /* Look for a mask */ imask = of_get_property(ipar, "interrupt-map-mask", NULL); - - /* If we were passed no "reg" property and we attempt to parse - * an interrupt-map, then #address-cells must be 0. - * Fail if it's not. - */ - if (addr == NULL && addrsize != 0) { - pr_debug(" -> no reg passed in when needed !\n"); - goto fail; - } + if (!imask) + imask = dummy_imask; /* Parse interrupt-map */ match = 0; - while (imaplen > (addrsize + intsize + 1) && !match) { + while (imaplen > (addrsize + intsize + 1)) { /* Compare specifiers */ match = 1; - for (i = 0; i < addrsize && match; ++i) { - __be32 mask = imask ? imask[i] - : cpu_to_be32(0xffffffffu); - match = ((addr[i] ^ imap[i]) & mask) == 0; - } - for (; i < (addrsize + intsize) && match; ++i) { - __be32 mask = imask ? imask[i] - : cpu_to_be32(0xffffffffu); - match = - ((intspec[i-addrsize] ^ imap[i]) & mask) == 0; - } - imap += addrsize + intsize; - imaplen -= addrsize + intsize; + for (i = 0; i < (addrsize + intsize); i++, imaplen--) + match &= !((match_array[i] ^ *imap++) & imask[i]); pr_debug(" -> match=%d (imaplen=%d)\n", match, imaplen); - /* Get the interrupt parent */ - if (of_irq_workarounds & OF_IMAP_NO_PHANDLE) - newpar = of_node_get(of_irq_dflt_pic); - else - newpar = of_find_node_by_phandle(be32_to_cpup(imap)); - imap++; - --imaplen; - - /* Check if not found */ - if (newpar == NULL) { - pr_debug(" -> imap parent not found !\n"); + oldimap = imap; + imap = of_irq_parse_imap_parent(oldimap, imaplen, out_irq); + if (!imap) goto fail; - } - /* Get #interrupt-cells and #address-cells of new - * parent - */ - tmp = of_get_property(newpar, "#interrupt-cells", NULL); - if (tmp == NULL) { - pr_debug(" -> parent lacks #interrupt-cells!\n"); - goto fail; - } - newintsize = be32_to_cpu(*tmp); - tmp = of_get_property(newpar, "#address-cells", NULL); - newaddrsize = (tmp == NULL) ? 0 : be32_to_cpu(*tmp); - - pr_debug(" -> newintsize=%d, newaddrsize=%d\n", - newintsize, newaddrsize); - - /* Check for malformed properties */ - if (imaplen < (newaddrsize + newintsize)) - goto fail; - - imap += newaddrsize + newintsize; - imaplen -= newaddrsize + newintsize; + match &= of_device_is_available(out_irq->np); + if (match) + break; + of_node_put(out_irq->np); + imaplen -= imap - oldimap; pr_debug(" -> imaplen=%d\n", imaplen); } if (!match) goto fail; - of_node_put(old); - old = of_node_get(newpar); - addrsize = newaddrsize; - intsize = newintsize; - intspec = imap - intsize; - addr = intspec - addrsize; + /* + * Successfully parsed an interrupt-map translation; copy new + * interrupt specifier into the out_irq structure + */ + match_array = oldimap + 1; + + newpar = out_irq->np; + intsize = out_irq->args_count; + addrsize = (imap - match_array) - intsize; + + if (ipar == newpar) { + /* + * We got @ipar's refcount, but the refcount was + * gotten again by of_irq_parse_imap_parent() via its + * alias @newpar. + */ + of_node_put(ipar); + pr_debug("%pOF interrupt-map entry to self\n", ipar); + return 0; + } skiplevel: /* Iterate again with new parent */ - pr_debug(" -> new parent: %s\n", of_node_full_name(newpar)); + pr_debug(" -> new parent: %pOF\n", newpar); of_node_put(ipar); ipar = newpar; newpar = NULL; } + rc = -ENOENT; /* No interrupt-map found */ + fail: of_node_put(ipar); - of_node_put(old); - of_node_put(newpar); - return -EINVAL; + return rc; } -EXPORT_SYMBOL_GPL(of_irq_map_raw); +EXPORT_SYMBOL_GPL(of_irq_parse_raw); /** - * of_irq_map_one - Resolve an interrupt for a device + * of_irq_parse_one - Resolve an interrupt for a device * @device: the device whose interrupt is to be resolved * @index: index of the interrupt to resolve - * @out_irq: structure of_irq filled by this function + * @out_irq: structure of_phandle_args filled by this function * - * This function resolves an interrupt, walking the tree, for a given - * device-tree node. It's the high level pendant to of_irq_map_raw(). + * This function resolves an interrupt for a node by walking the interrupt tree, + * finding which interrupt controller node it is attached to, and returning the + * interrupt specifier that can be used to retrieve a Linux IRQ number. + * + * Note: refcount of node @out_irq->np is increased by 1 on success. */ -int of_irq_map_one(struct device_node *device, int index, struct of_irq *out_irq) +int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq) { - struct device_node *p; - const __be32 *intspec, *tmp, *addr; - u32 intsize, intlen; - int res = -EINVAL; + struct device_node __free(device_node) *p = NULL; + const __be32 *addr; + u32 intsize; + int i, res, addr_len; + __be32 addr_buf[3] = { 0 }; - pr_debug("of_irq_map_one: dev=%s, index=%d\n", device->full_name, index); + pr_debug("of_irq_parse_one: dev=%pOF, index=%d\n", device, index); /* OldWorld mac stuff is "special", handle out of line */ if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC) - return of_irq_map_oldworld(device, index, out_irq); - - /* Get the interrupts property */ - intspec = of_get_property(device, "interrupts", &intlen); - if (intspec == NULL) - return -EINVAL; - intlen /= sizeof(*intspec); - - pr_debug(" intspec=%d intlen=%d\n", be32_to_cpup(intspec), intlen); + return of_irq_parse_oldworld(device, index, out_irq); /* Get the reg property (if any) */ - addr = of_get_property(device, "reg", NULL); - - /* Look for the interrupt parent. */ - p = of_irq_find_parent(device); - if (p == NULL) - return -EINVAL; - - /* Get size of interrupt specifier */ - tmp = of_get_property(p, "#interrupt-cells", NULL); - if (tmp == NULL) - goto out; - intsize = be32_to_cpu(*tmp); - - pr_debug(" intsize=%d intlen=%d\n", intsize, intlen); + addr_len = 0; + addr = of_get_property(device, "reg", &addr_len); + + /* Prevent out-of-bounds read in case of longer interrupt parent address size */ + if (addr_len > sizeof(addr_buf)) + addr_len = sizeof(addr_buf); + if (addr) + memcpy(addr_buf, addr, addr_len); + + /* Try the new-style interrupts-extended first */ + res = of_parse_phandle_with_args(device, "interrupts-extended", + "#interrupt-cells", index, out_irq); + if (!res) { + p = out_irq->np; + } else { + /* Look for the interrupt parent. */ + p = of_irq_find_parent(device); + /* Get size of interrupt specifier */ + if (!p || of_property_read_u32(p, "#interrupt-cells", &intsize)) + return -EINVAL; + + pr_debug(" parent=%pOF, intsize=%d\n", p, intsize); + + /* Copy intspec into irq structure */ + out_irq->np = p; + out_irq->args_count = intsize; + for (i = 0; i < intsize; i++) { + res = of_property_read_u32_index(device, "interrupts", + (index * intsize) + i, + out_irq->args + i); + if (res) + return res; + } - /* Check index */ - if ((index + 1) * intsize > intlen) - goto out; + pr_debug(" intspec=%d\n", *out_irq->args); + } - /* Get new specifier and map it */ - res = of_irq_map_raw(p, intspec + index * intsize, intsize, - addr, out_irq); - out: - of_node_put(p); - return res; + /* Check if there are any interrupt-map translations to process */ + return of_irq_parse_raw(addr_buf, out_irq); } -EXPORT_SYMBOL_GPL(of_irq_map_one); +EXPORT_SYMBOL_GPL(of_irq_parse_one); /** * of_irq_to_resource - Decode a node's IRQ and return it as a resource @@ -338,23 +420,26 @@ EXPORT_SYMBOL_GPL(of_irq_map_one); */ int of_irq_to_resource(struct device_node *dev, int index, struct resource *r) { - int irq = irq_of_parse_and_map(dev, index); + int irq = of_irq_get(dev, index); + + if (irq < 0) + return irq; /* Only dereference the resource if both the * resource and the irq are valid. */ if (r && irq) { const char *name = NULL; + memset(r, 0, sizeof(*r)); /* - * Get optional "interrupts-names" property to add a name + * Get optional "interrupt-names" property to add a name * to the resource. */ of_property_read_string_index(dev, "interrupt-names", index, &name); - r->start = r->end = irq; - r->flags = IORESOURCE_IRQ; - r->name = name ? name : dev->full_name; + *r = DEFINE_RES_IRQ_NAMED(irq, name ?: of_node_full_name(dev)); + r->flags |= irq_get_trigger_type(irq); } return irq; @@ -362,18 +447,99 @@ int of_irq_to_resource(struct device_node *dev, int index, struct resource *r) EXPORT_SYMBOL_GPL(of_irq_to_resource); /** + * of_irq_get - Decode a node's IRQ and return it as a Linux IRQ number + * @dev: pointer to device tree node + * @index: zero-based index of the IRQ + * + * Return: Linux IRQ number on success, or 0 on the IRQ mapping failure, or + * -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case + * of any other failure. + */ +int of_irq_get(struct device_node *dev, int index) +{ + int rc; + struct of_phandle_args oirq; + struct irq_domain *domain; + + rc = of_irq_parse_one(dev, index, &oirq); + if (rc) + return rc; + + domain = irq_find_host(oirq.np); + if (!domain) { + rc = -EPROBE_DEFER; + goto out; + } + + rc = irq_create_of_mapping(&oirq); +out: + of_node_put(oirq.np); + + return rc; +} +EXPORT_SYMBOL_GPL(of_irq_get); + +const struct cpumask *of_irq_get_affinity(struct device_node *dev, int index) +{ + struct of_phandle_args oirq; + struct irq_fwspec_info info; + struct irq_fwspec fwspec; + int rc; + + rc = of_irq_parse_one(dev, index, &oirq); + if (rc) + return NULL; + + of_phandle_args_to_fwspec(oirq.np, oirq.args, oirq.args_count, + &fwspec); + + if (irq_populate_fwspec_info(&fwspec, &info)) + return NULL; + + return info.affinity; +} + +/** + * of_irq_get_byname - Decode a node's IRQ and return it as a Linux IRQ number + * @dev: pointer to device tree node + * @name: IRQ name + * + * Return: Linux IRQ number on success, or 0 on the IRQ mapping failure, or + * -EPROBE_DEFER if the IRQ domain is not yet created, or error code in case + * of any other failure. + */ +int of_irq_get_byname(struct device_node *dev, const char *name) +{ + int index; + + if (unlikely(!name)) + return -EINVAL; + + index = of_property_match_string(dev, "interrupt-names", name); + if (index < 0) + return index; + + return of_irq_get(dev, index); +} +EXPORT_SYMBOL_GPL(of_irq_get_byname); + +/** * of_irq_count - Count the number of IRQs a node uses * @dev: pointer to device tree node */ int of_irq_count(struct device_node *dev) { + struct of_phandle_args irq; int nr = 0; - while (of_irq_to_resource(dev, nr, NULL)) + while (of_irq_parse_one(dev, nr, &irq) == 0) { + of_node_put(irq.np); nr++; + } return nr; } +EXPORT_SYMBOL_GPL(of_irq_count); /** * of_irq_to_resource_table - Fill in resource table with node's IRQ info @@ -381,7 +547,7 @@ int of_irq_count(struct device_node *dev) * @res: array of resources to fill in * @nr_irqs: the number of IRQs (and upper bound for num of @res elements) * - * Returns the size of the filled in table (up to @nr_irqs). + * Return: The size of the filled in table (up to @nr_irqs). */ int of_irq_to_resource_table(struct device_node *dev, struct resource *res, int nr_irqs) @@ -389,15 +555,16 @@ int of_irq_to_resource_table(struct device_node *dev, struct resource *res, int i; for (i = 0; i < nr_irqs; i++, res++) - if (!of_irq_to_resource(dev, i, res)) + if (of_irq_to_resource(dev, i, res) <= 0) break; return i; } EXPORT_SYMBOL_GPL(of_irq_to_resource_table); -struct intc_desc { +struct of_intc_desc { struct list_head list; + of_irq_init_cb_t irq_init_cb; struct device_node *dev; struct device_node *interrupt_parent; }; @@ -411,28 +578,49 @@ struct intc_desc { */ void __init of_irq_init(const struct of_device_id *matches) { + const struct of_device_id *match; struct device_node *np, *parent = NULL; - struct intc_desc *desc, *temp_desc; + struct of_intc_desc *desc, *temp_desc; struct list_head intc_desc_list, intc_parent_list; INIT_LIST_HEAD(&intc_desc_list); INIT_LIST_HEAD(&intc_parent_list); - for_each_matching_node(np, matches) { - if (!of_find_property(np, "interrupt-controller", NULL)) + for_each_matching_node_and_match(np, matches, &match) { + if (!of_property_read_bool(np, "interrupt-controller") || + !of_device_is_available(np)) + continue; + + if (WARN(!match->data, "of_irq_init: no init function for %s\n", + match->compatible)) continue; + /* - * Here, we allocate and populate an intc_desc with the node + * Here, we allocate and populate an of_intc_desc with the node * pointer, interrupt-parent device_node etc. */ desc = kzalloc(sizeof(*desc), GFP_KERNEL); - if (WARN_ON(!desc)) + if (!desc) { + of_node_put(np); goto err; + } - desc->dev = np; - desc->interrupt_parent = of_irq_find_parent(np); - if (desc->interrupt_parent == np) + desc->irq_init_cb = match->data; + desc->dev = of_node_get(np); + /* + * interrupts-extended can reference multiple parent domains. + * Arbitrarily pick the first one; assume any other parents + * are the same distance away from the root irq controller. + */ + desc->interrupt_parent = of_parse_phandle(np, "interrupts-extended", 0); + if (!desc->interrupt_parent && of_property_present(np, "interrupts")) + desc->interrupt_parent = of_irq_find_parent(np); + else if (!desc->interrupt_parent) + desc->interrupt_parent = of_parse_phandle(np, "interrupt-parent", 0); + if (desc->interrupt_parent == np) { + of_node_put(desc->interrupt_parent); desc->interrupt_parent = NULL; + } list_add_tail(&desc->list, &intc_desc_list); } @@ -448,28 +636,27 @@ void __init of_irq_init(const struct of_device_id *matches) * The assumption is that NULL parent means a root controller. */ list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { - const struct of_device_id *match; int ret; - of_irq_init_cb_t irq_init_cb; if (desc->interrupt_parent != parent) continue; list_del(&desc->list); - match = of_match_node(matches, desc->dev); - if (WARN(!match->data, - "of_irq_init: no init function for %s\n", - match->compatible)) { - kfree(desc); - continue; - } - pr_debug("of_irq_init: init %s @ %p, parent %p\n", - match->compatible, + of_node_set_flag(desc->dev, OF_POPULATED); + + pr_debug("of_irq_init: init %pOF (%p), parent %p\n", + desc->dev, desc->dev, desc->interrupt_parent); - irq_init_cb = (of_irq_init_cb_t)match->data; - ret = irq_init_cb(desc->dev, desc->interrupt_parent); + ret = desc->irq_init_cb(desc->dev, + desc->interrupt_parent); if (ret) { + pr_err("%s: Failed to init %pOF (%p), parent %p\n", + __func__, desc->dev, desc->dev, + desc->interrupt_parent); + of_node_clear_flag(desc->dev, OF_POPULATED); + of_node_put(desc->interrupt_parent); + of_node_put(desc->dev); kfree(desc); continue; } @@ -482,8 +669,9 @@ void __init of_irq_init(const struct of_device_id *matches) } /* Get the next pending parent that might have children */ - desc = list_first_entry(&intc_parent_list, typeof(*desc), list); - if (list_empty(&intc_parent_list) || !desc) { + desc = list_first_entry_or_null(&intc_parent_list, + typeof(*desc), list); + if (!desc) { pr_err("of_irq_init: children remain, but no parents\n"); break; } @@ -499,6 +687,134 @@ void __init of_irq_init(const struct of_device_id *matches) err: list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { list_del(&desc->list); + of_node_put(desc->interrupt_parent); + of_node_put(desc->dev); kfree(desc); } } + +static int of_check_msi_parent(struct device_node *dev_node, struct device_node **msi_node) +{ + struct of_phandle_args msi_spec; + int ret; + + /* + * An msi-parent phandle with a missing or == 0 #msi-cells + * property identifies a 1:1 ID translation mapping. + * + * Set the msi controller node if the firmware matches this + * condition. + */ + ret = of_parse_phandle_with_optional_args(dev_node, "msi-parent", "#msi-cells", + 0, &msi_spec); + if (ret) + return ret; + + if ((*msi_node && *msi_node != msi_spec.np) || msi_spec.args_count != 0) + ret = -EINVAL; + + if (!ret) { + /* Return with a node reference held */ + *msi_node = msi_spec.np; + return 0; + } + of_node_put(msi_spec.np); + + return ret; +} + +/** + * of_msi_xlate - map a MSI ID and find relevant MSI controller node + * @dev: device for which the mapping is to be done. + * @msi_np: Pointer to target MSI controller node + * @id_in: Device ID. + * + * Walk up the device hierarchy looking for devices with a "msi-map" + * or "msi-parent" property. If found, apply the mapping to @id_in. + * If @msi_np points to a non-NULL device node pointer, only entries targeting + * that node will be matched; if it points to a NULL value, it will receive the + * device node of the first matching target phandle, with a reference held. + * + * Returns: The mapped MSI id. + */ +u32 of_msi_xlate(struct device *dev, struct device_node **msi_np, u32 id_in) +{ + struct device *parent_dev; + u32 id_out = id_in; + + /* + * Walk up the device parent links looking for one with a + * "msi-map" or an "msi-parent" property. + */ + for (parent_dev = dev; parent_dev; parent_dev = parent_dev->parent) { + if (!of_map_id(parent_dev->of_node, id_in, "msi-map", + "msi-map-mask", msi_np, &id_out)) + break; + if (!of_check_msi_parent(parent_dev->of_node, msi_np)) + break; + } + return id_out; +} +EXPORT_SYMBOL_GPL(of_msi_xlate); + +/** + * of_msi_map_get_device_domain - Use msi-map to find the relevant MSI domain + * @dev: device for which the mapping is to be done. + * @id: Device ID. + * @bus_token: Bus token + * + * Walk up the device hierarchy looking for devices with a "msi-map" + * property. + * + * Returns: the MSI domain for this device (or NULL on failure) + */ +struct irq_domain *of_msi_map_get_device_domain(struct device *dev, u32 id, + u32 bus_token) +{ + struct device_node *np = NULL; + + of_msi_xlate(dev, &np, id); + return irq_find_matching_host(np, bus_token); +} + +/** + * of_msi_get_domain - Use msi-parent to find the relevant MSI domain + * @dev: device for which the domain is requested + * @np: device node for @dev + * @token: bus type for this domain + * + * Parse the msi-parent property and returns the corresponding MSI domain. + * + * Returns: the MSI domain for this device (or NULL on failure). + */ +struct irq_domain *of_msi_get_domain(struct device *dev, + const struct device_node *np, + enum irq_domain_bus_token token) +{ + struct of_phandle_iterator it; + struct irq_domain *d; + int err; + + of_for_each_phandle(&it, err, np, "msi-parent", "#msi-cells", 0) { + d = irq_find_matching_host(it.node, token); + if (d) { + of_node_put(it.node); + return d; + } + } + + return NULL; +} +EXPORT_SYMBOL_GPL(of_msi_get_domain); + +/** + * of_msi_configure - Set the msi_domain field of a device + * @dev: device structure to associate with an MSI irq domain + * @np: device node for that device + */ +void of_msi_configure(struct device *dev, const struct device_node *np) +{ + dev_set_msi_domain(dev, + of_msi_get_domain(dev, np, DOMAIN_BUS_PLATFORM_MSI)); +} +EXPORT_SYMBOL_GPL(of_msi_configure); |
