Interrupt Handling  Andy Wang Ted Baker CIS 4930 / COP 5641

advertisement
Interrupt Handling
Ted Baker  Andy Wang
CIS 4930 / COP 5641
Interrupts



Prevent CPUs from busy waiting
A signal that the hardware can send when it
wants the CPU’s attention
Need to pay attention to concurrency issues
Topics

Interrupt handling







Registration of handlers
Interaction with hardware
Limitations of handlers
Deregistration
Probing for interrupts
Tasklets and bottom halves
Interrupt sharing
Interrupt/Masking/Disabling/Blocking

Independent mechanisms exist at several
levels

CPU




Can be set to ignore all interrupts
Interrupts stay pending until unmasked/unblocked
E.g., local_irq_disable
Software IRQ layer



Interrupt handled by common handler code
Handler not called if disabled
E.g., disable_irq_nosync
Interrupt/Masking/Disabling/Blocking

Interrupt controller




Sits between CPU and devices that generate interrupts
Can be instructed not to pass interrupts through
E.g., disable_8259A_irq
I/O device




Generates interrupts
May be instructed whether it is OK to generate an
interrupt
Generally waits for interrupt to be acknowledged by
CPU
E.g., see enabling of parallel port interrupt in short.c
Preparing the Parallel Port

Setting bit 4 of port 2 (0x37a or 0x27a)
enables interrupt reporting (via outb call)

Once enabled, the parallel interface
generates an interrupt whenever the
electrical signal at pin 10 (ACK bit) changes
from low to high (edge-triggered)
Preparing the Parallel Port

Without a printer, one can connect pins 9 and
10 of the parallel connector



Pin 9 is the most significant bit of the parallel data
byte
Writing ASCII to /dev/short0 will not generate
any interrupts
Writing a binary data will generate several
interrupts
Installing an Interrupt Handler


Without a interrupt handler installed for an
interrupt, Linux simply acknowledges and
ignores it
Since interrupt lines are few, sharing is
expected
Installing an Interrupt Handler

A good idea to initialize interrupt handlers
when the device is first opened (vs. when a
driver is initialized)




Before the hardware is instructed to generate
interrupts
Many loaded modules are not used
Many devices are not used at the same time
Call free_irq in the last close

After the hardware is told not to create interrupts
Installing an Interrupt Handler

To register an interrupt handler, call
#include <linux/interrupt.h>
int request_irq(unsigned int irq,
irqreturn_t (*handler) (int, void *,
struct pt_regs *),
unsigned long flags, const char *dev_name,
void *dev_id);




irq: the requested interrupt number
handler: the interrupt handler function pointer
dev_name: for /proc/interrupts
dev_id: pointer for shared interrupt lines (can be
set to NULL if not shared)
Installing an Interrupt Handler

flags

SA_INTERRUPT indicates a “fast” interrupt handler




In 2.6.25, they are mapped to IRQF_DISABLED,
IRQF_SHARED, IRQF_SAMPLE_RANDOM
Interrupts are disabled on the current processor
SA_SHIRG signals that the interrupt can be shared
SA_SAMPLE_RANDOM indicates that the generated
interrupts can contribute to generate random numbers
(used by /dev/random and /dev/urandom)
To query the availability of an interrupt line
(x86), call
int can_request_irq(unsigned int irq, unsigned long flags);

Returns nonzero on success (for that moment)
Installing an Interrupt Handler

The short example
if (short_irq >= 0) {
result = request_irq(short_irq, short_interrupt,
SA_INTERRUPT, "short", NULL);
if (result) {
printk(KERN_INFO "short: can't get assigned irq %i\n",
short_irq);
short_irq = -1;
} else { /* enable it -- assume this *is* a parallel port */
outb(0x10,short_base+2);
}
}
The /proc Interface

/proc/interrupts shows interrupts with
Device names
installed handlers
0:
2:
8:
10:
11:
12:
NMI:
LOC:
ERR:
MIS:
CPU0
4848108
0
3
4335
8903
49
0
4848187
0
0
CPU1
34
0
1
1
0
1
0
4848186
IO-APIC-edge
XT-PIC
IO-APIC-edge
IO-APIC-level
IO-APIC-level
IO-APIC-edge
Linux handles interrupts on
the first CPU to maximize
cache locality
timer
cascade
rtc
aic7xxx
uhci_hcd
i8042
Programmable
interrupt
controllers
The /proc Interface

