Support for irq chips on slow busses (i2c, spi)
cover Support for irq chips on slow busses (i2c, spi)
The support for irq chips on slow busses eg. i2c, spi has been discussed to great length several times. Most of the details can be found in this thread: http://lkml.org/lkml/2009/7/21/266
The following patch series is a round up of the various patch snippets sent out during the discussion and the ideas we agreed on.
There are some subtle differences to the initial test patches:
1) Instead of using a separate flow handler we allow to request the one shot feature with a flag on request_irq and integrate the oneshot feature into the existing level/edge handlers.
2) Instead of conditional locking which makes the patch smaller but hides the already known fact that a device sits behind a slow irq chip I implemented a set of separate management functions (see patch 2/3). The initial idea with the conditional locking can be found here:
http://lkml.org/lkml/2009/7/22/163
3) patch 3/3 implements a helper function which allows the demultiplexing thread to call the demux handlers in its own thread context with out creating a separate thread for each subdevice interrupt. The device driver needs to request the interrupt via request_nested_slowbus_irq() to prevent the creation of the extra thread.
Please have a thorough look and hopefully a test ride on your favourite slowbus irq chip implementation so we can get this into .32
Thanks,
tglx
RFC PATCH 1/3 genirq: Add oneshot support
For threaded interrupt handlers we expect the hard interrupt handler part to mask the interrupt on the originating device. The interrupt line itself is reenabled after the hard interrupt handler has executed.
This requires access to the originating device from hard interrupt context which is not always possible. There are devices which can only be accessed via a bus (i2c, spi, ...). The bus access requires thread context. For such devices we need to keep the interrupt line masked until the threaded handler has executed.
Add a new flag IRQF_ONESHOT which allows drivers to request that the interrupt is not unmasked after the hard interrupt context handler has been executed and the thread has been woken. The interrupt line is unmasked after the thread handler function has been executed.
Note that for now IRQF_ONESHOT cannot be used with IRQF_SHARED to avoid complex accounting mechanisms.
--- include/linux/interrupt.h | 4 ++++ include/linux/irq.h | 1 + kernel/irq/chip.c | 14 +++++++++++--- kernel/irq/manage.c | 29 +++++++++++++++++++++++++++-- 4 files changed, 43 insertions(+), 5 deletions(-)
include/linux/interrupt.h +4 -0
@@ -49,6 +49,9 @@* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is * registered first in an shared interrupt is considered for * performance reasons) +* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished. +* Used by threaded interrupts which need to keep the +* irq line disabled until the threaded handler has been run. */ #define IRQF_DISABLED 0x00000020#define IRQF_SAMPLE_RANDOM 0x00000040
@@ -58,6 +61,7 @@#define IRQF_PERCPU 0x00000400#define IRQF_NOBALANCING 0x00000800#define IRQF_IRQPOLL 0x00001000 +#define IRQF_ONESHOT 0x00002000/* * Bits used by threaded handlers:
include/linux/irq.h +1 -0
@@ -69,6 +69,7 @@ typedef void (*irq_flow_handler_t)(unsig#define IRQ_MOVE_PCNTXT 0x01000000/* IRQ migration from process context */ #define IRQ_AFFINITY_SET 0x02000000/* IRQ affinity was set from userspace*/ #define IRQ_SUSPENDED 0x04000000/* IRQ has gone through suspend sequence */ +#define IRQ_ONESHOT 0x08000000/* IRQ is not unmasked after hardirq */ #ifdef CONFIG_IRQ_PER_CPU # define CHECK_IRQ_PER_CPU (var) ((var) & IRQ_PER_CPU)
kernel/irq/chip.c +11 -3
@@ -382,7 +382,10 @@ handle_level_irq(unsigned int irq, strucspin_lock (&desc->lock); desc->status &= ~IRQ_INPROGRESS ; -if (!(desc->status &IRQ_DISABLED ) && desc->chip->unmask) + +if (unlikely (desc->status &IRQ_ONESHOT )) + desc->status |=IRQ_MASKED ; +else if (!(desc->status &IRQ_DISABLED ) && desc->chip->unmask) desc->chip->unmask (irq); out_unlock:spin_unlock (&desc->lock);
@@ -478,8 +481,13 @@ handle_edge_irq(unsigned int irq, structkstat_incr_irqs_this_cpu (irq, desc);/* Start handling the irq */ -if (desc->chip->ack) - desc->chip->ack (irq); +if (unlikely (desc->status &IRQ_ONESHOT )) { + desc->status |=IRQ_MASKED ; +mask_ack_irq (desc, irq); + }else { +if (desc->chip->ack) + desc->chip->ack (irq); + }/* Mark the IRQ currently in progress.*/ desc->status |=IRQ_INPROGRESS ;
kernel/irq/manage.c +27 -2
@@ -444,6 +444,21 @@ static int irq_wait_for_interrupt(struct }/* +* Oneshot interrupts keep the irq line masked until the threaded +* handler finished. unmask if the interrupt has not been disabled and +* is marked MASKED. +*/ +static void irq_finalize_oneshot (unsigned int irq,struct irq_desc *desc) +{ +spin_lock_irq (&desc->lock); +if (!(desc->status &IRQ_DISABLED ) && (desc->status &IRQ_MASKED )) { + desc->status &= ~IRQ_MASKED ; + desc->chip->unmask (irq); + } +spin_unlock_irq (&desc->lock); +} + +/* * Interrupt handler thread */ static int irq_thread (void *data)
@@ -451,7 +466,7 @@ static int irq_thread(void *data)struct sched_param param = { .sched_priority =MAX_USER_RT_PRIO /2 , };struct irqaction *action = data;struct irq_desc *desc =irq_to_desc (action->irq); -int wake; +int wake, oneshot = desc->status &IRQ_ONESHOT ;sched_setscheduler (current,SCHED_FIFO , ¶m); current->irqaction = action;
@@ -475,6 +490,9 @@ static int irq_thread(void *data)spin_unlock_irq (&desc->lock); action->thread_fn (action->irq, action->dev_id); + +if (oneshot) +irq_finalize_oneshot (action->irq, desc); } wake =atomic_dec_and_test (&desc->threads_active);
@@ -547,6 +565,10 @@ __setup_irq(unsigned int irq, struct irqrand_initialize_irq (irq); } +/* Oneshot interrupts are not allowed with shared */ +if ((new->flags &IRQF_ONESHOT ) && (new->flags &IRQF_SHARED )) +return -EINVAL ; +/* * Threaded handler ? */
@@ -620,9 +642,12 @@ __setup_irq(unsigned int irq, struct irq desc->status |=IRQ_PER_CPU ;#endif - desc->status &= ~(IRQ_AUTODETECT |IRQ_WAITING | + desc->status &= ~(IRQ_AUTODETECT |IRQ_WAITING |IRQ_ONESHOT |IRQ_INPROGRESS |IRQ_SPURIOUS_DISABLED ); +if (new->flags &IRQF_ONESHOT ) + desc->status |=IRQ_ONESHOT ; +if (!(desc->status &IRQ_NOAUTOEN )) { desc->depth =0 ; desc->status &= ~IRQ_DISABLED ;
RFC PATCH 2/3 genirq: Add buslock support for irq chips on slow busses
In reply to Thomas GleixnerSome interrupt chips are connected to a "slow" bus (i2c, spi ...). The bus access needs to sleep and therefor cannot be called in atomic contexts.
Some of the generic interrupt management functions like disable_irq(), enable_irq() ... call interrupt chip functions with the irq_desc->lock held and interrupts disabled. This does not work for such devices.
Provide a separate synchronization mechanism for such interrupt chips. The irq_chip structure is extended by two optional functions (bus_lock and bus_sync_and_unlock).
The idea is to serialize the bus access for those operations in the core code so that drivers which are behind that bus operated interrupt controller do not have to worry about it and just can use the normal interfaces. To achieve this we add two function pointers to theirq_chip: bus_lock and bus_sync_unlock.
bus_lock() is called to serialize access to the interrupt controller bus.
Now the core code can issue chip->mask/unmask ... commands without changing the fast path code at all. The chip implementation merily stores that information in a chip private data structure and returns. No bus interaction as these functions are called from atomic context.
After that bus_sync_unlock() is called outside the atomic context. Now the chip implementation issues the bus commands, waits for completion and unlocks the interrupt controller bus.
The irq_chip implementation as pseudo code:
struct irq_chip_data {
struct mutex mutex;
unsigned int irq_offset;
unsigned long mask;
unsigned long mask_status;
}static void bus_lock(unsigned int irq)
{
struct irq_chip_data *data = get_irq_desc_chip_data(irq);
mutex_lock(&data->mutex);
}static void mask(unsigned int irq)
{
struct irq_chip_data *data = get_irq_desc_chip_data(irq);
irq -= data->irq_offset;
data->mask |= (1 << irq);
}static void unmask(unsigned int irq)
{
struct irq_chip_data *data = get_irq_desc_chip_data(irq);
irq -= data->irq_offset;
data->mask &= ~(1 << irq);
}static void bus_sync_unlock(unsigned int irq)
{
struct irq_chip_data *data = get_irq_desc_chip_data(irq);
if (data->mask != data->mask_status) {
do_bus_magic_to_set_mask(data->mask);
data->mask_status = data->mask;
}
mutex_unlock(&data->mutex);
}For the device drivers a new set of functions is provided:request_threaded_slowbus_irq, free_slowbus_irq, disable_slowbus_irq and enable_slowbus_irq. They work the same as the !slowbus variants with the only restriction that the calls need to come from non atomic context.
--- include/linux/interrupt.h | 9 +++ include/linux/irq.h | 6 ++ kernel/irq/manage.c | 117 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 1 deletion(-)
include/linux/interrupt.h +9 -0
@@ -113,6 +113,11 @@ request_threaded_irq(unsigned int irq, iirq_handler_t thread_fn, unsigned long flags, const char *name, void *dev); +extern int __must_check +request_threaded_slowbus_irq (unsigned int irq,irq_handler_t handler, +irq_handler_t thread_fn, +unsigned long flags,const char *name,void *dev); +static inline int __must_check request_irq (unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)
@@ -145,6 +150,7 @@ static inline void exit_irq_thread(void)#endif extern void free_irq (unsigned int ,void *); +extern void free_slowbus_irq (unsigned int irq,void *dev_id);struct device ;
@@ -186,6 +192,9 @@ extern void disable_irq_nosync(unsignedextern void disable_irq (unsigned int irq);extern void enable_irq (unsigned int irq); +extern void disable_slowbus_irq (unsigned int irq); +extern void enable_slowbus_irq (unsigned int irq); +/* The following three functions are for the core kernel use only. */ #ifdef CONFIG_GENERIC_HARDIRQS extern void suspend_device_irqs (void );
include/linux/irq.h +6 -0
@@ -101,6 +101,9 @@ struct msi_desc;* @set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ * @set_wake: enable/disable power-management wake-on of an IRQ * +* @bus_lock: function to lock access to slow bus (i2c) chips +* @bus_sync_unlock: function to sync and unlock slow bus (i2c) chips +* * @release: release function solely used by UML * @typename: obsoleted by name, kept as migration helper */
@@ -124,6 +127,9 @@ struct irq_chip {int (*set_type)(unsigned int irq,unsigned int flow_type);int (*set_wake)(unsigned int irq,unsigned int on); +void (*bus_lock)(unsigned int irq); +void (*bus_sync_unlock)(unsigned int irq); +/* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq,void *dev_id);
kernel/irq/manage.c +114 -0
135 lines not on this page · raw message
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 17 lines · Thomas Gleixner
kernel/irq/manage.c +16
-0+/** +* disable_slowbus_irq - disable an slowbus irq and wait for completion +* @irq: Interrupt to disable +* +* Disable the selected interrupt line. Enables and Disables are +* nested. +* This function waits for any pending IRQ handlers for this interrupt +* to complete before returning. If you use this function while +* holding a resource the IRQ handler may need you will deadlock. +* +* This function must not be called from IRQ context. +*/ +void disable_slowbus_irq (unsigned int irq) +{ +struct irq_desc *desc =irq_to_desc (irq); +
kernel/irq/manage.c void disable_irq(unsigned int irq) · line 281
Quoted 10 lines
kernel/irq/manage.c +10
-0+if (!desc || !desc->chip || !desc->chip->bus_lock) +return ; + + desc->chip->bus_lock (irq); +disable_irq_nosync (irq); +if (desc->action) +synchronize_irq (irq); + desc->chip->bus_sync_unlock (irq); +} +EXPORT_SYMBOL (disable_slowbus_irq);
Should we also not check that desc->chip->bus_lock is not set for the regular function disable_irq()?
It seems to me mixing disable_irq() and disable_slowbus_irq() is a recipe for disaster.
Same for the other slowbus functions of course.
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 32 lines · Peter Zijlstra
Quoted 17 lines · Thomas Gleixner
kernel/irq/manage.c +16
-0+/** +* disable_slowbus_irq - disable an slowbus irq and wait for completion +* @irq: Interrupt to disable +* +* Disable the selected interrupt line. Enables and Disables are +* nested. +* This function waits for any pending IRQ handlers for this interrupt +* to complete before returning. If you use this function while +* holding a resource the IRQ handler may need you will deadlock. +* +* This function must not be called from IRQ context. +*/ +void disable_slowbus_irq (unsigned int irq) +{ +struct irq_desc *desc =irq_to_desc (irq); +kernel/irq/manage.c void disable_irq(unsigned int irq) · line 281
Quoted 10 lines
kernel/irq/manage.c +10
-0+if (!desc || !desc->chip || !desc->chip->bus_lock) +return ; + + desc->chip->bus_lock (irq); +disable_irq_nosync (irq); +if (desc->action) +synchronize_irq (irq); + desc->chip->bus_sync_unlock (irq); +} +EXPORT_SYMBOL (disable_slowbus_irq);Should we also not check that
desc->chip->bus_lockis not set for the regular functiondisable_irq()?
Quoted 4 lines
It seems to me mixing
disable_irq()anddisable_slowbus_irq()is a recipe for disaster.Same for the other slowbus functions of course.
Yeah, that's what I wanted to avoid with the first version, which did the conditional locking and did not require a separate API, but Ingo frowned upon the conditional lock.
Thanks,
tglx
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 33 lines · Thomas Gleixner
Quoted 32 lines · Peter Zijlstra
Quoted 17 lines · Thomas Gleixner
kernel/irq/manage.c +16
-0+/** +* disable_slowbus_irq - disable an slowbus irq and wait for completion +* @irq: Interrupt to disable +* +* Disable the selected interrupt line. Enables and Disables are +* nested. +* This function waits for any pending IRQ handlers for this interrupt +* to complete before returning. If you use this function while +* holding a resource the IRQ handler may need you will deadlock. +* +* This function must not be called from IRQ context. +*/ +void disable_slowbus_irq (unsigned int irq) +{ +struct irq_desc *desc =irq_to_desc (irq); +kernel/irq/manage.c void disable_irq(unsigned int irq) · line 281
Quoted 10 lines
kernel/irq/manage.c +10
-0+if (!desc || !desc->chip || !desc->chip->bus_lock) +return ; + + desc->chip->bus_lock (irq); +disable_irq_nosync (irq); +if (desc->action) +synchronize_irq (irq); + desc->chip->bus_sync_unlock (irq); +} +EXPORT_SYMBOL (disable_slowbus_irq);Should we also not check that
desc->chip->bus_lockis not set for the regular functiondisable_irq()?
Quoted 8 lines
Quoted 4 lines
It seems to me mixing
disable_irq()anddisable_slowbus_irq()is a recipe for disaster.Same for the other slowbus functions of course.
Yeah, that's what I wanted to avoid with the first version, which did the conditional locking and did not require a separate API, but Ingo frowned upon the conditional lock.
Mind posting that version too?
Conditional locking is really nasty but if the only other option is nastier there's not much we can do, is there?
Ingo
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 40 lines · Ingo Molnar
Quoted 38 lines · Thomas Gleixner
Quoted 36 lines · Peter Zijlstra
Quoted 17 lines · Thomas Gleixner
kernel/irq/manage.c +16
-0+/** +* disable_slowbus_irq - disable an slowbus irq and wait for completion +* @irq: Interrupt to disable +* +* Disable the selected interrupt line. Enables and Disables are +* nested. +* This function waits for any pending IRQ handlers for this interrupt +* to complete before returning. If you use this function while +* holding a resource the IRQ handler may need you will deadlock. +* +* This function must not be called from IRQ context. +*/ +void disable_slowbus_irq (unsigned int irq) +{ +struct irq_desc *desc =irq_to_desc (irq); +kernel/irq/manage.c void disable_irq(unsigned int irq) · line 281
Quoted 10 lines
kernel/irq/manage.c +10
-0+if (!desc || !desc->chip || !desc->chip->bus_lock) +return ; + + desc->chip->bus_lock (irq); +disable_irq_nosync (irq); +if (desc->action) +synchronize_irq (irq); + desc->chip->bus_sync_unlock (irq); +} +EXPORT_SYMBOL (disable_slowbus_irq);Should we also not check that
desc->chip->bus_lockis not set for the regular functiondisable_irq()?It seems to me mixing
disable_irq()anddisable_slowbus_irq()is a recipe for disaster.Same for the other slowbus functions of course.
Quoted 8 lines
Quoted 3 lines
Yeah, that's what I wanted to avoid with the first version, which did the conditional locking and did not require a separate API, but Ingo frowned upon the conditional lock.
Mind posting that version too?
Conditional locking is really nasty but if the only other option is nastier there's not much we can do, is there?
Ah what I meant was that we can make the posted version better by adding
BUG_ON(desc && desc->chip && desc->chip->bus_lock);
to disable_irq/enable_irq/free_irq.
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
In reply to Ingo MolnarQuoted 8 lines · Ingo Molnar
Quoted 3 lines
Yeah, that's what I wanted to avoid with the first version, which did the conditional locking and did not require a separate API, but Ingo frowned upon the conditional lock.
Mind posting that version too?
Conditional locking is really nasty but if the only other option is nastier there's not much we can do, is there?
Here you go.
Thanks,
tglx
----
Subject: genirq: Add buslock support
From: Thomas Gleixner <[email protected]>
Date: Thu, 13 Aug 2009 12:17:48 +0200
Some interrupt chips are connected to a "slow" bus (i2c, spi ...). The bus access needs to sleep and therefor cannot be called in atomic contexts.
Some of the generic interrupt management functions like disable_irq(), enable_irq() ... call interrupt chip functions with the irq_desc->lock held and interrupts disabled. This does not work for such devices.
Provide a separate synchronization mechanism for such interrupt chips. The irq_chip structure is extended by two optional functions (bus_lock and bus_sync_and_unlock).
The idea is to serialize the bus access for those operations in the core code so that drivers which are behind that bus operated interrupt controller do not have to worry about it and just can use the normal interfaces. To achieve this we add two function pointers to theirq_chip: bus_lock and bus_sync_unlock.
bus_lock() is called to serialize access to the interrupt controller bus.
Now the core code can issue chip->mask/unmask ... commands without changing the fast path code at all. The chip implementation merily stores that information in a chip private data structure and returns. No bus interaction as these functions are called from atomic context.
After that bus_sync_unlock() is called outside the atomic context. Now the chip implementation issues the bus commands, waits for completion and unlocks the interrupt controller bus.
The irq_chip implementation as pseudo code:
struct irq_chip_data {
struct mutex mutex;
unsigned int irq_offset;
unsigned long mask;
unsigned long mask_status;
}static void bus_lock(unsigned int irq)
{
struct irq_chip_data *data = get_irq_desc_chip_data(irq);
mutex_lock(&data->mutex);
}static void mask(unsigned int irq)
{
struct irq_chip_data *data = get_irq_desc_chip_data(irq);
irq -= data->irq_offset;
data->mask |= (1 << irq);
}static void unmask(unsigned int irq)
{
struct irq_chip_data *data = get_irq_desc_chip_data(irq);
irq -= data->irq_offset;
data->mask &= ~(1 << irq);
}static void bus_sync_unlock(unsigned int irq)
{
struct irq_chip_data *data = get_irq_desc_chip_data(irq);
if (data->mask != data->mask_status) {
do_bus_magic_to_set_mask(data->mask);
data->mask_status = data->mask;
}
mutex_unlock(&data->mutex);
}The device drivers can use request_threaded_irq, free_irq, disable_irq and enable_irq as usual with the only restriction that the calls need to come from non atomic context.
--- include/linux/irq.h | 6 ++++++ kernel/irq/manage.c | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-)
include/linux/irq.h +6 -0
@@ -101,6 +101,9 @@ struct msi_desc;* @set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ * @set_wake: enable/disable power-management wake-on of an IRQ * +* @bus_lock: function to lock access to slow bus (i2c) chips +* @bus_sync_unlock: function to sync and unlock slow bus (i2c) chips +* * @release: release function solely used by UML * @typename: obsoleted by name, kept as migration helper */
@@ -124,6 +127,9 @@ struct irq_chip {int (*set_type)(unsigned int irq,unsigned int flow_type);int (*set_wake)(unsigned int irq,unsigned int on); +void (*bus_lock)(unsigned int irq); +void (*bus_sync_unlock)(unsigned int irq); +/* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq,void *dev_id);
kernel/irq/manage.c +33 -1
@@ -17,6 +17,22 @@#include "internals.h" +static inline void chip_bus_lock (unsigned int irq,struct irq_desc *desc) +{ +if (unlikely (desc->chip->bus_lock)) { +might_sleep (); + desc->chip->bus_lock (irq); + } +} + +static inline void chip_bus_sync_unlock (unsigned int irq,struct irq_desc *desc) +{ +if (unlikely (desc->chip->bus_sync_unlock)) { +might_sleep (); + desc->chip->bus_sync_unlock (irq); + } +} +/** * synchronize_irq - wait for pending IRQ handlers (on other CPUs) * @irq: interrupt number to wait for
@@ -222,9 +238,11 @@ void disable_irq_nosync(unsigned int irqif (!desc)return ; +chip_bus_lock (irq, desc);spin_lock_irqsave (&desc->lock, flags);__disable_irq (desc, irq, false);spin_unlock_irqrestore (&desc->lock, flags); +chip_bus_sync_unlock (irq, desc); }EXPORT_SYMBOL (disable_irq_nosync);
@@ -286,7 +304,8 @@ void __enable_irq(struct irq_desc *desc,* matches the last disable, processing of interrupts on this * IRQ line is re-enabled. * -* This function may be called from IRQ context. +* This function may be called from IRQ context only when +* desc->chip->bus_lock and desc->chip->bus_sync_unlock are NULL ! */ void enable_irq (unsigned int irq) {
@@ -296,9 +315,11 @@ void enable_irq(unsigned int irq)if (!desc)return ; +chip_bus_lock (irq, desc);spin_lock_irqsave (&desc->lock, flags);__enable_irq (desc, irq, false);spin_unlock_irqrestore (&desc->lock, flags); +chip_bus_sync_unlock (irq, desc); }EXPORT_SYMBOL (enable_irq);
@@ -854,7 +875,14 @@ EXPORT_SYMBOL_GPL(remove_irq);*/ void free_irq (unsigned int irq,void *dev_id) { +struct irq_desc *desc =irq_to_desc (irq); + +if (!desc) +return ; + +chip_bus_lock (irq, desc);kfree (__free_irq (irq, dev_id)); +chip_bus_sync_unlock (irq, desc); }EXPORT_SYMBOL (free_irq);
@@ -955,10 +983,14 @@ int request_threaded_irq(unsigned int ir action->name = devname; action->dev_id = dev_id; +chip_bus_lock (irq, desc); + retval =__setup_irq (irq, desc, action);if (retval)kfree (action); +chip_bus_sync_unlock (irq, desc); +#ifdef CONFIG_DEBUG_SHIRQ if (irqflags &IRQF_SHARED ) {/*
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
In reply to Thomas GleixnerHi!
Quoted 25 lines
Index:
linux-2.6-tip/include/linux/interrupt.h
===================================================================include/linux/interrupt.h +6
-0@@ -113,6 +113,11 @@ request_threaded_irq(unsigned int irq, iirq_handler_t thread_fn, unsigned long flags, const char *name, void *dev); +extern int __must_check +request_threaded_slowbus_irq (unsigned int irq,irq_handler_t handler, +irq_handler_t thread_fn, +unsigned long flags,const char *name,void *dev); +static inline int __must_check request_irq (unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)@@ -145,6 +150,7 @@ static inline void exit_irq_thread(void)#endif extern void free_irq (unsigned int ,void *); +extern void free_slowbus_irq (unsigned int irq,void *dev_id);struct device ;
include/linux/interrupt.h extern void disable_irq_nosync(unsigned · line 197
Quoted 6 lines
include/linux/interrupt.h +3
-0@@ -186,6 +192,9 @@ extern void disable_irq_nosync(unsignedextern void disable_irq (unsigned int irq);extern void enable_irq (unsigned int irq); +extern void disable_slowbus_irq (unsigned int irq); +extern void enable_slowbus_irq (unsigned int irq); +
AFAICT this means that driver would need to know what kind of IRQ it is hooked to, right? That will lead to some ugly code in drivers that can handle both normal and slowbus irqs, right?
Pavel
Signature · 2 lines
(english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 7 lines · Pavel Machek
include/linux/interrupt.h extern void disable_irq_nosync(unsigned · line 197
Quoted 3 lines
include/linux/interrupt.h +3
-0+extern void disable_slowbus_irq (unsigned int irq); +extern void enable_slowbus_irq (unsigned int irq); +AFAICT this means that driver would need to know what kind of IRQ it is hooked to, right? That will lead to some ugly code in drivers that can handle both normal and slowbus irqs, right?
Are there such drivers in reality ?
Thanks,
tglx
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 10 lines · Thomas Gleixner
Quoted 7 lines · Pavel Machek
include/linux/interrupt.h extern void disable_irq_nosync(unsigned · line 197
Quoted 3 lines
include/linux/interrupt.h +3
-0+extern void disable_slowbus_irq (unsigned int irq); +extern void enable_slowbus_irq (unsigned int irq); +AFAICT this means that driver would need to know what kind of IRQ it is hooked to, right? That will lead to some ugly code in drivers that can handle both normal and slowbus irqs, right?
Are there such drivers in reality ?
I think so. Between Spitz and Akita, some gpios were moved to slow i2c extender. For stuff like buttons...
Now... it is probably already fixed somehow for Spitz/Akita, but I believe more hardware designers do such things....
Pavel
Signature · 2 lines
(english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
In reply to Thomas GleixnerQuoted 7 lines · Thomas Gleixner
Quoted 3 lines · Pavel Machek
AFAICT this means that driver would need to know what kind of IRQ it is hooked to, right? That will lead to some ugly code in drivers that can handle both normal and slowbus irqs, right?
Are there such drivers in reality ?
Yes. The GPIO based stuff is the prime example but there's other examples - one is the WM831x touchscreen (no driver in mainline yet) which can use interrupts via the main interrupt controller on the CPU but also has the option of bringing the interrupt signals out to dedicated pins on the chip for direct connection to the CPU precisely to avoid the overheads of these slow interrupt controllers.
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 9 lines · Mark Brown
Quoted 7 lines · Thomas Gleixner
Quoted 3 lines · Pavel Machek
AFAICT this means that driver would need to know what kind of IRQ it is hooked to, right? That will lead to some ugly code in drivers that can handle both normal and slowbus irqs, right?
Are there such drivers in reality ?
Quoted 7 lines
Yes. The GPIO based stuff is the prime example but there's other examples - one is the WM831x touchscreen (no driver in mainline yet) which can use interrupts via the main interrupt controller on the CPU but also has the option of bringing the interrupt signals out to dedicated pins on the chip for direct connection to the CPU precisely to avoid the overheads of these slow interrupt controllers.
This would call for Thomas's first version of the patch, that is transparent to drivers - the IRQ subsystem will know how to lock access to the line.
How about implementing that first patch in a cleaner way - can we somehow express the slow-bus property purely via the irqchip? Or is that too lowlevel?
Ingo
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 19 lines · Ingo Molnar
Quoted 9 lines · Mark Brown
Quoted 7 lines · Thomas Gleixner
Quoted 3 lines · Pavel Machek
AFAICT this means that driver would need to know what kind of IRQ it is hooked to, right? That will lead to some ugly code in drivers that can handle both normal and slowbus irqs, right?
Are there such drivers in reality ?
Quoted 7 lines
Yes. The GPIO based stuff is the prime example but there's other examples - one is the WM831x touchscreen (no driver in mainline yet) which can use interrupts via the main interrupt controller on the CPU but also has the option of bringing the interrupt signals out to dedicated pins on the chip for direct connection to the CPU precisely to avoid the overheads of these slow interrupt controllers.
Quoted 7 lines
This would call for Thomas's first version of the patch, that is transparent to drivers - the IRQ subsystem will know how to lock access to the line.
How about implementing that first patch in a cleaner way - can we somehow express the slow-bus property purely via the irqchip? Or is that too lowlevel?
The problem here is that the management functions serialize via irqdesc->lock and call the chip level function with the lock held (preemption and interrupts disabled). So we can not access the slow bus chips from these low level functions.
If the low level functions just store the information and schedule it for bus access then there is no serialization anymore. Lets look at disable_irq():
spin_lock_irqsave(&desc->lock); .... desc->chip->mask(); schedule bus access; ... spin_lock_irqsave(&desc->lock);
So now we return, but the mask has not reached the chip.
The idea of bus_lock/bus_sync_unlock() was to provide well defined synchronization points for the chip level implementation to do the bus update and have this serialized against other management functions.
desc->chip->bus_lock(); Take the chip->bus mutex spin_lock_irqsave(&desc->lock); .... desc->chip->mask(); store mask information ... spin_lock_irqsave(&desc->lock); desc->chip->bus_sync_unlock(); Update the mask via the slow bus Release chip->bus mutex
That way we have made sure that the change to the chip actually hits the hardware before we allow further management operations and it simplifies the code for the chip implementation as the bus access can be done in the context of the caller w/o the need of an extra thread/workqueue ...
Thanks,
tglx
Re: [RFC patch 2/3] genirq: Add buslock support for irq chips on slow busses
Quoted 64 lines · Thomas Gleixner
Quoted 19 lines · Ingo Molnar
Quoted 9 lines · Mark Brown
Quoted 7 lines · Thomas Gleixner
Quoted 3 lines · Pavel Machek
AFAICT this means that driver would need to know what kind of IRQ it is hooked to, right? That will lead to some ugly code in drivers that can handle both normal and slowbus irqs, right?
Are there such drivers in reality ?
Quoted 7 lines
Yes. The GPIO based stuff is the prime example but there's other examples - one is the WM831x touchscreen (no driver in mainline yet) which can use interrupts via the main interrupt controller on the CPU but also has the option of bringing the interrupt signals out to dedicated pins on the chip for direct connection to the CPU precisely to avoid the overheads of these slow interrupt controllers.
Quoted 7 lines
This would call for Thomas's first version of the patch, that is transparent to drivers - the IRQ subsystem will know how to lock access to the line.
How about implementing that first patch in a cleaner way - can we somehow express the slow-bus property purely via the irqchip? Or is that too lowlevel?
The problem here is that the management functions serialize via
irqdesc->lockand call the chip level function with the lock held (preemption and interrupts disabled). So we can not access the slow bus chips from these low level functions.If the low level functions just store the information and schedule it for bus access then there is no serialization anymore. Lets look at
disable_irq():spin_lock_irqsave(&desc->lock); .... desc->chip->mask(); schedule bus access; ... spin_lock_irqsave(&desc->lock);So now we return, but the mask has not reached the chip.
The idea of bus_lock/bus_sync_unlock() was to provide well defined synchronization points for the chip level implementation to do the bus update and have this serialized against other management functions.
desc->chip->bus_lock(); Take the chip->bus mutex spin_lock_irqsave(&desc->lock); .... desc->chip->mask(); store mask information ... spin_lock_irqsave(&desc->lock); desc->chip->bus_sync_unlock(); Update the mask via the slow bus Release chip->bus mutex
Quoted 5 lines
That way we have made sure that the change to the chip actually hits the hardware before we allow further management operations and it simplifies the code for the chip implementation as the bus access can be done in the context of the caller w/o the need of an extra thread/workqueue ...
Given the alternatives i'd prefer this one now ...
Ingo
RFC PATCH 3/3 genirq: Support nested threaded irq handling
In reply to Thomas GleixnerInterrupt chips which are behind a slow bus (i2c, spi ...) and demultiplex other interrupt sources need to run their interrupt handler in a thread.
The demultiplexed interrupt handlers need to run in thread context as well and need to finish before the demux handler thread can reenable the interrupt line. So instead of creating separate threads which need to synchronize with the demux handler thread we provide a function which calls the interrupt thread function of the sub device in the context of the demux handler thread.
To avoid that a separate thread is created for the subdevices the function request_nested_slowbus_irq() is provided which sets a dummy handler for the primary handler which should never be called. A flag prevents the creation of the separate handler thread in __setup_irq().
--- include/linux/interrupt.h | 9 +++++++++ kernel/irq/chip.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ kernel/irq/internals.h | 2 ++ kernel/irq/manage.c | 28 +++++++++++++++++++++++++++- 4 files changed, 82 insertions(+), 1 deletion(-)
include/linux/interrupt.h +9 -0
@@ -52,6 +52,9 @@* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished. * Used by threaded interrupts which need to keep the * irq line disabled until the threaded handler has been run. +* IRQF_NESTED - Interrupt is nested into another threaded interrupt. Handler +* is called in the parent interrupt thread context +* */ #define IRQF_DISABLED 0x00000020#define IRQF_SAMPLE_RANDOM 0x00000040
@@ -62,6 +65,7 @@#define IRQF_NOBALANCING 0x00000800#define IRQF_IRQPOLL 0x00001000#define IRQF_ONESHOT 0x00002000 +#define IRQF_NESTED 0x00004000/* * Bits used by threaded handlers:
@@ -125,6 +129,11 @@ request_irq(unsigned int irq, irq_handle return request_threaded_irq(irq, handler, NULL, flags, name, dev); } +extern int __must_check +request_nested_slowbus_irq (unsigned int irq,irq_handler_t thread_fn, +unsigned long irqflags,const char *devname, +void *dev_id); +extern void exit_irq_thread (void );#else
kernel/irq/chip.c +44 -0
50 lines not on this page · raw message
kernel/irq/internals.h +2 -0
@@ -45,6 +45,8 @@ extern int irq_select_affinity_usr(unsigextern void irq_set_thread_affinity (struct irq_desc *desc,const struct cpumask *cpumask); +extern irqreturn_t handle_nested_irq_primary (int irq,void *dev_id); +/* * Debugging printout: */
kernel/irq/manage.c +27 -1
@@ -625,7 +625,7 @@ __setup_irq(unsigned int irq, struct irq/* * Threaded handler ? */ -if (new->thread_fn) { +if (new->thread_fn && !(new->flags &IRQF_NESTED )) {struct task_struct *t; t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
@@ -1095,3 +1095,29 @@ int request_slowbus_threaded_irq(unsignereturn res; }EXPORT_SYMBOL (request_slowbus_threaded_irq); + +/** +* request_nested_slowbus_irq - allocate an interrupt line for a nested irq +* @irq: Interrupt line to allocate +* @thread_fn: Function called from the irq handler thread +* @irqflags: Interrupt type flags +* @devname: An ascii name for the claiming device +* @dev_id: A cookie passed back to the handler function +* +* This function is used for interrupts which nest into another +* threaded interrupt handler, e.g. subdevice interrupts of an +* interrupt controller which is connected via i2c/spi or other +* slow path mechanisms. The thread_fn is called from the +* demultiplexing interrupt thread via handle_nested_irq(). +*/ +int request_nested_slowbus_irq (unsigned int irq,irq_handler_t thread_fn, +unsigned long irqflags,const char *devname, +void *dev_id) +{ + irqflags |=IRQF_NESTED ; + +return request_threaded_slowbus_irq (irq, handle_nested_irq_primary, + thread_fn, irqflags, devname, + dev_id); +} +EXPORT_SYMBOL (request_nested_slowbus_irq);