690 likes | 823 Views
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
E N D
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 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 to generate an interrupt • Generally waits for interrupt to be acked 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 binary data will generate several interrupts
Installing an Interrupt Handler • Without a interrupt handler installed for an interrupt, Linux simply acks and ignores it • Since interrupt lines are few, sharing is expected
Installing an Interrupt Handler • Better to initialize interrupt handlers when the device is first opened (vs. when a driver is initialized) to avoid hogging interrupt lines • Before HW 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 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 intcan_request_irq(unsigned intirq, 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 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
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
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 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 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}; inti, count = 0; 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); } 0 is the termination marker
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_tshort_probing(intirq, void *dev_id, structpt_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 (x86) • arch/x86/kernel/entry_32.S contains ENTRY(interrupt) • Jumps to do_IRQ in arch/x86/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 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_tshort_interrupt(intirq, void *dev_id, structpt_regs *regs) { 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; } This argument is removed in 2.6.21
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_tsample_interrupt(intirq, void *dev_id, structpt_regs *regs) { structsample_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 • Returns IRQ_HANDLED if the device needs attention; otherwise, returns IRQ_NONE • Typical open code static void sample_open(structinode *inode, struct file *filp) { structsample_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 <linux/irqflags.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 < linux/irqflags.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_tshort_tl_interrupt(intirq, void *dev_id, structpt_regs *regs) { /* cast to stop 'volatile' warning */ do_gettimeofday((structtimeval *) 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 structwork_structshort_wq; /* this line is in the short_init() */ INIT_WORK(&short_wq, (typeof(short_wq.func)) short_do_tasklet, NULL); irqreturn_tshort_wq_interrupt(intirq, void *dev_id, structpt_regs *regs) { do_gettimeofday((structtimeval *) 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_irqsuceeds if • The interrupt line is free • All handlers registered agree to share