/proc/stat shows number of interrupts
received since system boot


Architecture dependent file format
Look for the intr string
intr 5167833 5154006 2 0 2 4907 0 2 68 4 0 4406 9291 50 0 0
Total number
Interrupt number
4 used 4907
times
Autodetecting the IRQ Number

A bad practice to require the user to specify
the interrupt number



The user doesn’t know any better
Might not be aware of the jumper settings
For many devices, autodetection depends on
common default settings
Autodetecting the IRQ Number

The short example
if (short_irq < 0) /* not
switch(short_base) {
case 0x378: short_irq
case 0x278: short_irq
case 0x3bc: short_irq
}
...

yet specified: force the default on */
= 7; break;
= 2; break;
= 5; break;
The user can also override the default at load
time
insmod ./short.ko irq=x
Autodetecting the IRQ Number

The PCI standard requires devices to declare
what interrupt line(s) they are going to use


Autodetection involves just probing the device
The driver tells the device to generate interrupts
Kernel-assisted Probing


Works for nonshared interrupts
Consists of two functions
#include <linux/interrupt.h>
/* returns a bit mask of unassigned interrupts */
unsigned long probe_irq_on(void);
/* called after the device has requested an interrupt */
/* returns 0 if no interrupts occurred */
/* returns the IRQ number if only one interrupt occurred */
/* returns a negative value if multiple interrupts occurred */
int probe_irq_off(unsigned long);
Kernel-assisted Probing

The short example
int count = 0;
do {
unsigned long mask;
mask = probe_irq_on();
outb_p(0x10,short_base+2); /* enable reporting */
outb_p(0x00,short_base); /* clear the bit */
outb_p(0xFF,short_base); /* set the bit: interrupt! */
outb_p(0x00,short_base+2); /* disable reporting */
udelay(5); /* give it some time */
short_irq = probe_irq_off(mask);
if (short_irq == 0) { /* none of them? */
printk(KERN_INFO "short: no irq reported by probe\n");
short_irq = -1;
}
} while (short_irq < 0 && count++ < 5);
Kernel-assisted Probing
if (short_irq < 0) {
printk("short: probe failed %i times, giving up\n", count);
}

Probing can be a lengthy task



Frame grabber requires a delay of at least 20 ms
Probe one interrupt one at a time
Probing is not necessary for certain platforms
(PowerPC, MIPS, and SPARC)
Do-it-yourself Probing

The short example performs do-it-yourself
probing with probe=2

Probe only commonly used IRQs
void short_selfprobe(void) {
int trials[] = {3, 5, 7, 9, 0};
int tried[] = {0, 0, 0, 0, 0};
int i, count = 0;
0 is the
termination
marker
for (i = 0; trials[i]; i++) { /* install the probing handler */
/* request_irq returns 0 on success or –EBUSY */
tried[i] = request_irq(trials[i], short_probing,
SA_INTERRUPT, "short probe", NULL);
}
Do-it-yourself Probing
do {
short_irq = 0; /* none got, yet */
outb_p(0x10,short_base+2); /* enable */
outb_p(0x00,short_base);
outb_p(0xFF,short_base); /* toggle the bit */
outb_p(0x00,short_base+2); /* disable */
udelay(5); /* see if short_probing is invoked */
/* the value has been set by the handler */
if (short_irq == 0) { /* none of them? */
printk(KERN_INFO "short: no irq reported by probe\n");
}
/* short_irq < 0 if multiple lines are activated */
} while (short_irq <=0 && count++ < 5);
Do-it-yourself Probing
/* end of loop, uninstall the handler */
for (i = 0; trials[i]; i++) {
if (tried[i] == 0)
free_irq(trials[i], NULL);
}
if (short_irq < 0)
printk("short: probe failed %i times, giving up\n", count);
}
irqreturn_t short_probing(int irq, void *dev_id,
struct pt_regs *regs) {
if (short_irq == 0) short_irq = irq; /* found */
if (short_irq != irq) short_irq = -irq; /* ambiguous */
return IRQ_HANDLED;
}
Do-it-yourself Probing

