Skip to content

Commit

Permalink
Input: refactor evdev 32bit compat to be shareable with uinput
Browse files Browse the repository at this point in the history
Currently, evdev has working 32bit compatibility and uinput does not. uinput
needs the input_event code that evdev uses, so let's refactor it so it can
be shared.

[dtor@mail.ru: add fix for force feedback compat issues]
Signed-off-by: Philip Langdale <philipl@overt.org>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
  • Loading branch information
Philip Langdale authored and Dmitry Torokhov committed Oct 28, 2008
1 parent 49fdf67 commit 2d56f3a
Show file tree
Hide file tree
Showing 5 changed files with 383 additions and 217 deletions.
2 changes: 1 addition & 1 deletion drivers/input/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Each configuration option enables a list of files.

obj-$(CONFIG_INPUT) += input-core.o
input-core-objs := input.o ff-core.o
input-core-objs := input.o input-compat.o ff-core.o

obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o
obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o
Expand Down
197 changes: 8 additions & 189 deletions drivers/input/evdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include <linux/input.h>
#include <linux/major.h>
#include <linux/device.h>
#include <linux/compat.h>
#include "input-compat.h"

struct evdev {
int exist;
Expand Down Expand Up @@ -291,187 +291,6 @@ static int evdev_open(struct inode *inode, struct file *file)
return error;
}

#ifdef CONFIG_COMPAT

struct input_event_compat {
struct compat_timeval time;
__u16 type;
__u16 code;
__s32 value;
};

struct ff_periodic_effect_compat {
__u16 waveform;
__u16 period;
__s16 magnitude;
__s16 offset;
__u16 phase;

struct ff_envelope envelope;

__u32 custom_len;
compat_uptr_t custom_data;
};

struct ff_effect_compat {
__u16 type;
__s16 id;
__u16 direction;
struct ff_trigger trigger;
struct ff_replay replay;

union {
struct ff_constant_effect constant;
struct ff_ramp_effect ramp;
struct ff_periodic_effect_compat periodic;
struct ff_condition_effect condition[2]; /* One for each axis */
struct ff_rumble_effect rumble;
} u;
};

/* Note to the author of this code: did it ever occur to
you why the ifdefs are needed? Think about it again. -AK */
#ifdef CONFIG_X86_64
# define COMPAT_TEST is_compat_task()
#elif defined(CONFIG_IA64)
# define COMPAT_TEST IS_IA32_PROCESS(task_pt_regs(current))
#elif defined(CONFIG_S390)
# define COMPAT_TEST test_thread_flag(TIF_31BIT)
#elif defined(CONFIG_MIPS)
# define COMPAT_TEST test_thread_flag(TIF_32BIT_ADDR)
#else
# define COMPAT_TEST test_thread_flag(TIF_32BIT)
#endif

static inline size_t evdev_event_size(void)
{
return COMPAT_TEST ?
sizeof(struct input_event_compat) : sizeof(struct input_event);
}

static int evdev_event_from_user(const char __user *buffer,
struct input_event *event)
{
if (COMPAT_TEST) {
struct input_event_compat compat_event;

if (copy_from_user(&compat_event, buffer,
sizeof(struct input_event_compat)))
return -EFAULT;

event->time.tv_sec = compat_event.time.tv_sec;
event->time.tv_usec = compat_event.time.tv_usec;
event->type = compat_event.type;
event->code = compat_event.code;
event->value = compat_event.value;

} else {
if (copy_from_user(event, buffer, sizeof(struct input_event)))
return -EFAULT;
}

return 0;
}

static int evdev_event_to_user(char __user *buffer,
const struct input_event *event)
{
if (COMPAT_TEST) {
struct input_event_compat compat_event;

compat_event.time.tv_sec = event->time.tv_sec;
compat_event.time.tv_usec = event->time.tv_usec;
compat_event.type = event->type;
compat_event.code = event->code;
compat_event.value = event->value;

if (copy_to_user(buffer, &compat_event,
sizeof(struct input_event_compat)))
return -EFAULT;

} else {
if (copy_to_user(buffer, event, sizeof(struct input_event)))
return -EFAULT;
}

return 0;
}

static int evdev_ff_effect_from_user(const char __user *buffer, size_t size,
struct ff_effect *effect)
{
if (COMPAT_TEST) {
struct ff_effect_compat *compat_effect;

if (size != sizeof(struct ff_effect_compat))
return -EINVAL;

/*
* It so happens that the pointer which needs to be changed
* is the last field in the structure, so we can copy the
* whole thing and replace just the pointer.
*/

compat_effect = (struct ff_effect_compat *)effect;

if (copy_from_user(compat_effect, buffer,
sizeof(struct ff_effect_compat)))
return -EFAULT;

if (compat_effect->type == FF_PERIODIC &&
compat_effect->u.periodic.waveform == FF_CUSTOM)
effect->u.periodic.custom_data =
compat_ptr(compat_effect->u.periodic.custom_data);
} else {
if (size != sizeof(struct ff_effect))
return -EINVAL;

if (copy_from_user(effect, buffer, sizeof(struct ff_effect)))
return -EFAULT;
}

return 0;
}

#else

static inline size_t evdev_event_size(void)
{
return sizeof(struct input_event);
}

static int evdev_event_from_user(const char __user *buffer,
struct input_event *event)
{
if (copy_from_user(event, buffer, sizeof(struct input_event)))
return -EFAULT;

return 0;
}

static int evdev_event_to_user(char __user *buffer,
const struct input_event *event)
{
if (copy_to_user(buffer, event, sizeof(struct input_event)))
return -EFAULT;

return 0;
}

