genirq: Add buslock support
authorThomas Gleixner <tglx@linutronix.de>
Thu, 13 Aug 2009 10:17:48 +0000 (12:17 +0200)
committerThomas Gleixner <tglx@linutronix.de>
Mon, 17 Aug 2009 08:54:05 +0000 (10:54 +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 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);
}

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.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Trilok Soni <soni.trilok@gmail.com>
Cc: Pavel Machek <pavel@ucw.cz>
Cc: Brian Swetland <swetland@google.com>
Cc: Joonyoung Shim <jy0922.shim@samsung.com>
Cc: m.szyprowski@samsung.com
Cc: t.fujak@samsung.com
Cc: kyungmin.park@samsung.com,
Cc: David Brownell <david-b@pacbell.net>
Cc: Daniel Ribeiro <drwyrm@gmail.com>
Cc: arve@android.com
Cc: Barry Song <21cnbao@gmail.com>
include/linux/irq.h
kernel/irq/chip.c
kernel/irq/internals.h
kernel/irq/manage.c

index 5e7c6ee..ce8171b 100644 (file)
@@ -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 b08c0d2..f856330 100644 (file)
@@ -580,6 +580,7 @@ __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
                desc->chip = &dummy_irq_chip;
        }
 
+       chip_bus_lock(irq, desc);
        spin_lock_irqsave(&desc->lock, flags);
 
        /* Uninstall? */
@@ -599,6 +600,7 @@ __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
                desc->chip->startup(irq);
        }
        spin_unlock_irqrestore(&desc->lock, flags);
+       chip_bus_sync_unlock(irq, desc);
 }
 EXPORT_SYMBOL_GPL(__set_irq_handler);
 
index e70ed55..1b5d742 100644 (file)
@@ -44,6 +44,19 @@ extern int irq_select_affinity_usr(unsigned int irq);
 
 extern void irq_set_thread_affinity(struct irq_desc *desc);
 
+/* Inline functions for support of irq chips on slow busses */
+static inline void chip_bus_lock(unsigned int irq, struct irq_desc *desc)
+{
+       if (unlikely(desc->chip->bus_lock))
+               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))
+               desc->chip->bus_sync_unlock(irq);
+}
+
 /*
  * Debugging printout:
  */
index d7f7b5f..0a3fd5b 100644 (file)
@@ -230,9 +230,11 @@ void disable_irq_nosync(unsigned int irq)
        if (!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);
 
@@ -294,7 +296,8 @@ void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume)
  *     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)
 {
@@ -304,9 +307,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);
 
@@ -468,12 +473,14 @@ static int irq_wait_for_interrupt(struct irqaction *action)
  */
 static void irq_finalize_oneshot(unsigned int irq, struct irq_desc *desc)
 {
+       chip_bus_lock(irq, 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);
+       chip_bus_sync_unlock(irq, desc);
 }
 
 #ifdef CONFIG_SMP
@@ -904,7 +911,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);
 
@@ -1011,7 +1025,10 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
        action->name = devname;
        action->dev_id = dev_id;
 
+       chip_bus_lock(irq, desc);
        retval = __setup_irq(irq, desc, action);
+       chip_bus_sync_unlock(irq, desc);
+
        if (retval)
                kfree(action);