Without knowing the commonly used IRQs


Needs to probe IRQ 0 to IRQ NR_IRQS – 1
NR_IRQS defined in <asm/irq.h>
Fast and Slow Handlers

Fast interrupts are requested with the
SA_INTERRUPT flag (e.g., timer interrupt)


Disables all other interrupts on the current CPU
Other CPUs can still handle interrupts


No two CPUs handle the same IRQ at the same time
Slow interrupts have other interrupts enabled
The Internals of Interrupt Handling on
x86

arch/i386/kernel/entry.S contains
ENTRY(interrupt)

Jumps to do_IRQ in
arch/i386/kernel/irq.c

Prevents other CPUs from handling this IRQ
Calls the particular handler



If there is no handler, return
If a device is interrupting

Call handle_IRQ_event in
arch/i386/kernel/irq/handle.c
Implementing a Handler


Cannot transfer data to and from user space
Cannot sleep



Cannot call schedule, wait_event, down
Can only use GFP_ATOMIC to allocate memory
Might need to clear a bit on the interface
board

Allows subsequent interrupts to be received
Implementing a Handler

Wakes up processes waiting for the interrupt

The frame grabber example



Read blocks while waiting for a frame
The interrupt handler wakes up the process as each
new frame arrives
The handler needs to execute in a minimum
amount of time

Uses tasklet or workqueue to schedule
computation
Implementing a Handler

The short example
irqreturn_t short_interrupt(int irq, void *dev_id,
struct pt_regs *regs) {
struct timeval tv;
int written;
This argument
do_gettimeofday(&tv);
is removed in
2.6.21
written = sprintf((char *)short_head,"%08u.%06u\n",
(int)(tv.tv_sec % 100000000),
(int)(tv.tv_usec));
short_incr_bp(&short_head, written);
wake_up_interruptible(&short_queue);
return IRQ_HANDLED;
}
Implementing a Handler
Variable can be
accessed externally at
any time
static inline void short_incr_bp(volatile unsigned long *index,
int delta) {
unsigned long new = *index + delta;
barrier(); /* Don't optimize these two together */
*index
= (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;
}

Without barrier…
static inline void short_incr_bp(volatile unsigned long *index,
int delta) {
*index = *index + delta; /* could expose an incorrect value */
if (*index >= (short_buffer + PAGE_SIZE))
*index = short_buffer;
}
Implementing a Handler

To read the buffer, use /dev/shortint
ssize_t short_i_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos) {
int count0;
DEFINE_WAIT(wait);
while (short_head == short_tail) {
prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);
if (short_head == short_tail) {
schedule();
}
finish_wait(&short_queue, &wait);
if (signal_pending(current)) /* a signal arrived */
return -ERESTARTSYS; /* tell the fs layer to handle it */
}
Implementing a Handler
/* count0 is the number of readable data bytes */
count0 = short_head - short_tail;
if (count0 < 0) {/* wrapped */
count0 = short_buffer + PAGE_SIZE - short_tail;
}
if (count0 < count) {
count = count0;
}
if (copy_to_user(buf, (char *)short_tail, count)) {
return -EFAULT;
}
short_incr_bp(&short_tail, count); /* wrap the tail pointer */
return count;
}
Implementing a Handler

To raise interrupts


Connect pins 9 and 10 of the parallel connector
Write to /dev/shortint


Which alternately writes 0x00 and 0xff to the parallel
port
An interrupt is raised whenever the electrical signal at
pin 10 (ACK bit) changes from low to high
Implementing a Handler

To write to /dev/shortint
ssize_t short_i_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos) {
int written = 0, odd = *f_pos & 1;
unsigned long port = short_base;
void *address = (void *) short_base;
if (use_mem) { /* memory-mapped */
while (written < count)
iowrite8(0xff*((++written + odd) & 1), address);
} else {
while (written < count)
outb(0xff*((++written + odd) & 1), port);
}
*f_pos += count;
return written;
}
Implementing a Handler

