patch-2.1.67 linux/drivers/char/joystick.c

Next file: linux/drivers/char/misc.c
Previous file: linux/drivers/char/bttv.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.66/linux/drivers/char/joystick.c linux/drivers/char/joystick.c
@@ -1,380 +1,837 @@
 /*
+ *  $Id: joystick.c,v 1.2 1997/10/31 19:11:48 mj Exp $
+ *
+ *  Copyright (C) 1997 Vojtech Pavlik
+ */
 
-   linux/drivers/char/joystick.c
-   Copyright (C) 1992, 1993 Arthur C. Smith
-   Joystick driver for Linux running on an IBM compatible computer.
-
-VERSION INFO:
-01/08/93	ACS	0.1: Works but needs multi-joystick support
-01/13/93	ACS	0.2: Added multi-joystick support (minor 0 and 1)
-		  	     Added delay between measuring joystick axis
-		   	     Added scaling ioctl
-02/16/93	ACS	0.3: Modified scaling to use ints to prevent kernel
-			     panics 8-)
-02/28/93	ACS	0.4: Linux99.6 and fixed race condition in js_read.
-			     After looking at a schematic of a joystick card
-                             it became apparent that any write to the joystick
-			     port started ALL the joystick one shots. If the
-			     one that we are reading is short enough and the
-			     first one to be read, the second one will return
-			     bad data if it's one shot has not expired when
-			     the joystick port is written for the second time.
-			     Thus solves the mystery delay problem in 0.2!
-05/05/93       ACS/Eyal 0.5: Upgraded the driver to the 99.9 kernel, added
-			     joystick support to the make config options,
-			     updated the driver to return the buttons as
-			     positive logic, and read both axis at once
-			     (thanks Eyal!), and added some new ioctls.
-02/12/94   Jeff Tranter 0.6: Made necessary changes to work with 0.99pl15
-                             kernel (and hopefully 1.0). Also did some
-			     cleanup: indented code, fixed some typos, wrote
-			     man page, etc...
-05/17/95 Dan Fandrich 0.7.3: Added I/O port registration, cleaned up code
-04/03/96    Matt Rhoten 0.8: many minor changes:
-			     new read loop from Hal Maney <maney@norden.com>
-                             cleaned up #includes to allow #include of 
-                             joystick.h with gcc -Wall and from g++
-			     made js_init fail if it finds zero joysticks
-			     general source/comment cleanup
-			     use of MOD_(INC|DEC)_USE_COUNT
-			     changes from Bernd Schmidt <crux@Pool.Informatik.RWTH-Aachen.DE>
-			     to compile correctly under 1.3 in kernel or as module
-06/30/97       Alan Cox 0.9: Ported to 2.1.x
-			     Reformatted to resemble Linux coding standard
-			     Removed semaphore bug (we can dump the lot I think)
-			     Fixed xntp timer adjust during joystick timer0 bug
-			     Changed variable names to lower case. Kept binary
-			     	compatibility.
-			     Better ioctl names. Kept binary compatibility.
-			     Removed 'save_busy'. Just set busy to 1.
-11/03/97  Brian Gerst 0.9.1: Fixed bug which caused driver to always time out 
-                             but never report a timeout (broken while loop).
-                             Fixed js_read for new VFS code.
-*/
+/*
+ *  This is joystick driver for Linux. It supports up to two analog joysticks
+ *  on a PC compatible machine. See Documentation/joystick.txt for changelog
+ *  and credits.
+ */
 
+#include <linux/config.h>
+#include <linux/init.h>
 #include <linux/module.h>
-#include <linux/joystick.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
 #include <linux/mm.h>
+#include <linux/ptrace.h>
+#include <linux/interrupt.h>
+#include <linux/malloc.h>
+#include <linux/poll.h>
 #include <linux/major.h>
-#include <linux/ioport.h>
+#include <linux/joystick.h>
+
 #include <asm/io.h>
+#include <asm/ptrace.h>
 #include <asm/uaccess.h>
+#include <asm/param.h>
 
-static struct js_config js_data[JS_MAX];	/* misc data */
-static int js_exist;			/* which joysticks' axis exist? */
-static int js_read_semaphore;	/* to prevent two processes from trying
-				   to read different joysticks at the
-				   same time */
+#define PIT_HZ			1193180L	/* PIT clock is 1.19318 MHz */
 
