Linux Device Driver Tutorial Part 13 – Interrupts Example Program in Linux Kernel

This article is a continuation of the  Series on Linux Device Driver and carries on the discussion on character drivers and their implementation. This is Part 13 of the Linux device driver tutorial. In our previous tutorial, we have seen the What is an Interrupt and How it works through theory. Now we will see the Interrupt Example Program in Linux Kernel.

Interrupt Example Program in Linux Kernel

Before writing any interrupt program, you should keep these following points in mind.

  1. Interrupt handlers can not enter sleep, so to avoid calls to some functions which has sleep.
  2. When the interrupt handler has part of the code to enter the critical section, use spinlocks lock, rather than mutexes. Because if it couldn’t take mutex it will go to sleep until it takes the mute.
  3. Interrupt handlers can not exchange data with the userspace.
  4. The interrupt handlers must be executed as soon as possible. To ensure this, it is best to split the implementation into two parts, top half and bottom half. The top half of the handler will get the job done as soon as possible and then work late on the bottom half, which can be done with softirq or tasklet or workqueue.
  5. Interrupt handlers can not be called repeatedly. When a handler is already executing, its corresponding IRQ must be disabled until the handler is done.
  6. Interrupt handlers can be interrupted by higher authority handlers. If you want to avoid being interrupted by a highly qualified handler, you can mark the interrupt handler as a fast handler. However, if too many are marked as fast handlers, the performance of the system will be degraded, because the interrupt latency will be longer.

Functions Related to Interrupt

Before programming, we should know the basic functions which are useful for interrupts. This table explains the usage of all functions.

FunctionDescription
request_irq
(
unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id)
Register an IRQ, the parameters are as follows:

irq: IRQ number to allocate.

handler: This is Interrupt handler function.This function will be invoked whenever the operating system receives the interrupt.The data type of return is irq_handler_t, if its return value is IRQ_HANDLED, it indicates that the processing is completed successfully, but if the return value is IRQ_NONE, the processing fails.

flags: can be either zero or a bit mask of one or more of the flags defined in linux/interrupt.h. The most important of these flags are:
IRQF_DISABLED
IRQF_SAMPLE_RANDOM
IRQF_SHARED
IRQF_TIMER
(Explained after this table)

name: Used to identify the device name using this IRQ, for example, cat / proc / interrupts will list the IRQ number and device name.

dev_id: IRQ shared by many devices. When an interrupt handler is freed, dev provides a unique cookie to enable the removal of only the desired interrupt handler from the interrupt line. Without this parameter, it would be impossible for the kernel to know which handler to remove on a given interrupt line. You can pass NULL here if the line is not shared, but you must pass a unique cookie if your interrupt line is shared. This pointer is also passed into the interrupt handler on each invocation. A common practice is to pass the driver's device structure. This pointer is unique and might be useful to have within the handlers.

Return
returns zero on success and nonzero value indicates an error.

request_irq() cannot be called from interrupt context (other situations where code cannot block), because it can block.
free_irq(
unsigned int irq,
void *dev_id)
Release an IRQ registered by request_irq() with the following parameters:

irq: IRQ number.
dev_id: is the last parameter of request_irq.

If the specified interrupt line is not shared, this function removes the handler and disables the line.
If the interrupt line is shared, the handler identified via dev_id is removed, but the interrupt line is disabled only when the last handler is removed. With shared interrupt lines, a unique cookie is required to differentiate between the multiple handlers that can exist on a single line and enable free_irq() to remove only the correct handler.
In either case (shared or unshared), if dev_id is non-NULL, it must match the desired handler. A call to free_irq() must be made from process context.
enable_irq(unsigned int irq)Re-enable interrupt disabled by disable_irq or disable_irq_nosync.
disable_irq(unsigned int irq)Disable an IRQ from issuing an interrupt.
disable_irq_nosync(unsigned int irq)Disable an IRQ from issuing an interrupt, but wait until there is an interrupt handler being executed.
in_irq()returns true when in interrupt handler
in_interrupt()returns true when in interrupt handler or bottom half

Interrupts Flags