Without connecting pins 9 and 10

Use /dev/shortprint to drive a printer
Handler Arguments and Return Value

Typical use of the argument in an interrupt
handler
static irqreturn_t sample_interrupt(int irq, void *dev_id,
struct pt_regs *regs) {
struct sample_dev *dev = dev_id;
/* now `dev' points to the right hardware item */
/* .... */
}


irq: for printk
dev_id: for finding out which instance of device
is in charge of the current interrupt event
Handler Arguments and Return Value



pt_regs: holds the snapshot of the processor’s
context before running the interrupt code
(linux/include/asm-i386/ptrace.h)
Returns IRQ_HANDLED if the device needs
attention; otherwise, returns IRQ_NONE
Typical open code
static void sample_open(struct inode *inode, struct file *filp) {
struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev);
request_irq(dev->irq, sample_interrupt, 0 /* flags */,
"sample", dev /* dev_id */);
/*....*/
return 0;
}
Enabling and Disabling Interrupts


Often, interrupts must be blocked while
holding a spinlock to avoid deadlocks
Also, there are ways of disabling interrupts
that do not involve spinlocks

Should not be used within a driver
Disabling a Single Interrupt

Three functions


Their use is discouraged
Cannot disable shared interrupt lines
#include <asm/irq.h>
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

Calls can be nested

If disable_irq is called twice, two enable_irq
calls are required to reenable the IRQ
Disabling a Single Interrupt


The calling thread of the disable_irq
should not hold resource needed by the
current interrupt to complete
disable_irq_nosync returns immediately


Need to handle potential race conditions
Why disabling interrupts?

Sometimes to reduce the performance overhead
Disabling All Interrupts

To disable all interrupts on the current CPU,
call either one
#include <asm/system.h>
/* disables interrupts after saving the current interrupt state
into flags */
void local_irq_save(unsigned long flags);
/* shuts off interrupts without saving the state */
void local_irq_disable(void);

Avoid doing this when possible

Almost never use local_irq_disable
Disabling All Interrupts

To enable all interrupts on the current CPU,
call the corresponding function
#include <asm/system.h>
void local_irq_restore(unsigned long flags);
/* does not keep track multiple calls */
void local_irq_enable(void);
Top and Bottom Halves

Interrupt handling sometimes needs to
perform lengthy tasks

This problem is resolved by splitting the interrupt
handler into two halves


Top half responds to the interrupt

The one registered to request_irq

Saves data to device-specific buffer and schedules the
bottom half
Bottom half is scheduled by the top half to execute later


With all interrupts enabled
Wakes up processes, starts I/O operations, etc.
Top and Bottom Halves

Two mechanisms may be used to implement
bottom halves

Tasklets


No sleep
Workqueues

Can sleep
Tasklets



Cannot run in parallel with itself
Can run in parallel with other tasklets on SMP
systems
Guaranteed to run on the same CPU that first
scheduled them
Tasklets

In the short example, use tasklet=1 to
install the tasklet-based interrupt handler
void short_do_tasklet(unsigned long);
DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);
irqreturn_t short_tl_interrupt(int irq, void *dev_id,
struct pt_regs *regs) {
/* cast to stop 'volatile' warning */
do_gettimeofday((struct timeval *) tv_head);
short_incr_tv(&tv_head);
tasklet_schedule(&short_tasklet);
short_wq_count++; /* record that an interrupt arrived */
return IRQ_HANDLED;
}
Tasklets
void short_do_tasklet (unsigned long unused) {
int savecount = short_wq_count, written;
short_wq_count = 0; /* number of interrupts before this call */
written = sprintf((char *)short_head,
"bh after %6i\n",savecount);
short_incr_bp(&short_head, written);
do { /* write the time values */
written = sprintf((char *)short_head,"%08u.%06u\n",
(int)(tv_tail->tv_sec % 100000000),
(int)(tv_tail->tv_usec));
short_incr_bp(&short_head, written);
short_incr_tv(&tv_tail);
} while (tv_tail != tv_head);
wake_up_interruptible(&short_queue);
}
Workqueues


Can sleep
Cannot copy data to and from user space
Workqueues

