Interrupt Handling

69
Interrupt Handling Ted Baker Andy Wang CIS 4930 / COP 5641

description

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 - PowerPoint PPT Presentation

Transcript of Interrupt Handling

Page 1: Interrupt Handling

Interrupt Handling

Ted Baker Andy WangCIS 4930 / COP 5641

Page 2: Interrupt Handling

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

Page 3: Interrupt Handling

Topics

Interrupt handling Registration of handlers Interaction with hardware Limitations of handlers Deregistration Probing for interrupts Tasklets and bottom halves Interrupt sharing

Page 4: Interrupt Handling

Interrupt/Masking/Disabling/Blocking Independent mechanisms exist 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

Page 5: Interrupt Handling

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 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

Page 6: Interrupt Handling

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)

Page 7: Interrupt Handling

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 a binary data will generate several

interrupts

Page 8: Interrupt Handling

Installing an Interrupt Handler Without a interrupt handler installed for an

interrupt, Linux simply acknowledges and ignores it

Since interrupt lines are few, sharing is expected

Page 9: Interrupt Handling

Installing an Interrupt Handler A good idea to initialize interrupt handlers

when the device is first opened (vs. when a driver is initialized) 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 After the hardware is told not to create interrupts

Page 10: Interrupt Handling

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)

Page 11: Interrupt Handling

Installing an Interrupt Handler

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

int can_request_irq(unsigned int irq, unsigned long flags);

Returns nonzero on success (for that moment)

In 2.6.25, they are mapped to IRQF_DISABLED, IRQF_SHARED,

IRQF_SAMPLE_RANDOM

Page 12: Interrupt Handling

Installing an Interrupt Handler The short exampleif (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); }}

Page 13: Interrupt Handling

The /proc Interface

/proc/interrupts shows interrupts with installed handlers

CPU0 CPU10: 4848108 34 IO-APIC-edge timer2: 0 0 XT-PIC cascade8: 3 1 IO-APIC-edge rtc10: 4335 1 IO-APIC-level aic7xxx11: 8903 0 IO-APIC-level uhci_hcd12: 49 1 IO-APIC-edge i8042NMI: 0 0LOC: 4848187 4848186ERR: 0MIS: 0

Linux handles interrupts on the first CPU to

maximize cache locality

Device names

Programmable interrupt

controllers

Page 14: Interrupt Handling

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 numberInterrupt number

4 used 4907 times

Page 15: Interrupt Handling

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

Page 16: Interrupt Handling

Autodetecting the IRQ Number The short exampleif (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

Page 17: Interrupt Handling

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

Page 18: Interrupt Handling

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);

Page 19: Interrupt Handling

Kernel-assisted Probing

The short exampleint 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);

Page 20: Interrupt Handling

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)

Page 21: Interrupt Handling

Do-it-yourself Probing

The short example performs do-it-yourself probing with probe=2

Probe only commonly used IRQsvoid short_selfprobe(void) { int trials[] = {3, 5, 7, 9, 0}; int tried[] = {0, 0, 0, 0, 0}; int i, 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

Page 22: Interrupt Handling

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);

Page 23: Interrupt Handling

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_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;}

Page 24: Interrupt Handling

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>

Page 25: Interrupt Handling

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

Page 26: Interrupt Handling

The Internals of Interrupt Handling on x86 arch/i386/kernel/entry.S contains ENTRY(interrupt) Jumps to do_IRQ in arch/i386/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 arch/i386/kernel/irq/handle.c

Page 27: Interrupt Handling

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

Page 28: Interrupt Handling

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

Page 29: Interrupt Handling

Implementing a Handler

The short exampleirqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs) { 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); wake_up_interruptible(&short_queue); return IRQ_HANDLED;}

This argument is removed in

2.6.21

Page 30: Interrupt Handling

Implementing a Handler

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;}

Variable can be accessed externally at

any time

Page 31: Interrupt Handling

Implementing a Handler

To read the buffer, use /dev/shortintssize_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 */ }

Page 32: Interrupt Handling

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;}

Page 33: Interrupt Handling

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

Page 34: Interrupt Handling

