Linux Kernel Driver Series - 3 - Interrupts - IRQ, ISR
In the last article we have discussed the usage of Ultrasonic Sensor HC-SR04 to measure the distance using the GPIO pins of BananaPI R3 platform. But in the previous code, we were using while loop to wait for the GPIO to switch between the states to measure the distance. We were using the polling method to see if the GPIO value is set properly or not. This method was not so efficient and is a waste of CPU resources.
This article discusses the procedure of using interrupts in the kernel driver on a GPIO pin to detect the interrupt from Ultrasonic Sensor HC-SR04 for measuring the distance between the sensor and the target object.
💡 Concepts covered in this article
💡 Prerequisites
This article assumes the foundational knowledge of writing the basic kernel modules, compiling the kernel modules and familiarity with the GPIO Pins on the target platform and multithreaded environment using kthreads. If you are unfamiliar with these concepts, please visit my previous post for reference.
In the previous article, we discussed the HC-SR04 and its working principle in depth. I won't be providing the details again here. Please visit the links above to learn more about the working principle of the HC-SR04 Ultrasonic sensor.
💡 Introduction to the Linux Kernel Interrupts
An interrupt, in the context of Processors and Operating system is a signal or an event that temporarily halts the normal execution of the program in the CPU in order to run a specific task or event that needs to be executed immediately. After the event is completed, previously halted program will resume its execution. These events can be raised from any of the peripheral devices of the computer.
Polling vs Interrupts:
Let's take an example where we can understand Polling and Interrupts much better.
Usecase: We have a GPIO PIN as an input GPIO and we want to run a function when the GPIO value switches from 0 to 1.
Method 1: Let's consider a while loop where we are continuously checking whether the GPIO value is 0 or 1.
# A sample pseudo code of polling
while(gpio_get_value(PIN_NUMBER) == 1){
// Run some logic here ....
msleep(1000);
}
In this case the gpio_get_value() function is executed every 1 second to check if the value is 1 or not. This is called Polling, where we are continuously polling for every second to check the value. This is not efficient as the GPIO value can change from 0 to 1 with in any time between 0ms to 1000ms considering the first iteration only. So if the GPIO value is changed at 450ms, then the while loop has to wait unnecessarily for next 550ms before running out logic. This is a waste of CPU resource.
Method 2: Let's consider the same logic of running a function when the GPIO value switches from 0 to 1. Instead of running a loop to check the GPIO value at certain intervals, what if we register a function that will trigger the moment the GPIO value becomes 1? When this event triggers, the registered function that needs to execute our logic will be invoked automatically. This method is called "Interrupts."
# A sample pseudo code of the interrupts
run_my_logic(){
...
...
}
register_event(event_number, run_my_logic, ....);
Using of events is efficient and also handles the CPU resources affectively.
Types of Interrupts:
There are different types of interrupts offered by Linux Kernel
In many computers there are PIC- Programmable Interrupt Controller or APIC- Advanced Programmable Interrupt Controllers are present that are used to manage and prioritise interrupts.
Below is the image of the PIC
In the above diagram we can see the NMI - Non Maskable Interrupts, INTR - Interrupt from PIC and Interrupt requests from Device 0 to N
Exceptions: Comes under the category of Synchronous interrupts like
💡 Very Important Aspects of Interrupt Handling:
Interrupt Service Routines (ISRs) are designed to be short, fast, and non-blocking because they run in a special context, typically at a higher priority than regular code, and can disrupt the normal flow of a program. As a result, there are several tasks that should generally be avoided within an ISR:
But in real world situations there will be scenarios where we need to perform long processing functions or perform some complex operations whenever an interrupt occurs. To perform this Linux offers two modes
Top Half
Bottom Half
Recommended by LinkedIn
Bottom half mechanisms are
Note: We will discuss these aspects in the next article with examples
💡 Linux Kernel APIs to work with Interrupts
Interrupt handler or ISR -Interrupt Service Routines is a function that kernel runs when an interrupt triggers. In order for a interrupt to be working we need to register the interrupt in the kernel driver that we implementing. Let's look at the APIs to work with Interrupts
int
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
irq - interrupt number
irq_handler_t - ISR
flags - Flags for the IRQ to work
name - Name of the interrupt
dev - Cookie passed to the function
const void *free_irq(unsigned int irq, void *dev);
irq - Interrupt number
static irqreturn_t irq_handler(int irq, void *dev)
irq - Interrupt number
dev - Cookie
irqreturn_t - Interrupt status - returning IRQ_HANDLED will complete the interrupt handling
In this article we are working with these functions only to keep the context with in the boundaries of this article. In the upcoming articles we will go much depth in to the interrupts concepts.
💡 Modify the source code of Ultrasonic Distance Measurement Sensor HC-SR04 from Linux Kernel Driver Series - 2 to accommodate Interrupts.
In our previous article of measuring the distance using the Ultrasonic sensor, we used a polling method to see if the GPIO value is switching from 0 to 1 and 1 to 0 on the ECHO Pin as given below
// Rest of the code is eliminated to keep the context simple
while(!kthread_should_stop()){
// Send a trigger pulse
gpio_set_value(HCSR04_TRIGGER_PIN, 0);
udelay(2);
gpio_set_value(HCSR04_TRIGGER_PIN, 1);
udelay(10);
gpio_set_value(HCSR04_TRIGGER_PIN, 0);
// Wait for the echo pin to go high
while (gpio_get_value(HCSR04_ECHO_PIN) == 0);
start_time = ktime_get();
// Wait for the echo pin to go low
while (gpio_get_value(HCSR04_ECHO_PIN) == 1);
end_time = ktime_get();
...
...
...
...
}
So instead of using the code "while (gpio_get_value(HCSR04_ECHO_PIN) == 0);" and "while (gpio_get_value(HCSR04_ECHO_PIN) == 1);" to poll on the GPIO pins and see when the values are switching, we will change this code into IRQ and ISR mode.
Steps for registering the IRQ and ISR:
int hcsr04_echo_irq_number = gpio_to_irq(HCSR04_ECHO_PIN);
// Registering the IRQ on the RISING EDGE and FALLING EDGE of the
// GPIO PIN and giving hcsr04_interrupt_handler as the interrupt name
int result = request_irq(hcsr04_echo_irq_number,
echo_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"hcsr04_interrupt_handler",
NULL);
// ISR name is echo_irq_handler
// It checks the GPIO value when ever interrupt occurs
// If the value is 1, record the start time
// If the value is 0, record the end time and calculate the distance
static irqreturn_t echo_irq_handler(int irq, void *dev){
if(gpio_get_value(HCSR04_ECHO_PIN)){
// register start time at gpio value is 1
start_time = ktime_get();
} else {
// register end time at gpio value is 0
end_time = ktime_get();
// Calculate the time elapsed in microseconds
delta_us = ktime_us_delta(end_time, start_time);
// Calculate distance in centimeters
distance = delta_us / DISTANCE_UNIT;
if(distance < 100){
printk(KERN_INFO "SAMMY: IRQMODE: Distance: %d cms\n", distance);
}
}
return IRQ_HANDLED;
}
Note: We are not using bottom half concept here as the code very small and can be serviced directly, normally using of printk is not suggested in the
💡 Source code of Kernel Driver for HC-SR04 with Interrupts
Following is the complete source code of HC-SR04 with Interrupt Handling Mechanisms in Linux Kernel
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#define HCSR04_TRIGGER_PIN 475
#define HCSR04_ECHO_PIN 456
#define DISTANCE_UNIT 58
static struct task_struct *thread;
int hcsr04_echo_irq_number = 0;
ktime_t start_time, end_time;
s64 delta_us;
int distance;
static irqreturn_t echo_irq_handler(int irq, void *dev){
if(gpio_get_value(HCSR04_ECHO_PIN)){
// register start time at gpio value is 1
start_time = ktime_get();
} else {
// register end time at gpio value is 0
end_time = ktime_get();
// Calculate the time elapsed in microseconds
delta_us = ktime_us_delta(end_time, start_time);
// Calculate distance in centimeters
distance = delta_us / DISTANCE_UNIT;
if(distance < 100){
printk(KERN_INFO "SAMMY: IRQMODE: Distance: %d cms\n", distance);
}
}
return IRQ_HANDLED;
}
static int send_trigger_pulses(void *data) {
while(!kthread_should_stop()){
printk(KERN_INFO "SAMMY: Trigger pulse sent\n");
mdelay(500);
gpio_set_value(HCSR04_TRIGGER_PIN, 0);
udelay(2);
gpio_set_value(HCSR04_TRIGGER_PIN, 1);
udelay(10);
gpio_set_value(HCSR04_TRIGGER_PIN, 0);
}
return 0;
}
static int setup_gpio_pins(void){
int result = 0;
// Setup the TRIGGER and ECHO GPIO pins
if (gpio_request(HCSR04_TRIGGER_PIN, "HCSR04_TRIGGER_PIN") ||
gpio_request(HCSR04_ECHO_PIN, "HCSR04_ECHO_PIN")) {
printk(KERN_ERR "SAMMY: Failed to request GPIO pins\n");
return -1;
}
// Initialize GPIO pins
if (gpio_direction_output(HCSR04_TRIGGER_PIN, 0) ||
gpio_direction_input(HCSR04_ECHO_PIN)) {
printk(KERN_ERR "SAMMY: Failed to initialize GPIO pins\n");
gpio_free(HCSR04_TRIGGER_PIN);
gpio_free(HCSR04_ECHO_PIN);
return -1;
}
// setup the interrupt handlers on the GPIO
hcsr04_echo_irq_number = gpio_to_irq(HCSR04_ECHO_PIN);
if(hcsr04_echo_irq_number <= 0){
printk(KERN_ERR "SAMMY: Failed to get the irq number\n");
gpio_free(HCSR04_TRIGGER_PIN);
gpio_free(HCSR04_ECHO_PIN);
return -1;
}
printk(KERN_INFO "SAMMY: IRQ_NUMBER: %d\n", hcsr04_echo_irq_number);
result = request_irq(hcsr04_echo_irq_number, echo_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "hcsr04_interrupt_handler", NULL);
if(result){
printk(KERN_ERR "SAMMY: Failed to request irq\n");
gpio_free(HCSR04_TRIGGER_PIN);
gpio_free(HCSR04_ECHO_PIN);
return -1;
}
return 0;
}
static void free_gpio_pins(void){
// free up gpio pins
gpio_free(HCSR04_TRIGGER_PIN);
gpio_free(HCSR04_ECHO_PIN);
}
static int __init sammy_hcsr04_init(void) {
printk(KERN_INFO "SAMMY: HC-SR04 module init phase\n");
//setup the gpio pins
setup_gpio_pins();
// start the thread for measuring the distance
thread = kthread_run(send_trigger_pulses, NULL, "sammy_hcsr04_thread");
if(IS_ERR(thread)){
free_gpio_pins();
return -1;
}
printk(KERN_INFO "SAMMY: HC-SR04 module init success\n");
return 0;
}
static void __exit sammy_hcsr04_exit(void) {
// cleanup the interrupt handler
free_irq(hcsr04_echo_irq_number, NULL);
// stop the thread
if(thread){
kthread_stop(thread);
}
// Free GPIO pins
free_gpio_pins();
printk(KERN_INFO "SAMMY: HC-SR04 module unload success\n");
}
module_init(sammy_hcsr04_init);
module_exit(sammy_hcsr04_exit);
MODULE_AUTHOR("G. Naveen Kumar");
MODULE_DESCRIPTION("SAMMY HC-SR04 Ultrasonic Sensor Kernel Driver With Interrupts");
MODULE_VERSION("1.0.1");
MODULE_LICENSE("GPL");
💡 Testing the Kernel Driver on Banana PI R3 Platform
Once you have compiled and deployed the driver onto the Banana PI R3 platform, you will see a similar output as shown in the screenshot below
Congratulations on learning and getting a hands-on experience on Linux Kernel Interrupt handling mechanisms. Kudos !!!
You can see the entire source code of this in my GitHub Repository in the following link
In the further articles, we will learn in depth about the bottom half mechanisms such as Workqueue, Threaded IRQs, Softirq and Tasklets.
Conclusion
With this we have successfully completed the basics of Interrupt handling mechanisms in Linux Kernel Drivers. I hope you have enjoyed this article and if you like the content, kindly subscribe to receive more exciting information. Thank you all and see you soon with another exciting article, till then stay happy and happy coding...
References
Career Coach | Get noticed at work | LinkedIn, GenAI4Career| Salary hike | Establish thought leadership | Interview & appraisal | Resume writing | Build LinkedIn profile | Networking | Job Search
1yYou are sharing exceptional information in easy and understandable format