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 )
{
    unsigned long flags;
    local_irq_save(flags);

    printk( KERN_INFO "Interrupt" );

    local_irq_restore(flags);    
    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_FALLING, "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");

(Cross-)Compile and download the module as before.

Dump all interrupts before and after loading the module:

cat /proc/interrupts
insmod my_module.ko 
cat /proc/interrupts

Note that a new interrupt 171 appeares which is handled by our my_gpio_handler handler:

171:          1          0          0          0  pinctrl-bcm2835   5 Edge      my_gpio_handler

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 GPIO 06 which is routed to expansion header pin 31.

Apply the code from above and watch the module message output on the console:

echo 6 > /sys/class/gpio/export 
echo out > /sys/class/gpio/gpio6/direction
echo 1 > /sys/class/gpio/gpio6/value
echo 0 > /sys/class/gpio/gpio6/value
echo 1 > /sys/class/gpio/gpio6/value

Each falling edge should produce a line "Interrupt" in the kernel log, check with the dmesg -c command.

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