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

Next file: linux/drivers/char/joystick.c
Previous file: linux/drivers/char/joystick/joy-turbografx.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.124/linux/drivers/char/joystick/joystick.c linux/drivers/char/joystick/joystick.c
@@ -0,0 +1,1223 @@
+/*
+ * joystick.c  Version 1.2
+ *
+ * Copyright (c) 1996-1998 Vojtech Pavlik
+ */
+
+/*
+ * This is the main joystick driver for Linux. It doesn't support any
+ * devices directly, rather is lets you use sub-modules to do that job. See
+ * Documentation/joystick.txt for more info.
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or 
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * 
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
+ * Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00 Czech Republic
+ */
+
+#include <asm/io.h>
+#include <asm/system.h>
+#include <linux/config.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/joystick.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+#include <asm/spinlock.h>
+#include <linux/poll.h>
+#endif
+
+/*
+ * Configurable parameters.
+ */
+
+#define JS_REFRESH_TIME		HZ/50	/* Time between two reads of joysticks (20ms) */
+
+/*
+ * Buffer macros.
+ */
+
+#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 GOB(X)		((X)?(X)-1:JS_BUFF_SIZE-1)
+#define DIFF(X,Y)	((X)>(Y)?(X)-(Y):(Y)-(X))
+
+/*
+ * Global variables.
+ */
+
+static struct JS_DATA_SAVE_TYPE js_comp_glue;
+static struct js_port  *js_port  = NULL;
+static struct js_dev   *js_dev   = NULL;
+static struct timer_list js_timer;
+spinlock_t js_lock = SPIN_LOCK_UNLOCKED;
+static int js_use_count = 0;
+
+/*
+ * Exported variables.
+ */
+
+unsigned int js_time_speed = 0;
+js_time_func js_get_time;
+js_delta_func js_delta;
+
+unsigned int js_time_speed_a = 0;
+js_time_func js_get_time_a;
+js_delta_func js_delta_a;
+
+/*
+ * Module info.
+ */
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_SUPPORTED_DEVICE("js");
+
+/*
+ * js_get_time_*() are different functions to get current time.
+ * js_delta_*() are functions to compute time difference.
+ */
+
+#ifdef __i386__
+
+static unsigned int js_get_time_rdtsc(void)
+{
+	unsigned int x;
+	__asm__ __volatile__ ( "rdtsc" : "=A" (x) );
+	return x;
+}
+
+static unsigned int js_get_time_pit(void)
+{
+	unsigned long flags;
+	unsigned int x;
+
+	__save_flags(flags);
+	__cli();
+	outb(0, 0x43);
+	x = inb(0x40);
+	x |= inb(0x40) << 8;
+	__restore_flags(flags);
+
+	return x;
+}
+
+static int js_delta_pit(unsigned int x, unsigned int y)
+{
+	return y - x + ( y < x ? 1193180L / HZ : 0 );
+}
+
+static unsigned int js_get_time_counter(void)
+{
+	static int time_counter = 0;
+	return time_counter++;
+}
+
+#else
+#ifdef __alpha__
+
+static unsigned int js_get_time_rpcc(void)
+{
+	unsigned int x;
+	__asm__ __volatile__ ( "rpcc %0" : "=r" (x) );
+	return x;
+}
+
+#else
+
+#ifndef MODULE
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+static unsigned int js_get_time_system(void)
+{
+	static struct timeval js_tv;
+	get_fast_time(&js_tv);
+	return js_tv.tv_sec * 1000000L + js_tv.tv_usec;
+}
+#endif
+#endif
+
+#endif
+#endif
+
+static int js_delta_normal(unsigned int x, unsigned int y)
+{
+	return x - y;
+}
+
+/*
+ * js_calibrate_time() calibrates a given timer.
+ */
+
+static int __init js_calibrate_time(js_time_func get_time, js_delta_func delta)
+{
+	unsigned int t1, t2, t3;
+	unsigned long flags;
+
+	__save_flags(flags);
+	__cli();
+	t1 = get_time();
+	udelay(1000);
+	t2 = get_time();
+	t3 = get_time();
+	__restore_flags(flags);
+
+	return delta(t2, t1) - delta(t3, t2);
+}
+
+/*
+ * js_calibrate_time_counter() calibrates the counter timer, which can't
+ * be calibrated using the above function.
+ */
+
+#ifdef __i386__
+
+static int __init js_calibrate_time_counter(void)
+{
+	unsigned int i, j, t1, t2, t3;
+
+	j = jiffies; do { inb(0x201); t1 = js_get_time_counter(); } while (j == jiffies);
+	j = jiffies; do { inb(0x201); t2 = js_get_time_counter(); } while (j == jiffies);
+
+	j = (t2 - t1) * HZ / 1000;
+
+	t1 = js_get_time_pit();
+	for (i = 0; i < 1000; i++) {
+		inb(0x201);
+		js_get_time_counter();
+	}
+	t2 = js_get_time_pit();
+	t3 = js_get_time_pit();
+
+	i = 1193180L / (js_delta_pit(t2, t1) - js_delta_pit(t3, t2));
+
+	if (DIFF(i,j) > 5)
+		printk(KERN_WARNING "js: Counter timer calibration unsure,"
+			" pass1 (0.%d MHz) and pass2 (0.%d MHz) differ.\n", j, i);
+
+	return (i + j) >> 1;
+}
+
+#endif
+
+/*
+ * js_setup_time chooses the best available timers
+ * on the system and calibrates them.
+ */
+
+static int __init js_setup_time(void)
+{
+	int t;
+	char *name, *name_a;
+
+	name = "";
+	name_a = "";
+	js_time_speed = 0;
+	js_time_speed_a = 0;
+
+#ifdef __i386__
+
+	t = js_calibrate_time(js_get_time_pit, js_delta_pit);
+
+	if (DIFF(t, 1193) > 5)
+		printk(KERN_WARNING "js: Measured PIT speed is %d.%03d MHz, but should be 1.193 MHz.\n"
+		       KERN_WARNING "js: This is probably caused by wrong BogoMIPS value. It is: %ld, should be: %ld.\n",
+			t / 1000, t % 1000, loops_per_sec / 500000, loops_per_sec / (t * 500000 / 1193));
+
+	if (JS_HAS_RDTSC && (t = js_calibrate_time(js_get_time_rdtsc, js_delta_normal)) > 0) {
+
+		js_time_speed_a = t;
+		js_get_time_a = js_get_time_rdtsc;
+		js_delta_a = js_delta_normal;
+		js_time_speed = t;
+		js_get_time = js_get_time_rdtsc;
+		js_delta = js_delta_normal;
+		name = "RDTSC";
+
+	} else {
+
+		js_time_speed_a = t;
+		js_get_time_a = js_get_time_pit;
+		js_delta_a = js_delta_pit;
+		name_a = "PIT";
+
+		t = js_calibrate_time_counter();
+
+		js_time_speed = t;
+		js_get_time = js_get_time_counter;
+		js_delta = js_delta_normal;
+		name = "counter";
+
+	}
+
+#else
+#ifdef __alpha__
+
+	t = js_calibrate_time(js_get_time_rpcc, js_delta_normal);
+
+	js_time_speed_a = t;
+	js_get_time_a = js_get_time_rpcc;
+	js_delta_a = js_delta_normal;
+	js_time_speed = t;
+	js_get_time = js_get_time_rpcc;
+	js_delta = js_delta_normal;
+	name = "RPCC";
+
+#else
+
+#ifndef MODULE
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+	t = js_calibrate_time(js_get_time_system, js_delta_normal);
+
+	js_time_speed_a = t;
+	js_get_time_a = js_get_time_system;
+	js_delta_a = js_delta_normal;
+	js_time_speed = t;
+	js_get_time = js_get_time_system;
+	js_delta = js_delta_normal;
+	name = "system";
+#endif
+#endif
+
+#endif
+#endif
+
+	printk(KERN_INFO "js: Version %d.%d.%d ",
+		JS_VERSION >> 16 & 0xff, JS_VERSION >> 8 & 0xff, JS_VERSION & 0xff);
+
+	if (js_time_speed_a <= 0 || js_time_speed <= 0) {
+		printk("\n");
+		return -1;
+	}
+
+	printk("using ");
+
+	if (js_time_speed > 10000) {
+		t = js_time_speed / 1000 + (js_time_speed % 1000 >= 500);
+		printk("%d MHz ", t);
+	} else {
+		t = js_time_speed / 10 + (js_time_speed % 10 >= 5);
+		printk("%d.%02d MHz ", t / 100, t % 100);
+	}
+
+	if (js_get_time_a != js_get_time) {
+		t = js_time_speed_a / 10 + (js_time_speed_a % 10 >= 5);
+		printk("%s timer and %d.%02d MHz %s timer.\n",
+			name, t / 100, t % 100, name_a);
+	} else {
+		printk("%s timer.\n", name);
+	}
+
+	return 0;
+}
+
+
+/*
+ * js_correct() performs correction of raw joystick data.
+ */
+
+static int js_correct(int value, struct js_corr *corr)
+{
+	switch (corr->type) {
+		case JS_CORR_NONE:
+			break;
+		case JS_CORR_BROKEN:
+			value = value > corr->coef[0] ? (value < corr->coef[1] ? 0 :
+				((corr->coef[3] * (value - corr->coef[1])) >> 14)) :
+				((corr->coef[2] * (value - corr->coef[0])) >> 14);
+			break;
+
+		default:
+			return 0;
+	}
+
+	if (value < -32767) return -32767;
+	if (value >  32767) return  32767;
+
+	return value;
+}
+
+/*
+ * js_button() returns value of button number i.
+ */
+
+static inline int js_button(int *buttons, int i)
+{
+	return (buttons[i >> 5] >> (i & 0x1f)) & 1;
+}
+
+
+/*
+ * js_add_event() adds an event to the buffer. This requires additional
+ * queue post-processing done by js_sync_buff.
+ */
+
+static void js_add_event(struct js_dev *jd, __u32 time, __u8 type, __u8 number, __s16 value)
+{
+	jd->buff[jd->ahead].time = time;
+	jd->buff[jd->ahead].type = type;
+	jd->buff[jd->ahead].number = number;
+	jd->buff[jd->ahead].value = value;
+	if (++jd->ahead == JS_BUFF_SIZE) jd->ahead = 0;
+}
+
+/*
+ * js_flush_data() does the same as js_process_data, except for that it doesn't
+ * generate any events - it just copies the data from new to cur.
+ */
+
+static void js_flush_data(struct js_dev *jd)
+{
+	int i;
+
+	for (i = 0; i < ((jd->num_buttons - 1) >> 5) + 1; i++)
+		jd->cur.buttons[i] = jd->new.buttons[i];
+	for (i = 0; i < jd->num_axes; i++)
+		jd->cur.axes[i] = jd->new.axes[i];
+}
+
+/*
+ * js_process_data() finds changes in button states and axis positions and adds
+ * them as events to the buffer.
+ */
+
+static void js_process_data(struct js_dev *jd)
+{
+	int i, t;
+
+	for (i = 0; i < jd->num_buttons; i++)
+	if ((t = js_button(jd->new.buttons, i)) != js_button(jd->cur.buttons, i)) {
+		js_add_event(jd, jiffies, JS_EVENT_BUTTON, i, t);
+		jd->cur.buttons[i >> 5] ^= (1 << (i & 0x1f));
+	}
+
+	for (i = 0; i < jd->num_axes; i++) {
+		t = js_correct(jd->new.axes[i], &jd->corr[i]);
+		if (((jd->corr[i].prec == -1) && t) ||
+			((DIFF(jd->new.axes[i], jd->cur.axes[i]) > jd->corr[i].prec) &&
+			(t != js_correct(jd->cur.axes[i], &jd->corr[i])))) {
+			js_add_event(jd, jiffies, JS_EVENT_AXIS, i, t);
+			jd->cur.axes[i] = jd->new.axes[i];
+		}
+	}
+}
+
+/*
+ * js_sync_buff() checks for all overflows caused by recent additions to the buffer.
+ * These happen only if some process is reading the data too slowly. It
+ * wakes up any process waiting for data.
+ */
+
+static void js_sync_buff(struct js_dev *jd)
+{
+	struct js_list *curl = jd->list;
+
+	if (jd->bhead != jd->ahead) {
+		if(ROT(jd->bhead, jd->tail, jd->ahead) || (jd->tail == jd->bhead)) {
+			while (curl) {
+				if (ROT(jd->bhead, curl->tail, jd->ahead) || (curl->tail == jd->bhead)) {
+					curl->tail = jd->ahead;
+					curl->startup = 0;
+				}
+				curl = curl->next;
+			}
+			jd->tail = jd->ahead;
+		}
+		jd->bhead = jd->ahead;
+		wake_up_interruptible(&jd->wait);
+	}
+}
+
+/*
+ * js_do_timer() acts as an interrupt replacement. It reads the data
+ * from all ports and then generates events for all devices.
+ */
+
+static void js_do_timer(unsigned long data)
+{
+	struct js_port *curp = js_port;
+	struct js_dev *curd = js_dev;
+	unsigned long flags;
+
+	while (curp) {
+		curp->read(curp->info, curp->axes, curp->buttons);
+		curp = curp->next;
+	}
+
+	spin_lock_irqsave(&js_lock, flags);
+
+	while (curd) {
+		if (data) {
+			js_process_data(curd);
+			js_sync_buff(curd);
+		} else {
+			js_flush_data(curd);
+		}
+		curd = curd->next;
+	}
+
+	spin_unlock_irqrestore(&js_lock, flags);
+
+	js_timer.expires = jiffies + JS_REFRESH_TIME;
+	add_timer(&js_timer);
+}
+
+/*
+ * js_read() copies one or more entries from jsd[].buff to user
+ * space.
+ */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+static ssize_t js_read(struct file *file, char *buf, size_t count, loff_t *ppos)
+#else
+static int js_read(struct inode *inode, struct file *file, char *buf, int count)
+#endif
+{
+	struct wait_queue wait = { current, NULL };
+	struct js_event *buff = (void *) buf;
+	struct js_list *curl;
+	struct js_dev *jd;
+	unsigned long blocks = count / sizeof(struct js_event);
+	int written = 0;
+	int new_tail, orig_tail;
+	int retval = 0;
+	unsigned long flags;
+
+	curl = file->private_data;
+	jd = curl->dev;
+	orig_tail = curl->tail;
+
+/*
+ * Check user data.
+ */
+
+	if (!blocks)
+		return -EINVAL;
+
+/*
+ * Lock it.
+ */
+
+	spin_lock_irqsave(&js_lock, flags);
+
+/*
+ * Handle (non)blocking i/o.
+ */
+	if (count != sizeof(struct JS_DATA_TYPE)) {
+
+		if (GOF(curl->tail) == jd->bhead && curl->startup == jd->num_axes + jd->num_buttons) {
+
+			add_wait_queue(&jd->wait, &wait);
+			current->state = TASK_INTERRUPTIBLE;
+
+			while (GOF(curl->tail) == jd->bhead) {
+
+				if (file->f_flags & O_NONBLOCK) {
+					retval = -EAGAIN;
+					break;
+				}
+				if (signal_pending(current)) {
+					retval = -ERESTARTSYS;
+					break;
+				}
+
+				spin_unlock_irqrestore(&js_lock, flags);
+				schedule();
+				spin_lock_irqsave(&js_lock, flags);
+
+			}
+
+			current->state = TASK_RUNNING;
+			remove_wait_queue(&jd->wait, &wait);
+		}
+
+		if (retval) {
+			spin_unlock_irqrestore(&js_lock, flags);
+			return retval;
+		}
+
+/*
+ * Initial state.
+ */
+
+		while (curl->startup < jd->num_axes + jd->num_buttons && written < blocks && !retval) {
+
+			struct js_event tmpevent;
+
+			if (curl->startup < jd->num_buttons) {
+				tmpevent.type = JS_EVENT_BUTTON | JS_EVENT_INIT;
+				tmpevent.value = js_button(jd->cur.buttons, curl->startup);
+				tmpevent.number = curl->startup;
+			} else {
+				tmpevent.type = JS_EVENT_AXIS | JS_EVENT_INIT;
+				tmpevent.value = js_correct(jd->cur.axes[curl->startup - jd->num_buttons],
+								&jd->corr[curl->startup - jd->num_buttons]);
+				tmpevent.number = curl->startup - jd->num_buttons;
+			}
+
+			tmpevent.time = jiffies * (1000/HZ);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+			if (copy_to_user(&buff[written], &tmpevent, sizeof(struct js_event)))
+				retval = -EFAULT;
+#else
+			if (!(retval = verify_area(VERIFY_WRITE, &buff[written], sizeof(struct js_event))))
+				memcpy_tofs(&buff[written], &tmpevent, sizeof(struct js_event));
+#endif
+
+			curl->startup++;
+			written++;
+		}
+
+/*
+ * Buffer data.
+ */
+
+		while ((jd->bhead != (new_tail = GOF(curl->tail))) && (written < blocks) && !retval) {
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+			if (copy_to_user(&buff[written], &jd->buff[new_tail], sizeof(struct js_event)))
+				retval = -EFAULT;
+			if (put_user((__u32)(jd->buff[new_tail].time * (1000/HZ)), &buff[written].time))
+				retval = -EFAULT;
+#else
+			if (!(retval = verify_area(VERIFY_WRITE, &buff[written], sizeof(struct js_event)))) {
+				memcpy_tofs(&buff[written], &jd->buff[new_tail], sizeof(struct js_event));
+				put_user((__u32)(jd->buff[new_tail].time * (1000/HZ)), &buff[written].time);
+			}
+#endif
+			curl->tail = new_tail;
+			written++;
+		}
+	}
+
+	else
+
+/*
+ * Handle version 0.x compatibility.
+ */
+
+	{
+		struct JS_DATA_TYPE data;
+
+		data.buttons = jd->new.buttons[0];
+		data.x = jd->num_axes < 1 ? 0 :
+			((js_correct(jd->new.axes[0], &jd->corr[0]) / 256) + 128) >> js_comp_glue.JS_CORR.x;
+		data.y = jd->num_axes < 2 ? 0 :
+			((js_correct(jd->new.axes[1], &jd->corr[1]) / 256) + 128) >> js_comp_glue.JS_CORR.y;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+		retval = copy_to_user(buf, &data, sizeof(struct JS_DATA_TYPE)) ? -EFAULT : 0;
+#else
+		if (!(retval = verify_area(VERIFY_WRITE, buf, sizeof(struct JS_DATA_TYPE)))) {
+			memcpy_tofs(buf, &data, sizeof(struct JS_DATA_TYPE));
+		}
+#endif
+
+		curl->startup = 0;
+		curl->tail = GOB(jd->bhead);
+		if (!retval) retval = sizeof(struct JS_DATA_TYPE);
+	}
+
+/*
+ * Check main tail and move it.
+ */
+
+	if (orig_tail == jd->tail) {
+		new_tail = curl->tail;
+		curl = jd->list;
+		while (curl && curl->tail != jd->tail) {
+			if (ROT(jd->bhead, new_tail, curl->tail) ||
+				(jd->bhead == curl->tail)) new_tail = curl->tail;
+			curl = curl->next;
+		}
+		if (!curl) jd->tail = new_tail;
+	}
+
+	spin_unlock_irqrestore(&js_lock, flags);
+
+	return retval ? retval : written * sizeof(struct js_event);
+}
+
+/*
+ * js_poll() does select() support.
+ */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+
+static unsigned int js_poll(struct file *file, poll_table *wait)
+{
+	struct js_list *curl = file->private_data;
+	unsigned long flags;
+	int retval = 0;
+	poll_wait(file, &curl->dev->wait, wait);
+	spin_lock_irqsave(&js_lock, flags);	
+	if (GOF(curl->tail) != curl->dev->bhead ||
+		curl->startup < curl->dev->num_axes + curl->dev->num_buttons) retval = POLLIN | POLLRDNORM;
+	spin_unlock_irqrestore(&js_lock, flags);
+	return retval;
+}
+
+#else
+
+static int js_select(struct inode *inode, struct file *file, int sel_type, select_table *wait)
+{
+	struct js_list *curl = file->private_data;
+	if (sel_type == SEL_IN) {
+		if (GOF(curl->tail) != curl->dev->bhead) return 1;
+		select_wait(&curl->dev->wait, wait);
+	}
+	return 0;
+}
+
+#endif
+
+/*
+ * js_ioctl handles misc ioctl calls.
+ */
+
+static int js_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct js_list *curl;
+	struct js_dev *jd;
+	int len;
+
+	curl = file->private_data;
+	jd = curl->dev;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+
+	switch (cmd) {
+
+/*
+ * 0.x compatibility
+ */
+
+		case JS_SET_CAL:
+			return copy_from_user(&js_comp_glue.JS_CORR, (struct JS_DATA_TYPE *) arg,
+				sizeof(struct JS_DATA_TYPE)) ? -EFAULT : 0;
+		case JS_GET_CAL:
+			return copy_to_user((struct JS_DATA_TYPE *) arg, &js_comp_glue.JS_CORR,
+				sizeof(struct JS_DATA_TYPE)) ? -EFAULT : 0;
+		case JS_SET_TIMEOUT:
+			return get_user(js_comp_glue.JS_TIMEOUT, (int *) arg);
+		case JS_GET_TIMEOUT:
+			return put_user(js_comp_glue.JS_TIMEOUT, (int *) arg);
+		case JS_SET_TIMELIMIT:
+			return get_user(js_comp_glue.JS_TIMELIMIT, (long *) arg);
+		case JS_GET_TIMELIMIT:
+			return put_user(js_comp_glue.JS_TIMELIMIT, (long *) arg);
+		case JS_SET_ALL:
+			return copy_from_user(&js_comp_glue, (struct JS_DATA_SAVE_TYPE *) arg,
+						sizeof(struct JS_DATA_SAVE_TYPE)) ? -EFAULT : 0;
+		case JS_GET_ALL:
+			return copy_to_user((struct JS_DATA_SAVE_TYPE *) arg, &js_comp_glue,
+						sizeof(struct JS_DATA_SAVE_TYPE)) ? -EFAULT : 0;
+
+/*
+ * 1.x ioctl calls
+ */
+
+		case JSIOCGVERSION:
+			return put_user(JS_VERSION, (__u32 *) arg);
+		case JSIOCGAXES:
+			return put_user(jd->num_axes, (__u8 *) arg);
+		case JSIOCGBUTTONS:
+			return put_user(jd->num_buttons, (__u8 *) arg);
+		case JSIOCSCORR:
+			return copy_from_user(jd->corr, (struct js_corr *) arg,
+						sizeof(struct js_corr) * jd->num_axes) ? -EFAULT : 0;
+		case JSIOCGCORR:
+			return copy_to_user((struct js_corr *) arg, jd->corr,
+						sizeof(struct js_corr) * jd->num_axes) ? -EFAULT : 0;
+		default:
+			if ((cmd & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT)) == JSIOCGNAME(0)) {
+				len = strlen(jd->name) + 1;
+				if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd);
+				if (copy_to_user((char *) arg, jd->name, len)) return -EFAULT;
+				return len;
+			}
+	}
+
+#else
+
+	switch (cmd) {
+
+/*
+ * 0.x compatibility
+ */
+
+		case JS_SET_CAL:
+			if (verify_area(VERIFY_READ, (struct JS_DATA_TYPE *) arg,
+				sizeof(struct JS_DATA_TYPE))) return -EFAULT;
+			memcpy_fromfs(&js_comp_glue.JS_CORR, (struct JS_DATA_SAVE_TYPE *) arg,
+				sizeof(struct JS_DATA_TYPE));
+			return 0;
+		case JS_GET_CAL:
+			if (verify_area(VERIFY_WRITE, (struct JS_DATA_TYPE *) arg,
+				sizeof(struct JS_DATA_TYPE))) return -EFAULT;
+			memcpy_tofs((struct JS_DATA_SAVE_TYPE *) arg, &js_comp_glue.JS_CORR,
+				sizeof(struct JS_DATA_TYPE));
+			return 0;
+		case JS_SET_TIMEOUT:
+			if (verify_area(VERIFY_READ, (int *) arg, sizeof(int))) return -EFAULT;
+			js_comp_glue.JS_TIMEOUT = get_user((int *) arg);
+			return 0;
+		case JS_GET_TIMEOUT:
+			if (verify_area(VERIFY_WRITE, (int *) arg, sizeof(int))) return -EFAULT;
+			put_user(js_comp_glue.JS_TIMEOUT, (int *) arg);
+			return 0;
+		case JS_SET_TIMELIMIT:
+			if (verify_area(VERIFY_READ, (long *) arg, sizeof(long))) return -EFAULT;
+			js_comp_glue.JS_TIMELIMIT = get_user((long *) arg);
+			return 0;
+		case JS_GET_TIMELIMIT:
+			if (verify_area(VERIFY_WRITE, (long *) arg, sizeof(long))) return -EFAULT;
+			put_user(js_comp_glue.JS_TIMELIMIT, (long *) arg);
+			return 0;
+		case JS_SET_ALL:
+			if (verify_area(VERIFY_READ, (struct JS_DATA_SAVE_TYPE *) arg,
+				sizeof(struct JS_DATA_SAVE_TYPE))) return -EFAULT;
+			memcpy_fromfs(&js_comp_glue, (struct JS_DATA_SAVE_TYPE *) arg,
+				sizeof(struct JS_DATA_SAVE_TYPE));
+			return 0;
+		case JS_GET_ALL:
+			if (verify_area(VERIFY_WRITE, (struct JS_DATA_SAVE_TYPE *) arg,
+				sizeof(struct JS_DATA_SAVE_TYPE))) return -EFAULT;
+			memcpy_tofs((struct JS_DATA_SAVE_TYPE *) arg, &js_comp_glue,
+				sizeof(struct JS_DATA_SAVE_TYPE));
+			return 0;
+
+/*
+ * 1.x ioctl calls
+ */
+
+		case JSIOCGVERSION:
+			if (verify_area(VERIFY_WRITE, (__u32 *) arg, sizeof(__u32))) return -EFAULT;
+			put_user(JS_VERSION, (__u32 *) arg);
+			return 0;
+		case JSIOCGAXES:
+			if (verify_area(VERIFY_WRITE, (__u8 *) arg, sizeof(__u8))) return -EFAULT;
+			put_user(jd->num_axes, (__u8 *) arg);
+			return 0;
+		case JSIOCGBUTTONS:
+			if (verify_area(VERIFY_WRITE, (__u8 *) arg, sizeof(__u8))) return -EFAULT;
+			put_user(jd->num_buttons, (__u8 *) arg);
+			return 0;
+		case JSIOCSCORR:
+			if (verify_area(VERIFY_READ, (struct js_corr *) arg,
+				sizeof(struct js_corr) * jd->num_axes)) return -EFAULT;
+			memcpy_fromfs(jd->corr, (struct js_corr *) arg,
+				sizeof(struct js_corr) * jd->num_axes);
+			return 0;
+		case JSIOCGCORR:
+			if (verify_area(VERIFY_WRITE, (struct js_corr *) arg,
+				sizeof(struct js_corr) * jd->num_axes)) return -EFAULT;
+			memcpy_tofs((struct js_corr *) arg,
+				jd->corr, sizeof(struct js_corr) * jd->num_axes);
+			return 0;
+		default:
+			if ((cmd & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT)) == JSIOCGNAME(0)) {
+				len = strlen(jd->name) + 1;
+				if (verify_area(VERIFY_WRITE, (char *) arg, len)) return -EFAULT;
+				if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd);
+				memcpy_tofs((char *) arg, jd->name, len);
+				return len;
+			}
+	}
+
+#endif
+
+	return -EINVAL;
+}
+
+/*
+ * js_open() performs necessary initialization and adds
+ * an entry to the linked list.
+ */
+
+static int js_open(struct inode *inode, struct file *file)
+{
+	struct js_list *curl;
+	struct js_dev *jd = js_dev;
+	int i = MINOR(inode->i_rdev);
+	unsigned long flags;
+
+	if (MAJOR(inode->i_rdev) != JOYSTICK_MAJOR)
+		return -EINVAL;
+
+	while (i > 0 && jd) {
+		jd = jd->next;
+		i--;
+	}
+
+	if (!jd) return -ENODEV;
+
+	jd->open(jd);
+
+	MOD_INC_USE_COUNT;
+	if (!js_use_count++) js_do_timer(0);
+
+	curl = jd->list;
+
+	spin_lock_irqsave(&js_lock, flags);
+
+	jd->list = kmalloc(sizeof(struct js_list), GFP_KERNEL);
+	jd->list->next = curl;
+	jd->list->dev = jd;
+	jd->list->startup = 0;
+	jd->list->tail = GOB(jd->bhead);
+
+	spin_unlock_irqrestore(&js_lock, flags);
+
+	file->private_data = jd->list;
+
+	return 0;
+}
+
+/*
+ * js_release() removes an entry from list and deallocates memory
+ * used by it.
+ */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+static int js_release(struct inode *inode, struct file *file)
+#else
+static void js_release(struct inode *inode, struct file *file)
+#endif
+{
+	struct js_list *curl = file->private_data;
+	struct js_dev *jd = curl->dev;
+	struct js_list **curp = &jd->list;
+	int new_tail;
+	unsigned long flags;
+
+	spin_lock_irqsave(&js_lock, flags);
+
+	while (*curp && (*curp != curl)) curp = &((*curp)->next);
+	*curp = (*curp)->next;
+
+	if (jd->list)
+	if (curl->tail == jd->tail) {
+		curl = jd->list;
+		new_tail = curl->tail;
+		while (curl && curl->tail != jd->tail) {
+			if (ROT(jd->bhead, new_tail, curl->tail) ||
+			       (jd->bhead == curl->tail)) new_tail = curl->tail;
+			curl = curl->next;
+		}
+		if (!curl) jd->tail = new_tail;
+	}
+
+	spin_unlock_irqrestore(&js_lock, flags);
+
+	kfree(file->private_data);
+
+	if (!--js_use_count) del_timer(&js_timer);
+	MOD_DEC_USE_COUNT;
+
+	jd->close(jd);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+	return 0;
+#endif
+}
+
+/*
+ * js_dump_mem() dumps all data structures in memory.
+ * It's used for debugging only.
+ */
+
+#if 0
+static void js_dump_mem(void)
+{
+
+	struct js_port *curp = js_port;
+	struct js_dev *curd = js_dev;
+	int i;
+
+	printk(",--- Dumping Devices:\n");
+	printk("| js_dev = %x\n", (int) js_dev);
+
+	while (curd) {
+		printk("|  %s-device %x, next %x axes %d, buttons %d, port %x - %#x\n",
+			curd->next ? "|":"`",
+			(int) curd, (int) curd->next, curd->num_axes, curd->num_buttons, (int) curd->port, curd->port->io);
+		curd = curd->next;
+	}
+
+	printk(">--- Dumping ports:\n");
+	printk("| js_port = %x\n", (int) js_port);
+
+	while (curp) {
+		printk("|  %s-port %x, next %x, io %#x, devices %d\n",
+			curp->next ? "|":"`",
+			(int) curp, (int) curp->next, curp->io, curp->ndevs);
+		for (i = 0; i < curp->ndevs; i++) {
+			curd = curp->devs[i];
+			if (curd)
+			printk("|  %s %s-device %x, next %x axes %d, buttons %d, port %x\n",
+				curp->next ? "|":" ", (i < curp->ndevs-1) ? "|":"`",
+				(int) curd, (int) curd->next, curd->num_axes, curd->num_buttons, (int) curd->port);
+			else
+			printk("|  %s %s-device %x, not there\n",
+				curp->next ? "|":" ", (i < curp->ndevs-1) ? "|":"`", (int) curd);
+
+		}
+		curp = curp->next;
+	}
+
+	printk("`--- Done\n");
+}
+#endif
+
+
+struct js_port *js_register_port(struct js_port *port,
+				void *info, int devs, int infos, js_read_func read)
+{
+	struct js_port **ptrp = &js_port;
+	struct js_port *curp;
+	int i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&js_lock, flags);
+
+	while (*ptrp) ptrp=&((*ptrp)->next);
+	*ptrp = curp = kmalloc(sizeof(struct js_port), GFP_KERNEL);
+
+	curp->next = NULL;
+	curp->prev = port;
+	curp->read = read;
+	curp->ndevs = devs;
+
+	curp->devs = kmalloc(devs * sizeof(struct js_dev*), GFP_KERNEL);
+
+	for (i = 0; i < devs; i++)
+		curp->devs[i] = NULL;
+
+	curp->axes = kmalloc(devs * sizeof(int*), GFP_KERNEL);
+	curp->buttons = kmalloc(devs * sizeof(int*), GFP_KERNEL);
+	curp->corr = kmalloc(devs * sizeof(struct js_corr*), GFP_KERNEL);
+
+	if (infos) {
+		curp->info = kmalloc(infos, GFP_KERNEL);
+		memcpy(curp->info, info, infos);
+	} else {
+		curp->info = NULL;
+	}
+
+	spin_unlock_irqrestore(&js_lock, flags);
+
+	return curp;
+}
+
+struct js_port *js_unregister_port(struct js_port *port)
+{
+	struct js_port **curp = &js_port;
+	struct js_port *prev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&js_lock, flags);
+
+	while (*curp && (*curp != port)) curp = &((*curp)->next);
+	*curp = (*curp)->next;
+
+	prev = port->prev;
+	kfree(port->devs);
+	kfree(port->axes);
+	kfree(port->buttons);
+	kfree(port->corr);
+	if (port->info) kfree(port->info);
+	kfree(port);
+
+	spin_unlock_irqrestore(&js_lock, flags);
+
+	return prev;
+}
+
+int js_register_device(struct js_port *port, int number, int axes, int buttons, char *name,
+					js_ops_func open, js_ops_func close)
+{
+	struct js_dev **ptrd = &js_dev;
+	struct js_dev *curd;
+	int i = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&js_lock, flags);
+
+	while (*ptrd) {
+		ptrd=&(*ptrd)->next;
+		i++;
+	}
+
+	*ptrd = curd = kmalloc(sizeof(struct js_dev), GFP_KERNEL);
+
+	curd->next = NULL;
+	curd->list = NULL;
+	curd->port = port;
+	curd->wait = NULL;
+	curd->open = open;
+	curd->close = close;
+
+	curd->ahead = 0;
+	curd->bhead = 0;
+	curd->tail = JS_BUFF_SIZE - 1;
+	curd->num_axes = axes;
+	curd->num_buttons = buttons;
+
+	curd->name = kmalloc(strlen(name) + 1, GFP_KERNEL);
+	strcpy(curd->name, name);
+
+	curd->cur.axes = kmalloc(axes * sizeof(int), GFP_KERNEL);
+	curd->cur.buttons = kmalloc((((buttons - 1) >> 5) + 1) * sizeof(int), GFP_KERNEL);
+	curd->new.axes = kmalloc(axes * sizeof(int), GFP_KERNEL);
+	curd->new.buttons = kmalloc((((buttons -1 ) >> 5) + 1) * sizeof(int), GFP_KERNEL);
+	curd->corr = kmalloc(axes * sizeof(struct js_corr), GFP_KERNEL);
+
+	port->devs[number] = curd;
+	port->axes[number] = curd->new.axes;
+	port->buttons[number] = curd->new.buttons;
+	port->corr[number] = curd->corr;
+
+	spin_unlock_irqrestore(&js_lock, flags);	
+
+	return i;
+}
+
+void js_unregister_device(struct js_dev *dev)
+{
+	struct js_dev **curd = &js_dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&js_lock, flags);
+
+	while (*curd && (*curd != dev)) curd = &((*curd)->next);
+	*curd = (*curd)->next;
+
+	spin_unlock_irqrestore(&js_lock, flags);	
+
+	kfree(dev->cur.axes);
+	kfree(dev->new.axes);
+	kfree(dev->cur.buttons);
+	kfree(dev->new.buttons);
+	kfree(dev->corr);
+	kfree(dev->name);
+	kfree(dev);
+}
+
+/*
+ * The operations structure.
+ */
+
+static struct file_operations js_fops =
+{
+	read:		js_read,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
+	poll:		js_poll,
+#else
+	select:		js_select,
+#endif
+	ioctl:		js_ioctl,
+	open:		js_open,
+	release:	js_release,
+};
+
+/*
+ * js_init() registers the driver and calls the probe function.
+ * also initializes some crucial variables.
+ */
+
+#ifdef MODULE
+int init_module(void)
+#else
+int __init js_init(void)
+#endif
+{
+	int result;
+
+	js_setup_time();
+
+	if (register_chrdev(JOYSTICK_MAJOR, "js", &js_fops)) {
+		printk(KERN_ERR "js: unable to get major %d for joystick\n", JOYSTICK_MAJOR);
+		return -EBUSY;
+	}
+
+	spin_lock_init(&js_lock);
+
+	init_timer(&js_timer);
+	js_timer.function = js_do_timer;
+	js_timer.data = 1;
+
+	memset(&js_comp_glue, 0, sizeof(struct JS_DATA_SAVE_TYPE));
+	js_comp_glue.JS_TIMEOUT = JS_DEF_TIMEOUT;
+	js_comp_glue.JS_TIMELIMIT = JS_DEF_TIMELIMIT;
+
+#ifdef MODULE
+	result = 0;
+#else
+	result = -ENODEV;
+#ifdef CONFIG_JOY_LIGHTNING
+	if (!js_l4_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_SIDEWINDER
+	if (!js_sw_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_ASSASIN
+	if (!js_as_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_LOGITECH
+	if (!js_lt_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_THRUSTMASTER
+	if (!js_tm_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_GRAVIS
+	if (!js_gr_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_ANALOG
+	if (!js_an_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_CONSOLE
+	if (!js_console_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_DB9
+	if (!js_db9_init()) result = 0;
+#endif
+#ifdef CONFIG_JOY_AMIGA
+	if (!js_am_init()) result = 0;
+#endif
+	if (result) printk(KERN_ERR "js: no joysticks found\n");
+#endif
+
+	return result;
+}
+
+/*
+ * cleanup_module() handles module removal.
+ */
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+	del_timer(&js_timer);
+	if (unregister_chrdev(JOYSTICK_MAJOR, "js"))
+		printk(KERN_ERR "js: can't unregister device\n");
+}
+#endif

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