370 likes | 404 Views
Interrupt Handling. Linux Kernel Programming CIS 4930/COP 5641. Topics. Overview Registration of handlers Interrupt sharing Interaction with hardware Limitations of handlers Bottom halves. Interrupts. Provides a mechanism other than busy waiting to be notified of an event
E N D
Interrupt Handling Linux Kernel Programming CIS 4930/COP 5641
Topics • Overview • Registration of handlers • Interrupt sharing • Interaction with hardware • Limitations of handlers • Bottom halves
Interrupts • Provides a mechanism other than busy waiting to be notified of an event • E.g., Interrupt-driven I/O • Hardware generates interrupts when • New data arrives and is ready for retrieval • Ready to accept new data or to acknowledge a successful data transfer • Signal from device to notify the CPU of an event (hardware desires attention) • Concurrency issues arise
Interrupt-Driven I/O • Hardware generates interrupts when • New data arrives and is ready for retrieval • Ready to accept new data or to acknowledge a successful data transfer
Parallel Port Interrupts with short • LDD3 illustrates interrupt handling with the short module • Setting bit 4 of the parallel port HW’s control port (0x37a or 0x27a) enables interrupt reporting • Once enabled, the parallel interface generates an interrupt whenever the electrical signal at pin 10 (ACK bit) changes from low to high (edge-triggered)
Interrupts with short • Pins 9 and 10 of the parallel connector are shorted • Pin 9 is the most significant bit of the data byte • Writing ASCII values to /dev/short0 will not generate any interrupts • An interrupt is raised whenever the electrical signal at pin 10 (ACK bit) changes from low to high
Installing (Registering) an Interrupt Handler • Without an installed interrupt handler, Linux simply supplies an ack • request_irq() • irq number • handler • irqreturn_t (*irq_handler_t)(int, void *) • flags • name • Dev • void free_irq(unsigned int, void *)
request_irq() • returns zero on success • Common error is –EBUSY, which denotes given interrupt line is already in use and not shared • can sleep • Calls kmalloc() • Make sure device is completely set up before calling request_irq • Interrupt may occur during/after called
Flags (include/linux/interrupt.h) • IRQF_DISABLED • local_irq_enable_in_hardirq(); • http://lwn.net/Articles/380931/ • IRQF_SHARED • IRQF_TIMER • IRQF_NO_THREAD • IRQF_NO_SUSPEND • IRQF_SAMPLE_RANDOM • http://lwn.net/Articles/507115/ • …
IRQF_SHARED • If interrupt lines are few (typically 16), sharing is likely necessary • Each installed interrupt handler will be called • devparameter passed to request_irq cannot be NULL • Allows a means to distinguish between interrupt handlers when removal is requested
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
Installing an Interrupt Handler • The short example if (short_irq >= 0) { result = request_irq(short_irq, short_interrupt, NULL, "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 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 Linux often handles individual interrupts on the same CPU to maximize cache locality
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 Interrupt number 4 used 4907 times Total number
Implementing a Handler • No transferring data to and from user space • Cannot sleep • Cannot call schedule, wait_event • Can only use GFP_ATOMIC to allocate memory • Might need to clear a bit on the device • Inform device subsequent interrupts should be provided
Implementing a Handler • Wakes up processes waiting for device services • E.g., network card • 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
Implementing a Handler • The short example irqreturn_tshort_interrupt(intirq, void *dev_id) { structtimevaltv; 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; }
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_tshort_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; }
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 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 • Returns IRQ_HANDLED if from drivers device; otherwise, returns IRQ_NONE • Kernel can detect “spurious interrupts” if all interrupts on line return IRQ_NONE
Handler return Value • In the short example, use shared=1 to install a shared interrupted handler irqreturn_tshort_sh_interrupt(intirq, void *dev_id, structpt_regs *regs) { int value, written; structtimevaltv; /* 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
Enabling and Disabling Interrupts • Interfaces for manipulating state of interrupts • Disable interrupt system for current processor • Mask out interrupt line for entire machine • <asm/system.h> and <asm/irq.h> • 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
Enabling 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
Enabling 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)
Disabling a Single Interrupt • Can disable (mask out) a specific interrupt line for an entire system • E.g. disable delivery of a device’s interrupts before manipulating its state void disable_irq(intirq); void disable_irq_nosync(intirq); void enable_irq(intirq);
Disabling 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 target interrupt handler to disable • Returns only when any currently executing handlers complete • disable_irq_nosync returns immediately • Need to handle potential race conditions
Checking 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
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 • short module can be loaded to use either tasklet or workqueue
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) { /* 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; }
Workqueues • Invoke a function at some future time in the context of a special worker process • Can sleep • Generally do not copy data to and from user space