In the short example, set wq=1 to install the
workqueue-based interrupt handler
static struct work_struct short_wq;
/* this line is in the short_init() */
INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);
irqreturn_t short_wq_interrupt(int irq, void *dev_id,
struct pt_regs *regs) {
do_gettimeofday((struct timeval *) tv_head);
short_incr_tv(&tv_head);
schedule_work(&short_wq);
short_wq_count++; /* record that an interrupt arrived */
return IRQ_HANDLED;
}
Interrupt Sharing

Installing a shared handler


Set SA_SHIRQ flag when requesting the interrupt
The dev_id must be unique



Cannot be NULL
Returns IRQ_NONE if the handler is not the target
handler
request_irq suceeds if


The interrupt line is free
All handlers registered agree to share
Interrupt Sharing

When an interrupt arrives, the kernel invokes
every handler registered for that interrupt


The handler must be able to recognize its own
interrupts
No probing function is available for shared
handlers

Most hardware designed for interrupt sharing can
tell the CPU which interrupt it is using

No need for explicit probing
Interrupt Sharing


free_irq needs the correct dev_id
Watch out for enable_irq and
disable_irq


Not a good idea to disable other devices’
interrupts
Does not work well with edge-triggered
interrupts

Interrupts from other devices may be lost while
one device is holding the line active
Running a Handler

In the short example, use shared=1 to
install a shared interrupted handler
irqreturn_t short_sh_interrupt(int irq, void *dev_id,
struct pt_regs *regs) {
int value, written;
struct timeval tv;
/* If it wasn't short, return immediately */
value = inb(short_base);
Check the most
if (!(value & 0x80))
significant bit
return IRQ_NONE;
/* clear the interrupting bit */
outb(value & 0x7F, short_base);
Running a Handler
/* the rest is unchanged */
do_gettimeofday(&tv);
written = sprintf((char *)short_head,"%08u.%06u\n",
(int)(tv.tv_sec % 100000000),
(int)(tv.tv_usec));
short_incr_bp(&short_head, written);
wake_up_interruptible(&short_queue);
return IRQ_HANDLED;
}


Assumes that pins 9 and 10 are connected
The example would not work for printers,
since the printer protocol disallow sharing
The /proc Interface and Shared
Interrupts

Check /proc/interrupts
0:
1:
2:
5:
8:
9:
10:
11:
12:
14:
15:
NMI:
CPU0
892335412
453971
0
0
0
0
11365067
4391962
224
2787721
203048
41234
XT-PIC
XT-PIC
XT-PIC
XT-PIC
XT-PIC
XT-PIC
XT-PIC
XT-PIC
XT-PIC
XT-PIC
XT-PIC
timer
i8042
cascade
libata, ehci_hcd
rtc
acpi
ide2, uhci_hcd, uhci_hcd, SysKonnect
uhci_hcd, uhci_hcd
i8042
ide0
ide1
Interrupt-Driven I/O

Buffering improves performance

Also leads to interrupt-driven I/O

Input buffer is filled at interrupt time


Output buffer is filled by write processes


Emptied by the read processes
Emptied at interrupt time
Hardware generates interrupts when


New data has arrives and is ready for retrieval
When it is ready to accept new data or to
acknowledge a successful data transfer
A Write-Buffering Example

The write function calls shortp_write()

Calls shortp_start_output()

Schedules a timer that calls shortp_timeout()


Schedules shortp_do_work()


Calls either shortp_timeout() or
shortp_interrupt()
Calls shortp_do_write() to write individual characters
The printer calls shortp_interrupt()

Schedules shortp_do_work()
A Write-Buffering Example
shortp_write()
printer
write()
shortp_start_ouput()
shortp_timeout()
shortp_do_work()
shortp_interrupt()
shortp_do_write()
A Write-Buffering Example

The shortprint example maintains a onepage circular output buffer


