How the Linux kernel handles interrupts

How Linux got to be Linux: Test driving 1993-2003 distros

Internet Archive Book Images. Modified by Opensource.com. CC BY-SA 4.0

Interrupts are an essential part of how modern CPUs work. For example, every time you press a key on the keyboard, the CPU is interrupted so that the PC can read user input from the keyboard. This happens so quickly that you don't notice any change or impairment in user experience.

Moreover, the keyboard is not the only component that can cause interrupts. In general, there are three types of events that can cause the CPU to interrupt: Hardware interrupts , software interrupts , and exceptions . Before getting into the different types of interrupts, I'll define some terms.

Definitions

An interrupt request ( IRQ ) is requested by the programmable interrupt controller ( PIC ) with the aim of interrupting the CPU and executing the interrupt service routine ( ISR ). The ISR is a small program that processes certain data depending on the cause of the IRQ. Normal processing is interrupted until the ISR finishes.

In the past, IRQs were handled by a separate microchip—the PIC—and I/O devices were wired directly to the PIC. The PIC managed the various hardware IRQs and could talk directly to the CPU. When an IRQ occurred, the PIC wrote the data to the CPU and raised the interrupt request ( INTR ) pin.

Nowadays, IRQs are handled by an advanced programmable interrupt controller ( APIC ), which is part of the CPU. Each core has its own APIC.

Types of interrupts

As I mentioned, interrupts can be separated into three types depending on their source:

Hardware interrupts

When a hardware device wants to tell the CPU that certain data is ready to process (e.g., a keyboard entry or when a packet arrives at the network interface), it sends an IRQ to signal the CPU that the data is available. This invokes a specific ISR that was registered by the device driver during the kernel's start.

Software interrupts

More Linux resources

  • Linux commands cheat sheet
  • Advanced Linux commands cheat sheet
  • Free online course: RHEL Technical Overview
  • Linux networking cheat sheet
  • SELinux cheat sheet
  • Linux common commands cheat sheet
  • What are Linux containers?
  • Our latest Linux articles

When you're playing a video, it is essential to synchronize the music and video playback so that the music's speed doesn't vary. This is accomplished through a software interrupt that is repetitively fired by a precise timer system (known as jiffies ). This timer enables your music player to synchronize. A software interrupt can also be invoked by a special instruction to read or write data to a hardware device.

Software interrupts are also crucial when real-time capability is required (such as in industrial applications). You can find more information about this in the Linux Foundation's article  Intro to real-time Linux for embedded developers .

Exceptions are the type of interrupt that you probably know about. When the CPU executes a command that would result in division by zero or a page fault, any additional execution is interrupted. In such a case, you will be informed about it by a pop-up window or by seeing segmentation fault (core dumped) in the console output. But not every exception is caused by a faulty instruction.

Exceptions can be further divided into Faults , Traps , and Aborts .

  • Faults:  Faults are an exception that the system can correct, e.g., when a process tries to access data from a memory page that was swapped to the hard drive. The requested address is within the process address space, and the access rights are correct. If the page is not present in RAM, an IRQ is raised and it starts the page fault exception handler to load the desired memory page into RAM. If the operation is successful, execution will continue.
  • Traps:  Traps are mainly used for debugging. If you set a breakpoint in a program, you insert a special instruction that causes it to trigger a trap. A trap can trigger a context switch that allows your debugger to read and display values of local variables. Execution can continue afterward. Traps are also the default way to execute system calls (like killing a process).
  • Aborts:  Aborts are caused by hardware failure or inconsistent values in system tables. An abort does not report the location of the instruction that causes the exception. These are the most critical interrupts. An abort invokes the system's abort exception handler , which terminates the process that caused it.

Get hands-on

IRQs are ordered by priority in a vector on the APIC (0=highest priority). The first 32 interrupts (0–31) have a fixed sequence that is specified by the CPU. You can find an overview of them on OsDev's Exceptions page. Subsequent IRQs can be assigned differently. The interrupt descriptor table ( IDT ) contains the assignment between IRQ and ISR. Linux defines an IRQ vector from 0 to 256 for the assignment.

To print a list of registered interrupts on your system, open a console and type:

You should see something like this:

Registered interrupts list

Registered interrupts in kernel version 5.6.6. (Stephan Avenwedde, CC BY-SA 4.0 )

From left to right, the columns are: IRQ vector, interrupt count per CPU ( 0 .. n ), the hardware source, the hardware source's channel information, and the name of the device that caused the IRQ.

On the bottom of the table, there are some non-numeric interrupts. They are the architecture-specific interrupts, like the local timer interrupt ( LOC ) on IRQ 236. Some of them are specified in the Linux IRQ vector layout in the Linux kernel source tree.

Architecture-specific interrupts

Architecture-specific interrupts (Stephan Avenwedde, CC BY-SA 4.0 )

To get a live view of this table, run:

Proper IRQ handling is essential for the proper interaction of hardware, drivers, and software. Luckily, the Linux kernel does a really good job, and a normal PC user will hardly notice anything about the kernel's entire interrupt handling.

This can get very complicated, and this article gives only a brief overview of the topic. Good sources of information for a deeper dive into the subject are the Linux Inside eBook (CC BY-NC-SA 4.0) and the Linux Kernel Teaching repository.

arrows cycle symbol for failing faster

The lifecycle of Linux kernel testing

