From 63353810c08499476efc1e1e85ca7b8e33d54ef7 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sat, 18 Apr 2026 23:04:20 +0900 Subject: of/fdt: remove redundant memset in __unflatten_device_tree() Now that memblock and slab allocators are the only allocators and both return zero-initialized memory, zeroing the memory ourselves is redundant. The allocators used are: - kernel_tree_alloc uses kzalloc() - early_init_dt_alloc_memory_arch() and dt_alloc_memory() both use memblock_alloc() Remove redundant memset after the allocation. No funtional change. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260418140420.2221736-1-ekffu200098@gmail.com Signed-off-by: Rob Herring (Arm) --- drivers/of/fdt.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 82f7327c59ea..ba65e36e183c 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -391,8 +391,6 @@ void *__unflatten_device_tree(const void *blob, if (!mem) return NULL; - memset(mem, 0, size); - *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); pr_debug(" unflattening %p...\n", mem); -- cgit v1.2.3 From 1e54c31b9cbbb42162e2e4317c18c8a8b350a79d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 7 May 2026 16:18:10 +0800 Subject: drivers/of: validate live-tree string properties before string use `populate_properties()` stores live-tree property values as raw byte sequences plus a separate `length`. They are not globally guaranteed to be NUL-terminated. `of_prop_next_string()` iterates string-list properties by walking raw bytes, `__of_node_is_type()` checks `device_type`, `__of_device_is_status()` checks `status`, and `of_alias_from_compatible()` reads the first `compatible` entry. These paths must validate that the relevant string fits within the property bounds before they hand it to C string helpers. Validate these live-tree string properties within their declared bounds. In particular, make `of_prop_next_string()` reject malformed entries before returning them, keep the `device_type` check inside the existing no-lock helper path, and add unit coverage for malformed first and trailing string-list entries. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260507081812.91838-1-pengpeng@iscas.ac.cn Signed-off-by: Rob Herring (Arm) --- drivers/of/base.c | 43 ++++++++++++++++++++++++++----------------- drivers/of/property.c | 27 +++++++++++++++++++++------ drivers/of/unittest.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/of/base.c b/drivers/of/base.c index a650c91897cc..f493a7a99a52 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -82,9 +82,17 @@ EXPORT_SYMBOL(of_node_name_prefix); static bool __of_node_is_type(const struct device_node *np, const char *type) { - const char *match = __of_get_property(np, "device_type", NULL); + const char *match; + int len; + + if (!np || !type) + return false; + + match = __of_get_property(np, "device_type", &len); + if (!match || len <= 0 || strnlen(match, len) >= len) + return false; - return np && match && type && !strcmp(match, type); + return !strcmp(match, type); } #define EXCLUDED_DEFAULT_CELLS_PLATFORMS ( \ @@ -511,22 +519,22 @@ static bool __of_device_is_status(const struct device_node *device, return false; status = __of_get_property(device, "status", &statlen); - if (status == NULL) + if (!status || statlen <= 0) + return false; + if (strnlen(status, statlen) >= statlen) return false; - if (statlen > 0) { - while (*strings) { - unsigned int len = strlen(*strings); + while (*strings) { + unsigned int len = strlen(*strings); - if ((*strings)[len - 1] == '-') { - if (!strncmp(status, *strings, len)) - return true; - } else { - if (!strcmp(status, *strings)) - return true; - } - strings++; + if ((*strings)[len - 1] == '-') { + if (!strncmp(status, *strings, len)) + return true; + } else { + if (!strcmp(status, *strings)) + return true; } + strings++; } return false; @@ -1237,10 +1245,11 @@ EXPORT_SYMBOL(of_find_matching_node_and_match); int of_alias_from_compatible(const struct device_node *node, char *alias, int len) { const char *compatible, *p; - int cplen; + int ret; - compatible = of_get_property(node, "compatible", &cplen); - if (!compatible || strlen(compatible) > cplen) + ret = of_property_read_string_index(node, "compatible", 0, + &compatible); + if (ret) return -ENODEV; p = strchr(compatible, ','); strscpy(alias, p ? p + 1 : compatible, len); diff --git a/drivers/of/property.c b/drivers/of/property.c index 136946f8b746..b276d1de3222 100644 --- a/drivers/of/property.c +++ b/drivers/of/property.c @@ -648,16 +648,31 @@ EXPORT_SYMBOL_GPL(of_prop_next_u32); const char *of_prop_next_string(const struct property *prop, const char *cur) { - const void *curv = cur; + const char *curv; + const char *end; + size_t len; - if (!prop) + if (!prop || !prop->value || !prop->length) return NULL; - if (!cur) - return prop->value; + curv = cur ? cur : prop->value; + end = prop->value + prop->length; - curv += strlen(cur) + 1; - if (curv >= prop->value + prop->length) + if (curv < (const char *)prop->value || curv >= end) + return NULL; + + if (cur) { + len = strnlen(curv, end - curv); + if (len >= end - curv) + return NULL; + + curv += len + 1; + if (curv >= end) + return NULL; + } + + len = strnlen(curv, end - curv); + if (len >= end - curv) return NULL; return curv; diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c index 4078569a0f96..e255f54f4d76 100644 --- a/drivers/of/unittest.c +++ b/drivers/of/unittest.c @@ -713,6 +713,7 @@ static void __init of_unittest_parse_phandle_with_args_map(void) static void __init of_unittest_property_string(void) { const char *strings[4]; + const struct property *prop; struct device_node *np; int rc; @@ -789,6 +790,37 @@ static void __init of_unittest_property_string(void) strings[1] = NULL; rc = of_property_read_string_array(np, "phandle-list-names", strings, 1); unittest(rc == 1 && strings[1] == NULL, "Overwrote end of string array; rc=%i, str='%s'\n", rc, strings[1]); + + /* of_prop_next_string() tests */ + prop = of_find_property(np, "phandle-list-names", NULL); + strings[0] = of_prop_next_string(prop, NULL); + unittest(strings[0] && !strcmp(strings[0], "first"), + "of_prop_next_string() failure; got '%s'\n", strings[0]); + strings[0] = of_prop_next_string(prop, strings[0]); + unittest(strings[0] && !strcmp(strings[0], "second"), + "of_prop_next_string() failure; got '%s'\n", strings[0]); + strings[0] = of_prop_next_string(prop, strings[0]); + unittest(strings[0] && !strcmp(strings[0], "third"), + "of_prop_next_string() failure; got '%s'\n", strings[0]); + strings[0] = of_prop_next_string(prop, strings[0]); + unittest(!strings[0], + "of_prop_next_string() should return NULL at end of list\n"); + + prop = of_find_property(np, "unterminated-string", NULL); + strings[0] = of_prop_next_string(prop, NULL); + unittest(!strings[0], + "of_prop_next_string() should reject unterminated first string\n"); + + prop = of_find_property(np, "unterminated-string-list", NULL); + strings[0] = of_prop_next_string(prop, NULL); + unittest(strings[0] && !strcmp(strings[0], "first"), + "of_prop_next_string() failure; got '%s'\n", strings[0]); + strings[0] = of_prop_next_string(prop, strings[0]); + unittest(strings[0] && !strcmp(strings[0], "second"), + "of_prop_next_string() failure; got '%s'\n", strings[0]); + strings[0] = of_prop_next_string(prop, strings[0]); + unittest(!strings[0], + "of_prop_next_string() should reject unterminated trailing string\n"); } #define propcmp(p1, p2) (((p1)->length == (p2)->length) && \ -- cgit v1.2.3 From 0b6b12c5dcce16e604d4cde953bef46531b98571 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 7 May 2026 16:18:11 +0800 Subject: drivers/of: validate status properties in reconfig state changes Live-tree reconfiguration properties also carry raw values plus explicit lengths. `of_reconfig_get_state_change()` currently treats `status` property values as NUL-terminated strings and feeds them straight into `strcmp()`. Factor the `"okay"` / `"ok"` check out into a helper that first verifies that the property contains a bounded C string within `prop->length`. Malformed `status` updates should be treated as not enabling the node. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260507081812.91838-2-pengpeng@iscas.ac.cn Signed-off-by: Rob Herring (Arm) --- drivers/of/dynamic.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c index ade288372101..442590d6511a 100644 --- a/drivers/of/dynamic.c +++ b/drivers/of/dynamic.c @@ -74,6 +74,20 @@ static const char *action_names[] = { [OF_RECONFIG_UPDATE_PROPERTY] = "UPDATE_PROPERTY", }; +static bool of_property_status_ok(const struct property *prop) +{ + const char *status; + + if (!prop || !prop->value || prop->length <= 0) + return false; + + status = prop->value; + if (strnlen(status, prop->length) >= prop->length) + return false; + + return !strcmp(status, "okay") || !strcmp(status, "ok"); +} + #define _do_print(func, prefix, action, node, prop, ...) ({ \ func("changeset: " prefix "%-15s %pOF%s%s\n", \ ##__VA_ARGS__, action_names[action], node, \ @@ -135,11 +149,9 @@ int of_reconfig_get_state_change(unsigned long action, struct of_reconfig_data * if (prop && !strcmp(prop->name, "status")) { is_status = 1; - status_state = !strcmp(prop->value, "okay") || - !strcmp(prop->value, "ok"); + status_state = of_property_status_ok(prop); if (old_prop) - old_status_state = !strcmp(old_prop->value, "okay") || - !strcmp(old_prop->value, "ok"); + old_status_state = of_property_status_ok(old_prop); } switch (action) { -- cgit v1.2.3 From 7b9f263909a57e2463c6a543d72651e00a3fd0d6 Mon Sep 17 00:00:00 2001 From: Wandun Chen Date: Mon, 25 May 2026 20:17:00 +0800 Subject: of: reserved_mem: only support one entry in reg property A /reserved-memory child node may have multiple tuples in 'reg' property, but multiple entries in 'reg' have never been fully functional: - fdt_scan_reserved_mem() in the early pass loops over every tuple and reserves them all. - fdt_scan_reserved_mem_late() reads 'reg' by of_flat_dt_get_addr_size(), which returns false if entries != 1. So 'reg' property with multiple entries will be skipped, no reserved_mem entry is created in reserved_mem[]. Supporting multiple tuples is not a good idea: - It requires reserved_mem_ops->node_init support. Currently, CMA(rmem_cma_setup) and DMA(rmem_dma_setup) are not supported. - of_reserved_mem_lookup() is name-based, only the first entry in multiple tuples will be found. So change to support one entry in 'reg' property. Also update dt binding: https://github.com/devicetree-org/dt-schema/pull/197 Suggested-by: Rob Herring Signed-off-by: Wandun Chen Tested-by: Meijing Zhao Link: https://lore.kernel.org/all/20260506014752.GA280279-robh@kernel.org/ Link: https://patch.msgid.link/20260525121700.2706141-1-chenwandun1@gmail.com Signed-off-by: Rob Herring (Arm) --- drivers/of/of_reserved_mem.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index 8d5777cb5d1b..ce1d5530ec0f 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -128,42 +128,43 @@ static int __init early_init_dt_reserve_memory(phys_addr_t base, } /* - * __reserved_mem_reserve_reg() - reserve all memory described in 'reg' property + * __reserved_mem_reserve_reg() - reserve memory described in the + * first entry in 'reg' property */ static int __init __reserved_mem_reserve_reg(unsigned long node, const char *uname) { phys_addr_t base, size; - int i, len, err; + int len, err; const __be32 *prop; bool nomap; + u64 b, s; prop = of_flat_dt_get_addr_size_prop(node, "reg", &len); - if (!prop) + if (!prop || !len) return -ENOENT; + if (len > 1) + pr_warn("Reserved memory: node '%s' has %d entries, only the first is used\n", + uname, len); + nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; err = fdt_validate_reserved_mem_node(node, NULL); if (err && err != -ENODEV) return err; - for (i = 0; i < len; i++) { - u64 b, s; - - of_flat_dt_read_addr_size(prop, i, &b, &s); - - base = b; - size = s; + of_flat_dt_read_addr_size(prop, 0, &b, &s); + base = b; + size = s; - if (size && early_init_dt_reserve_memory(base, size, nomap) == 0) { - fdt_fixup_reserved_mem_node(node, base, size); - pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %lu MiB\n", - uname, &base, (unsigned long)(size / SZ_1M)); - } else { - pr_err("Reserved memory: failed to reserve memory for node '%s': base %pa, size %lu MiB\n", - uname, &base, (unsigned long)(size / SZ_1M)); - } + if (size && early_init_dt_reserve_memory(base, size, nomap) == 0) { + fdt_fixup_reserved_mem_node(node, base, size); + pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %lu MiB\n", + uname, &base, (unsigned long)(size / SZ_1M)); + } else { + pr_err("Reserved memory: failed to reserve memory for node '%s': base %pa, size %lu MiB\n", + uname, &base, (unsigned long)(size / SZ_1M)); } return 0; } @@ -274,20 +275,24 @@ void __init fdt_scan_reserved_mem_late(void) } fdt_for_each_subnode(child, fdt, node) { + const __be32 *prop; const char *uname; u64 b, s; int ret; + int len; if (!of_fdt_device_is_available(fdt, child)) continue; - if (!of_flat_dt_get_addr_size(child, "reg", &b, &s)) + prop = of_flat_dt_get_addr_size_prop(child, "reg", &len); + if (!prop || !len) continue; ret = fdt_validate_reserved_mem_node(child, NULL); if (ret && ret != -ENODEV) continue; + of_flat_dt_read_addr_size(prop, 0, &b, &s); base = b; size = s; -- cgit v1.2.3 From 0c1b852f13e207d642cef5c22002a91c8f07673e Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Thu, 4 Jun 2026 00:18:09 +0900 Subject: drivers/of: fdt: Make ibm,phandle logic only happen on pseries The "ibm,phandle" thing only seems to be needed on pseries machines but everyone gets it so they get a string and a little bit of useless code. In __of_attach_node() the pseries specific part uses IS_ENABLED(CONFIG_PPC_PSERIES) so do that here too. Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260603151809.3256280-1-daniel@thingy.jp Signed-off-by: Rob Herring (Arm) --- drivers/of/fdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index ba65e36e183c..26f66046cc32 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -145,7 +145,7 @@ static void populate_properties(const void *blob, * used in pSeries dynamic device tree * stuff */ - if (!strcmp(pname, "ibm,phandle")) + if (IS_ENABLED(CONFIG_PPC_PSERIES) && !strcmp(pname, "ibm,phandle")) np->phandle = be32_to_cpup(val); pp->name = (char *)pname; -- cgit v1.2.3 From e1686ca81dbf3edbde589b7daf312b45cbf76e03 Mon Sep 17 00:00:00 2001 From: Wandun Chen Date: Thu, 4 Jun 2026 09:53:32 +0800 Subject: of: reserved_mem: avoid post-init UAF when alloc_reserved_mem_array() fails The global pointer 'reserved_mem' continues to reference the reserved_mem_array which lives in __initdata if alloc_reserved_mem_array() fails. of_reserved_mem_lookup() is exported for post-init use, that would dereference freed memory and trigger a use-after-free. So reset reserved_mem_count to 0 when alloc_reserved_mem_array() fails. Fixes: 00c9a452a235 ("of: reserved_mem: Add code to dynamically allocate reserved_mem array") Signed-off-by: Wandun Chen Link: https://patch.msgid.link/20260604015332.3669384-1-chenwandun1@gmail.com Signed-off-by: Rob Herring (Arm) --- drivers/of/of_reserved_mem.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index ce1d5530ec0f..d918dc7f07fb 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -69,29 +69,32 @@ static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size, * the initial static array is copied over to this new array and * the new array is used from this point on. */ -static void __init alloc_reserved_mem_array(void) +static int __init alloc_reserved_mem_array(void) { struct reserved_mem *new_array; size_t alloc_size, copy_size, memset_size; + int ret; + + if (!total_reserved_mem_cnt) + return 0; alloc_size = array_size(total_reserved_mem_cnt, sizeof(*new_array)); if (alloc_size == SIZE_MAX) { - pr_err("Failed to allocate memory for reserved_mem array with err: %d", -EOVERFLOW); - return; + ret = -EOVERFLOW; + goto fail; } new_array = memblock_alloc(alloc_size, SMP_CACHE_BYTES); if (!new_array) { - pr_err("Failed to allocate memory for reserved_mem array with err: %d", -ENOMEM); - return; + ret = -ENOMEM; + goto fail; } copy_size = array_size(reserved_mem_count, sizeof(*new_array)); if (copy_size == SIZE_MAX) { memblock_free(new_array, alloc_size); - total_reserved_mem_cnt = MAX_RESERVED_REGIONS; - pr_err("Failed to allocate memory for reserved_mem array with err: %d", -EOVERFLOW); - return; + ret = -EOVERFLOW; + goto fail; } memset_size = alloc_size - copy_size; @@ -100,6 +103,12 @@ static void __init alloc_reserved_mem_array(void) memset(new_array + reserved_mem_count, 0, memset_size); reserved_mem = new_array; + return 0; + +fail: + pr_err("Failed to allocate memory for reserved_mem array with err: %d", ret); + reserved_mem_count = 0; + return ret; } static void fdt_init_reserved_mem_node(unsigned long node, const char *uname, @@ -267,7 +276,8 @@ void __init fdt_scan_reserved_mem_late(void) } /* Attempt dynamic allocation of a new reserved_mem array */ - alloc_reserved_mem_array(); + if (alloc_reserved_mem_array()) + return; if (__reserved_mem_check_root(node)) { pr_err("Reserved memory: unsupported node format, ignoring\n"); -- cgit v1.2.3 From 5901eda2ed99ba0d3661da6eb265970559323bb3 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Wed, 29 Apr 2026 23:14:39 +0300 Subject: of: cpu: add check in __of_find_n_match_cpu_property() In __of_find_n_match_cpu_property(), checking the variable ac for 0 won't prevent a possible overflow when multiplying it by sizeof(*cell). Besides, of_read_number() (called in the *for* loop) can't return correct result if that variable (which equals the #address-cells prop's value) exceeds 2, so additionally checking for that seems logical... Found by Linux Verification Center (linuxtesting.org) with the Svace static analysis tool. Fixes: f3cea45a77c8 ("of: Fix iteration bug over CPU reg properties") Signed-off-by: Sergey Shtylyov Link: https://patch.msgid.link/0c7bf7e9-887c-42d5-bcfb-0ba7fe1e70b6@auroraos.dev Signed-off-by: Rob Herring (Arm) --- drivers/of/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/of/cpu.c b/drivers/of/cpu.c index 5214dc3d05ae..bd0e918d6f29 100644 --- a/drivers/of/cpu.c +++ b/drivers/of/cpu.c @@ -60,7 +60,7 @@ static bool __of_find_n_match_cpu_property(struct device_node *cpun, cell = of_get_property(cpun, prop_name, &prop_len); if (!cell && !ac && arch_match_cpu_phys_id(cpu, 0)) return true; - if (!cell || !ac) + if (!cell || !ac || ac > 2) return false; prop_len /= sizeof(*cell) * ac; for (tid = 0; tid < prop_len; tid++) { -- cgit v1.2.3 From af96a303789410bb3face6b16bd9b9090ddfeec1 Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 19:51:21 +0100 Subject: drivers/of/overlay: Use memcpy() to copy known length strings Avoid calls to strcpy(). The lengths of the strings have been used for the kzalloc(), replace the strcpy() calls with memcpy() using the known lengths. Signed-off-by: David Laight Link: https://patch.msgid.link/20260608185121.22331-1-david.laight.linux@gmail.com Signed-off-by: Rob Herring (Arm) --- drivers/of/overlay.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index c1c5686fc7b1..656867009514 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -258,8 +258,8 @@ static struct property *dup_and_fixup_symbol_prop( if (!new_prop->name || !new_prop->value) goto err_free_new_prop; - strcpy(new_prop->value, target_path); - strcpy(new_prop->value + target_path_len, path_tail); + memcpy(new_prop->value, target_path, target_path_len); + memcpy(new_prop->value + target_path_len, path_tail, path_tail_len); of_property_set_flag(new_prop, OF_DYNAMIC); -- cgit v1.2.3 From cfba13a18672415591d4db5320ac56ef67b460f4 Mon Sep 17 00:00:00 2001 From: Wandun Chen Date: Wed, 27 May 2026 11:29:07 +0800 Subject: of: reserved_mem: handle NULL name in of_reserved_mem_lookup() Prepare for an upcoming change that appends /memreserve/ entries to reserved_mem[]; such entries have no name. No functional change. Signed-off-by: Wandun Chen Link: https://patch.msgid.link/20260527032917.3385849-2-chenwandun1@gmail.com Signed-off-by: Rob Herring (Arm) --- drivers/of/of_reserved_mem.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index d918dc7f07fb..c41f5550cb7b 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -803,7 +803,8 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np) name = kbasename(np->full_name); for (i = 0; i < reserved_mem_count; i++) - if (!strcmp(reserved_mem[i].name, name)) + if (reserved_mem[i].name && + !strcmp(reserved_mem[i].name, name)) return &reserved_mem[i]; return NULL; -- cgit v1.2.3 From 50a488de5fccfc58eedc6d0525f92dc64a41f806 Mon Sep 17 00:00:00 2001 From: Wandun Chen Date: Wed, 27 May 2026 11:29:10 +0800 Subject: of: reserved_mem: zero total_reserved_mem_cnt if no valid /reserved-memory entry Prepare for storing /memreserve/ entries in the reserved_mem array. Zero total_reserved_mem_cnt if no valid /reserved-memory entry, instead of keeping it's initial value of MAX_RESERVED_REGIONS, this allows accounting /memreserve entries based on total_reserved_mem_cnt in a follow-up patch. No functional change. Signed-off-by: Wandun Chen Link: https://patch.msgid.link/20260527032917.3385849-5-chenwandun1@gmail.com Signed-off-by: Rob Herring (Arm) --- drivers/of/of_reserved_mem.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index c41f5550cb7b..82222bd45ac6 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -336,11 +336,14 @@ int __init fdt_scan_reserved_mem(void) const void *fdt = initial_boot_params; node = fdt_path_offset(fdt, "/reserved-memory"); - if (node < 0) + if (node < 0) { + total_reserved_mem_cnt = 0; return -ENODEV; + } if (__reserved_mem_check_root(node) != 0) { pr_err("Reserved memory: unsupported node format, ignoring\n"); + total_reserved_mem_cnt = 0; return -EINVAL; } -- cgit v1.2.3 From d4b52f83f198310c871aa71816a26152eb3898c2 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Wed, 3 Jun 2026 12:43:12 +0530 Subject: of: Add convenience wrappers for of_map_id() Since we now have quite a few users parsing "iommu-map" and "msi-map" properties, give them some wrappers to conveniently encapsulate the appropriate sets of property names. This will also make it easier to then change of_map_id() to correctly account for specifier cells. Reviewed-by: Rob Herring (Arm) Reviewed-by: Frank Li Acked-by: Marc Zyngier Acked-by: Bjorn Helgaas Signed-off-by: Robin Murphy Signed-off-by: Vijayanand Jitta Link: https://patch.msgid.link/20260603-parse_iommu_cells-v16-1-dc509dacb19a@oss.qualcomm.com Signed-off-by: Rob Herring (Arm) --- drivers/cdx/cdx_msi.c | 5 ++--- drivers/iommu/of_iommu.c | 4 +--- drivers/irqchip/irq-gic-its-msi-parent.c | 2 +- drivers/of/base.c | 38 ++++++++++++++++++++++++++++++++ drivers/of/irq.c | 3 +-- drivers/pci/controller/dwc/pci-imx6.c | 6 ++--- drivers/pci/controller/pcie-apple.c | 3 +-- drivers/xen/grant-dma-ops.c | 3 +-- 8 files changed, 47 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/cdx/cdx_msi.c b/drivers/cdx/cdx_msi.c index 91b95422b263..78edb7308856 100644 --- a/drivers/cdx/cdx_msi.c +++ b/drivers/cdx/cdx_msi.c @@ -128,10 +128,9 @@ static int cdx_msi_prepare(struct irq_domain *msi_domain, int ret; /* Retrieve device ID from requestor ID using parent device */ - ret = of_map_id(parent->of_node, cdx_dev->msi_dev_id, "msi-map", "msi-map-mask", - NULL, &dev_id); + ret = of_map_msi_id(parent->of_node, cdx_dev->msi_dev_id, NULL, &dev_id); if (ret) { - dev_err(dev, "of_map_id failed for MSI: %d\n", ret); + dev_err(dev, "of_map_msi_id failed for MSI: %d\n", ret); return ret; } diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 6b989a62def2..a511ecf21fcd 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -48,9 +48,7 @@ static int of_iommu_configure_dev_id(struct device_node *master_np, struct of_phandle_args iommu_spec = { .args_count = 1 }; int err; - err = of_map_id(master_np, *id, "iommu-map", - "iommu-map-mask", &iommu_spec.np, - iommu_spec.args); + err = of_map_iommu_id(master_np, *id, &iommu_spec.np, iommu_spec.args); if (err) return err; diff --git a/drivers/irqchip/irq-gic-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c index a832cdb2e697..b30d0dc0a1aa 100644 --- a/drivers/irqchip/irq-gic-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-its-msi-parent.c @@ -179,7 +179,7 @@ static int of_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev, u struct device_node *msi_ctrl __free(device_node) = NULL; - return of_map_id(dev->of_node, dev->id, "msi-map", "msi-map-mask", &msi_ctrl, dev_id); + return of_map_msi_id(dev->of_node, dev->id, &msi_ctrl, dev_id); } static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, diff --git a/drivers/of/base.c b/drivers/of/base.c index f493a7a99a52..d936829ff2d2 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2230,3 +2230,41 @@ int of_map_id(const struct device_node *np, u32 id, return 0; } EXPORT_SYMBOL_GPL(of_map_id); + +/** + * of_map_iommu_id - Translate an ID using "iommu-map" bindings. + * @np: root complex device node. + * @id: Requester ID of the device (e.g. PCI RID/BDF or a platform + * stream/device ID) used as the lookup key in the iommu-map table. + * @target: optional pointer to a target device node. + * @id_out: optional pointer to receive the translated ID. + * + * Convenience wrapper around of_map_id() using "iommu-map" and "iommu-map-mask". + * + * Return: 0 on success or a standard error code on failure. + */ +int of_map_iommu_id(const struct device_node *np, u32 id, + struct device_node **target, u32 *id_out) +{ + return of_map_id(np, id, "iommu-map", "iommu-map-mask", target, id_out); +} +EXPORT_SYMBOL_GPL(of_map_iommu_id); + +/** + * of_map_msi_id - Translate an ID using "msi-map" bindings. + * @np: root complex device node. + * @id: Requester ID of the device (e.g. PCI RID/BDF or a platform + * stream/device ID) used as the lookup key in the msi-map table. + * @target: optional pointer to a target device node. + * @id_out: optional pointer to receive the translated ID. + * + * Convenience wrapper around of_map_id() using "msi-map" and "msi-map-mask". + * + * Return: 0 on success or a standard error code on failure. + */ +int of_map_msi_id(const struct device_node *np, u32 id, + struct device_node **target, u32 *id_out) +{ + return of_map_id(np, id, "msi-map", "msi-map-mask", target, id_out); +} +EXPORT_SYMBOL_GPL(of_map_msi_id); diff --git a/drivers/of/irq.c b/drivers/of/irq.c index 6367c67732d2..e37c1b3f8736 100644 --- a/drivers/of/irq.c +++ b/drivers/of/irq.c @@ -817,8 +817,7 @@ u32 of_msi_xlate(struct device *dev, struct device_node **msi_np, u32 id_in) * "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)) + if (!of_map_msi_id(parent_dev->of_node, id_in, msi_np, &id_out)) break; if (!of_check_msi_parent(parent_dev->of_node, msi_np)) break; diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index e35044cc5218..da7bfda5e95d 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1133,8 +1133,7 @@ static int imx_pcie_add_lut_by_rid(struct imx_pcie *imx_pcie, u32 rid) u32 sid = 0; target = NULL; - err_i = of_map_id(dev->of_node, rid, "iommu-map", "iommu-map-mask", - &target, &sid_i); + err_i = of_map_iommu_id(dev->of_node, rid, &target, &sid_i); if (target) { of_node_put(target); } else { @@ -1147,8 +1146,7 @@ static int imx_pcie_add_lut_by_rid(struct imx_pcie *imx_pcie, u32 rid) } target = NULL; - err_m = of_map_id(dev->of_node, rid, "msi-map", "msi-map-mask", - &target, &sid_m); + err_m = of_map_msi_id(dev->of_node, rid, &target, &sid_m); /* * err_m target diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 2d92fc79f6dd..a0937b7b3c4d 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -764,8 +764,7 @@ static int apple_pcie_enable_device(struct pci_host_bridge *bridge, struct pci_d dev_dbg(&pdev->dev, "added to bus %s, index %d\n", pci_name(pdev->bus->self), port->idx); - err = of_map_id(port->pcie->dev->of_node, rid, "iommu-map", - "iommu-map-mask", NULL, &sid); + err = of_map_iommu_id(port->pcie->dev->of_node, rid, NULL, &sid); if (err) return err; diff --git a/drivers/xen/grant-dma-ops.c b/drivers/xen/grant-dma-ops.c index c2603e700178..1b7696b2d762 100644 --- a/drivers/xen/grant-dma-ops.c +++ b/drivers/xen/grant-dma-ops.c @@ -325,8 +325,7 @@ static int xen_dt_grant_init_backend_domid(struct device *dev, struct pci_dev *pdev = to_pci_dev(dev); u32 rid = PCI_DEVID(pdev->bus->number, pdev->devfn); - if (of_map_id(np, rid, "iommu-map", "iommu-map-mask", &iommu_spec.np, - iommu_spec.args)) { + if (of_map_iommu_id(np, rid, &iommu_spec.np, iommu_spec.args)) { dev_dbg(dev, "Cannot translate ID\n"); return -ESRCH; } -- cgit v1.2.3 From f71f07bee9b56b94f7828cf3082ea19ec590de36 Mon Sep 17 00:00:00 2001 From: Charan Teja Kalla Date: Wed, 3 Jun 2026 12:43:13 +0530 Subject: of: Factor arguments passed to of_map_id() into a struct Change of_map_id() to take a pointer to struct of_phandle_args instead of passing target device node and translated IDs separately. Update all callers accordingly. Add an explicit filter_np parameter to of_map_id() and of_map_msi_id() to separate the filter input from the output. Previously, the target parameter served dual purpose: as an input filter (if non-NULL, only match entries targeting that node) and as an output (receiving the matched node with a reference held). Now filter_np is the explicit input filter and arg->np is the pure output. Previously, of_map_id() would call of_node_put() on the matched node when a filter was provided, making reference ownership inconsistent. Remove this internal of_node_put() call so that of_map_id() now always transfers ownership of the matched node reference to the caller via arg->np. Callers are now consistently responsible for releasing this reference with of_node_put(arg->np) when done. Acked-by: Frank Li Suggested-by: Rob Herring (Arm) Suggested-by: Dmitry Baryshkov Signed-off-by: Charan Teja Kalla Signed-off-by: Vijayanand Jitta Link: https://patch.msgid.link/20260603-parse_iommu_cells-v16-2-dc509dacb19a@oss.qualcomm.com Signed-off-by: Rob Herring (Arm) --- drivers/cdx/cdx_msi.c | 7 ++-- drivers/iommu/of_iommu.c | 4 +- drivers/irqchip/irq-gic-its-msi-parent.c | 10 +++-- drivers/of/base.c | 71 ++++++++++++++++++-------------- drivers/of/irq.c | 31 ++++++++++---- drivers/pci/controller/dwc/pci-imx6.c | 53 ++++++++++++------------ drivers/pci/controller/pcie-apple.c | 5 ++- drivers/xen/grant-dma-ops.c | 4 +- 8 files changed, 109 insertions(+), 76 deletions(-) (limited to 'drivers') diff --git a/drivers/cdx/cdx_msi.c b/drivers/cdx/cdx_msi.c index 78edb7308856..c8d832b0b1f5 100644 --- a/drivers/cdx/cdx_msi.c +++ b/drivers/cdx/cdx_msi.c @@ -121,22 +121,23 @@ static int cdx_msi_prepare(struct irq_domain *msi_domain, struct device *dev, int nvec, msi_alloc_info_t *info) { + struct of_phandle_args msi_spec = {}; struct cdx_device *cdx_dev = to_cdx_device(dev); struct device *parent = cdx_dev->cdx->dev; struct msi_domain_info *msi_info; - u32 dev_id; int ret; /* Retrieve device ID from requestor ID using parent device */ - ret = of_map_msi_id(parent->of_node, cdx_dev->msi_dev_id, NULL, &dev_id); + ret = of_map_msi_id(parent->of_node, cdx_dev->msi_dev_id, NULL, &msi_spec); if (ret) { dev_err(dev, "of_map_msi_id failed for MSI: %d\n", ret); return ret; } + of_node_put(msi_spec.np); #ifdef GENERIC_MSI_DOMAIN_OPS /* Set the device Id to be passed to the GIC-ITS */ - info->scratchpad[0].ul = dev_id; + info->scratchpad[0].ul = msi_spec.args[0]; #endif msi_info = msi_get_domain_info(msi_domain->parent); diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index a511ecf21fcd..a18bb60f6f3d 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -45,10 +45,10 @@ static int of_iommu_configure_dev_id(struct device_node *master_np, struct device *dev, const u32 *id) { - struct of_phandle_args iommu_spec = { .args_count = 1 }; + struct of_phandle_args iommu_spec = {}; int err; - err = of_map_iommu_id(master_np, *id, &iommu_spec.np, iommu_spec.args); + err = of_map_iommu_id(master_np, *id, &iommu_spec); if (err) return err; diff --git a/drivers/irqchip/irq-gic-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c index b30d0dc0a1aa..2b8f4cd8ecf6 100644 --- a/drivers/irqchip/irq-gic-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-its-msi-parent.c @@ -151,6 +151,8 @@ static int its_v5_pci_msi_prepare(struct irq_domain *domain, struct device *dev, static int of_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev, u32 *dev_id, phys_addr_t *pa) { + struct device_node *msi_ctrl = NULL; + struct of_phandle_args msi_spec = {}; struct of_phandle_iterator it; int ret; @@ -177,9 +179,11 @@ static int of_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev, u } } - struct device_node *msi_ctrl __free(device_node) = NULL; - - return of_map_msi_id(dev->of_node, dev->id, &msi_ctrl, dev_id); + ret = of_map_msi_id(dev->of_node, dev->id, &msi_ctrl, &msi_spec); + if (!ret) + *dev_id = msi_spec.args[0]; + of_node_put(msi_spec.np); + return ret; } static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, diff --git a/drivers/of/base.c b/drivers/of/base.c index d936829ff2d2..69aa023399d2 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2131,36 +2131,40 @@ int of_find_last_cache_level(unsigned int cpu) * @id: device ID to map. * @map_name: property name of the map to use. * @map_mask_name: optional property name of the mask to use. - * @target: optional pointer to a target device node. - * @id_out: optional pointer to receive the translated ID. + * @filter_np: pointer to an optional filter node, or NULL to allow bypass. + * If non-NULL, the map property must exist (-ENODEV if absent). If + * *filter_np is also non-NULL, only entries targeting that node match. + * @arg: pointer to a &struct of_phandle_args for the result. On success, + * @arg->args[0] will contain the translated ID. If a map entry was + * matched, @arg->np will be set to the target node with a reference + * held that the caller must release with of_node_put(). * * Given a device ID, look up the appropriate implementation-defined * platform ID and/or the target device which receives transactions on that - * ID, as per the "iommu-map" and "msi-map" bindings. Either of @target or - * @id_out may be NULL if only the other is required. If @target 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. + * ID, as per the "iommu-map" and "msi-map" bindings. * * Return: 0 on success or a standard error code on failure. */ int of_map_id(const struct device_node *np, u32 id, const char *map_name, const char *map_mask_name, - struct device_node **target, u32 *id_out) + struct device_node * const *filter_np, struct of_phandle_args *arg) { u32 map_mask, masked_id; int map_len; const __be32 *map = NULL; - if (!np || !map_name || (!target && !id_out)) + if (!np || !map_name || !arg) return -EINVAL; + /* Ensure bypass/no-match success never returns a stale target node. */ + arg->np = NULL; map = of_get_property(np, map_name, &map_len); if (!map) { - if (target) + if (filter_np) return -ENODEV; /* Otherwise, no map implies no translation */ - *id_out = id; + arg->args[0] = id; + arg->args_count = 1; return 0; } @@ -2202,18 +2206,14 @@ int of_map_id(const struct device_node *np, u32 id, if (!phandle_node) return -ENODEV; - if (target) { - if (*target) - of_node_put(phandle_node); - else - *target = phandle_node; - - if (*target != phandle_node) - continue; + if (filter_np && *filter_np && *filter_np != phandle_node) { + of_node_put(phandle_node); + continue; } - if (id_out) - *id_out = masked_id - id_base + out_base; + arg->np = phandle_node; + arg->args[0] = masked_id - id_base + out_base; + arg->args_count = 1; pr_debug("%pOF: %s, using mask %08x, id-base: %08x, out-base: %08x, length: %08x, id: %08x -> %08x\n", np, map_name, map_mask, id_base, out_base, @@ -2222,11 +2222,11 @@ int of_map_id(const struct device_node *np, u32 id, } pr_info("%pOF: no %s translation for id 0x%x on %pOF\n", np, map_name, - id, target && *target ? *target : NULL); + id, filter_np && *filter_np ? *filter_np : NULL); /* Bypasses translation */ - if (id_out) - *id_out = id; + arg->args[0] = id; + arg->args_count = 1; return 0; } EXPORT_SYMBOL_GPL(of_map_id); @@ -2236,17 +2236,19 @@ EXPORT_SYMBOL_GPL(of_map_id); * @np: root complex device node. * @id: Requester ID of the device (e.g. PCI RID/BDF or a platform * stream/device ID) used as the lookup key in the iommu-map table. - * @target: optional pointer to a target device node. - * @id_out: optional pointer to receive the translated ID. + * @arg: pointer to a &struct of_phandle_args for the result. On success, + * @arg->args[0] contains the translated ID. If a map entry was matched, + * @arg->np holds a reference to the target node that the caller must + * release with of_node_put(). * * Convenience wrapper around of_map_id() using "iommu-map" and "iommu-map-mask". * * Return: 0 on success or a standard error code on failure. */ int of_map_iommu_id(const struct device_node *np, u32 id, - struct device_node **target, u32 *id_out) + struct of_phandle_args *arg) { - return of_map_id(np, id, "iommu-map", "iommu-map-mask", target, id_out); + return of_map_id(np, id, "iommu-map", "iommu-map-mask", NULL, arg); } EXPORT_SYMBOL_GPL(of_map_iommu_id); @@ -2255,16 +2257,21 @@ EXPORT_SYMBOL_GPL(of_map_iommu_id); * @np: root complex device node. * @id: Requester ID of the device (e.g. PCI RID/BDF or a platform * stream/device ID) used as the lookup key in the msi-map table. - * @target: optional pointer to a target device node. - * @id_out: optional pointer to receive the translated ID. + * @filter_np: pointer to an optional filter node, or NULL to allow bypass. + * If non-NULL, the map property must exist (-ENODEV if absent). If + * *filter_np is also non-NULL, only entries targeting that node match. + * @arg: pointer to a &struct of_phandle_args for the result. On success, + * @arg->args[0] contains the translated ID. If a map entry was matched, + * @arg->np holds a reference to the target node that the caller must + * release with of_node_put(). * * Convenience wrapper around of_map_id() using "msi-map" and "msi-map-mask". * * Return: 0 on success or a standard error code on failure. */ int of_map_msi_id(const struct device_node *np, u32 id, - struct device_node **target, u32 *id_out) + struct device_node * const *filter_np, struct of_phandle_args *arg) { - return of_map_id(np, id, "msi-map", "msi-map-mask", target, id_out); + return of_map_id(np, id, "msi-map", "msi-map-mask", filter_np, arg); } EXPORT_SYMBOL_GPL(of_map_msi_id); diff --git a/drivers/of/irq.c b/drivers/of/irq.c index e37c1b3f8736..967c19100879 100644 --- a/drivers/of/irq.c +++ b/drivers/of/irq.c @@ -796,14 +796,15 @@ static int of_check_msi_parent(struct device_node *dev_node, struct device_node /** * 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 + * @msi_np: Pointer to target MSI controller node, or NULL if the caller + * only needs the translated ID without receiving the controller node. + * If non-NULL and pointing to a non-NULL node, only entries targeting + * that node will be matched. If non-NULL and pointing to NULL, it will + * receive the first matching target node with a reference held. * @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. */ @@ -817,9 +818,22 @@ u32 of_msi_xlate(struct device *dev, struct device_node **msi_np, u32 id_in) * "msi-map" or an "msi-parent" property. */ for (parent_dev = dev; parent_dev; parent_dev = parent_dev->parent) { - if (!of_map_msi_id(parent_dev->of_node, id_in, msi_np, &id_out)) + struct of_phandle_args msi_spec = {}; + + if (!of_map_msi_id(parent_dev->of_node, id_in, msi_np, &msi_spec)) { + if (msi_spec.np) { + /* msi-map matched: use the translated ID and target node */ + if (msi_spec.args_count > 0) + id_out = msi_spec.args[0]; + if (msi_np && !*msi_np) + *msi_np = of_node_get(msi_spec.np); + of_node_put(msi_spec.np); + } + /* msi-map present but no match → stop walking */ break; - if (!of_check_msi_parent(parent_dev->of_node, msi_np)) + } + /* -ENODEV: msi-map absent → check for msi-parent */ + if (msi_np && !of_check_msi_parent(parent_dev->of_node, msi_np)) break; } return id_out; @@ -841,9 +855,12 @@ struct irq_domain *of_msi_map_get_device_domain(struct device *dev, u32 id, u32 bus_token) { struct device_node *np = NULL; + struct irq_domain *d; of_msi_xlate(dev, &np, id); - return irq_find_matching_host(np, bus_token); + d = irq_find_matching_host(np, bus_token); + of_node_put(np); + return d; } /** diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index da7bfda5e95d..f972c760deee 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1126,41 +1126,42 @@ static void imx_pcie_remove_lut(struct imx_pcie *imx_pcie, u16 rid) static int imx_pcie_add_lut_by_rid(struct imx_pcie *imx_pcie, u32 rid) { + struct of_phandle_args iommu_spec = {}; + struct of_phandle_args msi_spec = {}; struct device *dev = imx_pcie->pci->dev; - struct device_node *target; + struct device_node *msi_filter = NULL; u32 sid_i, sid_m; int err_i, err_m; u32 sid = 0; - target = NULL; - err_i = of_map_iommu_id(dev->of_node, rid, &target, &sid_i); - if (target) { - of_node_put(target); - } else { - /* - * "target == NULL && err_i == 0" means RID out of map range. - * Use 1:1 map RID to streamID. Hardware can't support this - * because the streamID is only 6 bits - */ - err_i = -EINVAL; + err_i = of_map_iommu_id(dev->of_node, rid, &iommu_spec); + if (!err_i) { + if (!iommu_spec.np) + /* + * "iommu_spec.np == NULL && err_i == 0" means RID out of map + * range. Use 1:1 map RID to streamID. Hardware can't support + * this because the streamID is only 6 bits. + */ + err_i = -EINVAL; + else + sid_i = iommu_spec.args[0]; } + of_node_put(iommu_spec.np); - target = NULL; - err_m = of_map_msi_id(dev->of_node, rid, &target, &sid_m); - + err_m = of_map_msi_id(dev->of_node, rid, &msi_filter, &msi_spec); /* - * err_m target - * 0 NULL RID out of range. Use 1:1 map RID to - * streamID, Current hardware can't - * support it, so return -EINVAL. - * != 0 NULL msi-map does not exist, use built-in MSI - * 0 != NULL Get correct streamID from RID - * != 0 != NULL Invalid combination + * err_m msi_spec.np + * 0 != NULL Got correct streamID from RID via msi-map + * 0 NULL msi-map present but RID out of range + * -ENODEV NULL msi-map absent, use built-in MSI controller */ - if (!err_m && !target) - return -EINVAL; - else if (target) - of_node_put(target); /* Find streamID map entry for RID in msi-map */ + if (!err_m) { + if (!msi_spec.np) + /* msi-map present but RID out of range */ + return -EINVAL; + sid_m = msi_spec.args[0]; + } + of_node_put(msi_spec.np); /* * msi-map iommu-map diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index a0937b7b3c4d..c2cffc0659f4 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -755,6 +755,7 @@ static int apple_pcie_enable_device(struct pci_host_bridge *bridge, struct pci_d { u32 sid, rid = pci_dev_id(pdev); struct apple_pcie_port *port; + struct of_phandle_args iommu_spec = {}; int idx, err; port = apple_pcie_get_port(pdev); @@ -764,10 +765,12 @@ static int apple_pcie_enable_device(struct pci_host_bridge *bridge, struct pci_d dev_dbg(&pdev->dev, "added to bus %s, index %d\n", pci_name(pdev->bus->self), port->idx); - err = of_map_iommu_id(port->pcie->dev->of_node, rid, NULL, &sid); + err = of_map_iommu_id(port->pcie->dev->of_node, rid, &iommu_spec); if (err) return err; + of_node_put(iommu_spec.np); + sid = iommu_spec.args[0]; mutex_lock(&port->pcie->lock); idx = bitmap_find_free_region(port->sid_map, port->sid_map_sz, 0); diff --git a/drivers/xen/grant-dma-ops.c b/drivers/xen/grant-dma-ops.c index 1b7696b2d762..2aa1a772a0ff 100644 --- a/drivers/xen/grant-dma-ops.c +++ b/drivers/xen/grant-dma-ops.c @@ -319,13 +319,13 @@ static int xen_dt_grant_init_backend_domid(struct device *dev, struct device_node *np, domid_t *backend_domid) { - struct of_phandle_args iommu_spec = { .args_count = 1 }; + struct of_phandle_args iommu_spec = {}; if (dev_is_pci(dev)) { struct pci_dev *pdev = to_pci_dev(dev); u32 rid = PCI_DEVID(pdev->bus->number, pdev->devfn); - if (of_map_iommu_id(np, rid, &iommu_spec.np, iommu_spec.args)) { + if (of_map_iommu_id(np, rid, &iommu_spec)) { dev_dbg(dev, "Cannot translate ID\n"); return -ESRCH; } -- cgit v1.2.3 From ccb2fd725d411265df8f7ce12a66c226b16014e6 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Wed, 3 Jun 2026 12:43:14 +0530 Subject: of: Respect #{iommu,msi}-cells in maps So far our parsing of {iommu,msi}-map properties has always blindly assumed that the output specifiers will always have exactly 1 cell. This typically does happen to be the case, but is not actually enforced (and the PCI msi-map binding even explicitly states support for 0 or 1 cells) - as a result we've now ended up with dodgy DTs out in the field which depend on this behaviour to map a 1-cell specifier for a 2-cell provider, despite that being bogus per the bindings themselves. Since there is some potential use in being able to map at least single input IDs to multi-cell output specifiers (and properly support 0-cell outputs as well), add support for properly parsing and using the target nodes' #cells values, albeit with the unfortunate complication of still having to work around expectations of the old behaviour too. Since there are multi-cell output specifiers, the callers of of_map_id() may need to get the exact cell output value for further processing. Update of_map_id() to set args_count in the output to reflect the actual number of output specifier cells. Signed-off-by: Robin Murphy Signed-off-by: Charan Teja Kalla Signed-off-by: Vijayanand Jitta Link: https://patch.msgid.link/20260603-parse_iommu_cells-v16-3-dc509dacb19a@oss.qualcomm.com Signed-off-by: Rob Herring (Arm) --- drivers/of/base.c | 168 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 131 insertions(+), 37 deletions(-) (limited to 'drivers') diff --git a/drivers/of/base.c b/drivers/of/base.c index 69aa023399d2..7cb0d7e88247 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2125,19 +2125,49 @@ int of_find_last_cache_level(unsigned int cpu) return cache_level; } +/* + * Some DTs have an iommu-map targeting a 2-cell IOMMU node while + * specifying only 1 cell. Fortunately they all consist of value '1' + * as the 2nd cell entry with the same target, so check for that pattern. + * + * Example: + * IOMMU node: + * #iommu-cells = <2>; + * + * Device node: + * iommu-map = <0x0000 &smmu 0x0000 0x1>, + * <0x0100 &smmu 0x0100 0x1>; + */ +static bool of_check_bad_map(const __be32 *map, int len) +{ + __be32 phandle = map[1]; + + if (len % 4) + return false; + for (int i = 0; i < len; i += 4) { + if (map[i + 1] != phandle || map[i + 3] != cpu_to_be32(1)) + return false; + } + return true; +} + /** * of_map_id - Translate an ID through a downstream mapping. * @np: root complex device node. * @id: device ID to map. * @map_name: property name of the map to use. + * @cells_name: property name of target specifier cells. * @map_mask_name: optional property name of the mask to use. * @filter_np: pointer to an optional filter node, or NULL to allow bypass. * If non-NULL, the map property must exist (-ENODEV if absent). If * *filter_np is also non-NULL, only entries targeting that node match. * @arg: pointer to a &struct of_phandle_args for the result. On success, - * @arg->args[0] will contain the translated ID. If a map entry was - * matched, @arg->np will be set to the target node with a reference - * held that the caller must release with of_node_put(). + * @arg->args_count will be set to the number of output specifier cells + * as defined by @cells_name in the target node, and + * @arg->args[0..args_count-1] will contain the translated output + * specifier values. If a map entry was matched, @arg->np will be set + * to the target node with a reference held that the caller must release + * with of_node_put(). * * Given a device ID, look up the appropriate implementation-defined * platform ID and/or the target device which receives transactions on that @@ -2146,19 +2176,21 @@ int of_find_last_cache_level(unsigned int cpu) * Return: 0 on success or a standard error code on failure. */ int of_map_id(const struct device_node *np, u32 id, - const char *map_name, const char *map_mask_name, + const char *map_name, const char *cells_name, + const char *map_mask_name, struct device_node * const *filter_np, struct of_phandle_args *arg) { u32 map_mask, masked_id; - int map_len; + int map_bytes, map_len, offset = 0; + bool bad_map = false; const __be32 *map = NULL; - if (!np || !map_name || !arg) + if (!np || !map_name || !cells_name || !arg) return -EINVAL; /* Ensure bypass/no-match success never returns a stale target node. */ arg->np = NULL; - map = of_get_property(np, map_name, &map_len); + map = of_get_property(np, map_name, &map_bytes); if (!map) { if (filter_np) return -ENODEV; @@ -2168,11 +2200,9 @@ int of_map_id(const struct device_node *np, u32 id, return 0; } - if (!map_len || map_len % (4 * sizeof(*map))) { - pr_err("%pOF: Error: Bad %s length: %d\n", np, - map_name, map_len); - return -EINVAL; - } + if (map_bytes % sizeof(*map)) + goto err_map_len; + map_len = map_bytes / sizeof(*map); /* The default is to select all bits. */ map_mask = 0xffffffff; @@ -2185,39 +2215,93 @@ int of_map_id(const struct device_node *np, u32 id, of_property_read_u32(np, map_mask_name, &map_mask); masked_id = map_mask & id; - for ( ; map_len > 0; map_len -= 4 * sizeof(*map), map += 4) { + + while (offset < map_len) { struct device_node *phandle_node; - u32 id_base = be32_to_cpup(map + 0); - u32 phandle = be32_to_cpup(map + 1); - u32 out_base = be32_to_cpup(map + 2); - u32 id_len = be32_to_cpup(map + 3); + u32 id_base, phandle, id_len, id_off, cells = 0; + const __be32 *out_base; + + if (map_len - offset < 2) + goto err_map_len; + + id_base = be32_to_cpup(map + offset); if (id_base & ~map_mask) { - pr_err("%pOF: Invalid %s translation - %s-mask (0x%x) ignores id-base (0x%x)\n", - np, map_name, map_name, - map_mask, id_base); + pr_err("%pOF: Invalid %s translation - %s (0x%x) ignores id-base (0x%x)\n", + np, map_name, map_mask_name, map_mask, id_base); return -EFAULT; } - if (masked_id < id_base || masked_id >= id_base + id_len) - continue; - + phandle = be32_to_cpup(map + offset + 1); phandle_node = of_find_node_by_phandle(phandle); if (!phandle_node) return -ENODEV; + /* + * Assume 1-cell output specifier if the target node lacks the + * #cells property, for backward compatibility with controllers + * that predate the property (e.g. arm,gic-v2m-frame). + */ + if (bad_map || of_property_read_u32(phandle_node, cells_name, &cells)) + cells = 1; + + if (cells > MAX_PHANDLE_ARGS) { + pr_err("%pOF: %s cell count %d exceeds maximum\n", + phandle_node, cells_name, cells); + of_node_put(phandle_node); + return -EINVAL; + } + + if (offset == 0 && cells == 2) { + bad_map = of_check_bad_map(map, map_len); + if (bad_map) { + pr_warn_once("%pOF: %s has 1-cell entries targeting 2-cell %s, treating as 1-cell output\n", + np, map_name, cells_name); + cells = 1; + } + } + + if (map_len - offset < 3 + cells) { + of_node_put(phandle_node); + goto err_map_len; + } + + out_base = map + offset + 2; + offset += 3 + cells; + + id_len = be32_to_cpup(map + offset - 1); + id_off = masked_id - id_base; + if (masked_id < id_base || id_off >= id_len) { + of_node_put(phandle_node); + continue; + } + if (id_len > 1 && cells > 1) { + /* + * With 1 output cell we reasonably assume its value + * has a linear relationship to the input; with more, + * we'd need help from the provider to know what to do. + */ + pr_err("%pOF: Unsupported %s - cannot handle %d-ID range with %d-cell output specifier\n", + np, map_name, id_len, cells); + of_node_put(phandle_node); + return -EINVAL; + } + if (filter_np && *filter_np && *filter_np != phandle_node) { of_node_put(phandle_node); continue; } arg->np = phandle_node; - arg->args[0] = masked_id - id_base + out_base; - arg->args_count = 1; + for (int i = 0; i < cells; i++) + arg->args[i] = id_off + be32_to_cpu(out_base[i]); + arg->args_count = cells; pr_debug("%pOF: %s, using mask %08x, id-base: %08x, out-base: %08x, length: %08x, id: %08x -> %08x\n", - np, map_name, map_mask, id_base, out_base, - id_len, id, masked_id - id_base + out_base); + np, map_name, map_mask, id_base, + cells ? be32_to_cpup(out_base) : 0, + id_len, id, + cells ? id_off + be32_to_cpup(out_base) : id_off); return 0; } @@ -2228,6 +2312,10 @@ int of_map_id(const struct device_node *np, u32 id, arg->args[0] = id; arg->args_count = 1; return 0; + +err_map_len: + pr_err("%pOF: Error: Bad %s length: %d\n", np, map_name, map_bytes); + return -EINVAL; } EXPORT_SYMBOL_GPL(of_map_id); @@ -2237,18 +2325,21 @@ EXPORT_SYMBOL_GPL(of_map_id); * @id: Requester ID of the device (e.g. PCI RID/BDF or a platform * stream/device ID) used as the lookup key in the iommu-map table. * @arg: pointer to a &struct of_phandle_args for the result. On success, - * @arg->args[0] contains the translated ID. If a map entry was matched, - * @arg->np holds a reference to the target node that the caller must - * release with of_node_put(). + * @arg->args_count will be set to the number of output specifier cells + * and @arg->args[0..args_count-1] will contain the translated output + * specifier values. If a map entry was matched, @arg->np holds a + * reference to the target node that the caller must release with + * of_node_put(). * - * Convenience wrapper around of_map_id() using "iommu-map" and "iommu-map-mask". + * Convenience wrapper around of_map_id() using "iommu-map", "#iommu-cells", + * and "iommu-map-mask". * * Return: 0 on success or a standard error code on failure. */ int of_map_iommu_id(const struct device_node *np, u32 id, struct of_phandle_args *arg) { - return of_map_id(np, id, "iommu-map", "iommu-map-mask", NULL, arg); + return of_map_id(np, id, "iommu-map", "#iommu-cells", "iommu-map-mask", NULL, arg); } EXPORT_SYMBOL_GPL(of_map_iommu_id); @@ -2261,17 +2352,20 @@ EXPORT_SYMBOL_GPL(of_map_iommu_id); * If non-NULL, the map property must exist (-ENODEV if absent). If * *filter_np is also non-NULL, only entries targeting that node match. * @arg: pointer to a &struct of_phandle_args for the result. On success, - * @arg->args[0] contains the translated ID. If a map entry was matched, - * @arg->np holds a reference to the target node that the caller must - * release with of_node_put(). + * @arg->args_count will be set to the number of output specifier cells + * and @arg->args[0..args_count-1] will contain the translated output + * specifier values. If a map entry was matched, @arg->np holds a + * reference to the target node that the caller must release with + * of_node_put(). * - * Convenience wrapper around of_map_id() using "msi-map" and "msi-map-mask". + * Convenience wrapper around of_map_id() using "msi-map", "#msi-cells", + * and "msi-map-mask". * * Return: 0 on success or a standard error code on failure. */ int of_map_msi_id(const struct device_node *np, u32 id, struct device_node * const *filter_np, struct of_phandle_args *arg) { - return of_map_id(np, id, "msi-map", "msi-map-mask", filter_np, arg); + return of_map_id(np, id, "msi-map", "#msi-cells", "msi-map-mask", filter_np, arg); } EXPORT_SYMBOL_GPL(of_map_msi_id); -- cgit v1.2.3