Title: Interrupt%20Handling
1Interrupt Handling
- Ted Baker ? Andy Wang
- CIS 4930 / COP 5641
2Interrupts
- Prevent CPUs from busy waiting
- A signal that the hardware can send when it wants
the CPUs attention - Need to pay attention to concurrency issues
3Topics
- Interrupt handling
- Registration of handlers
- Interaction with hardware
- Limitations of handlers
- Deregistration
- Probing for interrupts
- Tasklets and bottom halves
- Interrupt sharing
4Interrupt/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
5Interrupt/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
6Preparing 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)
7Preparing 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
8Installing 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
9Installing 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
10Installing an Interrupt Handler
- To register an interrupt handler, call
- include ltlinux/interrupt.hgt
- 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)
11Installing an Interrupt Handler
In 2.6.25, they are mapped to IRQF_DISABLED,
IRQF_SHARED, IRQF_SAMPLE_RANDOM
- flags
- SA_INTERRUPT indicates a fast interrupt handler
- 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)
12Installing an Interrupt Handler
- The short example
- if (short_irq gt 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_base2)
-
-
13The /proc Interface
- /proc/interrupts shows interrupts with installed
handlers - CPU0 CPU1
- 0 4848108 34 IO-APIC-edge timer
- 2 0 0 XT-PIC cascade
- 8 3 1 IO-APIC-edge rtc
- 10 4335 1 IO-APIC-level aic7xxx
- 11 8903 0 IO-APIC-level uhci_hcd
- 12 49 1 IO-APIC-edge i8042
- NMI 0 0
- LOC 4848187 4848186
- ERR 0
- MIS 0
Device names
Programmable interrupt controllers
Linux handles interrupts on the first CPU to
maximize cache locality
14The /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 -
Interrupt number 4 used 4907 times
Total number
15Autodetecting the IRQ Number
- A bad practice to require the user to specify the
interrupt number - The user doesnt know any better
- Might not be aware of the jumper settings
- For many devices, autodetection depends on common
default settings
16Autodetecting the IRQ Number
- The short example
- if (short_irq lt 0) / not yet specified force
the default on / - switch(short_base)
- case 0x378 short_irq 7 break
- case 0x278 short_irq 2 break
- case 0x3bc short_irq 5 break
-
- ...
- The user can also override the default at load
time - insmod ./short.ko irqx
17Autodetecting 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
18Kernel-assisted Probing
- Works for nonshared interrupts
- Consists of two functions
- include ltlinux/interrupt.hgt
- / 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)
19Kernel-assisted Probing
- The short example
- int count 0
- do
- unsigned long mask
- mask probe_irq_on()
- outb_p(0x10,short_base2) / enable reporting
/ - outb_p(0x00,short_base) / clear the bit /
- outb_p(0xFF,short_base) / set the bit
interrupt! / - outb_p(0x00,short_base2) / 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 lt 0 count lt 5)
20Kernel-assisted Probing
- if (short_irq lt 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)
21Do-it-yourself Probing
- The short example performs do-it-yourself probing
with probe2 - 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
- for (i 0 trialsi i) / install the
probing handler / - / request_irq returns 0 on success or EBUSY
/ - triedi request_irq(trialsi,
short_probing, - SA_INTERRUPT, "short probe", NULL)
-
0 is the termination marker
22Do-it-yourself Probing
- do
- short_irq 0 / none got, yet /
- outb_p(0x10,short_base2) / enable /
- outb_p(0x00,short_base)
- outb_p(0xFF,short_base) / toggle the bit /
- outb_p(0x00,short_base2) / 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 lt 0 if multiple lines are
activated / - while (short_irq lt0 count lt 5)
23Do-it-yourself Probing
- / end of loop, uninstall the handler /
- for (i 0 trialsi i)
- if (triedi 0)
- free_irq(trialsi, NULL)
-
- if (short_irq lt 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
-
24Do-it-yourself Probing
- Without knowing the commonly used IRQs
- Needs to probe IRQ 0 to IRQ NR_IRQS 1
- NR_IRQS defined in ltasm/irq.hgt
25Fast 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
26The 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/hand
le.c
27Implementing 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
28Implementing 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
29Implementing a Handler
- The short example
- irqreturn_t short_interrupt(int irq, void
dev_id, - struct pt_regs regs)
- struct timeval tv
- int written
- 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
-
This argument is removed in 2.6.21
30Implementing 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 gt (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 gt (short_buffer PAGE_SIZE))
- index short_buffer
31Implementing 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 / -
32Implementing a Handler
- / count0 is the number of readable data bytes
/ - count0 short_head - short_tail
- if (count0 lt 0) / wrapped /
- count0 short_buffer PAGE_SIZE -
short_tail -
- if (count0 lt 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
-
33Implementing 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
34Implementing 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 lt count)
- iowrite8(0xff((written odd) 1),
address) - else
- while (written lt count)
- outb(0xff((written odd) 1), port)
-
- f_pos count
- return written
-
35Implementing a Handler
- Without connecting pins 9 and 10
- Use /dev/shortprint to drive a printer
36Handler 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
37Handler Arguments and Return Value
- pt_regs holds the snapshot of the processors
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-gti_rdev) - request_irq(dev-gtirq, sample_interrupt, 0 /
flags /, - "sample", dev / dev_id /)
- /..../
- return 0
-
38Enabling 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
39Disabling a Single Interrupt
- Three functions
- Their use is discouraged
- Cannot disable shared interrupt lines
- include ltasm/irq.hgt
- 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
40Disabling 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
41Disabling All Interrupts
- To disable all interrupts on the current CPU,
call either one - include ltasm/system.hgt
- / 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
42Disabling All Interrupts
- To enable all interrupts on the current CPU, call
the corresponding function - include ltasm/system.hgt
- void local_irq_restore(unsigned long flags)
- / does not keep track multiple calls /
- void local_irq_enable(void)
43Top 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.
44Top and Bottom Halves
- Two mechanisms may be used to implement bottom
halves - Tasklets
- No sleep
- Workqueues
- Can sleep
45Tasklets
- 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
46Tasklets
- In the short example, use tasklet1 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
-
47Tasklets
- 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.06
u\n", - (int)(tv_tail-gttv_sec
100000000), - (int)(tv_tail-gttv_usec))
- short_incr_bp(short_head, written)
- short_incr_tv(tv_tail)
- while (tv_tail ! tv_head)
- wake_up_interruptible(short_queue)
48Workqueues
- Can sleep
- Cannot copy data to and from user space
49Workqueues
- In the short example, set wq1 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
-
50Interrupt 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
51Interrupt 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
52Interrupt 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
53Running a Handler
- In the short example, use shared1 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)
- if (!(value 0x80))
- return IRQ_NONE
- / clear the interrupting bit /
- outb(value 0x7F, short_base)
Check the most significant bit
54Running 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
55The /proc Interface and Shared Interrupts
- Check /proc/interrupts
- CPU0
- 0 892335412 XT-PIC timer
- 1 453971 XT-PIC i8042
- 2 0 XT-PIC cascade
- 5 0 XT-PIC libata, ehci_hcd
- 8 0 XT-PIC rtc
- 9 0 XT-PIC acpi
- 10 11365067 XT-PIC ide2, uhci_hcd, uhci_hcd,
SysKonnect - 11 4391962 XT-PIC uhci_hcd, uhci_hcd
- 12 224 XT-PIC i8042
- 14 2787721 XT-PIC ide0
- 15 203048 XT-PIC ide1
- NMI 41234
56Interrupt-Driven I/O
- Buffering improves performance
- Also leads to interrupt-driven I/O
- Input buffer is filled at interrupt time
- Emptied by the read processes
- Output buffer is filled by write 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
57A Write-Buffering Example
- The write function calls shortp_write()
- Calls shortp_start_output()
- Schedules a timer that calls shortp_timeout()
- Calls either shortp_timeout() or
shortp_interrupt() - Schedules shortp_do_work()
- Calls shortp_do_write() to write individual
characters - The printer calls shortp_interrupt()
- Schedules shortp_do_work()
58A Write-Buffering Example
write()
59A Write-Buffering Example
- The shortprint example maintains a one-page
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
60A Write-Buffering Example
- while (written lt count)
- / Hang out until some buffer space is
available. / - space shortp_out_space()
- if (space lt 0)
- if (wait_event_interruptible(shortp_out_queu
e, - (space
shortp_out_space()) - gt 0))
- goto out
-
- ...
-
61A Write-Buffering Example
- / Move data into the buffer. /
- if ((space written) gt 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
- ...
-
62A 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
63shortp_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)
-
64shortp_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()
65shortp_do_work
- / If somebody's waiting, wake them up if
enough space. / - if (((PAGE_SIZE shortp_out_tail -
shortp_out_head) - PAGE_SIZE) gt SP_MIN_SPACE)
- wake_up_interruptible(shortp_out_queue)
-
- spin_unlock_irqrestore(shortp_out_lock,
flags)
66shortp_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_baseSP_DATA)
- shortp_incr_out_bp(shortp_out_tail, 1)
- if (shortp_delay) udelay(shortp_delay)
- outb_p(cr SP_CR_STROBE, shortp_baseSP_CONTROL
) - if (shortp_delay) udelay(shortp_delay)
- outb_p(cr SP_CR_STROBE, shortp_baseSP_CONTRO
L) -
67shortp_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
-
68shortp_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)
-
69shortp_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)