#define pr_fmt(fmt) "OF: " fmt
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/string.h>
#include <linux/slab.h>
#include "of_private.h"
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
unsigned int ret;
if (of_irq_parse_one(dev, index, &oirq))
return 0;
ret = irq_create_of_mapping(&oirq);
of_node_put(oirq.np);
return ret;
}
EXPORT_SYMBOL_GPL(irq_of_parse_and_map);
struct device_node *of_irq_find_parent(struct device_node *child)
{
struct device_node *p;
phandle parent;
if (!of_node_get(child))
return NULL;
do {
if (of_property_read_u32(child, "interrupt-parent", &parent)) {
p = of_get_parent(child);
} else {
if (of_irq_workarounds & OF_IMAP_NO_PHANDLE)
p = of_node_get(of_irq_dflt_pic);
else
p = of_find_node_by_phandle(parent);
}
of_node_put(child);
child = p;
} while (p && of_get_property(p, "#interrupt-cells", NULL) == NULL);
return p;
}
EXPORT_SYMBOL_GPL(of_irq_find_parent);
static const char * const of_irq_imap_abusers[] = {
"CBEA,platform-spider-pic",
"sti,platform-spider-pic",
"realtek,rtl-intc",
"fsl,ls1021a-extirq",
"fsl,ls1043a-extirq",
"fsl,ls1088a-extirq",
"renesas,rza1-irqc",
"pasemi,rootbus",
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;
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++;
len--;
if (!np) {
pr_debug(" -> imap parent not found !\n");
return NULL;
}
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);
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;
}
int of_imap_parser_init(struct of_imap_parser *parser, struct device_node *node,
struct of_imap_item *item)
{
int imaplen;
u32 tmp;
int ret;
parser->parent_offset = of_bus_n_addr_cells(node);
ret = of_property_read_u32(node, "#interrupt-cells", &tmp);
if (ret)
return ret;
parser->parent_offset += tmp;
if (WARN(parser->parent_offset > ARRAY_SIZE(item->child_imap),
"child part size = %u, cannot fit in array of %zu items",
parser->parent_offset, ARRAY_SIZE(item->child_imap)))
return -EINVAL;
parser->imap = of_get_property(node, "interrupt-map", &imaplen);
if (!parser->imap)
return -ENOENT;
imaplen /= sizeof(*parser->imap);
parser->imap_end = parser->imap + imaplen;
memset(item, 0, sizeof(*item));
item->child_imap_count = parser->parent_offset;
return 0;
}
EXPORT_SYMBOL_GPL(of_imap_parser_init);
struct of_imap_item *of_imap_parser_one(struct of_imap_parser *parser,
struct of_imap_item *item)
{
const __be32 *imap_parent, *imap_next;
int i;
of_node_put(item->parent_args.np);
if (parser->imap + parser->parent_offset + 1 >= parser->imap_end)
return NULL;
imap_parent = parser->imap + parser->parent_offset;
imap_next = of_irq_parse_imap_parent(imap_parent,
parser->imap_end - imap_parent,
&item->parent_args);
if (!imap_next)
return NULL;
for (i = 0; i < parser->parent_offset; i++)
item->child_imap[i] = be32_to_cpu(*(parser->imap + i));
parser->imap = imap_next;
return item;
}
EXPORT_SYMBOL_GPL(of_imap_parser_one);
int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
{
struct device_node *ipar, *tnode, *old = NULL;
__be32 initial_match_array[MAX_PHANDLE_ARGS];
const __be32 *match_array = initial_match_array;
const __be32 *tmp, dummy_imask[] = { [0 ... (MAX_PHANDLE_ARGS - 1)] = cpu_to_be32(~0) };
u32 intsize = 1, addrsize;
int i, rc = -EINVAL;
#ifdef DEBUG
of_print_phandle_args("of_irq_parse_raw: ", out_irq);
#endif
ipar = of_node_get(out_irq->np);
do {
if (!of_property_read_u32(ipar, "#interrupt-cells", &intsize))
break;
tnode = ipar;
ipar = of_irq_find_parent(ipar);
of_node_put(tnode);
} while (ipar);
if (ipar == NULL) {
pr_debug(" -> no parent found !\n");
goto fail;
}
pr_debug("of_irq_parse_raw: ipar=%pOF, size=%d\n", ipar, intsize);
if (out_irq->args_count != intsize)
goto fail;
old = of_node_get(ipar);
do {
tmp = of_get_property(old, "#address-cells", NULL);
tnode = of_get_parent(old);
of_node_put(old);
old = tnode;
} while (old && tmp == NULL);
of_node_put(old);
old = NULL;
addrsize = (tmp == NULL) ? 2 : be32_to_cpu(*tmp);
pr_debug(" -> addrsize=%d\n", addrsize);
if (WARN_ON(addrsize + intsize > MAX_PHANDLE_ARGS)) {
rc = -EFAULT;
goto fail;
}
for (i = 0; i < addrsize; i++)
initial_match_array[i] = addr ? addr[i] : 0;
for (i = 0; i < intsize; i++)
initial_match_array[addrsize + i] = cpu_to_be32(out_irq->args[i]);
while (ipar != NULL) {
int imaplen, match;
const __be32 *imap, *oldimap, *imask;
struct device_node *newpar;
bool intc = of_property_read_bool(ipar, "interrupt-controller");
imap = of_get_property(ipar, "interrupt-map", &imaplen);
if (intc &&
(!imap || of_device_compatible_match(ipar, of_irq_imap_abusers))) {
pr_debug(" -> got it !\n");
return 0;
}
if (addrsize && !addr) {
pr_debug(" -> no reg passed in when needed !\n");
goto fail;
}
if (imap == NULL) {
pr_debug(" -> no map, getting parent\n");
newpar = of_irq_find_parent(ipar);
goto skiplevel;
}
imaplen /= sizeof(u32);
imask = of_get_property(ipar, "interrupt-map-mask", NULL);
if (!imask)
imask = dummy_imask;
match = 0;
while (imaplen > (addrsize + intsize + 1)) {
match = 1;
for (i = 0; i < (addrsize + intsize); i++, imaplen--)
match &= !((match_array[i] ^ *imap++) & imask[i]);
pr_debug(" -> match=%d (imaplen=%d)\n", match, imaplen);
oldimap = imap;
imap = of_irq_parse_imap_parent(oldimap, imaplen, out_irq);
if (!imap)
goto fail;
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)
goto fail;
match_array = oldimap + 1;
newpar = out_irq->np;
intsize = out_irq->args_count;
addrsize = (imap - match_array) - intsize;
if (ipar == newpar) {
of_node_put(ipar);
pr_debug("%pOF interrupt-map entry to self\n", ipar);
return 0;
}
skiplevel:
pr_debug(" -> new parent: %pOF\n", newpar);
of_node_put(ipar);
ipar = newpar;
newpar = NULL;
}
rc = -ENOENT;
fail:
of_node_put(ipar);
return rc;
}
EXPORT_SYMBOL_GPL(of_irq_parse_raw);
int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
{
struct device_node __free(device_node) *p = NULL;
const __be32 *addr;
u32 intsize;
int i, res, addr_len;
__be32 addr_buf[3] = { 0 };
pr_debug("of_irq_parse_one: dev=%pOF, index=%d\n", device, index);
if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
return of_irq_parse_oldworld(device, index, out_irq);
addr_len = 0;
addr = of_get_property(device, "reg", &addr_len);
if (addr_len > sizeof(addr_buf))
addr_len = sizeof(addr_buf);
if (addr)
memcpy(addr_buf, addr, addr_len);
res = of_parse_phandle_with_args(device, "interrupts-extended",
"#interrupt-cells", index, out_irq);
if (!res) {
p = out_irq->np;
} else {
p = of_irq_find_parent(device);
if (!p || of_property_read_u32(p, "#interrupt-cells", &intsize))
return -EINVAL;
pr_debug(" parent=%pOF, intsize=%d\n", p, intsize);
out_irq->np = p;
out_irq->args_count = intsize;
for (i = 0; i < intsize; i++) {
res = of_property_read_u32_index(device, "interrupts",
(index * intsize) + i,
out_irq->args + i);
if (res)
return res;
}
pr_debug(" intspec=%d\n", *out_irq->args);
}
return of_irq_parse_raw(addr_buf, out_irq);
}
EXPORT_SYMBOL_GPL(of_irq_parse_one);
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
{
int irq = of_irq_get(dev, index);
if (irq < 0)
return irq;
if (r && irq) {
const char *name = NULL;
memset(r, 0, sizeof(*r));
of_property_read_string_index(dev, "interrupt-names", index,
&name);
*r = DEFINE_RES_IRQ_NAMED(irq, name ?: of_node_full_name(dev));
r->flags |= irq_get_trigger_type(irq);
}
return irq;
}
EXPORT_SYMBOL_GPL(of_irq_to_resource);
int of_irq_get(struct device_node *dev, int index)
{
int rc;
struct of_phandle_args oirq;
struct irq_domain *domain;
rc = of_irq_parse_one(dev, index, &oirq);
if (rc)
return rc;
domain = irq_find_host(oirq.np);
if (!domain) {
rc = -EPROBE_DEFER;
goto out;
}
rc = irq_create_of_mapping(&oirq);
out:
of_node_put(oirq.np);
return rc;
}
EXPORT_SYMBOL_GPL(of_irq_get);
const struct cpumask *of_irq_get_affinity(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
struct irq_fwspec_info info;
struct irq_fwspec fwspec;
int rc;
rc = of_irq_parse_one(dev, index, &oirq);
if (rc)
return NULL;
of_phandle_args_to_fwspec(oirq.np, oirq.args, oirq.args_count,
&fwspec);
if (irq_populate_fwspec_info(&fwspec, &info))
return NULL;
return info.affinity;
}
int of_irq_get_byname(struct device_node *dev, const char *name)
{
int index;
if (unlikely(!name))
return -EINVAL;
index = of_property_match_string(dev, "interrupt-names", name);
if (index < 0)
return index;
return of_irq_get(dev, index);
}
EXPORT_SYMBOL_GPL(of_irq_get_byname);
int of_irq_count(struct device_node *dev)
{
struct of_phandle_args irq;
int nr = 0;
while (of_irq_parse_one(dev, nr, &irq) == 0) {
of_node_put(irq.np);
nr++;
}
return nr;
}
EXPORT_SYMBOL_GPL(of_irq_count);
int of_irq_to_resource_table(struct device_node *dev, struct resource *res,
int nr_irqs)
{
int i;
for (i = 0; i < nr_irqs; i++, res++)
if (of_irq_to_resource(dev, i, res) <= 0)
break;
return i;
}
EXPORT_SYMBOL_GPL(of_irq_to_resource_table);
struct of_intc_desc {
struct list_head list;
of_irq_init_cb_t irq_init_cb;
struct device_node *dev;
struct device_node *interrupt_parent;
};
void __init of_irq_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np, *parent = NULL;
struct of_intc_desc *desc, *temp_desc;
struct list_head intc_desc_list, intc_parent_list;
INIT_LIST_HEAD(&intc_desc_list);
INIT_LIST_HEAD(&intc_parent_list);
for_each_matching_node_and_match(np, matches, &match) {
if (!of_property_read_bool(np, "interrupt-controller") ||
!of_device_is_available(np))
continue;
if (WARN(!match->data, "of_irq_init: no init function for %s\n",
match->compatible))
continue;
desc = kzalloc_obj(*desc);
if (!desc) {
of_node_put(np);
goto err;
}
desc->irq_init_cb = match->data;
desc->dev = of_node_get(np);
desc->interrupt_parent = of_parse_phandle(np, "interrupts-extended", 0);
if (!desc->interrupt_parent && of_property_present(np, "interrupts"))
desc->interrupt_parent = of_irq_find_parent(np);
else if (!desc->interrupt_parent)
desc->interrupt_parent = of_parse_phandle(np, "interrupt-parent", 0);
if (desc->interrupt_parent == np) {
of_node_put(desc->interrupt_parent);
desc->interrupt_parent = NULL;
}
list_add_tail(&desc->list, &intc_desc_list);
}
while (!list_empty(&intc_desc_list)) {
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
int ret;
if (desc->interrupt_parent != parent)
continue;
list_del(&desc->list);
of_node_set_flag(desc->dev, OF_POPULATED);
pr_debug("of_irq_init: init %pOF (%p), parent %p\n",
desc->dev,
desc->dev, desc->interrupt_parent);
ret = desc->irq_init_cb(desc->dev,
desc->interrupt_parent);
if (ret) {
pr_err("%s: Failed to init %pOF (%p), parent %p\n",
__func__, desc->dev, desc->dev,
desc->interrupt_parent);
of_node_clear_flag(desc->dev, OF_POPULATED);
of_node_put(desc->interrupt_parent);
of_node_put(desc->dev);
kfree(desc);
continue;
}
list_add_tail(&desc->list, &intc_parent_list);
}
desc = list_first_entry_or_null(&intc_parent_list,
typeof(*desc), list);
if (!desc) {
pr_err("of_irq_init: children remain, but no parents\n");
break;
}
list_del(&desc->list);
parent = desc->dev;
kfree(desc);
}
list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
list_del(&desc->list);
kfree(desc);
}
err:
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
list_del(&desc->list);
of_node_put(desc->interrupt_parent);
of_node_put(desc->dev);
kfree(desc);
}
}
static int of_check_msi_parent(struct device_node *dev_node, struct device_node **msi_node)
{
struct of_phandle_args msi_spec;
int ret;
ret = of_parse_phandle_with_optional_args(dev_node, "msi-parent", "#msi-cells",
0, &msi_spec);
if (ret)
return ret;
if ((*msi_node && *msi_node != msi_spec.np) || msi_spec.args_count != 0)
ret = -EINVAL;
if (!ret) {
*msi_node = msi_spec.np;
return 0;
}
of_node_put(msi_spec.np);
return ret;
}
u32 of_msi_xlate(struct device *dev, struct device_node **msi_np, u32 id_in)
{
struct device *parent_dev;
u32 id_out = id_in;
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))
break;
if (!of_check_msi_parent(parent_dev->of_node, msi_np))
break;
}
return id_out;
}
EXPORT_SYMBOL_GPL(of_msi_xlate);
struct irq_domain *of_msi_map_get_device_domain(struct device *dev, u32 id,
u32 bus_token)
{
struct device_node *np = NULL;
of_msi_xlate(dev, &np, id);
return irq_find_matching_host(np, bus_token);
}
struct irq_domain *of_msi_get_domain(struct device *dev,
const struct device_node *np,
enum irq_domain_bus_token token)
{
struct of_phandle_iterator it;
struct irq_domain *d;
int err;
of_for_each_phandle(&it, err, np, "msi-parent", "#msi-cells", 0) {
d = irq_find_matching_host(it.node, token);
if (d) {
of_node_put(it.node);
return d;
}
}
return NULL;
}
EXPORT_SYMBOL_GPL(of_msi_get_domain);
void of_msi_configure(struct device *dev, const struct device_node *np)
{
dev_set_msi_domain(dev,
of_msi_get_domain(dev, np, DOMAIN_BUS_PLATFORM_MSI));
}
EXPORT_SYMBOL_GPL(of_msi_configure);