Implementing a Handler

To write to /dev/shortintssize_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;}

Page 35: Interrupt Handling

Implementing a Handler

Without connecting pins 9 and 10 Use /dev/shortprint to drive a printer

Page 36: Interrupt Handling

Handler Arguments and Return Value Typical use of the argument in an interrupt

handlerstatic 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 */ /* .... */}

irq: for printk dev_id: for finding out which instance of device

is in charge of the current interrupt event

Page 37: Interrupt Handling

Handler Arguments and Return Value

pt_regs: holds the snapshot of the processor’s context before running the interrupt code (linux/include/asm-i386/ptrace.h)

Returns IRQ_HANDLED if the device needs attention; otherwise, returns IRQ_NONE

Typical open codestatic 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;}

Page 38: Interrupt Handling

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

Page 39: Interrupt Handling

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

Page 40: Interrupt Handling

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

Page 41: Interrupt Handling

Disabling All Interrupts

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);

Avoid doing this when possible Almost never use local_irq_disable

Page 42: Interrupt Handling

Disabling All Interrupts

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);

Page 43: Interrupt Handling

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.

Page 44: Interrupt Handling

Top and Bottom Halves

Two mechanisms may be used to implement bottom halves Tasklets

No sleep Workqueues

Can sleep

Page 45: Interrupt Handling

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

Page 46: Interrupt Handling

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, 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;}

Page 47: Interrupt Handling

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);}

Page 48: Interrupt Handling

Workqueues

Can sleep Cannot copy data to and from user space

Page 49: Interrupt Handling

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() */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;}

Page 50: Interrupt Handling

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_irq suceeds if

The interrupt line is free All handlers registered agree to share

Page 51: Interrupt Handling

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

Page 52: Interrupt Handling

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 Does not work well with edge-triggered

interrupts Interrupts from other devices may be lost while

one device is holding the line active

Page 53: Interrupt Handling

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); if (!(value & 0x80)) return IRQ_NONE;

/* clear the interrupting bit */ outb(value & 0x7F, short_base);

Check the most significant bit

Page 54: Interrupt Handling

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

Page 55: Interrupt Handling

The /proc Interface and Shared Interrupts Check /proc/interrupts

CPU00: 892335412 XT-PIC timer1: 453971 XT-PIC i80422: 0 XT-PIC cascade5: 0 XT-PIC libata, ehci_hcd8: 0 XT-PIC rtc9: 0 XT-PIC acpi10: 11365067 XT-PIC ide2, uhci_hcd, uhci_hcd,

SysKonnect 11: 4391962 XT-PIC uhci_hcd, uhci_hcd12: 224 XT-PIC i804214: 2787721 XT-PIC ide015: 203048 XT-PIC ide1NMI: 41234

Page 56: Interrupt Handling

Interrupt-Driven I/O

Buffering improves performance Also leads to interrupt-driven I/O

Input buffer is filled at interrupt time Emptied by the read processes

Output buffer is filled by write 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

Page 57: Interrupt Handling

A Write-Buffering Example

The write function calls shortp_write() Calls shortp_start_output()

Schedules a timer that calls shortp_timeout() Calls either shortp_timeout() or

shortp_interrupt() Schedules shortp_do_work()

Calls shortp_do_write() to write individual characters

The printer calls shortp_interrupt() Schedules shortp_do_work()

Page 58: Interrupt Handling

A Write-Buffering Example

shortp_write()

shortp_start_ouput()

shortp_timeout() shortp_do_work()

shortp_interrupt() shortp_do_write()

printer write()

Page 59: Interrupt Handling

A Write-Buffering Example

The shortprint example maintains a one-page 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;

Page 60: Interrupt Handling

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; } ...

Page 61: Interrupt Handling

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; ...

Page 62: Interrupt Handling

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;}

Page 63: Interrupt Handling

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);}

Page 64: Interrupt Handling

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();

Page 65: Interrupt Handling

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);}

Page 66: Interrupt Handling

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);}

Page 67: Interrupt Handling

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;}

Page 68: Interrupt Handling

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);

Page 69: Interrupt Handling

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);}