[RFC patch 2/3] genirq: Add buslock support for irq chips on slowbusses

From: Thomas Gleixner
Date: Thu Aug 13 2009 - 15:41:48 EST


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 the
irq_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.

Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxxxxx>

---
include/linux/interrupt.h | 9 +++
include/linux/irq.h | 6 ++
kernel/irq/manage.c | 117 +++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 131 insertions(+), 1 deletion(-)

Index: linux-2.6-tip/include/linux/interrupt.h
===================================================================
--- linux-2.6-tip.orig/include/linux/interrupt.h
+++ linux-2.6-tip/include/linux/interrupt.h
@@ -113,6 +113,11 @@ request_threaded_irq(unsigned int irq, i
irq_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(unsigned
extern 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);
Index: linux-2.6-tip/include/linux/irq.h
===================================================================
--- linux-2.6-tip.orig/include/linux/irq.h
+++ linux-2.6-tip/include/linux/irq.h
@@ -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);
Index: linux-2.6-tip/kernel/irq/manage.c
===================================================================
--- linux-2.6-tip.orig/kernel/irq/manage.c
+++ linux-2.6-tip/kernel/irq/manage.c
@@ -253,6 +253,33 @@ void disable_irq(unsigned int irq)
}
EXPORT_SYMBOL(disable_irq);

+/**
+ * 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);
+
+ 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);
+
void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume)
{
if (resume)
@@ -302,6 +329,32 @@ void enable_irq(unsigned int irq)
}
EXPORT_SYMBOL(enable_irq);

+/**
+ * enable_slowbus_irq - enable handling of a slowbus irq
+ * @irq: Interrupt to enable
+ *
+ * Undoes the effect of one call to disable_irq(). If this
+ * matches the last disable, processing of interrupts on this
+ * IRQ line is re-enabled.
+ *
+ * This function must not be called from IRQ context
+ */
+void enable_slowbus_irq(unsigned int irq)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+ unsigned long flags;
+
+ if (!desc || !desc->chip || !desc->chip->bus_lock)
+ return;
+
+ desc->chip->bus_lock(irq);
+ spin_lock_irqsave(&desc->lock, flags);
+ __enable_irq(desc, irq, false);
+ spin_unlock_irqrestore(&desc->lock, flags);
+ desc->chip->bus_sync_unlock(irq);
+}
+EXPORT_SYMBOL(enable_slowbus_irq);
+
static int set_irq_wake_real(unsigned int irq, unsigned int on)
{
struct irq_desc *desc = irq_to_desc(irq);
@@ -859,6 +912,33 @@ void free_irq(unsigned int irq, void *de
EXPORT_SYMBOL(free_irq);

/**
+ * free_slowbus_irq - free an interrupt allocated with request_slowbus_irq
+ * @irq: Interrupt line to free
+ * @dev_id: Device identity to free
+ *
+ * Remove an interrupt handler. The handler is removed and if the
+ * interrupt line is no longer in use by any driver it is disabled.
+ * On a shared IRQ the caller must ensure the interrupt is disabled
+ * on the card it drives before calling this function. The function
+ * does not return until any executing interrupts for this IRQ
+ * have completed.
+ *
+ * This function must not be called from interrupt context.
+ */
+void free_slowbus_irq(unsigned int irq, void *dev_id)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ if (!desc || !desc->chip || !desc->chip->bus_lock)
+ return;
+
+ desc->chip->bus_lock(irq);
+ kfree(__free_irq(irq, dev_id));
+ desc->chip->bus_sync_unlock(irq);
+}
+EXPORT_SYMBOL(free_slowbus_irq);
+
+/**
* request_threaded_irq - allocate an interrupt line
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs.
@@ -981,3 +1061,37 @@ int request_threaded_irq(unsigned int ir
return retval;
}
EXPORT_SYMBOL(request_threaded_irq);
+
+/**
+ * request_slowbus_threaded_irq - allocate a slowbus interrupt line
+ * @irq: Interrupt line to allocate
+ * @handler: Function to be called when the IRQ occurs.
+ * Primary handler for threaded interrupts
+ * @thread_fn: Function called from the irq handler thread
+ * If NULL, no irq thread is created
+ * @irqflags: Interrupt type flags
+ * @devname: An ascii name for the claiming device
+ * @dev_id: A cookie passed back to the handler function
+ *
+ * See request_threaded_irq for further information
+ */
+int request_slowbus_threaded_irq(unsigned int irq, irq_handler_t handler,
+ irq_handler_t thread_fn,
+ unsigned long irqflags,
+ const char *devname, void *dev_id)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+ int res;
+
+ if (!desc || !desc->chip || !desc->chip->bus_lock)
+ return -EINVAL;
+
+ desc->chip->bus_lock(irq);
+ res = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
+ dev_id);
+
+ desc->chip->bus_sync_unlock(irq);
+
+ return res;
+}
+EXPORT_SYMBOL(request_slowbus_threaded_irq);


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/