-
Notifications
You must be signed in to change notification settings - Fork 9
Kernel Module 5 IRQ
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");
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.
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.
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.