Skip to content

Commit 495c38d

Browse files
daviddaneyKAGA-KOKO
authored andcommitted
irqdomain: Add irq_domain_{push,pop}_irq() functions
For an already existing irqdomain hierarchy, as might be obtained via a call to pci_enable_msix_range(), a PCI driver wishing to add an additional irqdomain to the hierarchy needs to be able to insert the irqdomain to that already initialized hierarchy. Calling irq_domain_create_hierarchy() allows the new irqdomain to be created, but no existing code allows for initializing the associated irq_data. Add a couple of helper functions (irq_domain_push_irq() and irq_domain_pop_irq()) to initialize the irq_data for the new irqdomain added to an existing hierarchy. Signed-off-by: David Daney <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Reviewed-by: Marc Zyngier <[email protected]> Cc: Mark Rutland <[email protected]> Cc: Alexandre Courbot <[email protected]> Cc: Linus Walleij <[email protected]> Cc: [email protected] Link: http://lkml.kernel.org/r/[email protected]
1 parent 0d12ec0 commit 495c38d

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

include/linux/irqdomain.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,9 @@ extern void irq_domain_free_irqs_common(struct irq_domain *domain,
460460
extern void irq_domain_free_irqs_top(struct irq_domain *domain,
461461
unsigned int virq, unsigned int nr_irqs);
462462

463+
extern int irq_domain_push_irq(struct irq_domain *domain, int virq, void *arg);
464+
extern int irq_domain_pop_irq(struct irq_domain *domain, int virq);
465+
463466
extern int irq_domain_alloc_irqs_parent(struct irq_domain *domain,
464467
unsigned int irq_base,
465468
unsigned int nr_irqs, void *arg);

kernel/irq/irqdomain.c

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,175 @@ int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
14491449
return ret;
14501450
}
14511451

1452+
/* The irq_data was moved, fix the revmap to refer to the new location */
1453+
static void irq_domain_fix_revmap(struct irq_data *d)
1454+
{
1455+
void **slot;
1456+
1457+
if (d->hwirq < d->domain->revmap_size)
1458+
return; /* Not using radix tree. */
1459+
1460+
/* Fix up the revmap. */
1461+
mutex_lock(&revmap_trees_mutex);
1462+
slot = radix_tree_lookup_slot(&d->domain->revmap_tree, d->hwirq);
1463+
if (slot)
1464+
radix_tree_replace_slot(&d->domain->revmap_tree, slot, d);
1465+
mutex_unlock(&revmap_trees_mutex);
1466+
}
1467+
1468+
/**
1469+
* irq_domain_push_irq() - Push a domain in to the top of a hierarchy.
1470+
* @domain: Domain to push.
1471+
* @virq: Irq to push the domain in to.
1472+
* @arg: Passed to the irq_domain_ops alloc() function.
1473+
*
1474+
* For an already existing irqdomain hierarchy, as might be obtained
1475+
* via a call to pci_enable_msix(), add an additional domain to the
1476+
* head of the processing chain. Must be called before request_irq()
1477+
* has been called.
1478+
*/
1479+
int irq_domain_push_irq(struct irq_domain *domain, int virq, void *arg)
1480+
{
1481+
struct irq_data *child_irq_data;
1482+
struct irq_data *root_irq_data = irq_get_irq_data(virq);
1483+
struct irq_desc *desc;
1484+
int rv = 0;
1485+
1486+
/*
1487+
* Check that no action has been set, which indicates the virq
1488+
* is in a state where this function doesn't have to deal with
1489+
* races between interrupt handling and maintaining the
1490+
* hierarchy. This will catch gross misuse. Attempting to
1491+
* make the check race free would require holding locks across
1492+
* calls to struct irq_domain_ops->alloc(), which could lead
1493+
* to deadlock, so we just do a simple check before starting.
1494+
*/
1495+
desc = irq_to_desc(virq);
1496+
if (!desc)
1497+
return -EINVAL;
1498+
if (WARN_ON(desc->action))
1499+
return -EBUSY;
1500+
1501+
if (domain == NULL)
1502+
return -EINVAL;
1503+
1504+
if (WARN_ON(!irq_domain_is_hierarchy(domain)))
1505+
return -EINVAL;
1506+
1507+
if (domain->parent != root_irq_data->domain)
1508+
return -EINVAL;
1509+
1510+
if (!root_irq_data)
1511+
return -EINVAL;
1512+
1513+
child_irq_data = kzalloc_node(sizeof(*child_irq_data), GFP_KERNEL,
1514+
irq_data_get_node(root_irq_data));
1515+
if (!child_irq_data)
1516+
return -ENOMEM;
1517+
1518+
mutex_lock(&irq_domain_mutex);
1519+
1520+
/* Copy the original irq_data. */
1521+
*child_irq_data = *root_irq_data;
1522+
1523+
/*
1524+
* Overwrite the root_irq_data, which is embedded in struct
1525+
* irq_desc, with values for this domain.
1526+
*/
1527+
root_irq_data->parent_data = child_irq_data;
1528+
root_irq_data->domain = domain;
1529+
root_irq_data->mask = 0;
1530+
root_irq_data->hwirq = 0;
1531+
root_irq_data->chip = NULL;
1532+
root_irq_data->chip_data = NULL;
1533+
1534+
/* May (probably does) set hwirq, chip, etc. */
1535+
rv = irq_domain_alloc_irqs_hierarchy(domain, virq, 1, arg);
1536+
if (rv) {
1537+
/* Restore the original irq_data. */
1538+
*root_irq_data = *child_irq_data;
1539+
goto error;
1540+
}
1541+
1542+
irq_domain_fix_revmap(child_irq_data);
1543+
irq_domain_set_mapping(domain, root_irq_data->hwirq, root_irq_data);
1544+
1545+
error:
1546+
mutex_unlock(&irq_domain_mutex);
1547+
1548+
return rv;
1549+
}
1550+
EXPORT_SYMBOL_GPL(irq_domain_push_irq);
1551+
1552+
/**
1553+
* irq_domain_pop_irq() - Remove a domain from the top of a hierarchy.
1554+
* @domain: Domain to remove.
1555+
* @virq: Irq to remove the domain from.
1556+
*
1557+
* Undo the effects of a call to irq_domain_push_irq(). Must be
1558+
* called either before request_irq() or after free_irq().
1559+
*/
1560+
int irq_domain_pop_irq(struct irq_domain *domain, int virq)
1561+
{
1562+
struct irq_data *root_irq_data = irq_get_irq_data(virq);
1563+
struct irq_data *child_irq_data;
1564+
struct irq_data *tmp_irq_data;
1565+
struct irq_desc *desc;
1566+
1567+
/*
1568+
* Check that no action is set, which indicates the virq is in
1569+
* a state where this function doesn't have to deal with races
1570+
* between interrupt handling and maintaining the hierarchy.
1571+
* This will catch gross misuse. Attempting to make the check
1572+
* race free would require holding locks across calls to
1573+
* struct irq_domain_ops->free(), which could lead to
1574+
* deadlock, so we just do a simple check before starting.
1575+
*/
1576+
desc = irq_to_desc(virq);
1577+
if (!desc)
1578+
return -EINVAL;
1579+
if (WARN_ON(desc->action))
1580+
return -EBUSY;
1581+
1582+
if (domain == NULL)
1583+
return -EINVAL;
1584+
1585+
if (!root_irq_data)
1586+
return -EINVAL;
1587+
1588+
tmp_irq_data = irq_domain_get_irq_data(domain, virq);
1589+
1590+
/* We can only "pop" if this domain is at the top of the list */
1591+
if (WARN_ON(root_irq_data != tmp_irq_data))
1592+
return -EINVAL;
1593+
1594+
if (WARN_ON(root_irq_data->domain != domain))
1595+
return -EINVAL;
1596+
1597+
child_irq_data = root_irq_data->parent_data;
1598+
if (WARN_ON(!child_irq_data))
1599+
return -EINVAL;
1600+
1601+
mutex_lock(&irq_domain_mutex);
1602+
1603+
root_irq_data->parent_data = NULL;
1604+
1605+
irq_domain_clear_mapping(domain, root_irq_data->hwirq);
1606+
irq_domain_free_irqs_hierarchy(domain, virq, 1);
1607+
1608+
/* Restore the original irq_data. */
1609+
*root_irq_data = *child_irq_data;
1610+
1611+
irq_domain_fix_revmap(root_irq_data);
1612+
1613+
mutex_unlock(&irq_domain_mutex);
1614+
1615+
kfree(child_irq_data);
1616+
1617+
return 0;
1618+
}
1619+
EXPORT_SYMBOL_GPL(irq_domain_pop_irq);
1620+
14521621
/**
14531622
* irq_domain_free_irqs - Free IRQ number and associated data structures
14541623
* @virq: base IRQ number

0 commit comments

Comments
 (0)