Release 4.11 drivers/input/joydev.c
/*
* Joystick device driver for the input driver suite.
*
* Copyright (c) 1999-2002 Vojtech Pavlik
* Copyright (c) 1999 Colin Van Dyke
*
* 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/joystick.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/cdev.h>
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
MODULE_DESCRIPTION("Joystick device interfaces");
MODULE_SUPPORTED_DEVICE("input/js");
MODULE_LICENSE("GPL");
#define JOYDEV_MINOR_BASE 0
#define JOYDEV_MINORS 16
#define JOYDEV_BUFFER_SIZE 64
struct joydev {
int open;
struct input_handle handle;
wait_queue_head_t wait;
struct list_head client_list;
spinlock_t client_lock; /* protects client_list */
struct mutex mutex;
struct device dev;
struct cdev cdev;
bool exist;
struct js_corr corr[ABS_CNT];
struct JS_DATA_SAVE_TYPE glue;
int nabs;
int nkey;
__u16 keymap[KEY_MAX - BTN_MISC + 1];
__u16 keypam[KEY_MAX - BTN_MISC + 1];
__u8 absmap[ABS_CNT];
__u8 abspam[ABS_CNT];
__s16 abs[ABS_CNT];
};
struct joydev_client {
struct js_event buffer[JOYDEV_BUFFER_SIZE];
int head;
int tail;
int startup;
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct fasync_struct *fasync;
struct joydev *joydev;
struct list_head node;
};
static int joydev_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;
}
return clamp(value, -32767, 32767);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 111 | 94.87% | 2 | 50.00% |
Dmitry Torokhov | 6 | 5.13% | 2 | 50.00% |
Total | 117 | 100.00% | 4 | 100.00% |
static void joydev_pass_event(struct joydev_client *client,
struct js_event *event)
{
struct joydev *joydev = client->joydev;
/*
* IRQs already disabled, just acquire the lock
*/
spin_lock(&client->buffer_lock);
client->buffer[client->head] = *event;
if (client->startup == joydev->nabs + joydev->nkey) {
client->head++;
client->head &= JOYDEV_BUFFER_SIZE - 1;
if (client->tail == client->head)
client->startup = 0;
}
spin_unlock(&client->buffer_lock);
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 111 | 100.00% | 1 | 100.00% |
Total | 111 | 100.00% | 1 | 100.00% |
static void joydev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct joydev *joydev = handle->private;
struct joydev_client *client;
struct js_event event;
switch (type) {
case EV_KEY:
if (code < BTN_MISC || value == 2)
return;
event.type = JS_EVENT_BUTTON;
event.number = joydev->keymap[code - BTN_MISC];
event.value = value;
break;
case EV_ABS:
event.type = JS_EVENT_AXIS;
event.number = joydev->absmap[code];
event.value = joydev_correct(value,
&joydev->corr[event.number]);
if (event.value == joydev->abs[event.number])
return;
joydev->abs[event.number] = event.value;
break;
default:
return;
}
event.time = jiffies_to_msecs(jiffies);
rcu_read_lock();
list_for_each_entry_rcu(client, &joydev->client_list, node)
joydev_pass_event(client, &event);
rcu_read_unlock();
wake_up_interruptible(&joydev->wait);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 172 | 87.76% | 4 | 40.00% |
Dmitry Torokhov | 16 | 8.16% | 3 | 30.00% |
Vojtech Pavlik | 7 | 3.57% | 2 | 20.00% |
Tobias Klauser | 1 | 0.51% | 1 | 10.00% |
Total | 196 | 100.00% | 10 | 100.00% |
static int joydev_fasync(int fd, struct file *file, int on)
{
struct joydev_client *client = file->private_data;
return fasync_helper(fd, file, on, &client->fasync);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 37 | 90.24% | 1 | 33.33% |
Dmitry Torokhov | 3 | 7.32% | 1 | 33.33% |
Jonathan Corbet | 1 | 2.44% | 1 | 33.33% |
Total | 41 | 100.00% | 3 | 100.00% |
static void joydev_free(struct device *dev)
{
struct joydev *joydev = container_of(dev, struct joydev, dev);
input_put_device(joydev->handle.dev);
kfree(joydev);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 26 | 65.00% | 2 | 66.67% |
Christoph Hellwig | 14 | 35.00% | 1 | 33.33% |
Total | 40 | 100.00% | 3 | 100.00% |
static void joydev_attach_client(struct joydev *joydev,
struct joydev_client *client)
{
spin_lock(&joydev->client_lock);
list_add_tail_rcu(&client->node, &joydev->client_list);
spin_unlock(&joydev->client_lock);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 28 | 62.22% | 2 | 50.00% |
Linus Torvalds (pre-git) | 14 | 31.11% | 1 | 25.00% |
Vojtech Pavlik | 3 | 6.67% | 1 | 25.00% |
Total | 45 | 100.00% | 4 | 100.00% |
static void joydev_detach_client(struct joydev *joydev,
struct joydev_client *client)
{
spin_lock(&joydev->client_lock);
list_del_rcu(&client->node);
spin_unlock(&joydev->client_lock);
synchronize_rcu();
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 31 | 72.09% | 3 | 50.00% |
Linus Torvalds (pre-git) | 12 | 27.91% | 3 | 50.00% |
Total | 43 | 100.00% | 6 | 100.00% |
static void joydev_refresh_state(struct joydev *joydev)
{
struct input_dev *dev = joydev->handle.dev;
int i, val;
for (i = 0; i < joydev->nabs; i++) {
val = input_abs_get_val(dev, joydev->abspam[i]);
joydev->abs[i] = joydev_correct(val, &joydev->corr[i]);
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Raphaël Assénat | 78 | 100.00% | 1 | 100.00% |
Total | 78 | 100.00% | 1 | 100.00% |
static int joydev_open_device(struct joydev *joydev)
{
int retval;
retval = mutex_lock_interruptible(&joydev->mutex);
if (retval)
return retval;
if (!joydev->exist)
retval = -ENODEV;
else if (!joydev->open++) {
retval = input_open_device(&joydev->handle);
if (retval)
joydev->open--;
else
joydev_refresh_state(joydev);
}
mutex_unlock(&joydev->mutex);
return retval;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 58 | 64.44% | 3 | 50.00% |
Linus Torvalds (pre-git) | 15 | 16.67% | 1 | 16.67% |
Oliver Neukum | 11 | 12.22% | 1 | 16.67% |
Raphaël Assénat | 6 | 6.67% | 1 | 16.67% |
Total | 90 | 100.00% | 6 | 100.00% |
static void joydev_close_device(struct joydev *joydev)
{
mutex_lock(&joydev->mutex);
if (joydev->exist && !--joydev->open)
input_close_device(&joydev->handle);
mutex_unlock(&joydev->mutex);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 27 | 57.45% | 3 | 42.86% |
Linus Torvalds (pre-git) | 17 | 36.17% | 3 | 42.86% |
Vojtech Pavlik | 3 | 6.38% | 1 | 14.29% |
Total | 47 | 100.00% | 7 | 100.00% |
/*
* Wake up users waiting for IO so they can disconnect from
* dead device.
*/
static void joydev_hangup(struct joydev *joydev)
{
struct joydev_client *client;
spin_lock(&joydev->client_lock);
list_for_each_entry(client, &joydev->client_list, node)
kill_fasync(&client->fasync, SIGIO, POLL_HUP);
spin_unlock(&joydev->client_lock);
wake_up_interruptible(&joydev->wait);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 59 | 98.33% | 4 | 80.00% |
Linus Torvalds (pre-git) | 1 | 1.67% | 1 | 20.00% |
Total | 60 | 100.00% | 5 | 100.00% |
static int joydev_release(struct inode *inode, struct file *file)
{
struct joydev_client *client = file->private_data;
struct joydev *joydev = client->joydev;
joydev_detach_client(joydev, client);
kfree(client);
joydev_close_device(joydev);
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 48 | 88.89% | 1 | 50.00% |
Linus Torvalds (pre-git) | 6 | 11.11% | 1 | 50.00% |
Total | 54 | 100.00% | 2 | 100.00% |
static int joydev_open(struct inode *inode, struct file *file)
{
struct joydev *joydev =
container_of(inode->i_cdev, struct joydev, cdev);
struct joydev_client *client;
int error;
client = kzalloc(sizeof(struct joydev_client), GFP_KERNEL);
if (!client)
return -ENOMEM;
spin_lock_init(&client->buffer_lock);
client->joydev = joydev;
joydev_attach_client(joydev, client);
error = joydev_open_device(joydev);
if (error)
goto err_free_client;
file->private_data = client;
nonseekable_open(inode, file);
return 0;
err_free_client:
joydev_detach_client(joydev, client);
kfree(client);
return error;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 129 | 98.47% | 3 | 75.00% |
Linus Torvalds (pre-git) | 2 | 1.53% | 1 | 25.00% |
Total | 131 | 100.00% | 4 | 100.00% |
static int joydev_generate_startup_event(struct joydev_client *client,
struct input_dev *input,
struct js_event *event)
{
struct joydev *joydev = client->joydev;
int have_event;
spin_lock_irq(&client->buffer_lock);
have_event = client->startup < joydev->nabs + joydev->nkey;
if (have_event) {
event->time = jiffies_to_msecs(jiffies);
if (client->startup < joydev->nkey) {
event->type = JS_EVENT_BUTTON | JS_EVENT_INIT;
event->number = client->startup;
event->value = !!test_bit(joydev->keypam[event->number],
input->key);
} else {
event->type = JS_EVENT_AXIS | JS_EVENT_INIT;
event->number = client->startup - joydev->nkey;
event->value = joydev->abs[event->number];
}
client->startup++;
}
spin_unlock_irq(&client->buffer_lock);
return have_event;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 166 | 96.51% | 1 | 50.00% |
Linus Torvalds (pre-git) | 6 | 3.49% | 1 | 50.00% |
Total | 172 | 100.00% | 2 | 100.00% |
static int joydev_fetch_next_event(struct joydev_client *client,
struct js_event *event)
{
int have_event;
spin_lock_irq(&client->buffer_lock);
have_event = client->head != client->tail;
if (have_event) {
*event = client->buffer[client->tail++];
client->tail &= JOYDEV_BUFFER_SIZE - 1;
}
spin_unlock_irq(&client->buffer_lock);
return have_event;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 72 | 96.00% | 1 | 50.00% |
Linus Torvalds (pre-git) | 3 | 4.00% | 1 | 50.00% |
Total | 75 | 100.00% | 2 | 100.00% |
/*
* Old joystick interface
*/
static ssize_t joydev_0x_read(struct joydev_client *client,
struct input_dev *input,
char __user *buf)
{
struct joydev *joydev = client->joydev;
struct JS_DATA_TYPE data;
int i;
spin_lock_irq(&input->event_lock);
/*
* Get device state
*/
for (data.buttons = i = 0; i < 32 && i < joydev->nkey; i++)
data.buttons |=
test_bit(joydev->keypam[i], input->key) ? (1 << i) : 0;
data.x = (joydev->abs[0] / 256 + 128) >> joydev->glue.JS_CORR.x;
data.y = (joydev->abs[1] / 256 + 128) >> joydev->glue.JS_CORR.y;
/*
* Reset reader's event queue
*/
spin_lock(&client->buffer_lock);
client->startup = 0;
client->tail = client->head;
spin_unlock(&client->buffer_lock);
spin_unlock_irq(&input->event_lock);
if (copy_to_user(buf, &data, sizeof(struct JS_DATA_TYPE)))
return -EFAULT;
return sizeof(struct JS_DATA_TYPE);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 93 | 44.08% | 2 | 33.33% |
Dmitry Torokhov | 91 | 43.13% | 2 | 33.33% |
Linus Torvalds | 26 | 12.32% | 1 | 16.67% |
Al Viro | 1 | 0.47% | 1 | 16.67% |
Total | 211 | 100.00% | 6 | 100.00% |
static inline int joydev_data_pending(struct joydev_client *client)
{
struct joydev *joydev = client->joydev;
return client->startup < joydev->nabs + joydev->nkey ||
client->head != client->tail;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 42 | 100.00% | 1 | 100.00% |
Total | 42 | 100.00% | 1 | 100.00% |
static ssize_t joydev_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct joydev_client *client = file->private_data;
struct joydev *joydev = client->joydev;
struct input_dev *input = joydev->handle.dev;
struct js_event event;
int retval;
if (!joydev->exist)
return -ENODEV;
if (count < sizeof(struct js_event))
return -EINVAL;
if (count == sizeof(struct JS_DATA_TYPE))
return joydev_0x_read(client, input, buf);
if (!joydev_data_pending(client) && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
retval = wait_event_interruptible(joydev->wait,
!joydev->exist || joydev_data_pending(client));
if (retval)
return retval;
if (!joydev->exist)
return -ENODEV;
while (retval + sizeof(struct js_event) <= count &&
joydev_generate_startup_event(client, input, &event)) {
if (copy_to_user(buf + retval, &event, sizeof(struct js_event)))
return -EFAULT;
retval += sizeof(struct js_event);
}
while (retval + sizeof(struct js_event) <= count &&
joydev_fetch_next_event(client, &event)) {
if (copy_to_user(buf + retval, &event, sizeof(struct js_event)))
return -EFAULT;
retval += sizeof(struct js_event);
}
return retval;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 131 | 48.52% | 1 | 16.67% |
Dmitry Torokhov | 117 | 43.33% | 3 | 50.00% |
Vojtech Pavlik | 22 | 8.15% | 2 | 33.33% |
Total | 270 | 100.00% | 6 | 100.00% |
/* No kernel lock - fine */
static unsigned int joydev_poll(struct file *file, poll_table *wait)
{
struct joydev_client *client = file->private_data;
struct joydev *joydev = client->joydev;
poll_wait(file, &joydev->wait, wait);
return (joydev_data_pending(client) ? (POLLIN | POLLRDNORM) : 0) |
(joydev->exist ? 0 : (POLLHUP | POLLERR));
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 40 | 52.63% | 1 | 25.00% |
Vojtech Pavlik | 21 | 27.63% | 1 | 25.00% |
Dmitry Torokhov | 15 | 19.74% | 2 | 50.00% |
Total | 76 | 100.00% | 4 | 100.00% |
static int joydev_handle_JSIOCSAXMAP(struct joydev *joydev,
void __user *argp, size_t len)
{
__u8 *abspam;
int i;
int retval = 0;
len = min(len, sizeof(joydev->abspam));
/* Validate the map. */
abspam = memdup_user(argp, len);
if (IS_ERR(abspam))
return PTR_ERR(abspam);
for (i = 0; i < joydev->nabs; i++) {
if (abspam[i] > ABS_MAX) {
retval = -EINVAL;
goto out;
}
}
memcpy(joydev->abspam, abspam, len);
for (i = 0; i < joydev->nabs; i++)
joydev->absmap[joydev->abspam[i]] = i;
out:
kfree(abspam);
return retval;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Stephen Kitt | 115 | 74.68% | 1 | 25.00% |
Kenneth Waters | 29 | 18.83% | 1 | 25.00% |
Javier Martinez Canillas | 10 | 6.49% | 2 | 50.00% |
Total | 154 | 100.00% | 4 | 100.00% |
static int joydev_handle_JSIOCSBTNMAP(struct joydev *joydev,
void __user *argp, size_t len)
{
__u16 *keypam;
int i;
int retval = 0;
len = min(len, sizeof(joydev->keypam));
/* Validate the map. */
keypam = memdup_user(argp, len);
if (IS_ERR(keypam))
return PTR_ERR(keypam);
for (i = 0; i < joydev->nkey; i++) {
if (keypam[i] > KEY_MAX || keypam[i] < BTN_MISC) {
retval = -EINVAL;
goto out;
}
}
memcpy(joydev->keypam, keypam, len);
for (i = 0; i < joydev->nkey; i++)
joydev->keymap[keypam[i] - BTN_MISC] = i;
out:
kfree(keypam);
return retval;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Stephen Kitt | 151 | 93.79% | 1 | 33.33% |
Javier Martinez Canillas | 10 | 6.21% | 2 | 66.67% |
Total | 161 | 100.00% | 3 | 100.00% |
static int joydev_ioctl_common(struct joydev *joydev,
unsigned int cmd, void __user *argp)
{
struct input_dev *dev = joydev->handle.dev;
size_t len;
int i;
const char *name;
/* Process fixed-sized commands. */
switch (cmd) {
case JS_SET_CAL:
return copy_from_user(&joydev->glue.JS_CORR, argp,
sizeof(joydev->glue.JS_CORR)) ? -EFAULT : 0;
case JS_GET_CAL:
return copy_to_user(argp, &joydev->glue.JS_CORR,
sizeof(joydev->glue.JS_CORR)) ? -EFAULT : 0;
case JS_SET_TIMEOUT:
return get_user(joydev->glue.JS_TIMEOUT, (s32 __user *) argp);
case JS_GET_TIMEOUT:
return put_user(joydev->glue.JS_TIMEOUT, (s32 __user *) argp);
case JSIOCGVERSION:
return put_user(JS_VERSION, (__u32 __user *) argp);
case JSIOCGAXES:
return put_user(joydev->nabs, (__u8 __user *) argp);
case JSIOCGBUTTONS:
return put_user(joydev->nkey, (__u8 __user *) argp);
case JSIOCSCORR:
if (copy_from_user(joydev->corr, argp,
sizeof(joydev->corr[0]) * joydev->nabs))
return -EFAULT;
for (i = 0; i < joydev->nabs; i++) {
int val = input_abs_get_val(dev, joydev->abspam[i]);
joydev->abs[i] = joydev_correct(val, &joydev->corr[i]);
}
return 0;
case JSIOCGCORR:
return copy_to_user(argp, joydev->corr,
sizeof(joydev->corr[0]) * joydev->nabs) ? -EFAULT : 0;
}
/*
* Process variable-sized commands (the axis and button map commands
* are considered variable-sized to decouple them from the values of
* ABS_MAX and KEY_MAX).
*/
switch (cmd & ~IOCSIZE_MASK) {
case (JSIOCSAXMAP & ~IOCSIZE_MASK):
return joydev_handle_JSIOCSAXMAP(joydev, argp, _IOC_SIZE(cmd));
case (JSIOCGAXMAP & ~IOCSIZE_MASK):
len = min_t(size_t, _IOC_SIZE(cmd), sizeof(joydev->abspam));
return copy_to_user(argp, joydev->abspam, len) ? -EFAULT : len;
case (JSIOCSBTNMAP & ~IOCSIZE_MASK):
return joydev_handle_JSIOCSBTNMAP(joydev, argp, _IOC_SIZE(cmd));
case (JSIOCGBTNMAP & ~IOCSIZE_MASK):
len = min_t(size_t, _IOC_SIZE(cmd), sizeof(joydev->keypam));
return copy_to_user(argp, joydev->keypam, len) ? -EFAULT : len;
case JSIOCGNAME(0):
name = dev->name;
if (!name)
return 0;
len = min_t(size_t, _IOC_SIZE(cmd), strlen(name) + 1);
return copy_to_user(argp, name, len) ? -EFAULT : len;
}
return -EINVAL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 242 | 46.72% | 2 | 16.67% |
Stephen Kitt | 119 | 22.97% | 2 | 16.67% |
Linus Torvalds | 47 | 9.07% | 1 | 8.33% |
Glenn Burkhardt | 45 | 8.69% | 1 | 8.33% |
Jeremy Fitzhardinge | 35 | 6.76% | 1 | 8.33% |
Al Viro | 13 | 2.51% | 1 | 8.33% |
Daniel Mack | 11 | 2.12% | 2 | 16.67% |
Thadeu Lima de Souza Cascardo | 3 | 0.58% | 1 | 8.33% |
Dmitry Torokhov | 3 | 0.58% | 1 | 8.33% |
Total | 518 | 100.00% | 12 | 100.00% |
#ifdef CONFIG_COMPAT
static long joydev_compat_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct joydev_client *client = file->private_data;
struct joydev *joydev = client->joydev;
void __user *argp = (void __user *)arg;
s32 tmp32;
struct JS_DATA_SAVE_TYPE_32 ds32;
int retval;
retval = mutex_lock_interruptible(&joydev