Linux Kernel Driver Series - 3 - Interrupts - IRQ, ISR
Linux Kernel Driver Series - 3 - Interrupt Handling - IRQ, ISR

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

  1. Introduction to the Linux Kernel Interrupts
  2. Linux Kernel APIs to work with Interrupts
  3. Modify the source code of Ultrasonic Distance Measurement Sensor HC-SR04 from Linux Kernel Driver Series - 2 to accommodate Interrupts.
  4. Source Code of the Kernel Driver for HC-SR04 with Interrupts
  5. Testing the Kernel Driver on Banana PI R3 Platform


?? 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

  1. Synchronous: usually named exceptions, handle conditions detected by the processor itself in the course of executing an instruction. Divide by zero or a system call are examples of exceptions.
  2. Asynchronous: usually named interrupts, are external events generated by I/O devices. For example a network card generates an interrupts to signal that a packet has arrived.
  3. Maskable: which means we can temporarily postpone running the interrupt handler when we disable the interrupt until the time the interrupt is re-enabled
  4. Non-Maskable: These are the interrupts that cannot be stopped and has to be serviced.

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

PIC - Programmable Interrupt Controller Diagram - Image Credit linux-kernel-labs.github.io

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

  1. Faults - Divided by zero, Page Fault, Segmentation Faults
  2. Traps - Using of Breakpoints in the code
  3. Aborts - Severe Errors, Hardware failures etc.,


?? 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:

  1. Sleep functions shouldn't be used
  2. Acquiring a Mutex Locks
  3. Performing time consuming tasks
  4. Accessing the user space virtual memory
  5. Floating point operations
  6. Malloc and Free are not advisable

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

  • The "top half" of interrupt processing refers to the immediate and high-priority part of handling an interrupt.
  • It typically includes the Interrupt Service Routine (ISR) itself, which is the code that executes in response to the interrupt request.
  • The top half is executed with interrupts disabled to ensure that the ISR runs atomically, without interruption by other interrupts.
  • Since it runs with high priority, it is meant to perform the essential, time-critical tasks associated with the interrupt, such as acknowledging hardware, reading data, and updating critical system information.

Bottom Half

  • The "bottom half" of interrupt processing (also called the "deferred" or "softirq" part) comes after the top half has completed.
  • It handles less time-critical and potentially time-consuming tasks associated with the interrupt.
  • Unlike the top half, the bottom half code runs with interrupts enabled, allowing other interrupts to preempt it if necessary.
  • Bottom halves can be scheduled to run later, allowing the system to regain responsiveness quickly.
  • Examples of tasks handled in the bottom half include processing data, queuing work, or waking up user-space processes.

Bottom half mechanisms are

  1. Workqueue
  2. Threaded IRQs
  3. Softirq
  4. Tasklets

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

  • request_irq(): This is the function used to register the ISR in the kernel

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        

  • free_irq() - This is the function used to free the Interrupt

const void *free_irq(unsigned int irq, void *dev);

irq - Interrupt number        

  • irq_handler_t() - This is the ISR that gets executed upon an interrupt is triggered

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:

  • Get the IRQ number for GPIO Pin

int hcsr04_echo_irq_number = gpio_to_irq(HCSR04_ECHO_PIN);        

  • Register the IRQ

// 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);        

  • Write the ISR

// 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

Interrupt Handlers printing the distance measured with HC-SR04 Sensor

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

https://github.com/gvvsnrnaveen/buildroot/blob/main/kernel_drivers/sammy_openwrt/src/sammy_hcsr04_interrupt.c

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

  1. https://www.sparkfun.com/products/15569 - HC-SR04
  2. https://elixir.bootlin.com/linux/v5.5.2/source/include/linux/interrupt.h#L157
  3. https://linux-kernel-labs.github.io/refs/heads/master/lectures/interrupts.html

Balemarthy Vamsi Krishna

Career Coach | Get noticed at work | LinkedIn, GenAI4Career| Salary hike | Establish thought leadership | Interview & appraisal | Resume writing | Build LinkedIn profile | Networking | Job Search

1 å¹´

You are sharing exceptional information in easy and understandable format

要查看或添加评论,请登录

Naveen Kumar Gutti的更多文章

  • Run DeepSeek on Nvidia Jetson Orin Nano

    Run DeepSeek on Nvidia Jetson Orin Nano

    Hello everyone, in this article we will see the procedure to run the DeepSeek OLLAMA models on Nvidia Jetson Orin Nano.…

    1 条评论
  • Identification of NPU Neural Processing Unit in Windows 11

    Identification of NPU Neural Processing Unit in Windows 11

    Hi Everyone, I am back with another wonderful article to let you know how to identify if a NPU - Neural Processing Unit…

  • Installation of JetPack on NVIDIA Jetson Orin Nano - Walkthrough

    Installation of JetPack on NVIDIA Jetson Orin Nano - Walkthrough

    Hi Friends, today we are going to bring up the NVIDIA Jetson Orin Nano to working level by installing JetPack 6. This…

    2 条评论
  • Nvidia Jetson Orin Nano - Redefining Generative AI on the Edge Devices

    Nvidia Jetson Orin Nano - Redefining Generative AI on the Edge Devices

    Hi Everyone, I am back with another exciting article for all of you. Today we will discuss about a piece of hardware…

    2 条评论
  • Secure Boot in ARM

    Secure Boot in ARM

    Hi everyone, it's been a long time since I wrote articles, as from past few days I was a bit occupied in the personal…

  • Wolfshield - AI/ML Based Content Filtering

    Wolfshield - AI/ML Based Content Filtering

    Hello everyone, Good to be back with another exciting article. This time I am bringing the details about the software I…

  • Linux Kernel Series - 13 - Netlink and Generic Netlink Communication - Part 1

    Linux Kernel Series - 13 - Netlink and Generic Netlink Communication - Part 1

    Hello Everyone, it's good to be back with another article. In this article, we will discuss the Linux Kernel to…

    4 条评论
  • Linux Kernel Series - 12 - GDB Part 3 - Kernel Debugging

    Linux Kernel Series - 12 - GDB Part 3 - Kernel Debugging

    Hello Everyone, hope you are all doing great. I am back with another exciting article in the Linux Kernel Series by the…

  • LXC Containers - Create Custom Container

    LXC Containers - Create Custom Container

    Hi everyone, I am back with yet another exciting article of "LXC Containers - Creating a custom container from…

    7 条评论
  • LXC Containers - Introduction

    LXC Containers - Introduction

    Hello everyone, it is very nice to be back with another interesting topic "LXC Containers". LXC Linux Containers is an…

社区洞察

其他会员也浏览了