]> Git Repo - linux.git/commitdiff
of/irq: Factor out parsing of interrupt-map parent phandle+args from of_irq_parse_raw()
authorRob Herring (Arm) <[email protected]>
Wed, 29 May 2024 19:59:20 +0000 (14:59 -0500)
committerRob Herring (Arm) <[email protected]>
Fri, 31 May 2024 00:43:19 +0000 (19:43 -0500)
Factor out the parsing of interrupt-map interrupt parent phandle and its
arg cells to a separate function, of_irq_parse_imap_parent(), so that it
can be used in other parsing scenarios (e.g. fw_devlink).

There was a refcount leak on non-matching entries when iterating thru
"interrupt-map" which is fixed.

Tested-by: Marc Zyngier <[email protected]>
Tested-by: Anup Patel <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Rob Herring (Arm) <[email protected]>
drivers/of/irq.c
drivers/of/of_private.h

index 174900072c18cd7c255fe603c68966a1ca7ea4fb..462375b293e47577a68ab79a51d00101d59b1e9d 100644 (file)
@@ -25,6 +25,8 @@
 #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
  * @dev: Device node of the device whose interrupt is to be mapped
@@ -96,6 +98,57 @@ static const char * const of_irq_imap_abusers[] = {
        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++;
+
+       /* 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
@@ -112,12 +165,12 @@ static const char * const of_irq_imap_abusers[] = {
  */
 int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
 {
-       struct device_node *ipar, *tnode, *old = NULL, *newpar = NULL;
+       struct device_node *ipar, *tnode, *old = NULL;
        __be32 initial_match_array[MAX_PHANDLE_ARGS];
        const __be32 *match_array = initial_match_array;
-       const __be32 *tmp, *imap, *imask, dummy_imask[] = { [0 ... MAX_PHANDLE_ARGS] = cpu_to_be32(~0) };
-       u32 intsize = 1, addrsize, newintsize = 0, newaddrsize = 0;
-       int imaplen, match, i, rc = -EINVAL;
+       const __be32 *tmp, dummy_imask[] = { [0 ... MAX_PHANDLE_ARGS] = cpu_to_be32(~0) };
+       u32 intsize = 1, addrsize;
+       int i, rc = -EINVAL;
 
 #ifdef DEBUG
        of_print_phandle_args("of_irq_parse_raw: ", out_irq);
@@ -176,6 +229,9 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
 
        /* Now start the actual "proper" walk of the interrupt tree */
        while (ipar != NULL) {
+               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
@@ -216,7 +272,7 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
 
                /* 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 + intsize); i++, imaplen--)
@@ -224,48 +280,17 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
 
                        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");
-                               goto fail;
-                       }
-
-                       if (!of_device_is_available(newpar))
-                               match = 0;
-
-                       /* Get #interrupt-cells and #address-cells of new
-                        * parent
-                        */
-                       if (of_property_read_u32(newpar, "#interrupt-cells",
-                                                &newintsize)) {
-                               pr_debug(" -> parent lacks #interrupt-cells!\n");
-                               goto fail;
-                       }
-                       if (of_property_read_u32(newpar, "#address-cells",
-                                                &newaddrsize))
-                               newaddrsize = 0;
-
-                       pr_debug(" -> newintsize=%d, newaddrsize=%d\n",
-                           newintsize, newaddrsize);
-
-                       /* Check for malformed properties */
-                       if (WARN_ON(newaddrsize + newintsize > MAX_PHANDLE_ARGS)
-                           || (imaplen < (newaddrsize + newintsize))) {
-                               rc = -EFAULT;
+                       oldimap = imap;
+                       imap = of_irq_parse_imap_parent(oldimap, imaplen, out_irq);
+                       if (!imap)
                                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) {
@@ -287,11 +312,11 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
                 * Successfully parsed an interrupt-map translation; copy new
                 * interrupt specifier into the out_irq structure
                 */
-               match_array = imap - newaddrsize - newintsize;
-               for (i = 0; i < newintsize; i++)
-                       out_irq->args[i] = be32_to_cpup(imap - newintsize + i);
-               out_irq->args_count = intsize = newintsize;
-               addrsize = newaddrsize;
+               match_array = oldimap + 1;
+
+               newpar = out_irq->np;
+               intsize = out_irq->args_count;
+               addrsize = (imap - match_array) - intsize;
 
                if (ipar == newpar) {
                        pr_debug("%pOF interrupt-map entry to self\n", ipar);
@@ -300,7 +325,6 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
 
        skiplevel:
                /* Iterate again with new parent */
-               out_irq->np = newpar;
                pr_debug(" -> new parent: %pOF\n", newpar);
                of_node_put(ipar);
                ipar = newpar;
@@ -310,7 +334,6 @@ int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
 
  fail:
        of_node_put(ipar);
-       of_node_put(newpar);
 
        return rc;
 }
index 94fc0aa07af9e383a1e0d844ac620f26a025bfd1..04aa2a91f851acd9b2da47c5b53edb025b9a1f8f 100644 (file)
@@ -159,6 +159,9 @@ extern void __of_sysfs_remove_bin_file(struct device_node *np,
 extern int of_bus_n_addr_cells(struct device_node *np);
 extern int of_bus_n_size_cells(struct device_node *np);
 
+const __be32 *of_irq_parse_imap_parent(const __be32 *imap, int len,
+                                      struct of_phandle_args *out_irq);
+
 struct bus_dma_region;
 #if defined(CONFIG_OF_ADDRESS) && defined(CONFIG_HAS_DMA)
 int of_dma_get_range(struct device_node *np,
This page took 0.062998 seconds and 4 git commands to generate.