Interrupt Handling Sarah Diesburg COP 5641 Interrupt 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 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 Overview Registration of handlers Interaction with hardware Limitations of handlers Deregistration Tasklets and bottom halves Interrupt sharing Overview of Interrupts Preparing 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 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 binary data will generate several interrupts Use D-25 connector Installing 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 Installing 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 Installing an Interrupt Handler To register an interrupt handler, call #include <linux/interrupt.h> 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) Installing 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) Installing an Interrupt Handler On success, request_irq returns zero request_irq can sleep Common error is –EBUSY, which denotes given interrupt line is already in use and no sharing Calls kmalloc() later on… Make sure device is completely set up before calling request_irq Don’t want to start servicing interrupts before device is ready Installing an Interrupt Handler The short example if (short_irq >= 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_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 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 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 Implementing 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; } 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 Write implementation uses a circular buffer to store data to be printed Read implementation is same as shown 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 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->i_rdev); request_irq(dev->irq, sample_interrupt, 0 /* flags */, "sample", dev /* dev_id */); /*....*/ return 0; } 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 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); 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 current interrupt to 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 Workqueues No sleep Can sleep Short module can be loaded to use either tasklet or workqueue Tasklets 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 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; } 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 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 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() 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; } Interrupt 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 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 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 Shortprint module implements outputoriented driver for the parallel port 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); }