static int evdev_ff_effect_from_user(const char __user *buffer, size_t size,
struct ff_effect *effect)
{
if (size != sizeof(struct ff_effect))
return -EINVAL;

if (copy_from_user(effect, buffer, sizeof(struct ff_effect)))
return -EFAULT;

return 0;
}

#endif /* CONFIG_COMPAT */

static ssize_t evdev_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
Expand All @@ -491,14 +310,14 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer,

while (retval < count) {

if (evdev_event_from_user(buffer + retval, &event)) {
if (input_event_from_user(buffer + retval, &event)) {
retval = -EFAULT;
goto out;
}

input_inject_event(&evdev->handle,
event.type, event.code, event.value);
retval += evdev_event_size();
retval += input_event_size();
}

out:
Expand Down Expand Up @@ -532,7 +351,7 @@ static ssize_t evdev_read(struct file *file, char __user *buffer,
struct input_event event;
int retval;

if (count < evdev_event_size())
if (count < input_event_size())
return -EINVAL;

if (client->head == client->tail && evdev->exist &&
Expand All @@ -547,13 +366,13 @@ static ssize_t evdev_read(struct file *file, char __user *buffer,
if (!evdev->exist)
return -ENODEV;

while (retval + evdev_event_size() <= count &&
while (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {

if (evdev_event_to_user(buffer + retval, &event))
if (input_event_to_user(buffer + retval, &event))
return -EFAULT;

retval += evdev_event_size();
retval += input_event_size();
}

return retval;
Expand Down Expand Up @@ -824,7 +643,7 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,

if (_IOC_NR(cmd) == _IOC_NR(EVIOCSFF)) {

if (evdev_ff_effect_from_user(p, _IOC_SIZE(cmd), &effect))
if (input_ff_effect_from_user(p, _IOC_SIZE(cmd), &effect))
return -EFAULT;

error = input_ff_upload(dev, &effect, file);
Expand Down
135 changes: 135 additions & 0 deletions drivers/input/input-compat.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 32bit compatibility wrappers for the input subsystem.
*
* Very heavily based on evdev.c - Copyright (c) 1999-2002 Vojtech Pavlik
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/

#include <asm/uaccess.h>
#include "input-compat.h"

#ifdef CONFIG_COMPAT

int input_event_from_user(const char __user *buffer,
struct input_event *event)
{
if (INPUT_COMPAT_TEST) {
struct input_event_compat compat_event;

if (copy_from_user(&compat_event, buffer,
sizeof(struct input_event_compat)))
return -EFAULT;

event->time.tv_sec = compat_event.time.tv_sec;
event->time.tv_usec = compat_event.time.tv_usec;
event->type = compat_event.type;
event->code = compat_event.code;
event->value = compat_event.value;

} else {
if (copy_from_user(event, buffer, sizeof(struct input_event)))
return -EFAULT;
}

return 0;
}

int input_event_to_user(char __user *buffer,
const struct input_event *event)
{
if (INPUT_COMPAT_TEST) {
struct input_event_compat compat_event;

compat_event.time.tv_sec = event->time.tv_sec;
compat_event.time.tv_usec = event->time.tv_usec;
compat_event.type = event->type;
compat_event.code = event->code;
compat_event.value = event->value;

if (copy_to_user(buffer, &compat_event,
sizeof(struct input_event_compat)))
return -EFAULT;

} else {
if (copy_to_user(buffer, event, sizeof(struct input_event)))
return -EFAULT;
}

return 0;
}

int input_ff_effect_from_user(const char __user *buffer, size_t size,
struct ff_effect *effect)
{
if (INPUT_COMPAT_TEST) {
struct ff_effect_compat *compat_effect;

if (size != sizeof(struct ff_effect_compat))
return -EINVAL;

/*
* It so happens that the pointer which needs to be changed
* is the last field in the structure, so we can retrieve the
* whole thing and replace just the pointer.
*/
compat_effect = (struct ff_effect_compat *)effect;

if (copy_from_user(compat_effect, buffer,
sizeof(struct ff_effect_compat)))
return -EFAULT;

if (compat_effect->type == FF_PERIODIC &&
compat_effect->u.periodic.waveform == FF_CUSTOM)
effect->u.periodic.custom_data =
compat_ptr(compat_effect->u.periodic.custom_data);
} else {
if (size != sizeof(struct ff_effect))
return -EINVAL;

if (copy_from_user(effect, buffer, sizeof(struct ff_effect)))
return -EFAULT;
}

return 0;
}

#else

int input_event_from_user(const char __user *buffer,
struct input_event *event)
{
if (copy_from_user(event, buffer, sizeof(struct input_event)))
return -EFAULT;

return 0;
}

int input_event_to_user(char __user *buffer,
const struct input_event *event)
{
if (copy_to_user(buffer, event, sizeof(struct input_event)))
return -EFAULT;

return 0;
}

int input_ff_effect_from_user(const char __user *buffer, size_t size,
struct ff_effect *effect)
{
if (size != sizeof(struct ff_effect))
return -EINVAL;

if (copy_from_user(effect, buffer, sizeof(struct ff_effect)))
return -EFAULT;

return 0;
}

#endif /* CONFIG_COMPAT */

EXPORT_SYMBOL_GPL(input_event_from_user);
EXPORT_SYMBOL_GPL(input_event_to_user);
EXPORT_SYMBOL_GPL(input_ff_effect_from_user);
Loading

0 comments on commit 2d56f3a

Please sign in to comment.