diff options
Diffstat (limited to 'kernel/irq')
| -rw-r--r-- | kernel/irq/Kconfig | 6 | ||||
| -rw-r--r-- | kernel/irq/chip.c | 37 | ||||
| -rw-r--r-- | kernel/irq/devres.c | 127 | ||||
| -rw-r--r-- | kernel/irq/handle.c | 49 | ||||
| -rw-r--r-- | kernel/irq/irq_test.c | 55 | ||||
| -rw-r--r-- | kernel/irq/irqdesc.c | 7 | ||||
| -rw-r--r-- | kernel/irq/msi.c | 3 | 
7 files changed, 204 insertions, 80 deletions
| diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig index 1da5e9d9da71..1b4254d19a73 100644 --- a/kernel/irq/Kconfig +++ b/kernel/irq/Kconfig @@ -6,10 +6,6 @@ menu "IRQ subsystem"  config MAY_HAVE_SPARSE_IRQ         bool -# Legacy support, required for itanic -config GENERIC_IRQ_LEGACY -       bool -  # Enable the generic irq autoprobe mechanism  config GENERIC_IRQ_PROBE  	bool @@ -147,7 +143,9 @@ config GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD  config IRQ_KUNIT_TEST  	bool "KUnit tests for IRQ management APIs" if !KUNIT_ALL_TESTS  	depends on KUNIT=y +	depends on SPARSE_IRQ  	default KUNIT_ALL_TESTS +	select IRQ_DOMAIN  	imply SMP  	help  	  This option enables KUnit tests for the IRQ subsystem API. These are diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 0d0276378c70..3ffa0d80ddd1 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -1260,6 +1260,43 @@ int irq_chip_get_parent_state(struct irq_data *data,  EXPORT_SYMBOL_GPL(irq_chip_get_parent_state);  /** + * irq_chip_shutdown_parent - Shutdown the parent interrupt + * @data:	Pointer to interrupt specific data + * + * Invokes the irq_shutdown() callback of the parent if available or falls + * back to irq_chip_disable_parent(). + */ +void irq_chip_shutdown_parent(struct irq_data *data) +{ +	struct irq_data *parent = data->parent_data; + +	if (parent->chip->irq_shutdown) +		parent->chip->irq_shutdown(parent); +	else +		irq_chip_disable_parent(data); +} +EXPORT_SYMBOL_GPL(irq_chip_shutdown_parent); + +/** + * irq_chip_startup_parent - Startup the parent interrupt + * @data:	Pointer to interrupt specific data + * + * Invokes the irq_startup() callback of the parent if available or falls + * back to irq_chip_enable_parent(). + */ +unsigned int irq_chip_startup_parent(struct irq_data *data) +{ +	struct irq_data *parent = data->parent_data; + +	if (parent->chip->irq_startup) +		return parent->chip->irq_startup(parent); + +	irq_chip_enable_parent(data); +	return 0; +} +EXPORT_SYMBOL_GPL(irq_chip_startup_parent); + +/**   * irq_chip_enable_parent - Enable the parent interrupt (defaults to unmask if   * NULL)   * @data:	Pointer to interrupt specific data diff --git a/kernel/irq/devres.c b/kernel/irq/devres.c index eb16a58e0322..b41188698622 100644 --- a/kernel/irq/devres.c +++ b/kernel/irq/devres.c @@ -30,29 +30,22 @@ static int devm_irq_match(struct device *dev, void *res, void *data)  	return this->irq == match->irq && this->dev_id == match->dev_id;  } -/** - *	devm_request_threaded_irq - allocate an interrupt line for a managed device - *	@dev: device to request interrupt for - *	@irq: Interrupt line to allocate - *	@handler: Function to be called when the IRQ occurs - *	@thread_fn: function to be called in a threaded interrupt context. NULL - *		    for devices which handle everything in @handler - *	@irqflags: Interrupt type flags - *	@devname: An ascii name for the claiming device, dev_name(dev) if NULL - *	@dev_id: A cookie passed back to the handler function - * - *	Except for the extra @dev argument, this function takes the - *	same arguments and performs the same function as - *	request_threaded_irq().  IRQs requested with this function will be - *	automatically freed on driver detach. - * - *	If an IRQ allocated with this function needs to be freed - *	separately, devm_free_irq() must be used. - */ -int devm_request_threaded_irq(struct device *dev, unsigned int irq, -			      irq_handler_t handler, irq_handler_t thread_fn, -			      unsigned long irqflags, const char *devname, -			      void *dev_id) +static int devm_request_result(struct device *dev, int rc, unsigned int irq, +			       irq_handler_t handler, irq_handler_t thread_fn, +			       const char *devname) +{ +	if (rc >= 0) +		return rc; + +	return dev_err_probe(dev, rc, "request_irq(%u) %ps %ps %s\n", +			     irq, handler, thread_fn, devname ? : ""); +} + +static int __devm_request_threaded_irq(struct device *dev, unsigned int irq, +				       irq_handler_t handler, +				       irq_handler_t thread_fn, +				       unsigned long irqflags, +				       const char *devname, void *dev_id)  {  	struct irq_devres *dr;  	int rc; @@ -78,28 +71,48 @@ int devm_request_threaded_irq(struct device *dev, unsigned int irq,  	return 0;  } -EXPORT_SYMBOL(devm_request_threaded_irq);  /** - *	devm_request_any_context_irq - allocate an interrupt line for a managed device - *	@dev: device to request interrupt for - *	@irq: Interrupt line to allocate - *	@handler: Function to be called when the IRQ occurs - *	@irqflags: Interrupt type flags - *	@devname: An ascii name for the claiming device, dev_name(dev) if NULL - *	@dev_id: A cookie passed back to the handler function + * devm_request_threaded_irq - allocate an interrupt line for a managed device with error logging + * @dev:	Device to request interrupt for + * @irq:	Interrupt line to allocate + * @handler:	Function to be called when the interrupt occurs + * @thread_fn:	Function to be called in a threaded interrupt context. NULL + *		for devices which handle everything in @handler + * @irqflags:	Interrupt type flags + * @devname:	An ascii name for the claiming device, dev_name(dev) if NULL + * @dev_id:	A cookie passed back to the handler function   * - *	Except for the extra @dev argument, this function takes the - *	same arguments and performs the same function as - *	request_any_context_irq().  IRQs requested with this function will be - *	automatically freed on driver detach. + * Except for the extra @dev argument, this function takes the same + * arguments and performs the same function as request_threaded_irq(). + * Interrupts requested with this function will be automatically freed on + * driver detach. + * + * If an interrupt allocated with this function needs to be freed + * separately, devm_free_irq() must be used. + * + * When the request fails, an error message is printed with contextual + * information (device name, interrupt number, handler functions and + * error code). Don't add extra error messages at the call sites.   * - *	If an IRQ allocated with this function needs to be freed - *	separately, devm_free_irq() must be used. + * Return: 0 on success or a negative error number.   */ -int devm_request_any_context_irq(struct device *dev, unsigned int irq, -			      irq_handler_t handler, unsigned long irqflags, -			      const char *devname, void *dev_id) +int devm_request_threaded_irq(struct device *dev, unsigned int irq, +			      irq_handler_t handler, irq_handler_t thread_fn, +			      unsigned long irqflags, const char *devname, +			      void *dev_id) +{ +	int rc = __devm_request_threaded_irq(dev, irq, handler, thread_fn, +					     irqflags, devname, dev_id); + +	return devm_request_result(dev, rc, irq, handler, thread_fn, devname); +} +EXPORT_SYMBOL(devm_request_threaded_irq); + +static int __devm_request_any_context_irq(struct device *dev, unsigned int irq, +					  irq_handler_t handler, +					  unsigned long irqflags, +					  const char *devname, void *dev_id)  {  	struct irq_devres *dr;  	int rc; @@ -124,6 +137,40 @@ int devm_request_any_context_irq(struct device *dev, unsigned int irq,  	return rc;  } + +/** + * devm_request_any_context_irq - allocate an interrupt line for a managed device with error logging + * @dev:	Device to request interrupt for + * @irq:	Interrupt line to allocate + * @handler:	Function to be called when the interrupt occurs + * @irqflags:	Interrupt type flags + * @devname:	An ascii name for the claiming device, dev_name(dev) if NULL + * @dev_id:	A cookie passed back to the handler function + * + * Except for the extra @dev argument, this function takes the same + * arguments and performs the same function as request_any_context_irq(). + * Interrupts requested with this function will be automatically freed on + * driver detach. + * + * If an interrupt allocated with this function needs to be freed + * separately, devm_free_irq() must be used. + * + * When the request fails, an error message is printed with contextual + * information (device name, interrupt number, handler functions and + * error code). Don't add extra error messages at the call sites. + * + * Return: IRQC_IS_HARDIRQ or IRQC_IS_NESTED on success, or a negative error + * number. + */ +int devm_request_any_context_irq(struct device *dev, unsigned int irq, +				 irq_handler_t handler, unsigned long irqflags, +				 const char *devname, void *dev_id) +{ +	int rc = __devm_request_any_context_irq(dev, irq, handler, irqflags, +						devname, dev_id); + +	return devm_request_result(dev, rc, irq, handler, NULL, devname); +}  EXPORT_SYMBOL(devm_request_any_context_irq);  /** diff --git a/kernel/irq/handle.c b/kernel/irq/handle.c index 9489f93b3db3..e103451243a0 100644 --- a/kernel/irq/handle.c +++ b/kernel/irq/handle.c @@ -136,6 +136,44 @@ void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)  	wake_up_process(action->thread);  } +static DEFINE_STATIC_KEY_FALSE(irqhandler_duration_check_enabled); +static u64 irqhandler_duration_threshold_ns __ro_after_init; + +static int __init irqhandler_duration_check_setup(char *arg) +{ +	unsigned long val; +	int ret; + +	ret = kstrtoul(arg, 0, &val); +	if (ret) { +		pr_err("Unable to parse irqhandler.duration_warn_us setting: ret=%d\n", ret); +		return 0; +	} + +	if (!val) { +		pr_err("Invalid irqhandler.duration_warn_us setting, must be > 0\n"); +		return 0; +	} + +	irqhandler_duration_threshold_ns = val * 1000; +	static_branch_enable(&irqhandler_duration_check_enabled); + +	return 1; +} +__setup("irqhandler.duration_warn_us=", irqhandler_duration_check_setup); + +static inline void irqhandler_duration_check(u64 ts_start, unsigned int irq, +					     const struct irqaction *action) +{ +	u64 delta_ns = local_clock() - ts_start; + +	if (unlikely(delta_ns > irqhandler_duration_threshold_ns)) { +		pr_warn_ratelimited("[CPU%u] long duration of IRQ[%u:%ps], took: %llu us\n", +				    smp_processor_id(), irq, action->handler, +				    div_u64(delta_ns, NSEC_PER_USEC)); +	} +} +  irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)  {  	irqreturn_t retval = IRQ_NONE; @@ -155,7 +193,16 @@ irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)  			lockdep_hardirq_threaded();  		trace_irq_handler_entry(irq, action); -		res = action->handler(irq, action->dev_id); + +		if (static_branch_unlikely(&irqhandler_duration_check_enabled)) { +			u64 ts_start = local_clock(); + +			res = action->handler(irq, action->dev_id); +			irqhandler_duration_check(ts_start, irq, action); +		} else { +			res = action->handler(irq, action->dev_id); +		} +  		trace_irq_handler_exit(irq, action, res);  		if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n", diff --git a/kernel/irq/irq_test.c b/kernel/irq/irq_test.c index a75abebed7f2..e2d31914b3c4 100644 --- a/kernel/irq/irq_test.c +++ b/kernel/irq/irq_test.c @@ -41,21 +41,37 @@ static struct irq_chip fake_irq_chip = {  	.flags          = IRQCHIP_SKIP_SET_WAKE,  }; -static void irq_disable_depth_test(struct kunit *test) +static int irq_test_setup_fake_irq(struct kunit *test, struct irq_affinity_desc *affd)  {  	struct irq_desc *desc; -	int virq, ret; +	int virq; -	virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, NULL); +	virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, affd);  	KUNIT_ASSERT_GE(test, virq, 0); -	irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq); +	irq_set_chip_and_handler(virq, &fake_irq_chip, handle_simple_irq); + +	desc = irq_to_desc(virq); +	KUNIT_ASSERT_PTR_NE(test, desc, NULL); + +	/* On some architectures, IRQs are NOREQUEST | NOPROBE by default. */ +	irq_settings_clr_norequest(desc); + +	return virq; +} + +static void irq_disable_depth_test(struct kunit *test) +{ +	struct irq_desc *desc; +	int virq, ret; + +	virq = irq_test_setup_fake_irq(test, NULL);  	desc = irq_to_desc(virq);  	KUNIT_ASSERT_PTR_NE(test, desc, NULL);  	ret = request_irq(virq, noop_handler, 0, "test_irq", NULL); -	KUNIT_EXPECT_EQ(test, ret, 0); +	KUNIT_ASSERT_EQ(test, ret, 0);  	KUNIT_EXPECT_EQ(test, desc->depth, 0); @@ -73,16 +89,13 @@ static void irq_free_disabled_test(struct kunit *test)  	struct irq_desc *desc;  	int virq, ret; -	virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, NULL); -	KUNIT_ASSERT_GE(test, virq, 0); - -	irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq); +	virq = irq_test_setup_fake_irq(test, NULL);  	desc = irq_to_desc(virq);  	KUNIT_ASSERT_PTR_NE(test, desc, NULL);  	ret = request_irq(virq, noop_handler, 0, "test_irq", NULL); -	KUNIT_EXPECT_EQ(test, ret, 0); +	KUNIT_ASSERT_EQ(test, ret, 0);  	KUNIT_EXPECT_EQ(test, desc->depth, 0); @@ -93,7 +106,7 @@ static void irq_free_disabled_test(struct kunit *test)  	KUNIT_EXPECT_GE(test, desc->depth, 1);  	ret = request_irq(virq, noop_handler, 0, "test_irq", NULL); -	KUNIT_EXPECT_EQ(test, ret, 0); +	KUNIT_ASSERT_EQ(test, ret, 0);  	KUNIT_EXPECT_EQ(test, desc->depth, 0);  	free_irq(virq, NULL); @@ -112,10 +125,7 @@ static void irq_shutdown_depth_test(struct kunit *test)  	if (!IS_ENABLED(CONFIG_SMP))  		kunit_skip(test, "requires CONFIG_SMP for managed shutdown"); -	virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, &affinity); -	KUNIT_ASSERT_GE(test, virq, 0); - -	irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq); +	virq = irq_test_setup_fake_irq(test, &affinity);  	desc = irq_to_desc(virq);  	KUNIT_ASSERT_PTR_NE(test, desc, NULL); @@ -124,7 +134,7 @@ static void irq_shutdown_depth_test(struct kunit *test)  	KUNIT_ASSERT_PTR_NE(test, data, NULL);  	ret = request_irq(virq, noop_handler, 0, "test_irq", NULL); -	KUNIT_EXPECT_EQ(test, ret, 0); +	KUNIT_ASSERT_EQ(test, ret, 0);  	KUNIT_EXPECT_TRUE(test, irqd_is_activated(data));  	KUNIT_EXPECT_TRUE(test, irqd_is_started(data)); @@ -169,13 +179,12 @@ static void irq_cpuhotplug_test(struct kunit *test)  		kunit_skip(test, "requires more than 1 CPU for CPU hotplug");  	if (!cpu_is_hotpluggable(1))  		kunit_skip(test, "CPU 1 must be hotpluggable"); +	if (!cpu_online(1)) +		kunit_skip(test, "CPU 1 must be online");  	cpumask_copy(&affinity.mask, cpumask_of(1)); -	virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, &affinity); -	KUNIT_ASSERT_GE(test, virq, 0); - -	irq_set_chip_and_handler(virq, &fake_irq_chip, handle_simple_irq); +	virq = irq_test_setup_fake_irq(test, &affinity);  	desc = irq_to_desc(virq);  	KUNIT_ASSERT_PTR_NE(test, desc, NULL); @@ -184,7 +193,7 @@ static void irq_cpuhotplug_test(struct kunit *test)  	KUNIT_ASSERT_PTR_NE(test, data, NULL);  	ret = request_irq(virq, noop_handler, 0, "test_irq", NULL); -	KUNIT_EXPECT_EQ(test, ret, 0); +	KUNIT_ASSERT_EQ(test, ret, 0);  	KUNIT_EXPECT_TRUE(test, irqd_is_activated(data));  	KUNIT_EXPECT_TRUE(test, irqd_is_started(data)); @@ -196,13 +205,9 @@ static void irq_cpuhotplug_test(struct kunit *test)  	KUNIT_EXPECT_EQ(test, desc->depth, 1);  	KUNIT_EXPECT_EQ(test, remove_cpu(1), 0); -	KUNIT_EXPECT_FALSE(test, irqd_is_activated(data)); -	KUNIT_EXPECT_FALSE(test, irqd_is_started(data));  	KUNIT_EXPECT_GE(test, desc->depth, 1);  	KUNIT_EXPECT_EQ(test, add_cpu(1), 0); -	KUNIT_EXPECT_FALSE(test, irqd_is_activated(data)); -	KUNIT_EXPECT_FALSE(test, irqd_is_started(data));  	KUNIT_EXPECT_EQ(test, desc->depth, 1);  	enable_irq(virq); diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index b64c57b44c20..db714d3014b5 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -653,13 +653,6 @@ void irq_mark_irq(unsigned int irq)  	irq_insert_desc(irq, irq_desc + irq);  } -#ifdef CONFIG_GENERIC_IRQ_LEGACY -void irq_init_desc(unsigned int irq) -{ -	free_desc(irq); -} -#endif -  #endif /* !CONFIG_SPARSE_IRQ */  int handle_irq_desc(struct irq_desc *desc) diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c index 9b09ad3f9914..e7ad99254841 100644 --- a/kernel/irq/msi.c +++ b/kernel/irq/msi.c @@ -1644,9 +1644,6 @@ static void msi_domain_free_locked(struct device *dev, struct msi_ctrl *ctrl)  	else  		__msi_domain_free_irqs(dev, domain, ctrl); -	if (ops->msi_post_free) -		ops->msi_post_free(domain, dev); -  	if (info->flags & MSI_FLAG_FREE_MSI_DESCS)  		msi_domain_free_descs(dev, ctrl);  } | 