These are the second parameter of the function. It has several flags. Here I explained important flags.

  • IRQF_DISABLED.

    • When set, this flag instructs the kernel to disable all interrupts when executing this interrupt handler.
    • When unset, interrupt handlers run with all interrupts except their own enabled.

    Most interrupt handlers do not set this flag, as disabling all interrupts is bad form. Its use is reserved for performance-sensitive interrupts that execute quickly. This flag is the current manifestation of the SA_INTERRUPT flag, which in the past distinguished between “fast” and “slow” interrupts.

  • IRQF_SAMPLE_RANDOM. This flag specifies that interrupts generated by this device should contribute to the kernel entropy pool. The kernel entropy pool provides truly random numbers derived from various random events. If this flag is specified, the timing of interrupts from this device is fed to the pool as entropy. Do not set this if your device issues interrupt at a predictable rate (e.g. the system timer) or can be influenced by external attackers (e.g. a networking device). On the other hand, most other hardware generates interrupts at non-deterministic times and is, therefore, a good source of entropy.
  • IRQF_TIMER. This flag specifies that this handler process interrupts the system timer.
  • IRQF_SHARED. This flag specifies that the interrupt line can be shared among multiple interrupt handlers. Each handler registered on a given line must specify this flag; otherwise, only one handler can exist per line.

Registering an Interrupt Handler

Freeing an Interrupt Handler

Interrupt Handler

Programming

The interrupt can be coming from anywhere (any hardware) and anytime. In our tutorial, we are not going to use any hardware. Here instead of using hardware, we are going to trigger interrupt by simulating. If you have only a PC (without hardware), but you want to play around Interrupts in Linux you can follow our method.

Triggering Hardware Interrupt through Software

Intel processors handle interrupt using IDT (Interrupt Descriptor Table).  The IDT consists of 256 entries with each entry corresponding to a vector and of 8 bytes. All the entries are a pointer to the interrupt handling function. The CPU uses IDTR to point to IDT. The relation between those two can be depicted as below,

An interrupt can be programmatically raised using ‘int’ instruction. For example, the Linux system call was using int $0x80.

In Linux IRQ to vector, mapping is done in arch/x86/include/asm/irq_vectors.h.The used vector range is as follows,

Refer Here.

The IRQ0 is mapped to vector using the macro,

#define IRQ0_VECTOR (FIRST_EXTERNAL_VECTOR + 0x10)

where, FIRST_EXTERNAL_VECTOR = 0x20

So if we want to raise an interrupt IRQ11, programmatically we have to add 11 to a vector of IRQ0.

0x20 + 0x10 + 11 = 0x3B (59 in Decimal).

Hence executing “asm("int $0x3B")” will raise interrupt IRQ 11.

The instruction will be executed while reading the device file of our driver (/dev/etx_device).

Driver Source Code

Here I took the old source code from the sysfs tutorial. In that source, I have just added interrupt code like request_irq, free_irq along with the interrupt handler.

In this program, interrupt will be triggered whenever we are reading the device file of our driver (/dev/etx_device).

Whenever Interrupt triggers, it will print the “Shared IRQ: Interrupt Occurred” Text.

[Get the source from the GitHub]

MakeFile

Building and Testing Driver

  • Build the driver by using Makefile (sudo make)
  • Load the driver using sudo insmod driver.ko
  • To trigger the interrupt read device file (sudo cat /dev/etx_device)
  • Now see the Dmesg (dmesg)

  • We can able to see the print “Shared IRQ: Interrupt Occurred
  • Unload the module using sudo rmmod driver

A problem in New Linux kernel

If you are using the newer Linux kernel, then this may not work properly. You may get something like below.

do_IRQ: 1.59 No irq handler for vector

In order to solve that, you have to change the Linux kernel source code, Compile it, then install it.

Note: The complete process will take more than an hour and you need to download the Linux kernel source also. 

Modify and Build the Linux Kernel

Step 1: Previously, I have used an old kernel. Now I am updating the kernel to 5.4.47 with ubuntu 18.04 (Virtualbox). First, you need to download the Linux kernel source code using the below command.

Step 2: Once you have downloaded the kernel source, then extract it using the below command.

Step 3: Get into the directory and copy the config.

Step 4: Let’s install the required tools to compile the source code.

Step 5: Now we have the source code and tools that needed to compile. Let’s do our modification. Add the below line in the file arch/x86/kernel/irq.c(just in downloaded source code) right after all the include lines.

Step 6: Now build the config using the below commands.

Step 7: Let’s start compiling the kernel using the below command.

If you want to speed up the compilation time, just use like below.

You have to have more patience as a compilation takes more time. The build time depends upon your system’s resources such as available CPU core and the current system load. For me, it took more than 2 hours as I am building on Virtualbox.

Install the modified kernel

Step 8: Enter into the admin mode and Install the kernel modules.

Step 9: Install the modified Linux kernel using the below command.

Step 10: Update the grub config using the below commands.

Finally, Here we are. We have installed the new kernel. In order to reflect the changes, reboot it. Then check the kernel version.

You should see the updated kernel version if there is no issues in compilation and installation like below.

If you have any doubts, please refer here.

Driver source code for a modified kernel

We have customized the kernel. Let’s take the below source code try it.

[Get the source from the GitHub]

This is a simple example using Interrupts in the device drivers. This is just basic. You can also try using hardware. I hope this might helped you.

In our next tutorial, we will discuss one of the bottom half, which is workqueue.

5 2 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

15 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
manoj
manoj
February 27, 2018 2:52 PM

Also please add a tutorial on different ways to handle situations when critial region code is being accessed by cpu (such as spinlocks, mutexes, semaphones etc implementation in kernel space)

arun
arun
August 30, 2018 1:24 AM

do_IRQ: 2.59 No irq handler for vector

im getting this error plz help me out.

EmbeTronicx India
EmbeTronicx India
Reply to  arun
August 31, 2018 3:48 AM

Hi Arun,

Its looks like your hardware issue. Did you update the kernel recently? Please go through this below link.
https://unix.stackexchange.com/questions/367503/do-irq-0-163-no-irq-handler-for-vector-irq-1

EmbeTronicX
EmbeTronicX
Reply to  arun
April 1, 2020 5:50 AM

Please refer this link.

nguyendang
nguyendang
April 30, 2019 2:23 AM

do_IRQ: 2.59 No irq handler for vector

I have the same trouble as Arun but I cannot fix it so far, although following your instruction you replied to Arun.

Please help me, thank you!

EmbeTronicX
EmbeTronicX
Reply to  nguyendang
April 1, 2020 5:46 AM

Hi,

Please refer this Link to fix.

Kunapareddy Jeevan
Kunapareddy Jeevan
Reply to  EmbeTronicX
June 16, 2020 9:36 PM

I am unable to find the file in arch/x86/kernel/irq.c
I am using Virtualbox. does this giving me a problem to enable an hardware interrupt?
please help me

kunapareddy Jeevan
kunapareddy Jeevan
Reply to  owl
June 17, 2020 8:32 PM

my issue is same as nguyendang and arun
 
do_IRQ: 2.59 No irq handler for vector

Kunapareddy Jeevan
Kunapareddy Jeevan
Reply to  owl
June 17, 2020 11:11 PM

In the link, it given to add a line EXPORT_SYMBOL(vector_irq); in file arch/x86/kernel/irq.c   problem is that im unable to find that file in my kernel folder. when i add these lines in read function static ssize_t etx_read(struct file *filp,         char __user *buf, size_t len, loff_t *off) {     struct irq_desc *desc;       printk(KERN_INFO “Read function\n”);     desc = irq_to_desc(11);     if (!desc) return -EINVAL;     __this_cpu_write(vector_irq[59], desc);     asm(“int $0x3B”); // Corresponding to irq 11     return 0; }   im getting error at vector_irq[59] because of not able to use export_symbol.   i checked /proc/interrupts also.there i can see the interrupt.but when i check dmesg… Read more »

Kalpesh
Kalpesh
August 22, 2019 12:59 AM

What does IRQ 11 stands for(For which process is this IRQ number)

Sunil Vaghela
Sunil Vaghela
July 5, 2020 4:44 PM

Hi Thank you very much for such a good tutorials. I am trying above program on beaglebone black (armv7l). The same x86 assembly instructions won’t work in arm, so need to update instructions to generate software interrupts on arm. I explored armv7 assembly instruction set, TI am355x(beaglebone black processor) Technical reference manual and many other stuffs to what are the instructions and interrupt vector mapping there in armv7. It might be possible using SWI instruction in arm, but I am not sure how can I use it. Or I am not sure,if I misunderstood anything. It would be great if… Read more »

Tony
Tony
August 5, 2020 1:51 PM

Hi, may I know how to detect the available interrupt line automatically and assign that specific interrupt line to the new driver to be added? Looking forward for your answer. Thanks

15
0
Would love your thoughts, please comment.x
()
x
%d bloggers like this: