-
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");
(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
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 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.