The Continuous Kernel Integration (CKI) project aims to prevent bugs from entering the Linux kernel.

Linux kernel source code (C) in Visual Studio Code

Continuous integration testing for the Linux kernel

How this team works to prevent bugs from being merged into the Linux kernel.

User profile image.

Related Content

GNOME

  • I/O access and Interrupts
  • View page source

I/O access and Interrupts ¶

Lab objectives ¶.

  • communication with peripheral devices
  • implement interrupt handlers
  • synchronizing interrupts with process context

Keywords: IRQ, I/O port, I/O address, base address, UART, request_region, release_region, inb, outb

Background information ¶

A peripheral device is controlled by writing and reading its registers. Often, a device has multiple registers that can be accessed at consecutive addresses either in the memory address space or in the I/O address space. Each device connected to the I/O bus has a set of I/O addresses, called I/O ports. I/O ports can be mapped to physical memory addresses so that the processor can communicate with the device through instructions that work directly with the memory. For simplicity, we will directly use I/O ports (without mapping to physical memory addresses) to communicate with physical devices.

The I/O ports of each device are structured into a set of specialized registers to provide a uniform programming interface. Thus, most devices will have the following types of registers:

  • Control registers that receive device commands
  • Status registers, which contain information about the device's internal status
  • Input registers from which data is taken from the device
  • Output registers in which the data is written to transmit it to the device

Physical ports are differentiated by the number of bits: they can be 8, 16 or 32-bit ports.

For example, the parallel port has 8 8-bit I/O ports starting at base address 0x378. The data log is found at base address (0x378), status register at base + 1 (0x379), and control at base address + 2 (0x37a). The data log is both an entry and exit log.

Although there are devices that can be fully controlled using I/O ports or special memory areas, there are situations where this is insufficient. The main problem that needs to be addressed is that certain events occur at undefined moments in time and it is inefficient for the processor (CPU) to interrogate the status of the device repeatedly (polling). The way to solve this problem is using an Interrupt ReQuest (IRQ) which is a hardware notification by which the processor is announced that a particular external event happened.

For IRQs to be useful device drivers must implement handlers, i.e. a particular sequence of code that handles the interrupt. Because in many situations the number of interrupts available is limited, a device driver must behave in an orderly fashion with interruptions: interrupts must be requested before being used and released when they are no longer needed. In addition, in some situations, device drivers must share an interrupt or synchronize with interrupts. All of these will be discussed further.

When we need to access shared resources between an interrupt routine (A) and code running in process context or in bottom-half context (B), we must use a special synchronization technique. In (A) we need to use a spinlock primitive, and in (B) we must disable interrupts AND use a spinlock primitive. Disabling interrupts is not enough because the interrupt routine can run on a processor other than the one running (B).

Using only a spinlock can lead to a deadlock. The classic example of deadlock in this case is:

  • We run a process on the X processor, and we acquire the lock
  • Before releasing the lock, an interrupt is generated on the X processor
  • The interrupt handling routine will try to acquire the lock and it will go into an infinite loop

Accessing the hardware ¶

In Linux, the I/O ports access is implemented on all architectures and there are several APIs that can be used.

Request access to I/O ports ¶

Before accessing I/O ports we first must request access to them, to make sure there is only one user. In order to do so, one must use the request_region() function:

To release a reserved region one must use the release_region() function:

For example, the serial port COM1 has the base address 0x3F8 and it has 8 ports and this is a code snippet of how to request access to these ports:

To release the ports one would use something like:

Most of the time, port requests are done at the driver initialization or probe time and the port releasing is done at the removal of the device or module.

All of the port requests can be seen from userspace via the /proc/ioports file:

Accessing I/O ports ¶

After a driver has obtained the desired I/O port range, one can perform read or write operations on these ports. Since physical ports are differentiated by the number of bits (8, 16, or 32 bits), there are different port access functions depending on their size. The following port access functions are defined in asm/io.h:

  • unsigned inb(int port) , reads one byte (8 bits) from port
  • void outb(unsigned char byte, int port) , writes one byte (8 bits) to port
  • unsigned inw(int port) , reads two bytes (16-bit) ports
  • void outw(unsigned short word, int port) , writes two bytes (16-bits) to port
  • unsigned inl (int port) , reads four bytes (32-bits) from port
  • void outl(unsigned long word, int port) , writes four bytes (32-bits) to port

The port argument specifies the address of the port where the reads or writes are done, and its type is platform dependent (may be unsigned long or unsigned short).

Some devices may have problems when the processor is trying to transfer data too fast to and from the device. To avoid this issue we may need to insert a delay after an I/O operation and there are functions you can use that introduce this delay. Their names are similar to those described above, with the exception that it ends in _p: inb_p, outb_p, etc.

For example, the following sequence writes a byte on COM1 serial port and then reads it:

5. Accessing I/O ports from userspace ¶

Although the functions described above are defined for device drivers, they can also be used in user space by including the <sys/io.h> header. In order to be used, ioperm or iopl must first be called to get permission to perform port operations. The ioperm function obtains permission for individual ports, while iopl for the entire I/O address space. To use these features, the user must be root.

The following sequence used in user space gets permission for the first 3 ports of the serial port, and then releases them:

The third parameter of the ioperm function is used to request or release port permission: 1 to get permission and 0 to release.

Interrupt handling ¶

