
195b49af83445bdabd594b9e3e2c8ec1.ppt
- Количество слайдов: 69
Interrupt Handling Ted Baker Andy Wang CIS 4930 / COP 5641
Interrupts n n n 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 n Interrupt handling q q q q Registration of handlers Interaction with hardware Limitations of handlers Deregistration Probing for interrupts Tasklets and bottom halves Interrupt sharing
Interrupt/Masking/Disabling/Blocking n Independent mechanisms exist at several levels q CPU n n n q Can be set to ignore all interrupts Interrupts stay pending until unmasked/unblocked E. g. , local_irq_disable Software IRQ layer n n n Interrupt handled by common handler code Handler not called if disabled E. g. , disable_irq_nosync
Interrupt/Masking/Disabling/Blocking q Interrupt controller n n n q Sits between CPU and devices that generate interrupts Can be instructed not to pass interrupts through E. g. , disable_8259 A_irq I/O device n n 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
Preparing the Parallel Port n Setting bit 4 of port 2 (0 x 37 a or 0 x 27 a) enables interrupt reporting (via outb call) n 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 n Without a printer, one can connect pins 9 and 10 of the parallel connector q q q Pin 9 is the most significant bit of the parallel data byte Writing ASCII to /dev/short 0 will not generate any interrupts Writing a binary data will generate several interrupts
Installing an Interrupt Handler n n Without a interrupt handler installed for an interrupt, Linux simply acknowledges and ignores it Since interrupt lines are few, sharing is expected
Installing an Interrupt Handler n A good idea to initialize interrupt handlers when the device is first opened (vs. when a driver is initialized) q q q n 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 q After the hardware is told not to create interrupts
Installing an Interrupt Handler n 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); q q 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 q flags n SA_INTERRUPT indicates a “fast” interrupt handler q n n n In 2. 6. 25, they are mapped to IRQF_DISABLED, IRQF_SHARED, IRQF_SAMPLE_RANDOM 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 (x 86), call int can_request_irq(unsigned int irq, unsigned long flags); q Returns nonzero on success (for that moment)
Installing an Interrupt Handler n 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 %in", short_irq); short_irq = -1; } else { /* enable it -- assume this *is* a parallel port */ outb(0 x 10, short_base+2); } }
The /proc Interface n /proc/interrupts shows interrupts with Device names installed handlers 0: 2: 8: 10: 11: 12: NMI: LOC: ERR: MIS: CPU 0 4848108 0 3 4335 8903 49 0 4848187 0 0 CPU 1 34 IO-APIC-edge 0 XT-PIC 1 IO-APIC-edge 1 IO-APIC-level 0 IO-APIC-level 1 IO-APIC-edge 0 4848186 Linux handles interrupts on the first CPU to maximize cache locality timer cascade rtc aic 7 xxx uhci_hcd i 8042 Programmable interrupt controllers
The /proc Interface n /proc/stat shows number of interrupts received since system boot q q 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
Autodetecting the IRQ Number n A bad practice to require the user to specify the interrupt number q q n 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 n The short example if (short_irq < 0) /* not switch(short_base) { case 0 x 378: short_irq case 0 x 278: short_irq case 0 x 3 bc: short_irq }. . . n yet specified: force the default on */ = 7; break; = 2; break; = 5; break; The user can also override the default at load time insmod. /short. ko irq=x
Autodetecting the IRQ Number n The PCI standard requires devices to declare what interrupt line(s) they are going to use q q Autodetection involves just probing the device The driver tells the device to generate interrupts
Kernel-assisted Probing n n 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 n The short example int count = 0; do { unsigned long mask; mask = probe_irq_on(); outb_p(0 x 10, short_base+2); /* enable reporting */ outb_p(0 x 00, short_base); /* clear the bit */ outb_p(0 x. FF, short_base); /* set the bit: interrupt! */ outb_p(0 x 00, 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 proben"); short_irq = -1; } } while (short_irq < 0 && count++ < 5);
Kernel-assisted Probing if (short_irq < 0) { printk("short: probe failed %i times, giving upn", count); } n Probing can be a lengthy task q n n Frame grabber requires a delay of at least 20 ms Probe one interrupt one at a time Probing is not necessary for certain platforms (Power. PC, MIPS, and SPARC)
Do-it-yourself Probing n The short example performs do-it-yourself probing with probe=2 n Probe only commonly used IRQs void short_selfprobe(void) { int trials[] = {3, 5, 7, 9, 0}; int tried[] = {0, 0, 0}; int i, count = 0; 0 is the termination marker 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); }
Do-it-yourself Probing do { short_irq = 0; /* none got, yet */ outb_p(0 x 10, short_base+2); /* enable */ outb_p(0 x 00, short_base); outb_p(0 x. FF, short_base); /* toggle the bit */ outb_p(0 x 00, 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 proben"); } /* 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 upn", 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; }
Do-it-yourself Probing n Without knowing the commonly used IRQs q q Needs to probe IRQ 0 to IRQ NR_IRQS – 1 NR_IRQS defined in <asm/irq. h>
Fast and Slow Handlers n Fast interrupts are requested with the SA_INTERRUPT flag (e. g. , timer interrupt) q q Disables all other interrupts on the current CPU Other CPUs can still handle interrupts n n No two CPUs handle the same IRQ at the same time Slow interrupts have other interrupts enabled
The Internals of Interrupt Handling on x 86 n arch/i 386/kernel/entry. S contains ENTRY(interrupt) q q q Jumps to do_IRQ in arch/i 386/kernel/irq. c Prevents other CPUs from handling this IRQ Calls the particular handler n q If there is no handler, return If a device is interrupting n Call handle_IRQ_event in arch/i 386/kernel/irq/handle. c
Implementing a Handler n n Cannot transfer data to and from user space Cannot sleep q q n Cannot call schedule, wait_event, down Can only use GFP_ATOMIC to allocate memory Might need to clear a bit on the interface board q Allows subsequent interrupts to be received
Implementing a Handler n Wakes up processes waiting for the interrupt q The frame grabber example n n n 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 q Uses tasklet or workqueue to schedule computation
Implementing a Handler n The short example irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct timeval tv; int written; This argument do_gettimeofday(&tv); is removed in 2. 6. 21 written = sprintf((char *)short_head, "%08 u. %06 un", (int)(tv. tv_sec % 10000), (int)(tv. tv_usec)); short_incr_bp(&short_head, written); 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; } n 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 n 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 count 0; 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 /* count 0 is the number of readable data bytes */ count 0 = short_head - short_tail; if (count 0 < 0) {/* wrapped */ count 0 = short_buffer + PAGE_SIZE - short_tail; } if (count 0 < count) { count = count 0; } 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 n To raise interrupts q q Connect pins 9 and 10 of the parallel connector Write to /dev/shortint n n Which alternately writes 0 x 00 and 0 xff 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 n 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) iowrite 8(0 xff*((++written + odd) & 1), address); } else { while (written < count) outb(0 xff*((++written + odd) & 1), port); } *f_pos += count; return written; }
Implementing a Handler n Without connecting pins 9 and 10 q Use /dev/shortprint to drive a printer
Handler Arguments and Return Value n 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 */ /*. . */ } q q 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 q q n pt_regs: holds the snapshot of the processor’s context before running the interrupt code (linux/include/asm-i 386/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->i_rdev); request_irq(dev->irq, sample_interrupt, 0 /* flags */, "sample", dev /* dev_id */); /*. . */ return 0; }
Enabling and Disabling Interrupts n n Often, interrupts must be blocked while holding a spinlock to avoid deadlocks Also, there are ways of disabling interrupts that do not involve spinlocks q Should not be used within a driver
Disabling a Single Interrupt n Three functions q n 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); n Calls can be nested q If disable_irq is called twice, two enable_irq calls are required to reenable the IRQ
Disabling a Single Interrupt n n The calling thread of the disable_irq should not hold resource needed by the current interrupt to complete disable_irq_nosync returns immediately q n Need to handle potential race conditions Why disabling interrupts? q Sometimes to reduce the performance overhead
Disabling All Interrupts n To disable all interrupts on the current CPU, call either one #include <asm/system. 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); n Avoid doing this when possible q Almost never use local_irq_disable
Disabling All Interrupts n To enable all interrupts on the current CPU, call the corresponding function #include <asm/system. h> void local_irq_restore(unsigned long flags); /* does not keep track multiple calls */ void local_irq_enable(void);
Top and Bottom Halves n Interrupt handling sometimes needs to perform lengthy tasks q This problem is resolved by splitting the interrupt handler into two halves n Top half responds to the interrupt q q n 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 q q With all interrupts enabled Wakes up processes, starts I/O operations, etc.
Top and Bottom Halves n Two mechanisms may be used to implement bottom halves q Tasklets n q No sleep Workqueues n Can sleep
Tasklets n n n 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 n 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, 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; }
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 %6 in", savecount); short_incr_bp(&short_head, written); do { /* write the time values */ written = sprintf((char *)short_head, "%08 u. %06 un", (int)(tv_tail->tv_sec % 10000), (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 n n Can sleep Cannot copy data to and from user space
Workqueues n 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() */ 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; }
Interrupt Sharing n Installing a shared handler q q Set SA_SHIRQ flag when requesting the interrupt The dev_id must be unique n q n Cannot be NULL Returns IRQ_NONE if the handler is not the target handler request_irq suceeds if q q The interrupt line is free All handlers registered agree to share
Interrupt Sharing n When an interrupt arrives, the kernel invokes every handler registered for that interrupt q n The handler must be able to recognize its own interrupts No probing function is available for shared handlers q Most hardware designed for interrupt sharing can tell the CPU which interrupt it is using n No need for explicit probing
Interrupt Sharing n n free_irq needs the correct dev_id Watch out for enable_irq and disable_irq q n Not a good idea to disable other devices’ interrupts Does not work well with edge-triggered interrupts q Interrupts from other devices may be lost while one device is holding the line active
Running a Handler n 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 & 0 x 80)) significant bit return IRQ_NONE; /* clear the interrupting bit */ outb(value & 0 x 7 F, short_base);
Running a Handler /* the rest is unchanged */ do_gettimeofday(&tv); written = sprintf((char *)short_head, "%08 u. %06 un", (int)(tv. tv_sec % 10000), (int)(tv. tv_usec)); short_incr_bp(&short_head, written); wake_up_interruptible(&short_queue); return IRQ_HANDLED; } n n 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 n Check /proc/interrupts 0: 1: 2: 5: 8: 9: 10: 11: 12: 14: 15: NMI: CPU 0 892335412 453971 0 0 11365067 4391962 224 2787721 203048 41234 XT-PIC XT-PIC XT-PIC timer i 8042 cascade libata, ehci_hcd rtc acpi ide 2, uhci_hcd, Sys. Konnect uhci_hcd, uhci_hcd i 8042 ide 0 ide 1
Interrupt-Driven I/O n Buffering improves performance q Also leads to interrupt-driven I/O n Input buffer is filled at interrupt time q n Output buffer is filled by write processes q n Emptied by the read processes Emptied at interrupt time Hardware generates interrupts when q q 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 n The write function calls shortp_write() q Calls shortp_start_output() n Schedules a timer that calls shortp_timeout() q n Schedules shortp_do_work() q n Calls either shortp_timeout() or shortp_interrupt() Calls shortp_do_write() to write individual characters The printer calls shortp_interrupt() n 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 n The shortprint example maintains a onepage circular output buffer q q 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); }
195b49af83445bdabd594b9e3e2c8ec1.ppt