diff options
| -rw-r--r-- | Documentation/driver-model/devres.txt | 1 | ||||
| -rw-r--r-- | include/linux/irq.h | 2 | ||||
| -rw-r--r-- | include/linux/irq_sim.h | 44 | ||||
| -rw-r--r-- | include/linux/irqdomain.h | 3 | ||||
| -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/irq_sim.c | 164 | ||||
| -rw-r--r-- | kernel/irq/irqdomain.c | 230 | 
9 files changed, 533 insertions, 30 deletions
diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt index 30e04f7a690d..69f08c0f23a8 100644 --- a/Documentation/driver-model/devres.txt +++ b/Documentation/driver-model/devres.txt @@ -312,6 +312,7 @@ IRQ    devm_irq_alloc_descs_from()    devm_irq_alloc_generic_chip()    devm_irq_setup_generic_chip() +  devm_irq_sim_init()  LED    devm_led_classdev_register() diff --git a/include/linux/irq.h b/include/linux/irq.h index d2d543794093..d4728bf6a537 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -568,6 +568,8 @@ extern int irq_chip_compose_msi_msg(struct irq_data *data, struct msi_msg *msg);  extern int irq_chip_pm_get(struct irq_data *data);  extern int irq_chip_pm_put(struct irq_data *data);  #ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY +extern void handle_fasteoi_ack_irq(struct irq_desc *desc); +extern void handle_fasteoi_mask_irq(struct irq_desc *desc);  extern void irq_chip_enable_parent(struct irq_data *data);  extern void irq_chip_disable_parent(struct irq_data *data);  extern void irq_chip_ack_parent(struct irq_data *data); diff --git a/include/linux/irq_sim.h b/include/linux/irq_sim.h new file mode 100644 index 000000000000..0380d899b955 --- /dev/null +++ b/include/linux/irq_sim.h @@ -0,0 +1,44 @@ +#ifndef _LINUX_IRQ_SIM_H +#define _LINUX_IRQ_SIM_H +/* + * 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_work.h> +#include <linux/device.h> + +/* + * Provides a framework for allocating simulated interrupts which can be + * requested like normal irqs and enqueued from process context. + */ + +struct irq_sim_work_ctx { +	struct irq_work		work; +	int			irq; +}; + +struct irq_sim_irq_ctx { +	int			irqnum; +	bool			enabled; +}; + +struct irq_sim { +	struct irq_sim_work_ctx	work_ctx; +	int			irq_base; +	unsigned int		irq_count; +	struct irq_sim_irq_ctx	*irqs; +}; + +int irq_sim_init(struct irq_sim *sim, unsigned int num_irqs); +int devm_irq_sim_init(struct device *dev, struct irq_sim *sim, +		      unsigned int num_irqs); +void irq_sim_fini(struct irq_sim *sim); +void irq_sim_fire(struct irq_sim *sim, unsigned int offset); +int irq_sim_irqnum(struct irq_sim *sim, unsigned int offset); + +#endif /* _LINUX_IRQ_SIM_H */ diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index cac77a5c5555..2318f29054af 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -460,6 +460,9 @@ extern void irq_domain_free_irqs_common(struct irq_domain *domain,  extern void irq_domain_free_irqs_top(struct irq_domain *domain,  				     unsigned int virq, unsigned int nr_irqs); +extern int irq_domain_push_irq(struct irq_domain *domain, int virq, void *arg); +extern int irq_domain_pop_irq(struct irq_domain *domain, int virq); +  extern int irq_domain_alloc_irqs_parent(struct irq_domain *domain,  					unsigned int irq_base,  					unsigned int nr_irqs, void *arg); 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 a3cc37c0c85e..23958980189d 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -1092,6 +1092,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) @@ -1105,6 +1211,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 @@ -1119,6 +1226,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 @@ -1181,6 +1289,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/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..1ff9912211e9 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 (domain->parent != root_irq_data->domain) +		return -EINVAL; + +	if (!root_irq_data) +		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  | 
