Title: Interrupt Handling
1Interrupt Handling
2Interrupt Handling
- One big responsibility of an operating system is
to handle hardware connected to the machine - Hard drives, keyboards and mice, wireless radios,
and karaoke microphones - Processors are usually much faster than devices
3Interrupts
- 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
4Topics
- Interrupt handling
- Overview
- Registration of handlers
- Interaction with hardware
- Limitations of handlers
- Deregistration
- Tasklets and bottom halves
- Interrupt sharing
5Overview of Interrupts
6Preparing the Parallel Port
- LDD3 illustrates interrupt handling with the
short module - 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
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 binary data will generate several
interrupts - Use D-25 connector
8Installing an Interrupt Handler
- Without a interrupt handler installed for an
interrupt, Linux simply acks and ignores it - Since interrupt lines are few (typically 16),
sharing is expected - Typically, only crufty ISA drivers do not share
9Installing an Interrupt Handler
- If not sharing interrupt line (almost never)
- Initialize interrupt handler when the device is
first opened - Call free_irq in the last close
- If sharing interrupt line (almost always)
- Initialize interrupt handler during module load
- Call free_irq in module unload
10Installing an Interrupt Handler
- To register an interrupt handler, call
- include ltlinux/interrupt.hgt
- int request_irq(unsigned int irq,
- irq_handler_t handler,
- unsigned long flags, const char
name, - void dev)
- irq the requested interrupt number
- handler the interrupt handler function pointer
- flags various interrupt handler flags
- name for /proc/interrupts
- dev pointer/unique cookie for shared interrupt
lines (can be set to NULL if not shared)
11Installing an Interrupt Handler
- flags
- IRQF_DISABLED indicates a fast interrupt
handler - If set, interrupts are disabled on the current
processor - If unset, interrupt handlers run with all
interrupts enabled except their own (usual
setting) - Never have to worry about own interrupt handler
being nested - IRQF_SHARED signals that the interrupt can be
shared - IRQF_SAMPLE_RANDOM indicates that the generated
interrupts can contribute to generate random
numbers (used by /dev/random and /dev/urandom)
12Installing an Interrupt Handler
- On success, request_irq returns zero
- Common error is EBUSY, which denotes given
interrupt line is already in use and no sharing - request_irq can sleep
- Calls kmalloc() later on
- Make sure device is completely set up before
calling request_irq - Dont want to start servicing interrupts before
device is ready
13Installing an Interrupt Handler
- The short example
- if (short_irq gt 0)
- result request_irq(short_irq,
short_interrupt, - IRQF_DISABLED, "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)
-
-
14The /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
15The /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
16Implementing 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
17Implementing a Handler
- Wakes up processes waiting for the interrupt
- The network card example
- Must process packets while waiting for more
packets - The interrupt handler copies new networking
packets into main memory and readies network card
for more packets - The handler needs to execute in a minimum amount
of time - Uses bottom half (typically tasklet or workqueue)
to schedule computation later - E.g. network card to sort packets and send them
to correct application
18Implementing a Handler
- The short example
- irqreturn_t short_interrupt(int irq, void dev_id
- 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) / bp
buffer pointer / - wake_up_interruptible(short_queue)
- return IRQ_HANDLED
-
19Implementing 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
20Implementing 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 / -
21Implementing 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
-
22Implementing 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
23Implementing 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
-
24Implementing a Handler
- Without connecting pins 9 and 10
- Use /dev/shortprint to drive a printer
- Write implementation uses a circular buffer to
store data to be printed - Read implementation is same as shown
25Handler Arguments and Return Value
- Typical use of the argument in an interrupt
handler - static irqreturn_t sample_interrupt(int irq, void
dev_id) - 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
26Handler Arguments and Return Value
- Returns IRQ_HANDLED if the device needs
attention otherwise, returns IRQ_NONE - Kernel can detect spurious interrupts if all
interrupts on line return 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
-
27Enabling and Disabling Interrupts
- Interfaces for manipulating state of interrupts
- Disable interrupt system for current processor
- Mask out interrupt line for entire machine
- ltasm/system.hgt and ltasm/irq.hgt
- Why?
- Synchronization
- Common scenario
- Obtain lock to prevent another processor from
accessing shared data - Disabling interrupts provides protection against
concurrent access from a possible interrupt
handler
28Enabling and Disabling Interrupts
- For current processor only
- local_irq_disable() disables interrupts
- Dangerous to call if interrupts were already
disabled prior to invocation - local_irq_enable() enables interrupts
- Unconditionally enables interrupts
29Enabling and Disabling Interrupts
- Sometimes a mechanism is needed to restore
interrupts to a previous state - E.g. common code path can be reached both with
and without interrupts enabled - Since kernel is complex, much safer to restore to
previous state using flags - local_irq_save(flags)
- local_irq_restore(flags)
30Disabling a Single Interrupt
- Can disable (mask out) a specific interrupt line
for an entire system - E.g. disable delivery of a devices interrupts
before manipulating its state - Use of functions are discouraged, and cannot
disable shared interrupt lines (think ISA) - void disable_irq(int irq)
- void disable_irq_nosync(int irq)
- void enable_irq(int irq)
31Disabling a Single Interrupt
- Calls can be nested
- If disable_irq is called twice, two enable_irq
calls are required to reenable the IRQ - 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
32Checking Interrupt Status
- Macro irqs_disabled() returns nonzero if the
interrupt system on the local processor is
disabled - Checking current context
- in_interrupt()
- Returns nonzero if in interrupt handler or bottom
half - in_irq()
- Returns nonzero only if in interrupt handler
33Top 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.
34Top and Bottom Halves
- Two mechanisms may be used to implement bottom
halves - Tasklets
- No sleep
- Workqueues
- Can sleep
- Short module can be loaded to use either tasklet
or workqueue
35Tasklets
- Cannot run in parallel with itself
- Scheduling is not cumulative
- Can run in parallel with other tasklets on SMP
systems - Guaranteed to run on the same CPU that first
scheduled them - Can be sure that tasklet does not begin executing
before the handler has completed
36Tasklets
- 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) - / 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
-
37Tasklets
- 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)
38Workqueues
- Invoke a function at some future time in the
context of a special worker process - Can sleep
- Cannot copy data to and from user space
39Workqueues
- 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() to initialize
a work_struct/ - INIT_WORK(short_wq, (typeof(short_wq.func))
short_do_tasklet, NULL) - irqreturn_t short_wq_interrupt(int irq, void
dev_id) - 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
-
40Interrupt Sharing
- Installing a shared handler
- Set IRQF_SHARED flag when requesting interrupt
- The dev_id must be unique
- Cannot be NULL
- Returns IRQ_NONE if the handler is not the target
handler - request_irq() succeeds if
- The interrupt line is free
- All handlers registered agree to share
41Interrupt 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
42Interrupt 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
43Running 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
44Running 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
45The /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
46Interrupt-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
47A Write-Buffering Example
- Shortprint module implements output-oriented
driver for the parallel port - 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()
48A Write-Buffering Example
write()
49A 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
50A 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
-
- ...
-
51A 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
- ...
-
52A 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
53shortp_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)
-
54shortp_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()
55shortp_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)
56shortp_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) -
57shortp_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
-
58shortp_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)
-
59shortp_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)