-/* 
- *	get_timer0():
- *	returns the current value of timer 0. This is a 16 bit counter that starts
- *	at LATCH and counts down to 0 
+#define JS_MAXTIME		PIT_HZ/250	/* timeout for read (4 ms) */
+
+#define JS_BUTTON_PERIOD	HZ/50		/* button valid time (20 ms) */
+#define JS_AXIS_MIN_PERIOD	HZ/25		/* axis min valid time (40 ms) */
+#define JS_AXIS_MAX_PERIOD	HZ/25*2		/* axis max valid time (80 ms) */
+
+#define JS_FIFO_SIZE    	16		/* number of FIFO entries */
+#define JS_BUFF_SIZE		32 		/* output buffer size */
+#define JS_RETRIES		4		/* number of retries */ 
+#define JS_DEF_PREC		8		/* initial precision for all axes */
+
+#define JS_NUM			2		/* number of joysticks */
+
+#define JS_AXES 		0x0f		/* bit mask for all axes */
+#define JS_BUTTONS		0xf0		/* bit mask for all buttons */
+
+#define PIT_MODE 		0x43		/* timer mode port */
+#define PIT_DATA 		0x40		/* timer 0 data port */
+#define JS_PORT 		0x201		/* joystick port */
+
+#define JS_TRIGGER      	0xff		/* triggers one-shots */
+#define PIT_READ_TIMER		0x00		/* to read timer 0 */
+
+#define DELTA(X,Y,Z)	((X)-(Y)+(((X)>=(Y))?0:Z))				/* cyclic delta */
+#define DELTA_T(X,Y)	DELTA((X),(Y),(PIT_HZ/HZ))				/* for time measurement */
+#define DELTA_TX(X,Y,Z)	DELTA_T((X),((Y)&0xFF)|(((Z)&0xFF)<<8))
+#define ROT(A,B,C)	((((A)<(C))&&(((B)>(A))&&((B)<(C))))||(((A)>(C))&&(((B)>(A))||((B)<(C)))))
+#define GOF(X)		(((X)==JS_BUFF_SIZE-1)?0:(X)+1)
+#define GOFF(X)		(((X)==JS_FIFO_SIZE-1)?0:(X)+1)
+#define GOB(X)		((X)?(X)-1:JS_BUFF_SIZE-1)
+
+struct js_data {
+	int ahead;
+	int bhead;
+	int tail;
+	struct js_event buff[JS_BUFF_SIZE];
+	struct js_list *list;
+	struct wait_queue *wait;
+	unsigned int exist;
+};
+
+struct js_axis {
+	int value;
+	struct js_corr corr;
+};
+
+struct js_list {
+	struct js_list *next;			/* next-in-list pointer */
+	unsigned long time;			/* when the device was open */
+	int tail;				/* a tail for js_buff */
+	char startup;
+};
+
+struct js_fifo {
+	unsigned long time;
+	unsigned long event;
+};
+
+static struct js_data jsd[JS_NUM];			/* joystick data */
+static struct timer_list js_timer;			/* joystick timer */
+
+static unsigned char js_fifo_head = 0;			/* head of the fifo */
+static unsigned char js_fifo_tail = JS_FIFO_SIZE - 1;	/* tail of the fifo */
+static struct js_fifo js_fifo[JS_FIFO_SIZE];		/* the fifo */
+
+static unsigned char js_last_buttons = 0;		/* last read button state */
+static unsigned long js_axis_time = 0;			/* last read axis time */
+static unsigned long js_mark_time = 0;
+
+static unsigned char js_axes_exist;			/* all axes that exist */
+static unsigned char js_buttons_exist;			/* all buttons that exist */
+
+static struct js_axis js_axis[4];
+static unsigned int js_buttons = 0;
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@atrey.karlin.mff.cuni.cz>");
+MODULE_SUPPORTED_DEVICE("js");
+MODULE_PARM(js, "0-1b");
+
+static char js[] = {0, 0};
+
+/*
+ * get_pit() returns the immediate state of PIT0. Must be run
+ * with interrupts disabled.
  */
-   
-extern inline int get_timer0(void)
+
+static inline int get_pit(void)
 {
-	unsigned long flags;
-	int t0, t1;
+	int t, flags;
+	
 	save_flags(flags);
 	cli();
-	outb (0, PIT_MODE);
-	t0 = (int) inb (PIT_COUNTER_0);
-	t1 = ((int) inb (PIT_COUNTER_0) << 8) + t0;
+	outb(PIT_READ_TIMER, PIT_MODE);
+	t = inb(PIT_DATA);
+	t |= (int) inb(PIT_DATA) << 8;
 	restore_flags(flags);
-	return (t1);
+	return t;
 }
 
 /*
- *	find_axes():
- *
- *	returns which axes are hooked up, in a bitfield. 2^n is set if
- *	axis n is hooked up, for 0 <= n < 4.
- *
- *	REVIEW: should update this to handle eight-axis (four-stick) game port
- *	cards. anyone have one of these to test on? mattrh 3/23/96 
+ * count_bits() counts set bits in a byte.
  */
- 
-extern inline int find_axes(void)
+
+static int count_bits(unsigned char c)
 {
-	int j;
-	outb (0xff, JS_PORT);		/* trigger oneshots */
-					/* and see what happens */
-	for (j = JS_DEF_TIMEOUT; (0x0f & inb (JS_PORT)) && j; j--);
-				/* do nothing; wait for the timeout */
-	js_exist = inb (JS_PORT) & 0x0f; /* get joystick status byte */
-	js_exist = (~js_exist) & 0x0f;
-/*	printk("find_axes: js_exist is %d (0x%04X)\n", js_exist, js_exist);*/
-	return js_exist;
+	int i, t = 0;
+	for (i = 0; i < 8; i++)
+		if (c & (1 << i)) t++;
+	return t;
 }
 
