Interrupt Handling Ted Baker  Andy Wang CIS 4930 / COP 5641.

Slides:



Advertisements
Similar presentations
Device Drivers. Linux Device Drivers Linux supports three types of hardware device: character, block and network –character devices: R/W without buffering.
Advertisements

Accessing I/O Devices Processor Memory BUS I/O Device 1 I/O Device 2.
Tutorial 3 - Linux Interrupt Handling -
Input and Output CS 215 Lecture #20.
1/1/ / faculty of Electrical Engineering eindhoven university of technology Introduction Part 3: Input/output and co-processors dr.ir. A.C. Verschueren.
Chapter 6 Limited Direct Execution
CMPT 300: Operating Systems I Dr. Mohamed Hefeeda
Interrupts What is an interrupt? What does an interrupt do to the “flow of control” Interrupts used to overlap computation & I/O – Examples would be console.
COMP3221: Microprocessors and Embedded Systems Lecture 15: Interrupts I Lecturer: Hui Wu Session 1, 2005.
1 School of Computing Science Simon Fraser University CMPT 300: Operating Systems I Dr. Mohamed Hefeeda.
Introduction to Operating Systems – Windows process and thread management In this lecture we will cover Threads and processes in Windows Thread priority.
Home: Phones OFF Please Unix Kernel Parminder Singh Kang Home:
Informationsteknologi Friday, November 16, 2007Computer Architecture I - Class 121 Today’s class Operating System Machine Level.
Basic Input/Output Operations
1 Device Management The von Neumann Architecture System Architecture Device Management Polling Interrupts DMA operating systems.
Chapter 11 Operating Systems
CPS110: Implementing threads/locks on a uni-processor Landon Cox.
INPUT/OUTPUT ORGANIZATION INTERRUPTS CS147 Summer 2001 Professor: Sin-Min Lee Presented by: Jing Chen.
INTERRUPTS PROGRAMMING
I/O Tanenbaum, ch. 5 p. 329 – 427 Silberschatz, ch. 13 p
Interrupts. 2 Definition: An electrical signal sent to the CPU (at any time) to alert it to the occurrence of some event that needs its attention Purpose:
hardware and operating systems basics.
COMP201 Computer Systems Exceptions and Interrupts.
Interrupt Handling Sarah Diesburg COP Interrupt Handling One big responsibility of an operating system is to handle hardware connected to the machine.
Input and Output Computer Organization and Assembly Language: Module 9.
MICROPROCESSOR INPUT/OUTPUT
1-1 Embedded Network Interface (ENI) API Concepts Shared RAM vs. FIFO modes ENI API’s.
Lecture 3 Process Concepts. What is a Process? A process is the dynamic execution context of an executing program. Several processes may run concurrently,
I/O Interfacing A lot of handshaking is required between the CPU and most I/O devices. All I/O devices operate asynchronously with respect to the CPU.
Accessing I/O Devices Processor Memory BUS I/O Device 1 I/O Device 2.
13-Nov-15 (1) CSC Computer Organization Lecture 7: Input/Output Organization.
Sogang University Advanced Operating Systems (Enhanced Device Driver Operations) Advanced Operating Systems (Enhanced Device Driver Operations) Sang Gue.
Interrupt driven I/O. MIPS RISC Exception Mechanism The processor operates in The processor operates in user mode user mode kernel mode kernel mode Access.
CE Operating Systems Lecture 2 Low level hardware support for operating systems.
Operating Systems CSE 411 CPU Management Sept Lecture 10 Instructor: Bhuvan Urgaonkar.
Preemptive Context Switching
Ch 7. Interrupts and Interrupt Handlers. Overview (1) A primary responsibility of the kernel is managing the hardware connected to the machine  The kernel.
CE Operating Systems Lecture 2 Low level hardware support for operating systems.
CSNB334 Advanced Operating Systems 6. Device Management Lecturer: Asma Shakil.
1 Lecture 1: Computer System Structures We go over the aspects of computer architecture relevant to OS design  overview  input and output (I/O) organization.
Interrupt driven I/O Computer Organization and Assembly Language: Module 12.
Silberschatz, Galvin and Gagne ©2011 Operating System Concepts Essentials – 8 th Edition Chapter 2: The Linux System Part 2.
1 Device Controller I/O units typically consist of A mechanical component: the device itself An electronic component: the device controller or adapter.
Ch 6. Interrupts and Interrupt Handlers. Overview (1) A primary responsibility of the kernel is managing the hardware connected to the machine  The kernel.
Interrupts and Exception Handling. Execution We are quite aware of the Fetch, Execute process of the control unit of the CPU –Fetch and instruction as.
Interrupts and Interrupt Handling David Ferry, Chris Gill CSE 522S - Advanced Operating Systems Washington University in St. Louis St. Louis, MO
Ch6. Flow of Time Ch7. Getting Hold of Memory 홍원의.
Mutual Exclusion -- Addendum. Mutual Exclusion in Critical Sections.
Chapter 6 Limited Direct Execution Chien-Chung Shen CIS/UD
Lecture 7 Interrupt ,Trap and System Call
Transmitter Interrupts Review of Receiver Interrupts How to Handle Transmitter Interrupts? Critical Regions Text: Tanenbaum
Linux Kernel Development - Robert Love
Processes and threads.
Process Management Process Concept Why only the global variables?
Microprocessor Systems Design I
Protection of System Resources
Interrupts In 8085 and 8086.
Interrupts and Interrupt Handling
Ted Baker  Andy Wang CIS 4930 / COP 5641
Chapter 2: The Linux System Part 2
Operating Systems Chapter 5: Input/Output Management
Transmitter Interrupts
Threads Chapter 4.
Chapter 5: I/O Systems.
Top Half / Bottom Half Processing
Linux Kernel Programming CIS 4930/COP 5641
COMP3221: Microprocessors and Embedded Systems
Interrupts and Interrupt Handling
Chapter 13: I/O Systems.
Lecture 12 Input/Output (programmer view)
Presentation transcript:

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 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  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 , they are mapped to IRQF_DISABLED, IRQF_SHARED, IRQF_SAMPLE_RANDOM

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: IO-APIC-edge timer 2: 0 0 XT-PIC cascade 8: 3 1 IO-APIC-edge rtc 10: IO-APIC-level aic7xxx 11: IO-APIC-level uhci_hcd 12: 49 1 IO-APIC-edge i8042 NMI: 0 0 LOC: ERR: 0 MIS: 0 Linux handles interrupts on the first CPU to maximize cache locality Device names 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 Total number Interrupt number 4 used 4907 times

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

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_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 Without knowing the commonly used IRQs  Needs to probe IRQ 0 to IRQ NR_IRQS – 1  NR_IRQS defined in

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_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 % ), (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

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

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

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(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 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 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 /* 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 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_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 %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 % ), (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 struct work_struct short_wq; /* this line is in the short_init() */ INIT_WORK(&short_wq, (typeof(short_wq.func)) 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 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

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 Does not work well with edge-triggered interrupts  Interrupts from other devices may be lost while one device is holding the line active

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

Running a Handler /* the rest is unchanged */ do_gettimeofday(&tv); written = sprintf((char *)short_head,"%08u.%06u\n", (int)(tv.tv_sec % ), (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 CPU0 0: XT-PIC timer 1: XT-PIC i8042 2: 0 XT-PIC cascade 5: 0 XT-PIC libata, ehci_hcd 8: 0 XT-PIC rtc 9: 0 XT-PIC acpi 10: XT-PIC ide2, uhci_hcd, uhci_hcd, SysKonnect 11: XT-PIC uhci_hcd, uhci_hcd 12: 224 XT-PIC i : XT-PIC ide0 15: XT-PIC ide1 NMI: 41234

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

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

A Write-Buffering Example printer write()

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;

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