Pages

Saturday, May 31, 2014

Using Rapsberry Pi's GPIO as an interrupt source II

In my previous post I discussed about the experiment I am going to make to use one of the GPIO pins as an interrupt source for my device driver. The code for this driver is listed below:


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
 
#include <linux/interrupt.h>
#include <linux/gpio.h>
 
 
// The GPIO_23 is usually not used (pin 11 on P5 pinout raspberry pi rev. 2 
// board), we will use this to generate interrupt (rising edge). Refer  to
// the raspberry pi spec sheets.
#define GPIO_INT_GPIO23                23
 
// We need to define what string will be displayed in /proc/interrupt. 
// You may want to display the number of times our interrupt is called
// via cat /proc/interrupt
#define GPIO_INT_GPIO23_DESC           "GPIO_23 Interrupt switch"
 
// String below is not needed for now, it's needed for more complex cases. We
// can ignore this for now.
#define GPIO_INT_GPIO23_DEVICE_DESC    "GPIO23"
 
 
/****************************************************************************/
/* Interrupts variables block                                               */
/****************************************************************************/
short int irq_any_gpio    = 0;

/****************************************************************************/
/* Tasklets - provides the actual processing of the IRQ using the           */
/* bottom half approach (lower priority handlers). These are non re-entrant */
/* handlers (different from sofirqs). This is where we are going to set the */
/* processing of our leds (actually just output something to leds).         */
/****************************************************************************/

/* Forward declaration of the led_output_hander, we use this
 * for declaring a tasklet below.
 */

static void led_output_handler(unsigned long data);

DECLARE_TASKLET(ledtasklet, led_output_handler, 0L);

static void led_output_handler(unsigned long data)
{
   // We need to make sure that our tasklet is not scheduled again
   tasklet_disable(&ledtasklet);

   printk("%s: Takslets executed!\n", __FUNCTION__);
   
   // TODO: Set the led buttons here

   tasklet_enable(&ledtasklet);   
}


/****************************************************************************/
/* IRQ handler                                                              */
/****************************************************************************/
static irqreturn_t gpio23_irq_handler(int irq, void *dev_id, struct pt_regs *regs) 
{
 
   unsigned long flags;
   
   // disable hard interrupts (remember them in flag 'flags')
   local_irq_save(flags);
   
   // We defer handling of our IRQ to tasklets
   tasklet_schedule(&ledtasklet);
 
   // restore hard interrupts
   local_irq_restore(flags);
 
   return IRQ_HANDLED;
}
 
 
/****************************************************************************/
/* This is our GPIO initialization function for configuring GPIO17 as our   */
/* interrupt source.                                                        */
/****************************************************************************/
void gpio23_int_config(void) 
{
 // We first need to request the GPIO base that we require GPIO17
 // to be exported or made available.   
 if (gpio_request(GPIO_INT_GPIO23, GPIO_INT_GPIO23_DESC)) {
  printk("%s: GPIO request faiure: %s\n", __FUNCTION__, GPIO_INT_GPIO17_DESC);
  return;
 }
 // After a successful request, we need to instruct the kernel that this
 // pin will be used as an input source.
 if (gpio_direction_input(GPIO_INT_GPIO23) < 0)
 {
  printk("%s: Error setting GPIO direction!\n", __FUNCTION__);
  return;
 }

 if ( (irq_any_gpio = gpio_to_irq(GPIO_INT_GPIO23)) < 0 ) 
 {
  printk("%s: GPIO to IRQ mapping failure %s\n", __FUNCTION__, GPIO_INT_GPIO23_DESC);
  return;
 }
 
   printk("%s: Mapped interrupt %d\n", __FUNCTION__, irq_any_gpio);
 
   if (request_irq(irq_any_gpio,
                   (irq_handler_t ) gpio23_irq_handler,
                   IRQF_TRIGGER_RISING,
                   GPIO_INT_GPIO23_DESC,
                   GPIO_INT_GPIO23_DEVICE_DESC)) 
 {
  printk("%s: Irq Request failure\n", __FUNCTION__);
  return;
 }
 
   return;
}
 
 
/****************************************************************************/
/* This function releases interrupts.                                       */
/****************************************************************************/
void gpio23_int_release(void) {
 
   free_irq(irq_any_gpio, GPIO_INT_GPIO23_DEVICE_DESC);
   gpio_free(GPIO_INT_GPIO23);
 
   return;
}
 
 
/****************************************************************************/
/* Module init / cleanup block.                                             */
/****************************************************************************/
int gpio23_init(void) {
 
   printk(KERN_NOTICE "Hello !\n");
   gpio23_int_config();
 
   return 0;
}
 
