Skip to content

Kernel Module 5 IRQ

Frank Bauernöppel edited this page Oct 22, 2017 · 5 revisions

This example starts with a trivial interrupt handler. Interrupt handlers are not allowed to do lengthy tasks or wait for a resource (e.g. a semaphore). Real-world interrupt handlers use techniques like workqueues or tasklets to postpone the heavy part of their work.

Workqueues are a more sophisticated approach which is discussed later.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h> 
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>

static const int gpio_pin = 5; // BCM GPIO 05 is routed to expansion header pin 29. It has an internal pull-up. 
static int irq_input_pin = -1;


static irqreturn_t irq_handler( int irq, void *dev_id )
{
    printk( KERN_INFO "Interrupt" );
    // see http://www.ibm.com/developerworks/library/l-tasklets/
    return IRQ_HANDLED;
}


static int __init my_init(void)
{
    int rc;

    printk(KERN_INFO "my module: init\n");

    rc = gpio_request( gpio_pin, "my gpio pin" );
    if( rc < 0 ) {
        printk(KERN_ERR "my module: gpio_request failed with error %d\n", rc );
        return rc;
    }

    irq_input_pin = gpio_to_irq(gpio_pin);
    if( irq_input_pin < 0 ) {
        printk(KERN_ERR "my module: gpio_to_irq failed with error %d\n", rc );
        gpio_free(gpio_pin);
        return irq_input_pin;
    }
    
    // the string "my_gpio_handler" can be found in cat /proc/interrupts when module is loaded
    rc = request_irq( irq_input_pin, &irq_handler, IRQF_TRIGGER_RISING, "my_gpio_handler", NULL );
    if( rc < 0 ) {
        gpio_free(gpio_pin);
        printk(KERN_ERR "my module: request_irq failed with error %d\n", rc );
    }

    return 0;
}


static void __exit my_exit(void)
{
    printk(KERN_INFO "my module: exit\n" );
    free_irq( irq_input_pin, NULL );
    gpio_free(gpio_pin);
}


module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("F.B.");
MODULE_DESCRIPTION("My GPIO IRQ Driver");

Testing the interrupt handler with another GPIO

Build the module, copy it to the target and load it (insmod my_module.ko). Now, choose another pin as general purpose output, for example J10 pin 3 --> GPIO1_IO20 --> linux GPIO number 20. Apply the code from above and watch the module message output on the console:

echo 20 > /sys/class/gpio/export 
echo out > /sys/class/gpio/gpio20/direction
echo 1 > /sys/class/gpio/gpio20/value
echo 0 > /sys/class/gpio/gpio20/value
echo 1 > /sys/class/gpio/gpio20/value

Each falling edge should produce a single line of output.

Testing the interrupt handler with a button or a loose wire

Beware of bouncing, you may see more than one interrupts for each time you press the button. There is a gpio_set_debounce function but it is often not (correctly) implemented.

Blocking read

Consider the following pattern: A user mode process should do something as soon as an interrupt happened. This can be implemented by a blocking read.

Take Kernel Module 2 - Char Device as an example and add the irq handling code form here.

The next listing shows the additional kernel module code needed to communicate interrupts to a user mode process:

...
#include <linux/wait.h>
#include <linux/sched.h>
...
static wait_queue_head_t my_wait_queue;
static volatile int irq_counter;

static irqreturn_t irq_handler( int irq, void *dev_id )
{
    pr_info( " Interrupt" );
    irq_counter++;
    wake_up_interruptible(&my_wait_queue);
    return IRQ_HANDLED;
}

int my_read( struct file *filep, char *buffer, size_t count, loff_t *offp ) 
{
    int ret;
    
    interruptible_sleep_on(&my_wait_queue);

    snprintf(response, sizeof(response), "interrupts: %d", irq_counter );
    if( count > strlen(response) )
        count = strlen(response);

    ret = copy_to_user( buffer, response, count );
    if( ret != 0 )
        return -EINVAL;

    return count;
}
...
static int __init my_init(void)
{
    ...
    init_waitqueue_head(&my_wait_queue);
    ...
}

User mode code: When the user mode code calls read, that call will block until an interrupt happened. Often, the call to read is put into a separate thread caring for the interrupt. Another approach is to use a single (main) thread with an endless loop and a select statement.

Notes:

  • this simple example does not support non-blocking ( flag O_NONBLOC ) IO
  • this simple example does not protect the irq_counter variable from race conditions. See linux/atomic.h for protection.

Next: Kernel Module 6 - Workqueue

Clone this wiki locally