A write system call only writes data to the buffer
The actual write is scheduled later
static size_t shortp_write(struct file *filp,
const char __user *buf,
size_t count, loff_t *f_pos) {
int space, written = 0;
unsigned long flags;
if (down_interruptible(&shortp_out_sem))
return –ERESTARTSYS;
A Write-Buffering Example
while (written < count) {
/* Hang out until some buffer space is available. */
space = shortp_out_space();
if (space <= 0) {
if (wait_event_interruptible(shortp_out_queue,
(space = shortp_out_space())
> 0))
goto out;
}
...
A Write-Buffering Example
/* Move data into the buffer. */
if ((space + written) > count)
space = count - written;
if (copy_from_user((char *) shortp_out_head, buf, space)) {
up(&shortp_out_sem);
return -EFAULT;
}
shortp_incr_out_bp(&shortp_out_head, space);
buf += space;
written += space;
...
A Write-Buffering Example
/* If no output is active, make it active. */
spin_lock_irqsave(&shortp_out_lock, flags);
if (!shortp_output_active)
shortp_start_output();
spin_unlock_irqrestore(&shortp_out_lock, flags);
}
out:
*f_pos += written;
up(&shortp_out_sem);
return written;
}
shortp_start_output
static DECLARE_WORK(shortp_work, shortp_do_work, NULL);
static struct workqueue struct *shortp_workqueue;
static void shortp_start_output(void) {
if (shortp_output_active) /* Should never happen */
return;
/* Set up a timer to handle occasionally missed interrupts */
shortp_output_active = 1;
shortp_timer.expires = jiffies + TIMEOUT;
add_timer(&shortp_timer); /* calls shortp_timeout */
/* And get the process going. */
queue_work(shortp_workqueue, &shortp_work);
}
shortp_do_work
static void shortp_do_work(void *unused) {
int written;
unsigned long flags;
shortp_wait(); /* wait until the device is ready */
spin_lock_irqsave(&shortp_out_lock, flags);
/* Have we written everything? */
if (shortp_out_head == shortp_out_tail) { /* empty */
shortp_output_active = 0;
wake_up_interruptible(&shortp_empty_queue);
del_timer(&shortp_timer);
} else /* Nope, write another byte */
shortp_do_write();
shortp_do_work
/* If somebody's waiting, wake them up if enough space. */
if (((PAGE_SIZE + shortp_out_tail - shortp_out_head)
% PAGE_SIZE) > SP_MIN_SPACE) {
wake_up_interruptible(&shortp_out_queue);
}
spin_unlock_irqrestore(&shortp_out_lock, flags);
}
shortp_do_write
static void shortp_do_write(void) {
unsigned char cr = inb(shortp_base + SP_CONTROL);
/* Reset the timer */
mod_timer(&shortp_timer, jiffies + TIMEOUT);
/* Strobe a byte out to the device */
outb_p(*shortp_out_tail, shortp_base+SP_DATA);
shortp_incr_out_bp(&shortp_out_tail, 1);
if (shortp_delay) udelay(shortp_delay);
outb_p(cr | SP_CR_STROBE, shortp_base+SP_CONTROL);
if (shortp_delay) udelay(shortp_delay);
outb_p(cr & ~SP_CR_STROBE, shortp_base+SP_CONTROL);
}
shortp_interrupt
static irqreturn_t shortp_interrupt(int irq, void *dev_id,
struct pt_regs *regs) {
if (!shortp_output_active)
return IRQ_NONE;
/* Remember the time, and farm off the rest to the workqueue
function */
do_gettimeofday(&shortp_tv);
queue_work(shortp_workqueue, &shortp_work);
return IRQ_HANDLED;
}
shortp_timtout
static void shortp_timeout(unsigned long unused) {
unsigned long flags;
unsigned char status;
if (!shortp_output_active)
return;
spin_lock_irqsave(&shortp_out_lock, flags);
status = inb(shortp_base + SP_STATUS);
shortp_timtout
/* If the printer is still busy we just reset the timer */
if ((status & SP_SR_BUSY) == 0 || (status & SP_SR_ACK)) {
shortp_timer.expires = jiffies + TIMEOUT;
add_timer(&shortp_timer);
spin_unlock_irqrestore(&shortp_out_lock, flags);
return;
}
/* Otherwise we must have dropped an interrupt. */
spin_unlock_irqrestore(&shortp_out_lock, flags);
shortp_interrupt(shortp_irq, NULL, NULL);
}
Download