void gpio23_cleanup(void) {
   printk(KERN_NOTICE "Goodbye\n");
   gpio23_int_release();
 
   return;
}
 
 
module_init(gpio23_init);
module_exit(gpio23_cleanup);
 
 
/****************************************************************************/
/* Module licensing/description block.                                      */
/****************************************************************************/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Vergil Cola <vpcola@gmail.com>");
MODULE_DESCRIPTION("Sample GPIO Interrupt Handler");


Alternatively, you may also download the source and Makefile from git here.

For the example device driver above, there's a slight change on hardware we described from the previous posts. I hade the Xtrinsic sensor board installed and this board also makes use of GPIO17 for generating interrupts. I have to use a different interrupt source for this experiment - GPIO23. I also made changes to the input push button, I added a debounce circuitry using a Schmitt trigger, the schematics of this circuit is described below:


When I first experimented with the push button to trigger interrupts, I initially got 5 or more interrupt triggers in the device driver, and immediately realized that I would need a debounce circuit before the feeding the output GPIO23 pin. There is also a twist to the input login now, our previous hardware is by default logic "1" and "0" when turned on, for this modified circuit, our output from the push button circuit is now logic "1" when turned on (this is due to the inverting schmitt trigger above).

Our device driver above is noting more than print a kernel message, it doesn't do anything special at the moment, but I will make this as a template for my future projects.

Here's a summary of what the device driver above does :

- The device driver's module initialization and cleanup is handled by the gpio23_init() and gpio23_cleanup() functions respectively. The gpio23_init() function calls gpio23_init_config() which initializes our gpio23, and assigns an interrupt handler when the interrupt is received.

- In the gpio23_int_config() function, we first request that gpio pin 23 needs to be made available, this is done by the gpio_request() function. This step is similar to invoking and modifying the export attribute in /sys from my previous post.

- After the gpio has been requested, the driver then asks the kernel to mark the direction of this GPIO pin. In our case since this will be an interrupt source, we call gpio_direction_input(), this step is similar to modifying the direction kernel attribute in my previous post.

- Once the direction is set, we can then call gpio_to_irq(). This call asks the kernel to assign an IRQ number to our gpio pin. Normally interrupts are linux are soft interrupts, this means that in these systems interrupts are not hard wired to the processors interrupt pins. They are sometimes managed by a programmable interrupt controller or PIC. For the raspberry pi, this is managed by the SOC. Once the kernel has assigned an interrupt, we can now assign an interrupt handler.

- Assigning an interrupt handler in the above code is done by the request_irq() function. This kernel function accepts the newly assigned interrupt number as the first parameter, a pointer to the interrupt handler function gpio23_irq_handler(), whether the interrupt will be triggered on the rising edge of gpio23, and the interrupt descriptors. Note that in the above driver, we trigger on the rising edge since normally our circuit now is active low by default (because of the hex inverter).

- Our interrupt handler gpio23_irq_handler() is nothing more than a stub handler. What it does is to schedule the handling of this function via a tasklet - led_output_handler(), which currently does nothing but display something in the logs.

The driver above demonstrates split processing of the interrupt handler routine. Interrupts normally need to run and return the fastest way possible so as not to be a bottleneck in the kernel. It is for this reason that we defer the processing of the interrupt to tasklets, so that our interrupt handler can run and return from the IRQ handler function right away. Tasklets are deferrable functions which implements the bottom-half of the IRQ routine, the top-half being the IRQ handlers themselves. I'll put up more experiments of implementing tasklets and later as we go along.

Ok, so now we can compile and load this device driver. 



You may need to transfer the generated module gpiointerrupt.ko to your raspberry pi. Use insmod command to load the driver, once loaded, the /proc/interrupts lists all the interrupts that has been installed by the kernel:


From the screenshot above, note that our driver is loaded and mapped our interrupt service routine to interrupt 19. If you examine /proc/interrupts, you will also notice that our GPIO23 interrupt is given interrupt number 192 by the kernel. The second column in the display lists how many times the interrupt was called. 

When the button is pressed, notice that we do a printk from our tasklet - this lets us know that our tasklet is called and the interrupt was handled.



You can tail the /var/log/messages for kernel messages from our device driver. We can also examing /proc/interrupts to monitor how many times our button was pressed (notice column 2).


In summary, the raspberry pi provides an opportunity to develop device drivers that operate on hardware that generates interrupts. I will be using this template driver later to do more experiments, perhaps explore on using work queues in the kernel - which during my time as driver writer 10 years ago,  was not available.

No comments: