Monday, December 12, 2016

Interrupt Handling

Basic Interrupt explanation

Interrupt is a signal to the processor emitted by hardware (for example: mouse moved) or software (for example: exceptions in CPU like divide by zero, page fault) indicating an event that needs immediate attention.

This is the flow of handling an interrupt:
  • When an interrupt arrives, the CPU looks if it is masked, if so, it ignores it.
  • There is a special location called - the interrupt vector, in which it locates, where in memory, located the interrupt handler function for the current interrupt number.
  • The CPU masks interrupts and saves the contents of some registers in some place and execute the interrupt handler.
  • When the handler finishes executing, it executes a special return-from-interrupt instruction that restores the saved registers and unmasks interrupts.

Interrupts in Linux

In Linux, each interrupting device gets an interrupt request number (IRQ). When the processor detects that an interrupt has been generated on an IRQ, it stops what it's doing and invokes an interrupt service routine (ISR) registered for the corresponding IRQ.
To compensate for interrupting the current thread of execution, ISRs are executed in a restricted environment called interrupt context (or atomic context).

Process Context and Interrupt Context

Kernel code that services system calls issued by user applications runs on behalf of the corresponding application processes and is said to execute in process context. Interrupt handlers, on the other hand, run asynchronously in interrupt context.

Kernel code running in process context is preemptive by the scheduler. An interrupt context, however, always runs to completion and is not preemptive.
Because scheduler only handle process context, if you go to sleep, nothing will resume the code later on. Thats why, you may not use mutexes or any other code that may sleep inside ISR.

More about ISRs

  • Different instances of the same ISR will not run simultaneously on multiple processors, because while ISR run, the IRQ is disabled.
  • Different IRQs may be handled at the same time by different CPUs.
  • Interrupt handlers can be interrupted by handlers associated with IRQs that have higher priority. To prevent this, you must declare your ISR as fast handler. Fast handlers run with all interrupts disabled on the local processor.

How to request an IRQ

int request_irq (
    unsigned int irq,
    irq_handler_t handler,
    unsigned long irqflags,
    const char * devname,
    void * dev_id);
 


irq - Interrupt line to allocate
handler - Function to be called when the IRQ occurs
irqflags - Interrupt type flag, for example:
  • IRQF_TRIGGER_RISING - interrupt is generated on rising edge.
  • IRQF_TRIGGER_HIGH -  interrupt is generated if level is high.
  • IRQF_SHARED - specify that this IRQ is shared among multiple devices, each such a device should specify that flag.
  • SA_INTERRUPT - mark the ISR as fast, which tells the kernel to disable other interrupts on current CPU until it finished (otherwise, kernel may jump to another ISR, if its IRQ has higher priority). Other CPUs may still handle other IRQs.   
devname - Name for the claiming device, used in /proc/interrupts for identification.
dev_id - A cookie passed back to the handler function
 

Top half vs bottom half

ISRs must they work fast, because of the following reasons:
  • While ISR run, it doesn't let other interrupts to run (interrupts with higher priority will run).
  • Interrupts with same type will be missed.
In Linux interrupts are design in two part:
  1. Top half - the ISR part which interact with hardware and should exit as soon as possible.
  2. Bottom half - The relaxed part that does most of the processing with all interrupts enabled. The kernel decides when to execute them.
If the interrupt handler function could process and acknowledge interrupts within few microseconds consistently, there is absolutely no need for top-half/bottom-half delegation.

Bottom halves

There are 4 bottom half mechanizes available in Linux: Threaded IRQs, softirqs, tasklets, work-queue.  

threaded IRQs

A driver that wishes to request a threaded interrupt handler will use

int request_threaded_irq (

unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long irqflags,
const char * devname,
void * dev_id);

So, as you may notice it is a pretty similar to request_irq() function discussed already.
handler
Function to be called when the IRQ occurs. This is the top half and should return IRQ_WAKE_THREAD which will wake up the handler thread and run thread_fn.
thread_fn
This is the bottom half handler which runs in a process context.
Threaded IRQ handlers are preferred for bottom-half processing that would spill over half a jiffy consistently (e.g., more than 500 microseconds if CONFIG_HZ is set to 1000). 
When you use the request_threaded_irq() function passing two functions as arguments, this would create a kthread which would be visible in the 'ps ax' process listing, for example:

$ ps -ax
  465 ?        S      4:16 [irq/34-iwlwifi]
6334 ?        S      0:00 [irq/35-mei_me]

The numbers 34,35 are irq numbers and 'iwlwifi', 'mei_me' are device name related to the IRQs.

When the thread_fn completes, the associated kthread would take itself out of the runqueue and remain in blocked state until woken up again by the top half again.

Softirqs

They are used only by a few performance-sensitive subsystems such as the networking layer, SCSI layer, and kernel timers. 

Different instances of a softirq can run simultaneously on different processors, thats why their content must be protected with spinlocks. 

To define a softirq, you must statically add an entry to include/linux/interrupt.h, which means compiling the kernel.

Softirq run at a high-priority in an interrupt context, with scheduler preemption being disabled, not letting CPU to handle other processes/threads until it complete. If functions registered in SoftIRQs fail to finish within a jiffy (1 to 10 milliseconds based on CONFIG_HZ of the kernel), in which case it impact the responsiveness of the kernel, any new SoftIRQs raised by ISRs would be delegated to run in process context via ksoftirqd thread, thus making SoftIRQs compete for their CPU share along with other processes and threads on the runqueue. In general, SoftIRQs are preferred for bottom-half processing that could finish consistently in few 100 microseconds (well within a jiffy).

void __init mymodule_init()
{
 open_softirq(SOFT_IRQ_NUM, bottom_half_handler, NULL);
}

/* The bottom half */
void bottom_half_handler()
{
 //DO SOME BOTTOM-HALF WORK HERE
}

/* The top half */
static irqreturn_t some_interrupt(int irq, void *dev_id)
{
 //DO SOME TOP-HALF WORK HERE
 
 raise_softirq(SOFT_IRQ_NUM);
 return IRQ_HANDLED;
}
 

Tasklets

Tasklets are built on top of softirqs and are easier to use. It's recommended to use tasklets then softirqs unless you have crucial scalability or speed requirements. Tasklet can't be run in parallel on different CPUs, but different types of tasklets may be run simultaneously.

Tasklet can be statically allocated using DECLARE_TASKLET(name, func, data) or can also be allocated dynamically and initialized at runtime using tasklet_init(name, func, data) 

A tasklet can be scheduled to execute at normal priority or high priority. The latter group is always executed first. 

Tasklets are a bottom-half mechanism built on top of softirqs, they are represented by two softirqs: HI_SOFTIRQ and TASKLET_SOFTIRQ. Tasklets are actually run from a softirq. The only real difference in these types is that the HI_SOFTIRQ based tasklets run prior to the TASKLET_SOFTIRQ tasklets.

struct roller_device_struct { /* Device-specific structure */
/* ... */
struct tasklet_struct tsklt;
/* ... */
}
void __init roller_init()
{
struct roller_device_struct *dev_struct;
/* ... */
/* Initialize tasklet */
tasklet_init(&dev_struct->tsklt, roller_analyze, dev);
}
/* The bottom half */
void
roller_analyze()
{
/* Analyze the waveforms and switch to
polled mode if required */
}
/* The interrupt handler */
static irqreturn_t
roller_interrupt(int irq, void *dev_id)
{
struct roller_device_struct *dev_struct;
/* Capture the wave stream */
roller_capture();
/* Mark tasklet as pending */
tasklet_schedule(&dev_struct->tsklt);
return IRQ_HANDLED;
}

Work queues

 https://arkadiviner.blogspot.co.il/2016/11/work-queues.html

 

Summary



Threaded IRQ
(bottom-half)
Softirqs
Tasklets
Work Queues
Execution
context
process context
interrupt context
interrupt context
process context
Reentrancy
cannot run
simultaneously on different CPUs.
can run simultaneously on different CPUs.
cannot run
simultaneously on different CPUs.
Different tasklets may run on different CPUs.
cannot run
simultaneously on different CPUs.
Sleep
semantics
May go to sleep.
Cannot go to sleep.
Cannot go to sleep.
May go to sleep.
Preemption
The IRQ thread has a higher priority than work-queue thread.
They run at a high-priority with scheduler preemption being disabled, so processes/threads wait for them. If softirq doesn’t release the CPU in more than 1 jiffy, and another softirq pending execution, they will be executed in a kthread.


Cannot be
preempted/scheduled.
May be
preempted/scheduled.
When to use
Threaded IRQs are preferred if interrupt processing can take consistently long periods of time (exceeding a jiffy in most cases)
If you must do work at real-time, without sleeps.
If you don’t need to sleep.
If you go to sleep.

In general, tasklets are less recommended bottom-half interface because they constrained not to sleep and to execute on the same CPU.


No comments:

Post a Comment