I created a new patch.
Code: Select all
diff -rupN a/drivers/of/irq.c b/drivers/of/irq.c
--- a/drivers/of/irq.c	2024-06-23 23:08:54.000000000 +0200
+++ b/drivers/of/irq.c	2024-06-29 16:01:29.161973980 +0200
@@ -25,8 +25,6 @@
 #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
@@ -98,57 +96,6 @@ static const char * const of_irq_imap_ab
 	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
@@ -165,12 +112,12 @@ const __be32 *of_irq_parse_imap_parent(c
  */
 int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
 {
-	struct device_node *ipar, *tnode, *old = NULL;
+	struct device_node *ipar, *tnode, *old = NULL, *newpar = NULL;
 	__be32 initial_match_array[MAX_PHANDLE_ARGS];
 	const __be32 *match_array = initial_match_array;
-	const __be32 *tmp, dummy_imask[] = { [0 ... MAX_PHANDLE_ARGS] = cpu_to_be32(~0) };
-	u32 intsize = 1, addrsize;
-	int i, rc = -EINVAL;
+	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;
 
 #ifdef DEBUG
 	of_print_phandle_args("of_irq_parse_raw: ", out_irq);
@@ -229,9 +176,6 @@ int of_irq_parse_raw(const __be32 *addr,
 
 	/* 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
@@ -272,7 +216,7 @@ int of_irq_parse_raw(const __be32 *addr,
 
 		/* Parse interrupt-map */
 		match = 0;
-		while (imaplen > (addrsize + intsize + 1)) {
+		while (imaplen > (addrsize + intsize + 1) && !match) {
 			/* Compare specifiers */
 			match = 1;
 			for (i = 0; i < (addrsize + intsize); i++, imaplen--)
@@ -280,17 +224,48 @@ int of_irq_parse_raw(const __be32 *addr,
 
 			pr_debug(" -> match=%d (imaplen=%d)\n", match, imaplen);
 
-			oldimap = imap;
-			imap = of_irq_parse_imap_parent(oldimap, imaplen, out_irq);
-			if (!imap)
+			/* 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;
+			}
 
-			match &= of_device_is_available(out_irq->np);
-			if (match)
-				break;
+			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;
+				goto fail;
+			}
+
+			imap += newaddrsize + newintsize;
+			imaplen -= newaddrsize + newintsize;
 
-			of_node_put(out_irq->np);
-			imaplen -= imap - oldimap;
 			pr_debug(" -> imaplen=%d\n", imaplen);
 		}
 		if (!match) {
@@ -312,11 +287,11 @@ int of_irq_parse_raw(const __be32 *addr,
 		 * 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;
+		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;
 
 		if (ipar == newpar) {
 			pr_debug("%pOF interrupt-map entry to self\n", ipar);
@@ -325,6 +300,7 @@ int of_irq_parse_raw(const __be32 *addr,
 
 	skiplevel:
 		/* Iterate again with new parent */
+		out_irq->np = newpar;
 		pr_debug(" -> new parent: %pOF\n", newpar);
 		of_node_put(ipar);
 		ipar = newpar;
@@ -334,6 +310,7 @@ int of_irq_parse_raw(const __be32 *addr,
 
  fail:
 	of_node_put(ipar);
+	of_node_put(newpar);
 
 	return rc;
 }
diff -rupN a/drivers/of/of_private.h b/drivers/of/of_private.h
--- a/drivers/of/of_private.h	2024-06-23 23:08:54.000000000 +0200
+++ b/drivers/of/of_private.h	2024-06-29 16:01:29.166973915 +0200
@@ -159,9 +159,6 @@ extern void __of_sysfs_remove_bin_file(s
 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,
diff -rupN a/drivers/of/property.c b/drivers/of/property.c
--- a/drivers/of/property.c	2024-06-23 23:08:54.000000000 +0200
+++ b/drivers/of/property.c	2024-06-29 16:02:54.141867362 +0200
@@ -1306,10 +1306,10 @@ static struct device_node *parse_interru
 static struct device_node *parse_interrupt_map(struct device_node *np,
 					       const char *prop_name, int index)
 {
-	const __be32 *imap, *imap_end;
+	const __be32 *imap, *imap_end, *addr;
 	struct of_phandle_args sup_args;
 	u32 addrcells, intcells;
-	int imaplen;
+	int i, imaplen;
 
 	if (!IS_ENABLED(CONFIG_OF_IRQ))
 		return NULL;
@@ -1322,23 +1322,33 @@ static struct device_node *parse_interru
 	addrcells = of_bus_n_addr_cells(np);
 
 	imap = of_get_property(np, "interrupt-map", &imaplen);
-	imaplen /= sizeof(*imap);
-	if (!imap)
+	if (!imap || imaplen <= (addrcells + intcells))
 		return NULL;
-
 	imap_end = imap + imaplen;
 
-	for (int i = 0; imap + addrcells + intcells + 1 < imap_end; i++) {
-		imap += addrcells + intcells;
-
-		imap = of_irq_parse_imap_parent(imap, imap_end - imap, &sup_args);
-		if (!imap)
+	while (imap < imap_end) {
+		addr = imap;
+		imap += addrcells;
+
+		sup_args.np = np;
+		sup_args.args_count = intcells;
+		for (i = 0; i < intcells; i++)
+			sup_args.args[i] = be32_to_cpu(imap[i]);
+		imap += intcells;
+
+		/*
+		 * Upon success, the function of_irq_parse_raw() returns
+		 * interrupt controller DT node pointer in sup_args.np.
+		 */
+		if (of_irq_parse_raw(addr, &sup_args))
 			return NULL;
 
-		if (i == index)
+		if (!index)
 			return sup_args.np;
 
 		of_node_put(sup_args.np);
+		imap += sup_args.args_count + 1;
+		index--;
 	}
 
 	return NULL;