Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions drivers/cdx/cdx_msi.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,23 +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_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, &msi_spec);
if (ret) {
dev_err(dev, "of_map_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);
Expand Down
6 changes: 2 additions & 4 deletions drivers/iommu/of_iommu.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +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_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);
if (err)
return err;

Expand Down
21 changes: 12 additions & 9 deletions drivers/irqchip/irq-gic-its-msi-parent.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,13 @@ static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev,
} while (!ret);

if (ret) {
struct device_node *np = NULL;
struct of_phandle_args msi_spec = {};

ret = of_map_id(dev->of_node, dev->id, "msi-map", "msi-map-mask", &np, dev_id);
if (np)
of_node_put(np);
ret = of_map_msi_id(dev->of_node, dev->id, NULL, &msi_spec);
if (!ret) {
of_node_put(msi_spec.np);
*dev_id = msi_spec.args[0];
}
}

return ret;
Expand Down Expand Up @@ -209,12 +211,13 @@ static int of_v5_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev
} while (!ret);

if (ret) {
struct device_node *np = NULL;
struct of_phandle_args msi_spec = {};

ret = of_map_id(dev->of_node, dev->id, "msi-map", "msi-map-mask", &np, dev_id);
if (np) {
ret = its_translate_frame_address(np, pa);
of_node_put(np);
ret = of_map_msi_id(dev->of_node, dev->id, NULL, &msi_spec);
if (!ret) {
*dev_id = msi_spec.args[0];
ret = its_translate_frame_address(msi_spec.np, pa);
of_node_put(msi_spec.np);
}
}

Expand Down
213 changes: 169 additions & 44 deletions drivers/of/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -2045,50 +2045,81 @@ 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.
* @target: optional pointer to a target device node.
* @id_out: optional pointer to receive the translated ID.
* @filter_np: optional device node to filter matches by, or NULL to match any.
* If non-NULL, only map entries targeting this node will be matched.
* @arg: pointer to a &struct of_phandle_args for the result. On success,
* @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
* 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)
const char *map_name, const char *cells_name,
const char *map_mask_name,
const struct device_node *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 || (!target && !id_out))
if (!np || !map_name || !arg)
return -EINVAL;

map = of_get_property(np, map_name, &map_len);
map = of_get_property(np, map_name, &map_bytes);
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;
}

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;
Expand All @@ -2101,52 +2132,146 @@ 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;

if (target) {
if (*target)
of_node_put(phandle_node);
else
*target = phandle_node;
if (!bad_map && of_property_read_u32(phandle_node, cells_name, &cells)) {
pr_err("%pOF: missing %s property\n", phandle_node, cells_name);
of_node_put(phandle_node);
return -EINVAL;
}

if (*target != phandle_node)
continue;
if (map_len - offset < 3 + cells) {
of_node_put(phandle_node);
goto err_map_len;
}

if (id_out)
*id_out = masked_id - id_base + out_base;
if (offset == 0 && cells == 2) {
bad_map = of_check_bad_map(map, map_len);
if (bad_map) {
pr_warn_once("%pOF: %s mismatches target %s, assuming extra cell of 0\n",
np, map_name, cells_name);
cells = 1;
}
}

out_base = map + offset + 2;
offset += 3 + cells;

id_len = be32_to_cpup(map + offset - 1);
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;
}
id_off = masked_id - id_base;
if (masked_id < id_base || id_off >= id_len) {
of_node_put(phandle_node);
continue;
}

if (filter_np && filter_np != phandle_node) {
of_node_put(phandle_node);
continue;
}

arg->np = phandle_node;
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, be32_to_cpup(out_base),
id_len, id, id_off + be32_to_cpup(out_base));
return 0;
}

pr_info("%pOF: no %s translation for id 0x%x on %pOF\n", np, map_name,
id, target && *target ? *target : NULL);
id, filter_np);

/* Bypasses translation */
if (id_out)
*id_out = 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);

/**
* 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.
* @arg: pointer to a &struct of_phandle_args for the result. On success,
* @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", "#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-cells", "iommu-map-mask", NULL, arg);
}
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.
* @filter_np: optional MSI controller node to filter matches by, or NULL
* to match any. If non-NULL, only map entries targeting this node will
* be matched.
* @arg: pointer to a &struct of_phandle_args for the result. On success,
* @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", "#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,
const struct device_node *filter_np, struct of_phandle_args *arg)
{
return of_map_id(np, id, "msi-map", "#msi-cells", "msi-map-mask", filter_np, arg);
}
EXPORT_SYMBOL_GPL(of_map_msi_id);
11 changes: 9 additions & 2 deletions drivers/of/irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -725,9 +725,16 @@ 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))
struct of_phandle_args msi_spec = {};

if (!of_map_msi_id(parent_dev->of_node, id_in, *msi_np, &msi_spec)) {
id_out = msi_spec.args[0];
if (!*msi_np)
*msi_np = msi_spec.np;
else
of_node_put(msi_spec.np);
break;
}
if (!of_check_msi_parent(parent_dev->of_node, msi_np))
break;
}
Expand Down
Loading