Re: [PATCH v16 3/3] of: Respect #{iommu,msi}-cells in maps
Vijayanand Jitta <[email protected]> Tue, 28 Jul 2026 09:35:58 +0530
| Newsgroups | dev.linux.lists.iommu,dev.linux.lists.imx,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci,org.xenproject.lists.xen-devel |
|---|---|
| Message-ID | <[email protected]> |
On 7/23/2026 6:47 PM, Neil Armstrong wrote: > Hi, > > On 6/3/26 09:13, Vijayanand Jitta wrote: >> From: Robin Murphy <[email protected]> >> >> 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 <[email protected]> >> Signed-off-by: Charan Teja Kalla <[email protected]> >> Signed-off-by: Vijayanand Jitta <[email protected]> >> --- >> drivers/of/base.c | 168 +++++++++++++++++++++++++++++++++++++++++------------ >> include/linux/of.h | 6 +- >> 2 files changed, 135 insertions(+), 39 deletions(-) >> >> diff --git a/drivers/of/base.c b/drivers/of/base.c >> index d658c2620135..ac7961cbab94 100644 >> --- a/drivers/of/base.c >> +++ b/drivers/of/base.c >> @@ -2116,19 +2116,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>; > > So the sm8650 PCIe controllers has: > > pcie@1c08000: > iommu-map = <0 &apps_smmu 0x1480 0x1>, > <0x100 &apps_smmu 0x1481 0x1>; > > and > > pcie@1c00000: > > iommu-map = <0 &apps_smmu 0x1400 0x1>, > <0x100 &apps_smmu 0x1401 0x1>; > > and apps_smmu has #iommu-cells = <2>, but gets flagged at wrong: > > [ 7.538800] OF: /soc@0/pcie@1c08000: iommu-map has 1-cell entries targeting 2-cell #iommu-cells, treating as 1-cell output > > Returning false in of_check_bad_map() triggers: > > [ 7.642680] OF: /soc@0/pcie@1c08000: Unsupported iommu-map - cannot handle 256-ID range with 2-cell output specifier > > I don't understand the issue here, we use 2 cells as expected by > the iommu-cells, so why is it wrong ? can somebody explain in > comprehensive words ? I'm super confused, it worked like a charm until now. > > Neil > Hi Neil, iommu-map = <0 &apps_smmu 0x1480 0x1>, <0x100 &apps_smmu 0x1481 0x1>; Entries here are not 2-cell format, Even though apps_smmu declares #iommu-cells = <2>, this DT only supplies one output cell (0x1480/0x1481) — the trailing 0x1 is the length field, not a second output cell. (<id-base phandle out-base length>) The new code detects exactly this pattern (same target phandle across all entries, length always 1) and falls back to treating the map as 1-cell output for backward compatibility — hence the pr_warn_once. It's harmless and expected, your RIDs still resolve to the correct SIDs (0 → 0x1480, 0x100 → 0x1481). The second message is a different case and shouldn't be coming from this same map — once the 1-cell fallback triggers on the first entry, it applies to the whole map, so you shouldn't hit both warnings together on the same node. That error only fires for a genuine 2-cell output specifier combined with an id_len > 1, e.g.: iommu-map = <0x0 &apps_smmu 0x1480 0x1 0x100>; (<id-base, phandle, out0, out1, length=256>) — which isn't supported, since there's no way to linearly scale a multi-cell output specifier across a range of IDs. Are you seeing that second error on the same pcie node, or a different one? If it's the same node, can you share the exact iommu-map entry that triggers it? Thanks, Vijay >> + */ >> +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 >> @@ -2137,19 +2167,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; >> @@ -2159,11 +2191,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; >> @@ -2176,39 +2206,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; >> } >> @@ -2219,6 +2303,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); >> @@ -2228,18 +2316,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); >> @@ -2252,17 +2343,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); >> diff --git a/include/linux/of.h b/include/linux/of.h >> index ea50b45d9ff7..374b249766a2 100644 >> --- a/include/linux/of.h >> +++ b/include/linux/of.h >> @@ -465,7 +465,8 @@ const char *of_prop_next_string(const struct property *prop, const char *cur); >> bool of_console_check(const struct device_node *dn, char *name, int index); >> 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); >> @@ -950,7 +951,8 @@ static inline void of_property_clear_flag(struct property *p, unsigned long flag >> } >> static inline 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) >> { >> >