diff options
Diffstat (limited to 'shortprint')
-rw-r--r-- | shortprint/Makefile | 31 | ||||
-rw-r--r-- | shortprint/shortprint.c | 521 | ||||
-rw-r--r-- | shortprint/shortprint.h | 46 | ||||
-rw-r--r-- | shortprint/shortprint_load | 31 | ||||
-rw-r--r-- | shortprint/shortprint_unload | 15 |
5 files changed, 644 insertions, 0 deletions
diff --git a/shortprint/Makefile b/shortprint/Makefile new file mode 100644 index 0000000..42a9b53 --- /dev/null +++ b/shortprint/Makefile @@ -0,0 +1,31 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + +CFLAGS += -O2 -I.. + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := shortprint.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +endif + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/shortprint/shortprint.c b/shortprint/shortprint.c new file mode 100644 index 0000000..d7c8520 --- /dev/null +++ b/shortprint/shortprint.c @@ -0,0 +1,521 @@ +/* + * 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 <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/delay.h> /* udelay */ +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/timer.h> +#include <linux/poll.h> + +#include <asm/io.h> +#include <asm/semaphore.h> +#include <asm/atomic.h> + +#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); diff --git a/shortprint/shortprint.h b/shortprint/shortprint.h new file mode 100644 index 0000000..1531025 --- /dev/null +++ b/shortprint/shortprint.h @@ -0,0 +1,46 @@ +/* + * Useful info describing the parallel port device. + * + * 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. + * + */ + +/* + * Register offsets + */ +#define SP_DATA 0x00 +#define SP_STATUS 0x01 +#define SP_CONTROL 0x02 +#define SP_NPORTS 3 + +/* + * Status register bits. + */ +#define SP_SR_BUSY 0x80 +#define SP_SR_ACK 0x40 +#define SP_SR_PAPER 0x20 +#define SP_SR_ONLINE 0x10 +#define SP_SR_ERR 0x08 + +/* + * Control register. + */ +#define SP_CR_IRQ 0x10 +#define SP_CR_SELECT 0x08 +#define SP_CR_INIT 0x04 +#define SP_CR_AUTOLF 0x02 +#define SP_CR_STROBE 0x01 + +/* + * Minimum space before waking up a writer. + */ +#define SP_MIN_SPACE PAGE_SIZE/2 diff --git a/shortprint/shortprint_load b/shortprint/shortprint_load new file mode 100644 index 0000000..4b08609 --- /dev/null +++ b/shortprint/shortprint_load @@ -0,0 +1,31 @@ +#!/bin/sh +module="shortprint" +device="shortprint" +mode="666" + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +# Create 8 entry points, as SHORT_NR_PORTS is 8 by default +rm -f /dev/${device} +mknod /dev/${device} c $major 0 + +chgrp $group /dev/${device} +chmod $mode /dev/${device} + + + + + + diff --git a/shortprint/shortprint_unload b/shortprint/shortprint_unload new file mode 100644 index 0000000..88e62a7 --- /dev/null +++ b/shortprint/shortprint_unload @@ -0,0 +1,15 @@ +#!/bin/sh +module="shortprint" +device="shortprint" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# Remove stale nodes +rm -f /dev/${device} + + + + + + |