-static int js_ioctl (struct inode *inode,
-		     struct file *file,
-		     unsigned int cmd,
-		     unsigned long arg)
+/*
+ * js_correct() performs correction of raw joystick data.
+ */
+
+static int js_correct(int value, struct js_corr *corr)
 {
-	unsigned int minor = MINOR (inode->i_rdev);
-	if (minor >= JS_MAX)
-		return -ENODEV;
-		
-	if ((((inb (JS_PORT) & 0x0f) >> (minor << 1)) & 0x03) == 0x03)	/*js minor exists?*/
-		return -ENODEV;
-	switch (cmd) 
-	{
-	
-		case JSIOCSCAL:	/*from struct *arg to js_data[minor]*/
-			if(copy_from_user(&js_data[minor].js_corr, 
-				(void *)arg, sizeof(struct js_status)))
-					return -EFAULT;
-			break;
-		case JSIOCGCAL:	/*to struct *arg from js_data[minor]*/
-			if(copy_to_user((void *) arg, &js_data[minor].js_corr, 
-				sizeof(struct js_status)))
-				return -EFAULT;
-			break;
-		case JSIOCSTIMEOUT:
-			if(copy_from_user(&js_data[minor].js_timeout,
-				(void *)arg, sizeof(js_data[0].js_timeout)))
-					return -EFAULT;
-			break;
-		case JSIOCGTIMEOUT:
-			if(copy_to_user((void *)arg, &js_data[minor].js_timeout,
-				sizeof(js_data[0].js_timeout)))
-					return -EFAULT;
-			break;
-		case JSIOCSTIMELIMIT:
-			if(copy_from_user(&js_data[minor].js_timelimit,
-				(void *)arg, sizeof(js_data[0].js_timelimit)))
-					return -EFAULT;
-			break;
-		case JSIOCGTIMELIMIT:
-			if(copy_to_user((void *)arg, &js_data[minor].js_timelimit,
-				sizeof(js_data[0].js_timelimit)))
-					return -EFAULT;
-			break;
-		case JSIOCGCONFIG:
-			if(copy_to_user((void *)arg, &js_data[minor],
-				sizeof(struct js_config)))
-					return -EFAULT;
-			break;
-		case JSIOCSCONFIG:
-			if(copy_from_user(&js_data[minor], (void *)arg, 
-				sizeof(struct js_config)))
-					return -EFAULT;
-			/* Must be busy to do this ioctl! */
-			js_data[minor].busy = 1;
-			break;
-		default:
-			return -EINVAL;
+	int t;
+
+	if (corr->type == JS_CORR_NONE) return value;
+	t = value > corr->coef[0] ? (value < corr->coef[1] ? corr->coef[0] : value - corr->coef[1] + corr->coef[0]) : value;
+	if (t == corr->coef[0]) return 32768;
+
+	switch (corr->type) {
+	case JS_CORR_BROKEN:
+		t = t < corr->coef[0] ? ((corr->coef[2] * t) >> 14) + corr->coef[3] :
+					((corr->coef[4] * t) >> 14) + corr->coef[5];
+		break;
+	default: 
+		return 0;
 	}
-	return 0;
+
+	if (t < 0) return 0;
+        if (t > 65535) return 65535;
+
+        return t;
 }
 
 /*
- *	js_open():
- *	device open routine. increments module usage count, initializes
- *	data for that joystick.
- *
- *	returns: 0 or
- *	-ENODEV: asked for joystick other than #0 or #1
- *	-ENODEV: asked for joystick on axis where there is none
- *	-EBUSY: attempt to open joystick already open
- */
- 
-static int js_open (struct inode *inode, struct file *file)
-{
-	unsigned int minor = MINOR (inode->i_rdev);
-	int j;
-
-	if (minor >= JS_MAX)
-		return -ENODEV;	/*check for joysticks*/
-
-	for (j = JS_DEF_TIMEOUT; (js_exist & inb (JS_PORT)) && j; j--);
-	cli();			/*block js_read while js_exist is being modified*/
-	/*js minor exists?*/
-	if ((((js_exist = inb (JS_PORT)) >> (minor << 1)) & 0x03) == 0x03) {
-		js_exist = (~js_exist) & 0x0f;
-		sti();
-		return -ENODEV;
+ * js_compare() compares two close axis values and decides 
+ * whether they are "same".
+ */
+
+static int js_compare(int x, int y, int prec)
+{
+	return (x < y + prec) && (y < x + prec); 
+}
+
+/*
+ * js_probe() probes for joysticks 
+ */
+
+inline int js_probe(void)
+{
+	int t;
+
+	outb(JS_TRIGGER, JS_PORT);
+	t = get_pit();
+	while (DELTA_T(t, get_pit()) < JS_MAXTIME);
+	t = inb(JS_PORT);
+
+	if (js[0] || js[1]) {
+		jsd[0].exist = js[0] & ~(t & JS_AXES);
+		jsd[1].exist = js[1] & ~(t & JS_AXES);
+	} else
+	switch (t & JS_AXES) {
+		case 0x0c: jsd[0].exist = 0x33; jsd[1].exist = 0x00; break;	/* joystick 0 connected */
+		case 0x03: jsd[0].exist = 0x00; jsd[1].exist = 0xcc; break;	/* joystick 1 connected */
+		case 0x04: jsd[0].exist = 0xfb; jsd[1].exist = 0x00; break;	/* 3-axis joystick connected */
+		case 0x00: jsd[0].exist = 0x33; jsd[1].exist = 0xcc; break;	/* joysticks 0 and 1 connected */
+		default:   jsd[0].exist = 0x00; jsd[1].exist = 0x00; return -1;	/* no joysticks */	
 	}
-	js_exist = (~js_exist) & 0x0f;
-	sti();
 
-	if (js_data[minor].busy)
-		return -EBUSY;
-	js_data[minor].busy = JS_TRUE;
-	js_data[minor].js_corr.x = JS_DEF_CORR;	/*default scale*/
-	js_data[minor].js_corr.y = JS_DEF_CORR;
-	js_data[minor].js_timeout = JS_DEF_TIMEOUT;
-	js_data[minor].js_timelimit = JS_DEF_TIMELIMIT;
-	js_data[minor].js_expiretime = jiffies;
+	js_axes_exist = (jsd[0].exist | jsd[1].exist) & JS_AXES;
+	js_buttons_exist = (jsd[0].exist | jsd[1].exist) & JS_BUTTONS;
 
-	MOD_INC_USE_COUNT;
 	return 0;
 }
 
-static int js_release (struct inode *inode, struct file *file)
+/* 
+ * js_do_timer() controls the action by adding entries to the event
+ * fifo each time a button changes its state or axis valid time
+ * expires.
+ */
+
+static void js_do_timer(unsigned long data)
 {
-	unsigned int minor = MINOR (inode->i_rdev);
-	inode->i_atime = CURRENT_TIME;
-	js_data[minor].busy = JS_FALSE;
-	MOD_DEC_USE_COUNT;
-	return 0;
+	int t = ~inb(JS_PORT) & js_buttons_exist;
+	if ((js_last_buttons != t) && (js_fifo_head != js_fifo_tail)) {
+		js_fifo[js_fifo_head].event = js_last_buttons = t;
+		js_fifo[js_fifo_head].time = jiffies;
+		js_fifo_head++;
+		if (js_fifo_head == JS_FIFO_SIZE) js_fifo_head = 0;
+		if (!js_mark_time) {
+			js_mark_time = jiffies;
+			mark_bh(JS_BH);
+		}
+	} 
+	else
+ 	if ((jiffies > js_axis_time + JS_AXIS_MAX_PERIOD) && !js_mark_time) {
+		js_mark_time = jiffies;
+		mark_bh(JS_BH);
+	}
+	js_timer.expires = jiffies + JS_BUTTON_PERIOD;
+	add_timer(&js_timer);
 }
 
 /*
- *	js_read() reads the buttons x, and y axis from both joysticks if a
- *	given interval has expired since the last read or is equal to
- *	-1l. The buttons are in port 0x201 in the high nibble. The axis are
- *	read by writing to 0x201 and then measuring the time it takes the
- *	one shots to clear.
+ * js_do_bh() does the main processing and adds events to output buffers.
+ */
+
+static void js_do_bh(void)
+{
+
+	int i, j, k;
+	unsigned int t;
+
+	if (jiffies > js_axis_time + JS_AXIS_MIN_PERIOD) {
+
+		unsigned int old_axis[4];
+		unsigned int t_low, t_high;
+		unsigned int flags, joy_state;
+		unsigned int t1l, t1h, jsm;
+		unsigned char jss;
+		unsigned char again;
+		unsigned char retries = 0;
+
+		for (i = 0; i < 4; i++)
+			old_axis[i] = js_axis[i].value;
+
+		do {
+			i = 0;
+			again = 0;
+			t_low = 0;
+			t_high = 0;
+			joy_state = JS_AXES;
+
+/*
+ * Measure the axes.
  */
 
-static ssize_t js_read (struct file *file, char *buf, 
-		     size_t count, loff_t *ppos)
-{
-	int j, chk, jsmask;
-	int t0, t_x0, t_y0, t_x1, t_y1;
-	unsigned int minor;
-	int buttons;
-	struct inode *inode=file->f_dentry->d_inode;
+			save_flags(flags);
+			cli();						/* no interrupts */
+			outb(JS_TRIGGER, JS_PORT);			/* trigger one-shots */
+			outb(PIT_READ_TIMER, PIT_MODE);			/* read timer */
+			t = (t1l = inb(PIT_DATA)) | 
+			    (t1h = inb(PIT_DATA)) << 8;	
+			restore_flags(flags);
+
+			do {
+				jss = inb(JS_PORT);
+				if ((jss ^ joy_state) & js_axes_exist) {
+					t_low = (t_low << 8) | t1l;
+					t_high = (t_high << 8) | t1h;
+					joy_state = (joy_state << 8) | jss;
+					i++;
+				}
+				
+				cli();
+				outb(PIT_READ_TIMER, PIT_MODE);
+				t1l = inb(PIT_DATA);
+				t1h = inb(PIT_DATA);
+				restore_flags(flags);
+
+			} while ((jss & js_axes_exist) && (DELTA_TX(t, t1l, t1h) < JS_MAXTIME));
+
+/*
+ * Process the gathered axis data in joy_state.
+ */
+
+			joy_state ^= ((joy_state >> 8) | 0xff000000L);  /* More magic */
+
+			for (; i > 0; i--) {
+				for (j = 0; j < 4; j++)
+				if (joy_state & js_axes_exist & (1 << j)) {
+					jsm = js_correct(DELTA_TX(t, t_low, t_high), &js_axis[j].corr);
+					if (!js_compare(jsm, js_axis[j].value, js_axis[j].corr.prec)) {
+						if (jsm < js_axis[j].value || !retries)
+							 js_axis[j].value = jsm;
+						again = 1;
+					}
+				}
+				joy_state = joy_state >> 8;
+				t_low = t_low >> 8;
+				t_high = t_high >> 8;
+			}
+
+		} while (retries++ < JS_RETRIES && again); 
+
+/*
+ * Check if joystick lost.
+ */
+
+		for (i = 0; i < JS_NUM; i++) {
+
+			if (jsd[i].exist && ((jss & jsd[i].exist & JS_AXES) == (jsd[i].exist & JS_AXES))) {
+				printk(KERN_WARNING "js%d: joystick lost.\n", i);
+				js_buttons_exist &= ~jsd[i].exist;
+				js_axes_exist &= ~jsd[i].exist;
+				jsd[i].exist = 0;
+				wake_up_interruptible(&jsd[i].wait);
+			}
+
+			if ((jss & jsd[i].exist & JS_AXES)) {
+				printk(KERN_WARNING "js%d: joystick broken. Check cables.\n", i);
+			}
+
+		}
+
+/*
+ * Put changed axes into output buffer.
+ */
+
+		if (retries > 1)
+		for (i = 0; i < JS_NUM; i++)
+		if (jsd[i].list) {
+			k = 0;
+			for (j = 0; j < 4; j++)
+			if ((1 << j) & jsd[i].exist) {
+				if (!js_compare(js_axis[j].value, old_axis[j], js_axis[j].corr.prec)) {
+					jsd[i].buff[jsd[i].ahead].time = js_mark_time;
+					jsd[i].buff[jsd[i].ahead].type = JS_EVENT_AXIS;
+					jsd[i].buff[jsd[i].ahead].number = k;
+					jsd[i].buff[jsd[i].ahead].value = js_axis[j].value;
+					jsd[i].ahead++;
+					if (jsd[i].ahead == JS_BUFF_SIZE) jsd[i].ahead = 0;
+				}
+				k++;
+			}
+		}
+		js_axis_time = jiffies;
+	}
+	js_mark_time = 0;
+
+/*
+ * And now process the button fifo.
+ */
+
+	while (js_fifo_head != (t = GOFF(js_fifo_tail))) {
+		for (i = 0; i < JS_NUM; i++)
+		if (jsd[i].list) {
+			k = 0;
+			for (j = 4; j < 8; j++)
+			if ((1 << j) & jsd[i].exist) {
+				if ((1 << j) & (js_buttons ^ js_fifo[t].event)) {
+					jsd[i].buff[jsd[i].ahead].time = js_fifo[t].time;
+					jsd[i].buff[jsd[i].ahead].type = JS_EVENT_BUTTON;
+					jsd[i].buff[jsd[i].ahead].number = k;
+					jsd[i].buff[jsd[i].ahead].value = (js_fifo[t].event >> j) & 1;
+					jsd[i].ahead++;
+					if (jsd[i].ahead == JS_BUFF_SIZE) jsd[i].ahead = 0;
+					}
+				k++;
+			}
+		}
+		js_buttons = js_fifo[js_fifo_tail = t].event;
+	}
+
+/*
+ * Sync ahead with bhead and cut too long tails.
+ */
 	
-	if (count != JS_RETURN)
+	for (i = 0; i < JS_NUM; i++)
+	if (jsd[i].list)
+	if (jsd[i].bhead != jsd[i].ahead)	{
+		if (ROT(jsd[i].bhead, jsd[i].tail, jsd[i].ahead) || (jsd[i].tail == jsd[i].bhead)) {
+			struct js_list *curl;
+			curl = jsd[i].list;
+			while (curl) {
+				if (ROT(jsd[i].bhead, curl->tail, jsd[i].ahead) || (curl->tail == jsd[i].bhead)) {
+					curl->tail = jsd[i].ahead; 				
+					curl->startup = jsd[i].exist;
+				}
+				curl = curl->next;
+			}
+			jsd[i].tail = jsd[i].ahead;		
+		}
+		jsd[i].bhead = jsd[i].ahead;
+		wake_up_interruptible(&jsd[i].wait);
+	}
+
+}
+
+/*
+ * js_lseek() just returns with error.
+ */
+
+static loff_t js_lseek(struct file *file, loff_t offset, int origin)
+{
+	return -ESPIPE;
+}
+
+/*
+ * js_read() copies one or more entries from jsd[].buff to user
+ * space.
+ */
+
+static ssize_t js_read(struct file *file, char *buf, size_t count, loff_t *ppos)
+{
+	unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev);
+	struct wait_queue wait = { current, NULL };
+	struct js_list *curl = file->private_data;
+	struct js_event *buff = (void *) buf;
+	unsigned long blocks = count / sizeof(struct js_event);
+	unsigned long i = 0, j;
+	int t, u = curl->tail;
+        int retval = 0;
+
+/*
+ * Check user data.
+ */
+
+	if (MAJOR(file->f_dentry->d_inode->i_rdev) != JOYSTICK_MAJOR)
 		return -EINVAL;
-	minor = MINOR (inode->i_rdev); 
-	inode->i_atime = CURRENT_TIME;
-	if (jiffies >= js_data[minor].js_expiretime) 
-	{
-		j = js_data[minor].js_timeout;
-		for (; (js_exist & inb (JS_PORT)) && j; j--);
-		if (j == 0)
-			return -ENODEV;	/*no joystick here*/
-		/*Make sure no other proc is using port*/
-	
-		cli();
-		js_read_semaphore++;
-		sti();
-		
-		buttons = ~(inb (JS_PORT) >> 4);
-		js_data[0].js_save.buttons = buttons & 0x03;
-		js_data[1].js_save.buttons = (buttons >> 2) & 0x03;
-		j = js_data[minor].js_timeout;
-		jsmask = 0;
-
-		cli();		/*no interrupts!*/
-		outb (0xff, JS_PORT);	/*trigger one-shots*/
-		/*get init timestamp*/
-		t_x0 = t_y0 = t_x1 = t_y1 = t0 = get_timer0 ();
-		/*wait for an axis' bit to clear or timeout*/
-		do {
-			chk = (inb (JS_PORT) & js_exist) | jsmask;
-			if (!(chk & JS_X_0)) {
-				t_x0 = get_timer0();
-				jsmask |= JS_X_0;
+	if (file->f_pos < 0)
+		return -EINVAL;
+	if (!blocks)
+		return -EINVAL;
+	if (!curl)
+		return -EINVAL;
+
+	if (minor > JS_NUM)
+		return -ENODEV;
+	if (!jsd[minor].exist)
+		return -ENODEV;
+
+/*
+ * Handle (non)blocking i/o.
+ */
+
+	if (count != sizeof(struct JS_DATA_TYPE)) {
+
+		if ((GOF(curl->tail) == jsd[minor].ahead && !curl->startup) || (curl->startup && !js_axis_time)) {
+			add_wait_queue(&jsd[minor].wait, &wait);
+			current->state = TASK_INTERRUPTIBLE;
+			while ((GOF(curl->tail) == jsd[minor].ahead && !curl->startup) || (curl->startup && !js_axis_time)) {
+				if (file->f_flags & O_NONBLOCK) {
+					retval = -EAGAIN;
+					break;
+				}
+				if (current->signal & ~current->blocked) {
+					retval = -ERESTARTSYS;
+					break;
+				}
+				schedule();
+				if (!jsd[minor].exist) {
+					retval = -ENODEV;
+					break;
+				}
 			}
-			if (!(chk & JS_Y_0)) {
-				t_y0 = get_timer0();
-				jsmask |= JS_Y_0;
+			current->state = TASK_RUNNING;
+			remove_wait_queue(&jsd[minor].wait, &wait);
+		}
+
+		if (retval) return retval;
+	
+/*
+ * Do the i/o.
+ */
+
+		if (curl->startup) {
+			struct js_event tmpevent;
+
+			t = 0;
+			for (j = 0; j < 4 && (i < blocks) && !retval; j++)
+			if (jsd[minor].exist & (1 << j)) {
+				if (curl->startup & (1 << j)) {
+					tmpevent.type = JS_EVENT_AXIS | JS_EVENT_INIT;
+					tmpevent.number = t;
+					tmpevent.value = js_axis[j].value;
+  					if (copy_to_user(&buff[i], &tmpevent, sizeof(struct js_event)))
+						retval = -EFAULT;
+					if (put_user((__u32)((jiffies - curl->time) * (1000/HZ)), &buff[i].time))
+						retval = -EFAULT;
+					curl->startup &= ~(1 << j);
+					i++;
+				}
+				t++;	
 			}
-			if (!(chk & JS_X_1)) {
-				t_x1 = get_timer0();
-				jsmask |= JS_X_1;
+
+			t = 0;
+			for (j = 4; j < 8 && (i < blocks) && !retval; j++)
+			if (jsd[minor].exist & (1 << j)) {
+				if (curl->startup & (1 << j)) {
+					tmpevent.type = JS_EVENT_BUTTON | JS_EVENT_INIT;
+					tmpevent.number = t;
+					tmpevent.value = (js_buttons >> j) & 1;
+  					if (copy_to_user(&buff[i], &tmpevent, sizeof(struct js_event)))
+						retval = -EFAULT;
+					if (put_user((__u32)((jiffies - curl->time) * (1000/HZ)), &buff[i].time))
+						retval = -EFAULT;
+					curl->startup &= ~(1 << j);
+					i++;
+				}
+				t++;	
 			}
-			if (!(chk & JS_Y_1)) {
-				t_y1 = get_timer0();
-				jsmask |= JS_Y_1;
+		}
+
+
+		while ((jsd[minor].ahead != (t = GOF(curl->tail))) && (i < blocks) && !retval) {
+			if (copy_to_user(&buff[i], &jsd[minor].buff[t], sizeof(struct js_event)))
+				retval = -EFAULT;
+			if (put_user((__u32)((jsd[minor].buff[t].time - curl->time) * (1000/HZ)), &buff[i].time))
+				retval = -EFAULT;
+			curl->tail = t;
+			i++;
+		}
+	
+	}
+
+	else
+
+/*
+ * Handle version 0.x compatibility.
+ */
+
+	{
+		struct JS_DATA_TYPE *bufo = (void *) buf;
+		int buttons = 0;
+
+		while (~jsd[minor].exist & (1<<i)) i++;
+		copy_to_user(&bufo->x, &js_axis[i].value, sizeof(int));
+
+		i++;
+		while (~jsd[minor].exist & (1<<i)) i++;
+		copy_to_user(&bufo->y, &js_axis[i].value, sizeof(int));
+
+		i = 0;
+		for (j = 4; j < 8; j++)
+		if ((1 << j) & jsd[minor].exist)
+			buttons |= (!!(js_last_buttons & (1 << j))) << (i++);
+		copy_to_user(&bufo->buttons, &buttons, sizeof(int));
+
+		curl->tail = GOB(jsd[minor].ahead);
+		retval = sizeof(struct JS_DATA_TYPE);
+	}
+
+/*
+ * Check main tail and move it.
+ */
+
+	if (u == jsd[minor].tail) {
+		t = curl->tail;
+		curl = jsd[minor].list;
+		while (curl && curl->tail != jsd[minor].tail) {
+			if (ROT(jsd[minor].ahead, t, curl->tail) ||
+				(jsd[minor].ahead == curl->tail)) t = curl->tail;
+			curl = curl->next;
+		}
+		if (!curl) jsd[minor].tail = t;
+	}
+	
+	return retval ? retval : i*sizeof(struct js_event);
+}
+
+/*
+ * js_poll() does select() support.
+ */
+
+static unsigned int js_poll(struct file *file, poll_table *wait)
+{
+	struct js_list *curl;
+	unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev);
+ 	curl = file->private_data;
+
+	poll_wait(&jsd[minor].wait, wait);
+	if (GOF(curl->tail) != jsd[minor].ahead) 
+		return POLLIN | POLLRDNORM;
+        return 0;
+}
+
+/*
+ * js_ioctl handles misc ioctl calls.
+ */
+
+static int js_ioctl(struct inode *inode,
+		     struct file *file,
+		     unsigned int cmd,
+		     unsigned long arg)
+{
+	unsigned int minor = MINOR(inode->i_rdev);
+	int i, j;
+
+	if (MAJOR(inode->i_rdev) != JOYSTICK_MAJOR)
+		return -EINVAL;
+	if (minor > JS_NUM)
+		return -ENODEV;
+	if (!jsd[minor].exist)
+		return -ENODEV;
+
+	switch (cmd) {
+	case JSIOCGVERSION:
+		if(put_user(JS_VERSION, (__u32 *) arg)) return -EFAULT;
+		break;
+	case JSIOCGAXES:
+		if(put_user(count_bits(jsd[minor].exist & JS_AXES), (__u8 *) arg)) return -EFAULT;
+		break;
+	case JSIOCGBUTTONS:
+		if(put_user(count_bits(jsd[minor].exist & JS_BUTTONS), (__u8 *) arg)) return -EFAULT;
+		break;
+	case JSIOCSCORR:
+		j = 0;
+		for (i = 0; i < 4; i++)
+		if ((1 << i) & jsd[minor].exist) {
+			if (copy_from_user(&js_axis[i].corr, (void *) arg + j * sizeof(struct js_corr),
+				sizeof(struct js_corr))) return -EFAULT;
+			j++;
+		}
+		js_axis_time = 0;
+		break;
+	case JSIOCGCORR:
+		j = 0;
+		for (i = 0; i < 4; i++)
+		if ((1 << i) & jsd[minor].exist) {
+			if (copy_to_user((void *) arg + j * sizeof(struct js_corr), &js_axis[i].corr, 
+				sizeof(struct js_corr))) return -EFAULT;
+			j++;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	
+	return 0;
+}
+
+/*
+ * js_open() performs necessary initialization and adds
+ * an entry to the linked list.
+ */
+
+static int js_open(struct inode *inode, struct file *file)
+{
+	unsigned int minor = MINOR(inode->i_rdev);
+	struct js_list *curl;
+	int t;
+
+	if (MAJOR(inode->i_rdev) != JOYSTICK_MAJOR)
+		return -EINVAL;
+	if (minor > JS_NUM)
+		return -ENODEV;
+	if (!jsd[minor].exist) {
+		js_probe();
+		if (jsd[minor].exist) printk(KERN_INFO "js%d: %d-axis joystick at %#x\n", 
+			minor,  count_bits(jsd[minor].exist & JS_AXES), JS_PORT);
+		else return -ENODEV;
+	}
+
+	MOD_INC_USE_COUNT;
+
+	if (!jsd[0].list && !jsd[1].list) { 
+		js_timer.expires = jiffies + JS_BUTTON_PERIOD;
+		add_timer(&js_timer);
+	}
+
+	curl = jsd[minor].list;
+	jsd[minor].list = kmalloc(sizeof(struct js_list), GFP_KERNEL);
+	jsd[minor].list->next = curl;
+	jsd[minor].list->startup = jsd[minor].exist;
+	jsd[minor].list->tail = t = GOB(jsd[minor].ahead);
+	jsd[minor].list->time = jiffies;
+
+	file->private_data = jsd[minor].list;
+
+	return 0;
+}
+
+/*
+ * js_release() removes an entry from list and deallocates memory
+ * used by it.
+ */
+
+static int js_release(struct inode *inode, struct file *file)
+{
+	unsigned int minor = MINOR(inode->i_rdev);
+	struct js_list **curp, *curl;
+	int t;
+
+	curp = &jsd[minor].list;
+	curl = file->private_data;
+
+	while (*curp && (*curp != curl)) curp = &((*curp)->next);
+ 	*curp = (*curp)->next;
+
+	if (jsd[minor].list) {
+		if (curl->tail == jsd[minor].tail) {
+			curl = jsd[minor].list;
+			t = curl->tail;
+			while (curl && curl->tail != jsd[minor].tail) {
+				if (ROT(jsd[minor].ahead, t, curl->tail) ||
+					(jsd[minor].ahead == curl->tail)) t = curl->tail;
+				curl = curl->next;
 			}
-		} while (--j && jsmask != js_exist);
-		sti();					/* allow interrupts */
+			if (!curl) jsd[minor].tail = t;
+		}
+	}
 
-		js_read_semaphore = 0;	/* allow other reads to progress */
-		if (j == 0)
-			return -ENODEV;	/*read timed out*/
-		js_data[0].js_expiretime = jiffies +
-			js_data[0].js_timelimit;	/*update data*/
-		js_data[1].js_expiretime = jiffies +
-			js_data[1].js_timelimit;
-		js_data[0].js_save.x = DELTA_TIME (t0, t_x0) >>
-			js_data[0].js_corr.x;
-		js_data[0].js_save.y = DELTA_TIME (t0, t_y0) >>
-			js_data[0].js_corr.y;
-		js_data[1].js_save.x = DELTA_TIME (t0, t_x1) >>
-			js_data[1].js_corr.x;
-		js_data[1].js_save.y = DELTA_TIME (t0, t_y1) >>
-			js_data[1].js_corr.y;
-	}
-
-	if(copy_to_user(buf, &js_data[minor].js_save, JS_RETURN))
-		return -EFAULT;
-	return JS_RETURN;
+	kfree(file->private_data);
+	if (!jsd[0].list && !jsd[1].list) del_timer(&js_timer);
+
+	MOD_DEC_USE_COUNT;
+	return 0;
 }
 
+/*
+ * The operations structure.
+ */
 
 static struct file_operations js_fops =
 {
-	NULL,			/* js_lseek*/
+	js_lseek,		/* js_lseek */
 	js_read,		/* js_read */
-	NULL,			/* js_write*/
-	NULL,			/* js_readaddr*/
-	NULL,			/* js_select */
-	js_ioctl,		/* js_ioctl*/
+	NULL,			/* js_write */
+	NULL,			/* js_readdir */
+	js_poll,		/* js_poll */
+	js_ioctl,		/* js_ioctl */
 	NULL,			/* js_mmap */
-	js_open,		/* js_open*/
-	js_release,		/* js_release*/
-	NULL			/* js_fsync */
+	js_open,		/* js_open */
+	js_release,		/* js_release */
+	NULL			/* js_sync */
 };
 
-#ifdef MODULE
+/*
+ * js_setup() parses kernel command line parametres.
+ */
 
-#define joystick_init init_module
+#ifndef MODULE
+__initfunc(void js_setup(char *str, int *ints))
 
-void cleanup_module (void)
 {
-	if (unregister_chrdev (JOYSTICK_MAJOR, "joystick"))
-		printk ("joystick: cleanup_module failed\n");
-	release_region(JS_PORT, 1);
+	js[0] = ((ints[0] > 0) ? ints[1] : 0 );
+        js[1] = ((ints[0] > 1) ? ints[2] : 0 );
 }
+#endif
 
-#endif /* MODULE */
+/*
+ * js_init() registres the driver and calls the probe function.
+ * also initializes some crucial variables.
+ */
 
-int joystick_init(void)
+#ifdef MODULE
+int init_module(void) 
+#else
+__initfunc(int js_init(void))
+#endif
 {
-	int js_num;
-	int js_count;
+	int i;
 
 	if (check_region(JS_PORT, 1)) {
-		printk("js_init: port already in use\n");
+		printk(KERN_ERR "js: port %#x already in use\n", JS_PORT);
 		return -EBUSY;
 	}
 
-	js_num = find_axes();
-	js_count = !!(js_num & 0x3) + !!(js_num & 0xC);
-
-
-	if (js_count == 0) 
-	{
-		printk("No joysticks found.\n");
+	if (js_probe() < 0) {
+		printk(KERN_INFO "js: no joysticks found\n");
 		return -ENODEV;
-		/* if the user boots the machine, which runs insmod, and THEN
-		   decides to hook up the joystick, well, then we do the wrong
-		   thing. But it's a good idea to avoid giving out a false sense
-		   of security by letting the module load otherwise. */
 	}
 
-	if (register_chrdev (JOYSTICK_MAJOR, "joystick", &js_fops)) {
-		printk ("Unable to get major=%d for joystick\n",
-					JOYSTICK_MAJOR);
+	if (register_chrdev(JOYSTICK_MAJOR, "js", &js_fops)) {
+		printk(KERN_ERR "js: unable to get major %d for joystick\n", JOYSTICK_MAJOR);
 		return -EBUSY;
 	}
-	request_region(JS_PORT, 1, "joystick");
+
+	for (i = 0; i < JS_NUM; i++) {
+		if (jsd[i].exist) printk(KERN_INFO "js%d: %d-axis joystick at %#x\n",
+			 i,  count_bits(jsd[i].exist & JS_AXES), JS_PORT);
+		jsd[i].ahead = jsd[i].bhead = 0;
+		jsd[i].tail = JS_BUFF_SIZE - 1;
+		jsd[i].list = NULL;
+		jsd[i].wait = NULL;
+		memset(jsd[i].buff, 0, JS_BUFF_SIZE * sizeof(struct js_event));
+	}
+
+	for (i = 0; i < 4; i++) {
+		js_axis[i].corr.type = JS_CORR_NONE; 
+		js_axis[i].corr.prec = JS_DEF_PREC;
+	}
+
+	request_region(JS_PORT, 1, "js");
+	init_bh(JS_BH, &js_do_bh);
+	enable_bh(JS_BH);
+	init_timer(&js_timer);
+	js_timer.function = js_do_timer;
 		
-	for (js_num = 0; js_num < JS_MAX; js_num++)
-		  js_data[js_num].busy = JS_FALSE;
-	js_read_semaphore = 0;
-
-	printk (KERN_INFO "Found %d joystick%c.\n",
-			js_count,
-		    (js_num == 1) ? ' ' : 's');
 	return 0;
 }
 
+/*
+ * cleanup_module() handles module removal.
+ */
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+	if (MOD_IN_USE)
+		printk(KERN_NOTICE "js: device busy, remove delayed\n");
+	else {
+		del_timer(&js_timer);
+		disable_bh(JS_BH);
+		if (unregister_chrdev(JOYSTICK_MAJOR, "js"))
+			printk(KERN_ERR "js: module cleanup failed\n");
+		release_region(JS_PORT, 1);
+	}
+}
+#endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov