/* * A version of the "short" driver which drives a parallel printer directly, * with a lot of simplifying assumptions. * * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet * Copyright (C) 2001 O'Reilly & Associates * * The source code in this file can be freely used, adapted, * and redistributed in source or binary form, so long as an * acknowledgment appears in derived source files. The citation * should list that the code comes from the book "Linux Device * Drivers" by Alessandro Rubini and Jonathan Corbet, published * by O'Reilly & Associates. No warranty is attached; * we cannot take responsibility for errors or fitness for use. * * $Id: shortprint.c,v 1.4 2004/09/26 08:01:04 gregkh Exp $ */ #include #include #include #include #include /* printk() */ #include /* everything... */ #include /* error codes */ #include /* udelay */ #include #include #include #include #include #include #include #include #include #include "shortprint.h" #define SHORTP_NR_PORTS 3 /* * all of the parameters have no "shortp_" prefix, to save typing when * specifying them at load time */ static int major = 0; /* dynamic by default */ module_param(major, int, 0); /* default is the first printer port on PC's. "shortp_base" is there too because it's what we want to use in the code */ static unsigned long base = 0x378; unsigned long shortp_base = 0; module_param(base, long, 0); /* The interrupt line is undefined by default. "shortp_irq" is as above */ static int irq = -1; static int shortp_irq = -1; module_param(irq, int, 0); /* Microsecond delay around strobe. */ static int delay = 0; static int shortp_delay; module_param(delay, int, 0); MODULE_AUTHOR ("Jonathan Corbet"); MODULE_LICENSE("Dual BSD/GPL"); /* * Forwards. */ static void shortp_cleanup(void); static void shortp_timeout(unsigned long unused); /* * Input is managed through a simple circular buffer which, among other things, * is allowed to overrun if the reader isn't fast enough. That makes life simple * on the "read" interrupt side, where we don't want to block. */ static unsigned long shortp_in_buffer = 0; static unsigned long volatile shortp_in_head; static volatile unsigned long shortp_in_tail; DECLARE_WAIT_QUEUE_HEAD(shortp_in_queue); static struct timeval shortp_tv; /* When the interrupt happened. */ /* * Atomicly increment an index into shortp_in_buffer */ static inline void shortp_incr_bp(volatile unsigned long *index, int delta) { unsigned long new = *index + delta; barrier (); /* Don't optimize these two together */ *index = (new >= (shortp_in_buffer + PAGE_SIZE)) ? shortp_in_buffer : new; } /* * On the write side we have to be more careful, since we don't want to drop * data. The semaphore is used to serialize write-side access to the buffer; * there is only one consumer, so read-side access is unregulated. The * wait queue will be awakened when space becomes available in the buffer. */ static unsigned char *shortp_out_buffer = NULL; static volatile unsigned char *shortp_out_head, *shortp_out_tail; static struct semaphore shortp_out_sem; static DECLARE_WAIT_QUEUE_HEAD(shortp_out_queue); /* * Feeding the output queue to the device is handled by way of a * workqueue. */ static void shortp_do_work(void *); static DECLARE_WORK(shortp_work, shortp_do_work, NULL); static struct workqueue_struct *shortp_workqueue; /* * Available space in the output buffer; should be called with the semaphore * held. Returns contiguous space, so caller need not worry about wraps. */ static inline int shortp_out_space(void) { if (shortp_out_head >= shortp_out_tail) { int space = PAGE_SIZE - (shortp_out_head - shortp_out_buffer); return (shortp_out_tail == shortp_out_buffer) ? space - 1 : space; } else return (shortp_out_tail - shortp_out_head) - 1; } static inline void shortp_incr_out_bp(volatile unsigned char **bp, int incr) { unsigned char *new = (unsigned char *) *bp + incr; if (new >= (shortp_out_buffer + PAGE_SIZE)) new -= PAGE_SIZE; *bp = new; } /* * The output "process" is controlled by a spin lock; decisions on * shortp_output_active or manipulation of shortp_out_tail require * that this lock be held. */ static spinlock_t shortp_out_lock; volatile static int shortp_output_active; DECLARE_WAIT_QUEUE_HEAD(shortp_empty_queue); /* waked when queue empties */ /* * When output is active, the timer is too, in case we miss interrupts. Hold * shortp_out_lock if you mess with the timer. */ static struct timer_list shortp_timer; #define TIMEOUT 5*HZ /* Wait a long time */ /* * Open the device. */ static int shortp_open(struct inode *inode, struct file *filp) { return 0; } static int shortp_release(struct inode *inode, struct file *filp) { /* Wait for any pending output to complete */ wait_event_interruptible(shortp_empty_queue, shortp_output_active==0); return 0; } static unsigned int shortp_poll(struct file *filp, poll_table *wait) { return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM; } /* * The read routine, which doesn't return data from the device; instead, it * returns timing information just like the "short" device. */ static ssize_t shortp_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { int count0; DEFINE_WAIT(wait); while (shortp_in_head == shortp_in_tail) { prepare_to_wait(&shortp_in_queue, &wait, TASK_INTERRUPTIBLE); if (shortp_in_head == shortp_in_tail) schedule(); finish_wait(&shortp_in_queue, &wait); if (signal_pending (current)) /* a signal arrived */ return -ERESTARTSYS; /* tell the fs layer to handle it */ } /* count0 is the number of readable data bytes */ count0 = shortp_in_head - shortp_in_tail; if (count0 < 0) /* wrapped */ count0 = shortp_in_buffer + PAGE_SIZE - shortp_in_tail; if (count0 < count) count = count0; if (copy_to_user(buf, (char *)shortp_in_tail, count)) return -EFAULT; shortp_incr_bp(&shortp_in_tail, count); return count; } /* * Wait for the printer to be ready; this can sleep. */ static void shortp_wait(void) { if ((inb(shortp_base + SP_STATUS) & SP_SR_BUSY) == 0) { printk(KERN_INFO "shortprint: waiting for printer busy\n"); printk(KERN_INFO "Status is 0x%x\n", inb(shortp_base + SP_STATUS)); while ((inb(shortp_base + SP_STATUS) & SP_SR_BUSY) == 0) { set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(10*HZ); } } } /* * Write the next character from the buffer. There should *be* a next * character... The spinlock should be held when this routine is called. */ static void shortp_do_write(void) { unsigned char cr = inb(shortp_base + SP_CONTROL); /* Something happened; 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); } /* * Start output; call under lock. */ static void shortp_start_output(void) { if (shortp_output_active) /* Should never happen */ return; /* Set up our 'missed interrupt' timer */ shortp_output_active = 1; shortp_timer.expires = jiffies + TIMEOUT; add_timer(&shortp_timer); /* And get the process going. */ queue_work(shortp_workqueue, &shortp_work); } /* * Write to the device. */ static ssize_t shortp_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { int space, written = 0; unsigned long flags; /* * Take and hold the semaphore for the entire duration of the operation. The * consumer side ignores it, and it will keep other data from interleaving * with ours. */ if (down_interruptible(&shortp_out_sem)) return -ERESTARTSYS; /* * Out with the data. */ 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; } /* 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; /* 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; } /* * The bottom-half handler. */ static void shortp_do_work(void *unused) { int written; unsigned long flags; /* Wait until the device is ready */ shortp_wait(); 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); } /* Nope, write another byte */ else shortp_do_write(); /* If somebody's waiting, maybe wake them up. */ 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); /* Handle the "read" side operation */ written = sprintf((char *)shortp_in_head, "%08u.%06u\n", (int)(shortp_tv.tv_sec % 100000000), (int)(shortp_tv.tv_usec)); shortp_incr_bp(&shortp_in_head, written); wake_up_interruptible(&shortp_in_queue); /* awake any reading process */ } /* * The top-half interrupt handler. */ 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; } /* * Interrupt timeouts. Just because we got a timeout doesn't mean that * things have gone wrong, however; printers can spend an awful long time * just thinking about things. */ 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); /* 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); } static struct file_operations shortp_fops = { .read = shortp_read, .write = shortp_write, .open = shortp_open, .release = shortp_release, .poll = shortp_poll, .owner = THIS_MODULE }; /* * Module initialization */ static int shortp_init(void) { int result; /* * first, sort out the base/shortp_base ambiguity: we'd better * use shortp_base in the code, for clarity, but allow setting * just "base" at load time. Same for "irq". */ shortp_base = base; shortp_irq = irq; shortp_delay = delay; /* Get our needed resources. */ if (! request_region(shortp_base, SHORTP_NR_PORTS, "shortprint")) { printk(KERN_INFO "shortprint: can't get I/O port address 0x%lx\n", shortp_base); return -ENODEV; } /* Register the device */ result = register_chrdev(major, "shortprint", &shortp_fops); if (result < 0) { printk(KERN_INFO "shortp: can't get major number\n"); release_region(shortp_base, SHORTP_NR_PORTS); return result; } if (major == 0) major = result; /* dynamic */ /* Initialize the input buffer. */ shortp_in_buffer = __get_free_pages(GFP_KERNEL, 0); /* never fails */ shortp_in_head = shortp_in_tail = shortp_in_buffer; /* And the output buffer. */ shortp_out_buffer = (unsigned char *) __get_free_pages(GFP_KERNEL, 0); shortp_out_head = shortp_out_tail = shortp_out_buffer; sema_init(&shortp_out_sem, 1); /* And the output info */ shortp_output_active = 0; spin_lock_init(&shortp_out_lock); init_timer(&shortp_timer); shortp_timer.function = shortp_timeout; shortp_timer.data = 0; /* Set up our workqueue. */ shortp_workqueue = create_singlethread_workqueue("shortprint"); /* If no IRQ was explicitly requested, pick a default */ if (shortp_irq < 0) switch(shortp_base) { case 0x378: shortp_irq = 7; break; case 0x278: shortp_irq = 2; break; case 0x3bc: shortp_irq = 5; break; } /* Request the IRQ */ result = request_irq(shortp_irq, shortp_interrupt, 0, "shortprint", NULL); if (result) { printk(KERN_INFO "shortprint: can't get assigned irq %i\n", shortp_irq); shortp_irq = -1; shortp_cleanup (); return result; } /* Initialize the control register, turning on interrupts. */ outb(SP_CR_IRQ | SP_CR_SELECT | SP_CR_INIT, shortp_base + SP_CONTROL); return 0; } static void shortp_cleanup(void) { /* Return the IRQ if we have one */ if (shortp_irq >= 0) { outb(0x0, shortp_base + SP_CONTROL); /* disable the interrupt */ free_irq(shortp_irq, NULL); } /* All done with the device */ unregister_chrdev(major, "shortprint"); release_region(shortp_base,SHORTP_NR_PORTS); /* Don't leave any timers floating around. Note that any active output is effectively stopped by turning off the interrupt */ if (shortp_output_active) del_timer_sync (&shortp_timer); flush_workqueue(shortp_workqueue); destroy_workqueue(shortp_workqueue); if (shortp_in_buffer) free_page(shortp_in_buffer); if (shortp_out_buffer) free_page((unsigned long) shortp_out_buffer); } module_init(shortp_init); module_exit(shortp_cleanup);