diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-09-04 13:08:27 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-09-04 13:08:27 -0700 |
commit | 93cc1228b4a60584f3dadbd84f19f365bb945acb (patch) | |
tree | 9b50949e72f6df7116727e898a137f0881cb1b43 /kernel/irq | |
parent | dd90cccffc20a15d8e4c3ac8813f4b6a6cd4766f (diff) | |
parent | 9fbd7fd28d1a1053325967670915c12b4b246a61 (diff) |
Merge branch 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull irq updates from Thomas Gleixner:
"The interrupt subsystem delivers this time:
- Refactoring of the GIC-V3 driver to prepare for the GIC-V4 support
- Initial GIC-V4 support
- Consolidation of the FSL MSI support
- Utilize the effective affinity interface in various ARM irqchip
drivers
- Yet another interrupt chip driver (UniPhier AIDET)
- Bulk conversion of the irq chip driver to use %pOF
- The usual small fixes and improvements all over the place"
* 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (77 commits)
irqchip/ls-scfg-msi: Add MSI affinity support
irqchip/ls-scfg-msi: Add LS1043a v1.1 MSI support
irqchip/ls-scfg-msi: Add LS1046a MSI support
arm64: dts: ls1046a: Add MSI dts node
arm64: dts: ls1043a: Share all MSIs
arm: dts: ls1021a: Share all MSIs
arm64: dts: ls1043a: Fix typo of MSI compatible string
arm: dts: ls1021a: Fix typo of MSI compatible string
irqchip/ls-scfg-msi: Fix typo of MSI compatible strings
irqchip/irq-bcm7120-l2: Use correct I/O accessors for irq_fwd_mask
irqchip/mmp: Make mmp_intc_conf const
irqchip/gic: Make irq_chip const
irqchip/gic-v3: Advertise GICv4 support to KVM
irqchip/gic-v4: Enable low-level GICv4 operations
irqchip/gic-v4: Add some basic documentation
irqchip/gic-v4: Add VLPI configuration interface
irqchip/gic-v4: Add VPE command interface
irqchip/gic-v4: Add per-VM VPE domain creation
irqchip/gic-v3-its: Set implementation defined bit to enable VLPIs
irqchip/gic-v3-its: Allow doorbell interrupts to be injected/cleared
...
Diffstat (limited to 'kernel/irq')
-rw-r--r-- | kernel/irq/Kconfig | 9 | ||||
-rw-r--r-- | kernel/irq/Makefile | 1 | ||||
-rw-r--r-- | kernel/irq/chip.c | 109 | ||||
-rw-r--r-- | kernel/irq/debugfs.c | 50 | ||||
-rw-r--r-- | kernel/irq/internals.h | 2 | ||||
-rw-r--r-- | kernel/irq/irq_sim.c | 164 | ||||
-rw-r--r-- | kernel/irq/irqdomain.c | 230 | ||||
-rw-r--r-- | kernel/irq/manage.c | 14 | ||||
-rw-r--r-- | kernel/irq/proc.c | 8 |
9 files changed, 549 insertions, 38 deletions
diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig index 27c4e774071c..a117adf7084b 100644 --- a/kernel/irq/Kconfig +++ b/kernel/irq/Kconfig @@ -63,11 +63,20 @@ config GENERIC_IRQ_CHIP config IRQ_DOMAIN bool +# Support for simulated interrupts +config IRQ_SIM + bool + select IRQ_WORK + # Support for hierarchical irq domains config IRQ_DOMAIN_HIERARCHY bool select IRQ_DOMAIN +# Support for hierarchical fasteoi+edge and fasteoi+level handlers +config IRQ_FASTEOI_HIERARCHY_HANDLERS + bool + # Generic IRQ IPI support config GENERIC_IRQ_IPI bool diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile index e4aef7351f2b..1970cafe8f2a 100644 --- a/kernel/irq/Makefile +++ b/kernel/irq/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_IRQ_TIMINGS) += timings.o obj-$(CONFIG_GENERIC_IRQ_CHIP) += generic-chip.o obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o +obj-$(CONFIG_IRQ_SIM) += irq_sim.o obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o obj-$(CONFIG_GENERIC_IRQ_MIGRATION) += cpuhotplug.o diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 3675c6004f2a..f51b7b6d2451 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -1098,6 +1098,112 @@ void irq_cpu_offline(void) } #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY + +#ifdef CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS +/** + * handle_fasteoi_ack_irq - irq handler for edge hierarchy + * stacked on transparent controllers + * + * @desc: the interrupt description structure for this irq + * + * Like handle_fasteoi_irq(), but for use with hierarchy where + * the irq_chip also needs to have its ->irq_ack() function + * called. + */ +void handle_fasteoi_ack_irq(struct irq_desc *desc) +{ + struct irq_chip *chip = desc->irq_data.chip; + + raw_spin_lock(&desc->lock); + + if (!irq_may_run(desc)) + goto out; + + desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); + + /* + * If its disabled or no action available + * then mask it and get out of here: + */ + if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) { + desc->istate |= IRQS_PENDING; + mask_irq(desc); + goto out; + } + + kstat_incr_irqs_this_cpu(desc); + if (desc->istate & IRQS_ONESHOT) + mask_irq(desc); + + /* Start handling the irq */ + desc->irq_data.chip->irq_ack(&desc->irq_data); + + preflow_handler(desc); + handle_irq_event(desc); + + cond_unmask_eoi_irq(desc, chip); + + raw_spin_unlock(&desc->lock); + return; +out: + if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED)) + chip->irq_eoi(&desc->irq_data); + raw_spin_unlock(&desc->lock); +} +EXPORT_SYMBOL_GPL(handle_fasteoi_ack_irq); + +/** + * handle_fasteoi_mask_irq - irq handler for level hierarchy + * stacked on transparent controllers + * + * @desc: the interrupt description structure for this irq + * + * Like handle_fasteoi_irq(), but for use with hierarchy where + * the irq_chip also needs to have its ->irq_mask_ack() function + * called. + */ +void handle_fasteoi_mask_irq(struct irq_desc *desc) +{ + struct irq_chip *chip = desc->irq_data.chip; + + raw_spin_lock(&desc->lock); + mask_ack_irq(desc); + + if (!irq_may_run(desc)) + goto out; + + desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); + + /* + * If its disabled or no action available + * then mask it and get out of here: + */ + if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) { + desc->istate |= IRQS_PENDING; + mask_irq(desc); + goto out; + } + + kstat_incr_irqs_this_cpu(desc); + if (desc->istate & IRQS_ONESHOT) + mask_irq(desc); + + preflow_handler(desc); + handle_irq_event(desc); + + cond_unmask_eoi_irq(desc, chip); + + raw_spin_unlock(&desc->lock); + return; +out: + if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED)) + chip->irq_eoi(&desc->irq_data); + raw_spin_unlock(&desc->lock); +} +EXPORT_SYMBOL_GPL(handle_fasteoi_mask_irq); + +#endif /* CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS */ + /** * irq_chip_enable_parent - Enable the parent interrupt (defaults to unmask if * NULL) @@ -1111,6 +1217,7 @@ void irq_chip_enable_parent(struct irq_data *data) else data->chip->irq_unmask(data); } +EXPORT_SYMBOL_GPL(irq_chip_enable_parent); /** * irq_chip_disable_parent - Disable the parent interrupt (defaults to mask if @@ -1125,6 +1232,7 @@ void irq_chip_disable_parent(struct irq_data *data) else data->chip->irq_mask(data); } +EXPORT_SYMBOL_GPL(irq_chip_disable_parent); /** * irq_chip_ack_parent - Acknowledge the parent interrupt @@ -1187,6 +1295,7 @@ int irq_chip_set_affinity_parent(struct irq_data *data, return -ENOSYS; } +EXPORT_SYMBOL_GPL(irq_chip_set_affinity_parent); /** * irq_chip_set_type_parent - Set IRQ type on the parent interrupt diff --git a/kernel/irq/debugfs.c b/kernel/irq/debugfs.c index 4d384edc0c64..c3fdb36dec30 100644 --- a/kernel/irq/debugfs.c +++ b/kernel/irq/debugfs.c @@ -5,6 +5,7 @@ */ #include <linux/irqdomain.h> #include <linux/irq.h> +#include <linux/uaccess.h> #include "internals.h" @@ -171,8 +172,55 @@ static int irq_debug_open(struct inode *inode, struct file *file) return single_open(file, irq_debug_show, inode->i_private); } +static ssize_t irq_debug_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct irq_desc *desc = file_inode(file)->i_private; + char buf[8] = { 0, }; + size_t size; + + size = min(sizeof(buf) - 1, count); + if (copy_from_user(buf, user_buf, size)) + return -EFAULT; + + if (!strncmp(buf, "trigger", size)) { + unsigned long flags; + int err; + + /* Try the HW interface first */ + err = irq_set_irqchip_state(irq_desc_get_irq(desc), + IRQCHIP_STATE_PENDING, true); + if (!err) + return count; + + /* + * Otherwise, try to inject via the resend interface, + * which may or may not succeed. + */ + chip_bus_lock(desc); + raw_spin_lock_irqsave(&desc->lock, flags); + + if (irq_settings_is_level(desc)) { + /* Can't do level, sorry */ + err = -EINVAL; + } else { + desc->istate |= IRQS_PENDING; + check_irq_resend(desc); + err = 0; + } + + raw_spin_unlock_irqrestore(&desc->lock, flags); + chip_bus_sync_unlock(desc); + + return err ? err : count; + } + + return count; +} + static const struct file_operations dfs_irq_ops = { .open = irq_debug_open, + .write = irq_debug_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, @@ -186,7 +234,7 @@ void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *desc) return; sprintf(name, "%d", irq); - desc->debugfs_file = debugfs_create_file(name, 0444, irq_dir, desc, + desc->debugfs_file = debugfs_create_file(name, 0644, irq_dir, desc, &dfs_irq_ops); } diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index a2c48058354c..a4aa39009f0d 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -151,7 +151,7 @@ static inline void chip_bus_sync_unlock(struct irq_desc *desc) #define IRQ_GET_DESC_CHECK_PERCPU (_IRQ_DESC_CHECK | _IRQ_DESC_PERCPU) #define for_each_action_of_desc(desc, act) \ - for (act = desc->act; act; act = act->next) + for (act = desc->action; act; act = act->next) struct irq_desc * __irq_get_desc_lock(unsigned int irq, unsigned long *flags, bool bus, diff --git a/kernel/irq/irq_sim.c b/kernel/irq/irq_sim.c new file mode 100644 index 000000000000..24caabf1a0f7 --- /dev/null +++ b/kernel/irq/irq_sim.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2017 Bartosz Golaszewski <brgl@bgdev.pl> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/irq_sim.h> +#include <linux/irq.h> + +struct irq_sim_devres { + struct irq_sim *sim; +}; + +static void irq_sim_irqmask(struct irq_data *data) +{ + struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data); + + irq_ctx->enabled = false; +} + +static void irq_sim_irqunmask(struct irq_data *data) +{ + struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data); + + irq_ctx->enabled = true; +} + +static struct irq_chip irq_sim_irqchip = { + .name = "irq_sim", + .irq_mask = irq_sim_irqmask, + .irq_unmask = irq_sim_irqunmask, +}; + +static void irq_sim_handle_irq(struct irq_work *work) +{ + struct irq_sim_work_ctx *work_ctx; + + work_ctx = container_of(work, struct irq_sim_work_ctx, work); + handle_simple_irq(irq_to_desc(work_ctx->irq)); +} + +/** + * irq_sim_init - Initialize the interrupt simulator: allocate a range of + * dummy interrupts. + * + * @sim: The interrupt simulator object to initialize. + * @num_irqs: Number of interrupts to allocate + * + * Returns 0 on success and a negative error number on failure. + */ +int irq_sim_init(struct irq_sim *sim, unsigned int num_irqs) +{ + int i; + + sim->irqs = kmalloc_array(num_irqs, sizeof(*sim->irqs), GFP_KERNEL); + if (!sim->irqs) + return -ENOMEM; + + sim->irq_base = irq_alloc_descs(-1, 0, num_irqs, 0); + if (sim->irq_base < 0) { + kfree(sim->irqs); + return sim->irq_base; + } + + for (i = 0; i < num_irqs; i++) { + sim->irqs[i].irqnum = sim->irq_base + i; + sim->irqs[i].enabled = false; + irq_set_chip(sim->irq_base + i, &irq_sim_irqchip); + irq_set_chip_data(sim->irq_base + i, &sim->irqs[i]); + irq_set_handler(sim->irq_base + i, &handle_simple_irq); + irq_modify_status(sim->irq_base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE); + } + + init_irq_work(&sim->work_ctx.work, irq_sim_handle_irq); + sim->irq_count = num_irqs; + + return 0; +} +EXPORT_SYMBOL_GPL(irq_sim_init); + +/** + * irq_sim_fini - Deinitialize the interrupt simulator: free the interrupt + * descriptors and allocated memory. + * + * @sim: The interrupt simulator to tear down. + */ +void irq_sim_fini(struct irq_sim *sim) +{ + irq_work_sync(&sim->work_ctx.work); + irq_free_descs(sim->irq_base, sim->irq_count); + kfree(sim->irqs); +} +EXPORT_SYMBOL_GPL(irq_sim_fini); + +static void devm_irq_sim_release(struct device *dev, void *res) +{ + struct irq_sim_devres *this = res; + + irq_sim_fini(this->sim); +} + +/** + * irq_sim_init - Initialize the interrupt simulator for a managed device. + * + * @dev: Device to initialize the simulator object for. + * @sim: The interrupt simulator object to initialize. + * @num_irqs: Number of interrupts to allocate + * + * Returns 0 on success and a negative error number on failure. + */ +int devm_irq_sim_init(struct device *dev, struct irq_sim *sim, + unsigned int num_irqs) +{ + struct irq_sim_devres *dr; + int rv; + + dr = devres_alloc(devm_irq_sim_release, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + rv = irq_sim_init(sim, num_irqs); + if (rv) { + devres_free(dr); + return rv; + } + + dr->sim = sim; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_irq_sim_init); + +/** + * irq_sim_fire - Enqueue an interrupt. + * + * @sim: The interrupt simulator object. + * @offset: Offset of the simulated interrupt which should be fired. + */ +void irq_sim_fire(struct irq_sim *sim, unsigned int offset) +{ + if (sim->irqs[offset].enabled) { + sim->work_ctx.irq = irq_sim_irqnum(sim, offset); + irq_work_queue(&sim->work_ctx.work); + } +} +EXPORT_SYMBOL_GPL(irq_sim_fire); + +/** + * irq_sim_irqnum - Get the allocated number of a dummy interrupt. + * + * @sim: The interrupt simulator object. + * @offset: Offset of the simulated interrupt for which to retrieve + * the number. + */ +int irq_sim_irqnum(struct irq_sim *sim, unsigned int offset) +{ + return sim->irqs[offset].irqnum; +} +EXPORT_SYMBOL_GPL(irq_sim_irqnum); diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index f1f251479aa6..d62351714f3e 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -455,6 +455,31 @@ void irq_set_default_host(struct irq_domain *domain) } EXPORT_SYMBOL_GPL(irq_set_default_host); +static void irq_domain_clear_mapping(struct irq_domain *domain, + irq_hw_number_t hwirq) +{ + if (hwirq < domain->revmap_size) { + domain->linear_revmap[hwirq] = 0; + } else { + mutex_lock(&revmap_trees_mutex); + radix_tree_delete(&domain->revmap_tree, hwirq); + mutex_unlock(&revmap_trees_mutex); + } +} + +static void irq_domain_set_mapping(struct irq_domain *domain, + irq_hw_number_t hwirq, + struct irq_data *irq_data) +{ + if (hwirq < domain->revmap_size) { + domain->linear_revmap[hwirq] = irq_data->irq; + } else { + mutex_lock(&revmap_trees_mutex); + radix_tree_insert(&domain->revmap_tree, hwirq, irq_data); + mutex_unlock(&revmap_trees_mutex); + } +} + void irq_domain_disassociate(struct irq_domain *domain, unsigned int irq) { struct irq_data *irq_data = irq_get_irq_data(irq); @@ -483,13 +508,7 @@ void irq_domain_disassociate(struct irq_domain *domain, unsigned int irq) domain->mapcount--; /* Clear reverse map for this hwirq */ - if (hwirq < domain->revmap_size) { - domain->linear_revmap[hwirq] = 0; - } else { - mutex_lock(&revmap_trees_mutex); - radix_tree_delete(&domain->revmap_tree, hwirq); - mutex_unlock(&revmap_trees_mutex); - } + irq_domain_clear_mapping(domain, hwirq); } int irq_domain_associate(struct irq_domain *domain, unsigned int virq, @@ -533,13 +552,7 @@ int irq_domain_associate(struct irq_domain *domain, unsigned int virq, } domain->mapcount++; - if (hwirq < domain->revmap_size) { - domain->linear_revmap[hwirq] = virq; - } else { - mutex_lock(&revmap_trees_mutex); - radix_tree_insert(&domain->revmap_tree, hwirq, irq_data); - mutex_unlock(&revmap_trees_mutex); - } + irq_domain_set_mapping(domain, hwirq, irq_data); mutex_unlock(&irq_domain_mutex); irq_clear_status_flags(virq, IRQ_NOREQUEST); @@ -1138,16 +1151,9 @@ static void irq_domain_insert_irq(int virq) for (data = irq_get_irq_data(virq); data; data = data->parent_data) { struct irq_domain *domain = data->domain; - irq_hw_number_t hwirq = data->hwirq; domain->mapcount++; - if (hwirq < domain->revmap_size) { - domain->linear_revmap[hwirq] = virq; - } else { - mutex_lock(&revmap_trees_mutex); - radix_tree_insert(&domain->revmap_tree, hwirq, data); - mutex_unlock(&revmap_trees_mutex); - } + irq_domain_set_mapping(domain, data->hwirq, data); /* If not already assigned, give the domain the chip's name */ if (!domain->name && data->chip) @@ -1171,13 +1177,7 @@ static void irq_domain_remove_irq(int virq) irq_hw_number_t hwirq = data->hwirq; domain->mapcount--; - if (hwirq < domain->revmap_size) { - domain->linear_revmap[hwirq] = 0; - } else { - mutex_lock(&revmap_trees_mutex); - radix_tree_delete(&domain->revmap_tree, hwirq); - mutex_unlock(&revmap_trees_mutex); - } + irq_domain_clear_mapping(domain, hwirq); } } @@ -1362,7 +1362,8 @@ static void irq_domain_free_irqs_hierarchy(struct irq_domain *domain, unsigned int irq_base, unsigned int nr_irqs) { - domain->ops->free(domain, irq_base, nr_irqs); + if (domain->ops->free) + domain->ops->free(domain, irq_base, nr_irqs); } int irq_domain_alloc_irqs_hierarchy(struct irq_domain *domain, @@ -1448,6 +1449,175 @@ out_free_desc: return ret; } +/* The irq_data was moved, fix the revmap to refer to the new location */ +static void irq_domain_fix_revmap(struct irq_data *d) +{ + void **slot; + + if (d->hwirq < d->domain->revmap_size) + return; /* Not using radix tree. */ + + /* Fix up the revmap. */ + mutex_lock(&revmap_trees_mutex); + slot = radix_tree_lookup_slot(&d->domain->revmap_tree, d->hwirq); + if (slot) + radix_tree_replace_slot(&d->domain->revmap_tree, slot, d); + mutex_unlock(&revmap_trees_mutex); +} + +/** + * irq_domain_push_irq() - Push a domain in to the top of a hierarchy. + * @domain: Domain to push. + * @virq: Irq to push the domain in to. + * @arg: Passed to the irq_domain_ops alloc() function. + * + * For an already existing irqdomain hierarchy, as might be obtained + * via a call to pci_enable_msix(), add an additional domain to the + * head of the processing chain. Must be called before request_irq() + * has been called. + */ +int irq_domain_push_irq(struct irq_domain *domain, int virq, void *arg) +{ + struct irq_data *child_irq_data; + struct irq_data *root_irq_data = irq_get_irq_data(virq); + struct irq_desc *desc; + int rv = 0; + + /* + * Check that no action has been set, which indicates the virq + * is in a state where this function doesn't have to deal with + * races between interrupt handling and maintaining the + * hierarchy. This will catch gross misuse. Attempting to + * make the check race free would require holding locks across + * calls to struct irq_domain_ops->alloc(), which could lead + * to deadlock, so we just do a simple check before starting. + */ + desc = irq_to_desc(virq); + if (!desc) + return -EINVAL; + if (WARN_ON(desc->action)) + return -EBUSY; + + if (domain == NULL) + return -EINVAL; + + if (WARN_ON(!irq_domain_is_hierarchy(domain))) + return -EINVAL; + + if (!root_irq_data) + return -EINVAL; + + if (domain->parent != root_irq_data->domain) + return -EINVAL; + + child_irq_data = kzalloc_node(sizeof(*child_irq_data), GFP_KERNEL, + irq_data_get_node(root_irq_data)); + if (!child_irq_data) + return -ENOMEM; + + mutex_lock(&irq_domain_mutex); + + /* Copy the original irq_data. */ + *child_irq_data = *root_irq_data; + + /* + * Overwrite the root_irq_data, which is embedded in struct + * irq_desc, with values for this domain. + */ + root_irq_data->parent_data = child_irq_data; + root_irq_data->domain = domain; + root_irq_data->mask = 0; + root_irq_data->hwirq = 0; + root_irq_data->chip = NULL; + root_irq_data->chip_data = NULL; + + /* May (probably does) set hwirq, chip, etc. */ + rv = irq_domain_alloc_irqs_hierarchy(domain, virq, 1, arg); + if (rv) { + /* Restore the original irq_data. */ + *root_irq_data = *child_irq_data; + goto error; + } + + irq_domain_fix_revmap(child_irq_data); + irq_domain_set_mapping(domain, root_irq_data->hwirq, root_irq_data); + +error: + mutex_unlock(&irq_domain_mutex); + + return rv; +} +EXPORT_SYMBOL_GPL(irq_domain_push_irq); + +/** + * irq_domain_pop_irq() - Remove a domain from the top of a hierarchy. + * @domain: Domain to remove. + * @virq: Irq to remove the domain from. + * + * Undo the effects of a call to irq_domain_push_irq(). Must be + * called either before request_irq() or after free_irq(). + */ +int irq_domain_pop_irq(struct irq_domain *domain, int virq) +{ + struct irq_data *root_irq_data = irq_get_irq_data(virq); + struct irq_data *child_irq_data; + struct irq_data *tmp_irq_data; + struct irq_desc *desc; + + /* + * Check that no action is set, which indicates the virq is in + * a state where this function doesn't have to deal with races + * between interrupt handling and maintaining the hierarchy. + * This will catch gross misuse. Attempting to make the check + * race free would require holding locks across calls to + * struct irq_domain_ops->free(), which could lead to + * deadlock, so we just do a simple check before starting. + */ + desc = irq_to_desc(virq); + if (!desc) + return -EINVAL; + if (WARN_ON(desc->action)) + return -EBUSY; + + if (domain == NULL) + return -EINVAL; + + if (!root_irq_data) + return -EINVAL; + + tmp_irq_data = irq_domain_get_irq_data(domain, virq); + + /* We can only "pop" if this domain is at the top of the list */ + if (WARN_ON(root_irq_data != tmp_irq_data)) + return -EINVAL; + + if (WARN_ON(root_irq_data->domain != domain)) + return -EINVAL; + + child_irq_data = root_irq_data->parent_data; + if (WARN_ON(!child_irq_data)) + return -EINVAL; + + mutex_lock(&irq_domain_mutex); + + root_irq_data->parent_data = NULL; + + irq_domain_clear_mapping(domain, root_irq_data->hwirq); + irq_domain_free_irqs_hierarchy(domain, virq, 1); + + /* Restore the original irq_data. */ + *root_irq_data = *child_irq_data; + + irq_domain_fix_revmap(root_irq_data); + + mutex_unlock(&irq_domain_mutex); + + kfree(child_irq_data); + + return 0; +} +EXPORT_SYMBOL_GPL(irq_domain_pop_irq); + /** * irq_domain_free_irqs - Free IRQ number and associated data structures * @virq: base IRQ number diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 1d1a5b945ab4..573dc52b0806 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -400,8 +400,18 @@ int irq_set_vcpu_affinity(unsigned int irq, void *vcpu_info) return -EINVAL; data = irq_desc_get_irq_data(desc); - chip = irq_data_get_irq_chip(data); - if (chip && chip->irq_set_vcpu_affinity) + do { + chip = irq_data_get_irq_chip(data); + if (chip && chip->irq_set_vcpu_affinity) + break; +#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY + data = data->parent_data; +#else + data = NULL; +#endif + } while (data); + + if (data) ret = chip->irq_set_vcpu_affinity(data, vcpu_info); irq_put_desc_unlock(desc, flags); diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index 7f9642a1e267..6376b4a598d3 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -61,12 +61,12 @@ static int show_irq_affinity(int type, struct seq_file *m) case EFFECTIVE: case EFFECTIVE_LIST: #ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK - mask = desc->irq_common_data.effective_affinity; + mask = irq_data_get_effective_affinity_mask(&desc->irq_data); break; -#else - return -EINVAL; #endif - }; + default: + return -EINVAL; + } switch (type) { case AFFINITY_LIST: |