Requesting an interrupt ¶.

As with other resources, a driver must gain access to an interrupt line before it can use it and release it at the end of the execution.

In Linux, the request to obtain and release an interrupt is done using the requests_irq() and free_irq() functions:

Note that to get an interrupt, the developer calls request_irq() . When calling this function you must specify the interrupt number ( irq_no ), a handler that will be called when the interrupt is generated ( handler ), flags that will instruct the kernel about the desired behaviour ( flags ), the name of the device using this interrupt ( dev_name ), and a pointer that can be configured by the user at any value, and that has no global significance ( dev_id ). Most of the time, dev_id will be pointer to the device driver's private data. When the interrupt is released, using the free_irq() function, the developer must send the same pointer value ( dev_id ) along with the same interrupt number ( irq_no ). The device name ( dev_name ) is used to display statistics in /proc/interrupts .

The value that request_irq() returns is 0 if the entry was successful or a negative error code indicating the reason for the failure. A typical value is -EBUSY which means that the interrupt was already requested by another device driver.

The handler function is executed in interrupt context which means that we can't call blocking APIs such as mutex_lock() or msleep() . We must also avoid doing a lot of work in the interrupt handler and instead use deferred work if needed. The actions performed in the interrupt handler include reading the device registers to get the status of the device and acknowledge the interrupt, operations that most of the time can be performed with non-blocking calls.

There are situations where although a device uses interrupts we can't read the device's registers in a non-blocking mode (for example a sensor connected to an I2C or SPI bus whose driver does not guarantee that bus read / write operations are non-blocking ). In this situation, in the interruption, we must plan a work-in-process action (work queue, kernel thread) to access the device's registers. Because such a situation is relatively common, the kernel provides the request_threaded_irq() function to write interrupt handling routines running in two phases: a process-phase and an interrupt context phase:

handler is the function running in interrupt context, and will implement critical operations while the thread_fn function runs in process context and implements the rest of the operations.

The flags that can be transmitted when an interruption is made are:

  • IRQF_SHARED announces the kernel that the interrupt can be shared with other devices. If this flag is not set, then if there is already a handler associated with the requested interrupt, the request for interrupt will fail. A shared interrupt is handled in a special way by the kernel: all the associated interrupt handlers will be executed until the device that generated the interrupt will be identified. But how can a device driver know if the interrupt handling routine was activated by an interrupt generated by the device it manages? Virtually all devices that offer interrupt support have a status register that can be interrogated in the handling routine to see if the interrupt was or was not generated by the device (for example, in the case of the 8250 serial port, this status register is IIR - Interrupt Information Register). When requesting a shared interrupt, the dev_id argument must be unique and it must not be NULL. Usually it is set to module's private data.
  • IRQF_ONESHOT interrupt will be reactivated after running the process context routine; Without this flag, the interrupt will be reactivated after running the handler routine in the context of the interrupt

Requesting the interrupt can be done either at the initialization of the driver ( init_module() ), when the device is probed, or when the device is used (e.g. during open ).

The following example performs the interrupt request for the COM1 serial port:

As you can see, the IRQ for serial port COM1 is 4, which is used in shared mode (IRQF_SHARED).

When requesting a shared interrupt (IRQF_SHARED) the dev_id argument can not be NULL.

To release the interrupt associated with the serial port, the following operations will be executed:

During the initialization function ( init_module() ), or in the function that opens the device, interrupts must be activated for the device. This operation is dependent on the device, but most often involves setting a bit from the control register.

As an example, for the 8250 serial port, the following operations must be performed to enable interrupts:

In the above example, two operations are performed:

  • All interruptions are activated by setting bit 3 (Aux Output 2) in the MCR register - Modem Control Register
  • The RDAI (Transmit Holding Register Empty Interrupt) is activated by setting the appropriate bit in the IER - Interrupt Enable Register.

Implementing an interrupt handler ¶

Lets take a look at the signature of the interrupt handler function:

The function receives as parameters the number of the interrupt ( irq_no ) and the pointer sent to request_irq() when the interrupt was requested. The interrupt handling routine must return a value with a type of typedef irqreturn_t . For the current kernel version, there are three valid values: IRQ_NONE , IRQ_HANDLED , and IRQ_WAKE_THREAD . The device driver must return IRQ_NONE if it notices that the interrupt has not been generated by the device it is in charge. Otherwise, the device driver must return IRQ_HANDLED if the interrupt can be handled directly from the interrupt context or IRQ_WAKE_THREAD to schedule the running of the process context processing function.

The skeleton for an interrupt handler is:

Typically, the first thing executed in the interrupt handler is to determine whether the interrupt was generated by the device that the driver ordered. This usually reads information from the device's registers to indicate whether the device has generated an interrupt. The second thing is to reset the interrupt pending bit on the physical device as most devices will no longer generate interruptions until this bit has been reset (e.g. for the 8250 serial port bit 0 in the IIR register must be cleared).

Because the interrupt handlers run in interrupt context the actions that can be performed are limited: unable to access user space memory, can't call blocking functions. Also, synchronization using spinlocks is tricky and can lead to deadlocks if the spinlock used is already acquired by a process that has been interrupted by the running handler.

However, there are cases where device drivers have to synchronize using interrupts, such as when data is shared between the interrupt handler and process context or bottom-half handlers. In these situations it is necessary to both deactivate the interrupt and use spinlocks.

