370 likes | 409 Views
Learn about handling interrupts in Linux kernel programming: registration, sharing, interaction with hardware, limitations, bottom halves, and more. Discover interrupt-driven I/O, hardware configurations, installing interrupt handlers, and implementing handlers efficiently. Explore parallel port interrupts, LED device interrupts, and concurrency issues. Understand interrupt sharing, flags, and the impact on hardware. This comprehensive guide covers essential concepts for effective interrupt management in Linux kernel programming.
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