Commit 935df1bd authored by Rob Herring (Arm)'s avatar Rob Herring (Arm)
Browse files

of/irq: Factor out parsing of interrupt-map parent phandle+args from of_irq_parse_raw()



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: default avatarMarc Zyngier <maz@kernel.org>
Tested-by: default avatarAnup Patel <apatel@ventanamicro.com>
Link: https://lore.kernel.org/r/20240529-dt-interrupt-map-fix-v2-1-ef86dc5bcd2a@kernel.org


Signed-off-by: default avatarRob Herring (Arm) <robh@kernel.org>
parent 321e4fa6
Loading
Loading
Loading
Loading
+74 −51
Original line number Diff line number Diff line
@@ -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");
			oldimap = imap;
			imap = of_irq_parse_imap_parent(oldimap, imaplen, out_irq);
			if (!imap)
				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;
			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;
}
+3 −0
Original line number Diff line number Diff line
@@ -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,