There are two ways to disable interrupts: disabling all interrupts, at the processor level, or disabling a particular interrupt at the device or interrupt controller level. Processor disabling is faster and is therefore preferred. For this purpose, there are locking functions that disable and enable interrupts acquiring and release a spinlock at the same time: spin_lock_irqsave() , spin_unlock_irqrestore() , spin_lock_irq() , and spin_unlock_irq() :

The spin_lock_irqsave() function disables interrupts for the local processor before it obtains the spinlock; The previous state of the interrupts is saved in flags .

If you are absolutely sure that the interrupts on the current processor have not already been disabled by someone else and you are sure you can activate the interrupts when you release the spinlock, you can use spin_lock_irq() .

For read / write spinlocks there are similar functions available:

  • read_lock_irqsave()
  • read_unlock_irqrestore()
  • read_lock_irq()
  • read_unlock_irq()
  • write_lock_irqsave()
  • write_unlock_irqrestore()
  • write_lock_irq()
  • write_unlock_irq()

If we want to disable interrupts at the interrupt controller level (not recommended because disabling a particular interrupt is slower, we can not disable shared interrupts) we can do this with disable_irq() , disable_irq_nosync() , and enable_irq() . Using these functions will disable the interrupts on all processors. Calls can be nested: if disable_irq is called twice, it will require as many calls enable_irq to enable it. The difference between disable_irq and disable_irq_nosync is that the first one will wait for the executed handlers to finish. Because of this, disable_irq_nosync() is generally faster, but may lead to races with the interrupts handler, so when not sure use disable_irq() .

The following sequence disables and then enables the interrupt for the COM1 serial port:

It is also possible to disable interrupts at the device level. This approach is also slower than disabling interrupts at the processor level, but it works with shared interrupts. The way to accomplish this is device specific and it usually means we have to clear a bit from one of the control registers.

It is also possible to disable all interrupts for the current processor independent of taking locks. Disabling all interruptions by device drivers for synchronization purposes is inappropriate because races are still possible if the interrupt is handled on another CPU. For reference, the functions that disable / enable interrupts on the local processor are local_irq_disable() and local_irq_enable() .

In order to use a resource shared between process context and the interrupt handling routine, the functions described above will be used as follows:

The my_access function above runs in process context. To synchronize access to the shared data, we disable the interrupts and use the spinlock lock , i.e. the spin_lock_irqsave() and spin_unlock_irqrestore() functions.

In the interrupt handling routine, we use the spin_lock() and spin_unlock() functions to access the shared resource.

The flags argument for spin_lock_irqsave() and spin_unlock_irqrestore() is a value and not a pointer but keep in mind that spin_lock_irqsave() function changes the value of the flag, since this is actually a macro.

Interrupt statistics ¶

Information and statistics about system interrupts can be found in /proc/interrupts or /proc/stat . Only system interrupts with associated interrupt handlers appear in /proc/interrupts :

The first column specifies the IRQ associated with the interrupt. The following column shows the number of interrupts that were generated for each processor in the system; The last two columns provide information about the interrupt controller and the device name that registered the handler for that interrupt.

The /proc/state file provides information about system activity, including the number of interruptions generated since the last (re)boot of the system:

Each line in the /proc/state file begins with a keyword that specifies the meaning of the information on the line. For information on interrupts, this keyword is intr. The first number on the line represents the total number of interrupts, and the other numbers represent the number of interrupts for each IRQ, starting at 0. The counter includes the number of interrupts for all processors in the system.

Further reading ¶

Serial port ¶.

  • Serial Port
  • Interfacing the Serial / RS232 Port

Parallel port ¶

  • Interfacing the Standard Parallel Port
  • Parallel Port Central

Keyboard controller ¶

  • drivers/input/serio/i8042.c
  • drivers/input/keyboard/atkbd.c

Linux device drivers ¶

  • Linux Device Drivers, 3rd ed., Ch. 9 - Communicating with Hardware
  • Linux Device Drivers, 3rd ed., Ch. 10 - Interrupt Handling
  • Interrupt Handlers

Exercises ¶

We strongly encourage you to use the setup from this repository .

  • prepare skeletons from templates
  • build modules
  • start the VM and test the module in the VM.

The current lab name is interrupts. See the exercises for the task name.

The skeleton code is generated from full source examples located in tools/labs/templates . To solve the tasks, start by generating the skeleton code for a complete lab:

You can also generate the skeleton for a single task, using

Once the skeleton drivers are generated, build the source:

Then, start the VM:

The modules are placed in /home/root/skels/interrupts/<task_name>.

You DO NOT need to STOP the VM when rebuilding modules! The local skels directory is shared with the VM.

Review the Exercises section for more detailed information.

Before starting the exercises or generating the skeletons, please run git pull inside the Linux repo, to make sure you have the latest version of the exercises.

If you have local changes, the pull command will fail. Check for local changes using git status . If you want to keep them, run git stash before pull and git stash pop after. To discard the changes, run git reset --hard master .

If you already generated the skeleton before git pull you will need to generate it again.

Using LXR , find the definitions of the following symbols in the Linux kernel:

  • struct resource
  • request_region() and __request_region()
  • request_irq() and request_threaded_irq()
  • inb() for the x86 architecture.

Analyze the following Linux code:

  • Keyboard initialization function i8042_setup_kbd()
  • The AT or PS/2 keyboard interrupt function atkbd_interrupt()

Keyboard driver ¶

The next exercise's objective is to create a driver that uses the keyboard IRQ, inspect the incoming key codes and stores them in a buffer. The buffer will be accessible from userspace via character device driver.

1. Request the I/O ports ¶

To start with, we aim to allocate memory in the I/O space for hardware devices. We will see that we cannot allocate space for the keyboard because the designated region is already allocated. Then we will allocate I/O space for unused ports.

The kbd.c file contains a skeleton for the keyboard driver. Browse the source code and inspect kbd_init() . Notice that the I/O ports we need are I8042_STATUS_REG and I8042_DATA_REG.

Follow the sections maked with TODO 1 in the skeleton. Request the I/O ports in kbd_init() and make sure to check for errors and to properly clean-up in case of errors. When requesting, set the reserving caller's ID string ( name ) with MODULE_NAME macro. Also, add code to release the I/O ports in kbd_exit() .

You can review the Request access to I/O ports section before proceeding.

Now build the module and copy it to the VM image:

Now start the VM and insert the module:

Notice that you get an error when trying to request the I/O ports. This is because we already have a driver that has requested the I/O ports. To validate check the /proc/ioports file for the STATUS_REG and DATA_REG values:

Lets find out which driver register these ports and try to remove the module associated with it.

It looks like the I/O ports are registered by the kernel during the boot, and we won't be able to remove the associated module. Instead, let's trick the kernel and register ports 0x61 and 0x65.

Use the function request_region() (inside the kbd_init() function) to allocate the ports and the function release_region() (inside the kbd_exit() function) to release the allocated memory.

This time we can load the module and /proc/ioports shows that the owner of these ports is our module:

Let's remove the module and check that the I/O ports are released:

2. Interrupt handling routine ¶

For this task we will implement and register an interrupt handler for the keyboard interrupt. You can review the Requesting an interrupt section before proceeding.

Follow the sections marked with TODO 2 in the skeleton.

First, define an empty interrupt handling routine named kbd_interrupt_handler() .

Since we already have a driver that uses this interrupt we should report the interrupt as not handled (i.e. return IRQ_NONE ) so that the original driver still has a chance to process it.

Then register the interrupt handler routine using request_irq . The interrupt number is defined by the I8042_KBD_IRQ macro. The interrupt handling routine must be requested with IRQF_SHARED to share the interrupt line with the keyboard driver (i8042).

For shared interrupts, dev_id can not be NULL . Use &devs[0] , that is pointer to struct kbd . This structure contains all the information needed for device management. To see the interrupt in /proc/interrupts , do not use NULL for dev_name . You can use the MODULE_NAME macro.

If the interrupt requesting fails make sure to properly cleanup by jumping to the right label, in this case the one the releases the I/O ports and continues with unregistering the character device driver.

Compile, copy and load module in the kernel. Check that the interrupt line has been registered by looking at /proc/interrupts . Determine the IRQ number from the source code (see I8042_KBD_IRQ ) and verify that there are two drivers registered at this interrupt line (which means that we have a shared interrupt line): the i8042 initial driver and our driver.

More details about the format of the /proc/interrupts can be found in the Interrupt statistics section.

Print a message inside the routine to make sure it is called. Compile and reload the module into the kernel. Check that the interrupt handling routine is called when you press the keyboard on the virtual machine, using dmesg . Also note that when you use the serial port no keyboard interrupt is generated.

To get access to the keyboard on the virtual machine boot with "QEMU_DISPLAY=gtk make boot".

3. Store ASCII keys to buffer ¶

Next, we want to collect the keystrokes in a buffer whose content we will then send to the user space. For this routine we will add the following in the interrupt handling:

  • capture the pressed keys (only pressed, ignore released)
  • identify the ASCII characters.
  • copy the ASCII characters corresponding to the keystrokes and store them in the buffer of the device

Follow the sections marked TODO 3 in the skeleton.

Reading the data register ¶

First, fill in the i8042_read_data() function to read the I8042_DATA_REG of the keyboard controller. The function just needs to return the value of the register. The value of the registry is also called scancode, which is what is generated at each keystroke.

Read the I8042_DATA_REG register using inb() and store the value in the local variable val . Revisit the Accessing I/O ports section.

Call the i8042_read_data() in the kbd_interrupt_handler() and print the value read.

Print information about the keystrokes in the following format:

Where scancode is the value of the read register using the i8042_read_data() function.

Notice that the scancode (reading of the read register) is not an ASCII character of the pressed key. We'll have to understand the scancode.

Interpreting the scancode ¶

Note that the registry value is a scancode, not the ASCII value of the character pressed. Also note that an interrupt is sent both when the key is pressed and when the key is released. We only need to select the code when the key is pressed and then and decode the ASCII character.

To check scancode, we can use the showkey command (showkey -s).

In this form, the command will display the key scancodes for 10 seconds after the last pressed key end then it will stop. If you press and release a key you will get two scancodes: one for the pressed key and one for the released key. E.g:

If you press the ENTER key, you will get the 0x1c ( 0x1c ) and 0x9c (for the released key)

If you press the key a you will get the 0x1e (key pressed) and 0x9e (for the key release)

If you press b you will get 0x30 (key pressed) and 0xb0 (for the release key)

If you press the c key, you will get the 0x2e (key pressed) 0xae and 0xae (for the released key)

If you press the Shift key you will get the 0x2a (key pressed) 0xaa and 0xaa (for the released key)

If you press the Ctrl key you will get the 0x1d (key pressed) and 0x9d (for the release key)

As also indicated in this article , a key release scancode is 128 (0x80) higher then a key press scancode. This is how we can distinguish between a press key scancode and a release scancode.

A scancode is translated into a keycode that matches a key. A pressed scanned keycode and a released scancode have the same keycode. For the keys shown above we have the following table:

The press / release key is performed in the is_key_press() function and obtaining the ASCII character of a scancode takes place in the get_ascii() function.

In the interrupt handler check the scancode to see if the key is pressed or released then determine the corresponding ASCII character.

To check for press / release, use is_key_press() . Use get_ascii() function to get the corresponding ASCII code. Both functions expect the scancode.

To display the received information use the following format.

Where scancode is the value of the data register, and ch is the value returned by the get_ascii() function.

Store characters to the buffer ¶

We want to collect the pressed characters (not the other keys) into a circular buffer that can be consumed from user space.

Update the interrupt handler to add a pressed ASCII character to the end of the device buffer. If the buffer is full, the character will be discarded.

The device buffer is the field buf in the device's struct kbd . To get the device data from the interrupt handler use the following construct:

The buffer's dimension is located in struct kbd 's field, count . The put_idx and get_idx fields specify the next writing and reading index. Take a look at the put_char() function's implementation to observe how the data is added to the circular buffer.

Synchronize the access to the buffer and the helper indexes with a spinlock. Define the spinlock in the device struct struct kbd and initialize it in kbd_init() .

Use the spin_lock() and spin_unlock() functions to protect the buffer in the interrupt handler.

Revisit the Locking section.

4. Reading the buffer ¶

In order to have access to the keylogger's data, we have to send it to the user space. We will do this using the /dev/kbd character device. When reading from this device, we will get the data from the buffer in the kernel space, where we collected the keys pressed.

For this step follow the sections marked with TODO 4 in the kbd_read() function.

Implement get_char() in a similar way to put_char() . Be careful when implementing the circular buffer.

In the kbd_read() function copy the data from the buffer to the userspace buffer.

Use get_char() to read a character from the buffer and put_user() to store it to the user buffer.

In the read function, use spin_lock_irqsave() and spin_unlock_irqrestore() for locking.

We cannot use put_user() or copy_to_user() while holding the lock, as userpace access is not permitted from atomic contexts.

For more info, read the Access to the address space of the process section in the previous lab.

For testing, you will need to create the /dev/kbd character device driver using the mknod before reading from it. The device master and minor are defined as KBD_MAJOR and KBD_MINOR :

Build, copy and boot the virtual machine and load the module. Test it using the command:

5. Reset the buffer ¶

Reset the buffer if the device is written to. For this step follow the sections marked with TODO 5 in the skeleton.

Implement reset_buffer() and add the write operation to kbd_fops .

In the write function Use spin_lock_irqsave() and spin_unlock_irqrestore() for locking when resetting the buffer.

Press some keys, then run the command echo "clear" > /dev/kbd . Check the buffer's content again. It should be reset.

Extra Exercises ¶

Implement a keylogger using the kfifo API .

Follow the API call examples from the kernel code . For example, the file bytestream-examples.c .

  • I Tried Both: Apple Watch 9 vs Fitbit Charge 6
  • Best Places to Print Photos Online

What Is an IRQ (Interrupt Request)?

Devices send an IRQ to the processor to request access

irq assignments linux

  • Emporia State University

In This Article

Jump to a Section

Purpose of IRQ

  • Viewing and Editing IRQ Settings

How to Change IRQ Settings

Common irq channels, what are non-maskable interrupts.

An IRQ, short for Interrupt Request, is used in a computer to send exactly that—a request to interrupt the CPU by some other piece of hardware .

An Interrupt Request is necessary for things like keyboard presses, mouse movements, printer actions, and more. When the request is made by a device to momentarily stop the processor, the computer is then able to give the device some time to run its own operation.

For example, each time you press a key on the keyboard, an interrupt handler tells the processor that it needs to stop what it's currently doing so that it can handle the keystrokes.

Each device communicates the request over a unique data line called a channel. Most of the time you see IRQ referenced, it's alongside this channel number, also called an IRQ number . For example, IRQ 4 might be used for one device and IRQ 7 for another.

IRQ is pronounced as the letters I-R-Q, not as  erk .

Errors related to Interrupt Request are usually only seen when installing new hardware or changing the settings in existing hardware. Here are some IRQ errors you might see:

See How to Fix a Blue Screen of Death  if you're experiencing either of those stop errors .

While it's possible for the same IRQ channel to be used for more than one device (so long as both aren't actually being used at the same time), it's normally not the case. An IRQ conflict most likely occurs when two pieces of hardware are attempting to use the same channel for an interrupt request. 

Since the Programmable Interrupt Controller (PIC) doesn't support this, the computer might freeze up or the devices will stop working as expected (or stop working entirely).

Back in the early Windows days, IRQ errors were common, and it took a lot of troubleshooting to fix them. This is because it was more common to set IRQ channels manually, like with DIP switches, which made it more likely that more than one device was using the same IRQ line.

However, IRQs are handled much better in newer versions of Windows that use plug and play , so you'll rarely see an IRQ conflict or other IRQ issue.

Viewing and Editing IRQ Settings

The easiest way to view IRQ information in Windows is with  Device Manager . Change the  View  menu option to  Resources by type  to see the  Interrupt request (IRQ)  section.

You can also use System Information. Execute the  msinfo32.exe  command from the Run dialog box ( WIN+R ), and then go to  Hardware Resources > IRQs .

Linux users can run the  cat /proc/interrupts  command to view IRQ mappings.

You might need to change the IRQ line for a specific device if it's using the same IRQ as another, though it's usually unnecessary since  system resources  are automatically allocated for newer devices. It's only older Industry Standard Architecture (ISA) devices that might need manual IRQ adjustments.

You can change IRQ settings in the BIOS  or within Windows via Device Manager. Here's how to change IRQ settings with Device Manager:

Remember that making incorrect changes to these settings can cause problems you didn't have before. Make sure you know what you're doing and have recorded any existing settings and values so that you know what to revert to should something go wrong.

Open Device Manager and double-click or double-tap a device to open its  Properties  window. You'll need to open that device's category first before you'll be able to see it, which you can do by double-clicking/tapping.

In the  Resources  tab, deselect Use automatic settings .

If you can't find this tab or the option is greyed out or not enabled, it means that either you cannot specify a resource for that device or that the device has no other settings that can be applied to it.

Use the Settings based on drop down menu to select the hardware configuration that should be changed.

Select IRQ from the Resource settings area of the properties.

Use the  Change Setting button to edit the IRQ value.

Here are what some of the more common IRQ channels are used for:

Since IRQ 2 has a designated purpose, any device configured to use it will instead use IRQ 9.

There are also non-maskable interrupts (NMIs), which are interrupt requests that can't be blocked. A non-maskable interrupt occurs when dealing with system resets or hardware errors. The interrupt request is executed immediately.

Get the Latest Tech News Delivered Every Day

  • What Are the Types of System Resources in a Computer?
  • Everything You Need to Know About Computer Hardware
  • What the Red Light on a Motherboard Means
  • How to Control the CPU Fan on Windows 11
  • How to Fix a Computer That Turns On and Then Off
  • What Is Device Manager?
  • Is Twitch Down... Or Is It Just You?
  • This Device Cannot Start: How to Fix Code 10 Errors
  • What Is a CPU? (Central Processing Unit)
  • How to Fix a Blue Screen of Death (BSOD)
  • How to Fix It When Your Samsung Tablet Is Frozen
  • How to Fix Stopping, Freezing, and Reboot Issues During the POST
  • What Is iphlpsvc in Windows 10?
  • What Settings Are in the BIOS?
  • Task Manager
  • How to Fix it When Windows 11 Won't Install

Select Your Language

  • Single-page

Language and Page Formatting Options

Red hat training.

A Red Hat training course is available for Red Hat Enterprise Linux

E.3.6. /proc/irq/

Quick links.

  • Subscriptions
  • Support Cases
  • Customer Service
  • Product Documentation
  • Contact Customer Portal
  • Customer Portal FAQ
  • Log-in Assistance
  • Trust Red Hat
  • Browser Support Policy
  • Accessibility
  • Awards and Recognition

Related Sites

  • developers.redhat.com
  • connect.redhat.com
  • cloud.redhat.com

Systems Status

  • Red Hat Subscription Value
  • About Red Hat
  • Red Hat Jobs

Red Hat legal and privacy links

  • Contact Red Hat
  • Red Hat Blog
  • Diversity, equity, and inclusion
  • Cool Stuff Store
  • Red Hat Summit
  • Privacy statement
  • Terms of use
  • All policies and guidelines
  • Digital accessibility
  • Docs »
  • Core API Documentation »
  • View page source
  • What is an IRQ?
  • SMP IRQ affinity
  • The irq_domain interrupt number mapping library
  • IRQ-flags state tracing

COMMENTS

  1. Linux list all IROs currently in use

    Q. How do I list all IRQs currently used under CentOS Linux? A .. There is a file called /proc/interrupts. The proc filesystem is a pseudo filesystem which is used as an interface to kernel data structures. It is commonly mounted at /proc. This is used to record the number of interrupts per each IRQ on (at least) the i386 architecture.

  2. How the Linux kernel handles interrupts

    Subsequent IRQs can be assigned differently. The interrupt descriptor table (IDT) contains the assignment between IRQ and ISR. Linux defines an IRQ vector from 0 to 256 for the assignment. To print a list of registered interrupts on your system, open a console and type: cat /proc/interrupts. You should see something like this:

  3. What is an IRQ?

    An IRQ number is a kernel identifier used to talk about a hardware interrupt source. Typically this is an index into the global irq_desc array, but except for what linux/interrupt.h implements the details are architecture specific. An IRQ number is an enumeration of the possible interrupt sources on a machine. Typically what is enumerated is ...

  4. 4.3. Interrupts and IRQ Tuning

    As an example, to set the interrupt affinity for the Ethernet driver on a server with four CPU cores, first determine the IRQ number associated with the Ethernet driver: # grep eth0 /proc/interrupts. 32: 0 140 45 850264 PCI-MSI-edge eth0. Use the IRQ number to locate the appropriate smp_affinity file:

  5. Linux generic IRQ handling

    The generic interrupt handling layer is designed to provide a complete abstraction of interrupt handling for device drivers. It is able to handle all the different types of interrupt controller hardware. Device drivers use generic API functions to request, enable, disable and free interrupts.

  6. 2.4. Interrupt and Process Binding

    Red Hat Customer Portal - Access to 24x7 support and knowledge. Focus mode. 2.4. Interrupt and Process Binding. Real-time environments need to minimize or eliminate latency when responding to various events. Ideally, interrupts (IRQs) and user processes can be isolated from one another on different dedicated CPUs.

  7. Linux generic IRQ handling

    bool irq_percpu_is_enabled (unsigned int irq) ¶ Check whether the per cpu irq is enabled. Parameters. unsigned int irq Linux irq number to check for. Description. Must be called from a non migratable context. Returns the enable state of a per cpu interrupt on the current cpu. void remove_percpu_irq (unsigned int irq, struct irqaction * act) ¶

  8. 4.6. Interrupt Handling

    IRQ sharing. The interrupt handler executes several interrupt service routines (ISRs).Each ISR is a function related to a single device sharing the IRQ line. Because it is not possible to know in advance which particular device issued the IRQ, each ISR is executed to verify whether its device needs attention; if so, the ISR performs all the operations that need to be executed when the device ...

  9. I/O access and Interrupts

    #include <linux/interrupt.h> int request_threaded_irq (unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char * name, void * dev); handler is the function running in interrupt context, and will implement critical operations while the thread_fn function runs in process context and implements the rest of ...

  10. Interrupt request

    Interrupt request. In a computer, an interrupt request (or IRQ) is a hardware signal sent to the processor that temporarily stops a running program and allows a special program, an interrupt handler, to run instead. Hardware interrupts are used to handle events such as receiving data from a modem or network card, key presses, or mouse movements.

  11. What are linux irq domains, why are they needed?

    The most interesting part of that commit is this line (in max732x_irq_handler() ): handle_nested_irq(irq_find_mapping(chip->gpio_chip.irqdomain, level)); irq_find_mapping() will find linux IRQ number by hardware IRQ number (using IRQ domain mapping function). Then handle_nested_irq() function will be called, which will run IRQ handler of "Some ...

  12. linux

    1. PCI configuration space is configured by the Bios, which means that Bios is supposed to enumerate all PCI devices at boot time. When a device is enumerated, the Bios routes an IRQ line to the IOAPIC input and set BAR registers, then, the kernel can request_irq () with the appropriate irq number read from the pci configuration space. Just to ...

  13. IRQs

    IRQs. ¶. What is an IRQ? SMP IRQ affinity. The irq_domain interrupt number mapping library. IRQ-flags state tracing. ©The kernel development community. | Powered by Sphinx 5.0.1 & Alabaster 0.7.12 | Page source.

  14. How can I know which IRQ is responsible of high CPU usage

    Since then I've noticed that constantly a 25% of one of the cores goes always to IRQ however I haven't managed myself to know which is the IRQ responsible for that. The kernel is a Linux 2.6.18-194.3.1.el5 (CentOS). mpstat -P ALL shows: This is the /proc/interrupts. CPU0 CPU1 CPU2 CPU3.

  15. What is an interrupt request (IRQ) and how does it work?

    IRQ (interrupt request): An IRQ ( interrupt request ) value is an assigned location where the computer can expect a particular device to interrupt it when the device sends the computer signals about its operation. For example, when a printer has finished printing, it sends an interrupt signal to the computer. The signal momentarily interrupts ...

  16. What Is an Interrupt Request (IRQ)?

    Purpose of IRQ. An Interrupt Request is necessary for things like keyboard presses, mouse movements, printer actions, and more. When the request is made by a device to momentarily stop the processor, the computer is then able to give the device some time to run its own operation. For example, each time you press a key on the keyboard, an ...

  17. The irq_domain interrupt number mapping library

    The irq_domain library adds mapping between hwirq and IRQ numbers on top of the irq_alloc_desc* () API. An irq_domain to manage mapping is preferred over interrupt controller drivers open coding their own reverse mapping scheme. irq_domain also implements translation from an abstract irq_fwspec structure to hwirq numbers (Device Tree and ACPI ...

  18. E.3.6. /proc/irq/ Red Hat Enterprise Linux 6

    E.3.6. /proc/irq/. This directory is used to set IRQ to CPU affinity, which allows the system to connect a particular IRQ to only one CPU. Alternatively, it can exclude a CPU from handling any IRQs. Each IRQ has its own directory, allowing for the individual configuration of each IRQ. The /proc/irq/prof_cpu_mask file is a bitmask that contains ...

  19. IRQs

    The Linux Kernel 5.10.0 The Linux kernel user's and administrator's guide; Kernel Build System; The Linux kernel firmware guide; Open Firmware and Device Tree ... What is an IRQ? SMP IRQ affinity; The irq_domain interrupt number mapping library; IRQ-flags state tracing; Next Previous

  20. Chapter 5 Practice Exam

    Get CompTIA Linux+ Practice Tests, 3rd Edition now with the O'Reilly learning platform. O'Reilly members experience books, live events, courses curated by job role, and more from O'Reilly and nearly 200 top publishers. Chapter 5Practice Exam Which command enables you to view the current IRQ assignments? view /proc/irq cat /proc/interrupts ...