Skip to content

Commit

Permalink
Input: joystick - use ktime for measuring timing
Browse files Browse the repository at this point in the history
The current codes in gameport and analog joystick drivers for the time
accounting have a long-standing problem when the system is running
with CPU freq; since the timing is measured via TSC or sample counter,
the calculation isn't reliable.

In this patch, as a simple fix, use the standard ktime to measure the
timing.  In case where no high resolution timer is available,
use_ktime bool option is provided to both modules.  Setting
use_ktime=false switches to the old methods.

Tested-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
  • Loading branch information
Takashi Iwai authored and Dmitry Torokhov committed Sep 12, 2014
1 parent a3b3ca7 commit 76460a7
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 20 deletions.
41 changes: 40 additions & 1 deletion drivers/input/gameport/gameport.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@
#include <linux/workqueue.h>
#include <linux/sched.h> /* HZ */
#include <linux/mutex.h>
#include <linux/timekeeping.h>

/*#include <asm/io.h>*/

MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
MODULE_DESCRIPTION("Generic gameport layer");
MODULE_LICENSE("GPL");

static bool use_ktime = true;
module_param(use_ktime, bool, 0400);
MODULE_PARM_DESC(use_ktime, "Use ktime for measuring I/O speed");

/*
* gameport_mutex protects entire gameport subsystem and is taken
* every time gameport port or driver registrered or unregistered.
Expand Down Expand Up @@ -75,6 +80,38 @@ static unsigned int get_time_pit(void)
*/

static int gameport_measure_speed(struct gameport *gameport)
{
unsigned int i, t, tx;
u64 t1, t2, t3;
unsigned long flags;

if (gameport_open(gameport, NULL, GAMEPORT_MODE_RAW))
return 0;

tx = ~0;

for (i = 0; i < 50; i++) {
local_irq_save(flags);
t1 = ktime_get_ns();
for (t = 0; t < 50; t++)
gameport_read(gameport);
t2 = ktime_get_ns();
t3 = ktime_get_ns();
local_irq_restore(flags);
udelay(i * 10);
t = (t2 - t1) - (t3 - t2);
if (t < tx)
tx = t;
}

gameport_close(gameport);
t = 1000000 * 50;
if (tx)
t /= tx;
return t;
}

static int old_gameport_measure_speed(struct gameport *gameport)
{
#if defined(__i386__)

Expand Down Expand Up @@ -521,7 +558,9 @@ static void gameport_add_port(struct gameport *gameport)
if (gameport->parent)
gameport->parent->child = gameport;

gameport->speed = gameport_measure_speed(gameport);
gameport->speed = use_ktime ?
gameport_measure_speed(gameport) :
old_gameport_measure_speed(gameport);

list_add_tail(&gameport->node, &gameport_list);

Expand Down
71 changes: 52 additions & 19 deletions drivers/input/joystick/analog.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@
#include <linux/gameport.h>
#include <linux/jiffies.h>
#include <linux/timex.h>
#include <linux/timekeeping.h>

#define DRIVER_DESC "Analog joystick and gamepad driver"

MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

static bool use_ktime = true;
module_param(use_ktime, bool, 0400);
MODULE_PARM_DESC(use_ktime, "Use ktime for measuring I/O speed");

/*
* Option parsing.
*/
Expand Down Expand Up @@ -171,6 +176,25 @@ static unsigned long analog_faketime = 0;
#warning Precise timer not defined for this architecture.
#endif

static inline u64 get_time(void)
{
if (use_ktime) {
return ktime_get_ns();
} else {
unsigned int x;
GET_TIME(x);
return x;
}
}

static inline unsigned int delta(u64 x, u64 y)
{
if (use_ktime)
return y - x;
else
return DELTA((unsigned int)x, (unsigned int)y);
}

/*
* analog_decode() decodes analog joystick data and reports input events.
*/
Expand Down Expand Up @@ -226,7 +250,8 @@ static void analog_decode(struct analog *analog, int *axes, int *initial, int bu
static int analog_cooked_read(struct analog_port *port)
{
struct gameport *gameport = port->gameport;
unsigned int time[4], start, loop, now, loopout, timeout;
u64 time[4], start, loop, now;
unsigned int loopout, timeout;
unsigned char data[4], this, last;
unsigned long flags;
int i, j;
Expand All @@ -236,7 +261,7 @@ static int analog_cooked_read(struct analog_port *port)

local_irq_save(flags);
gameport_trigger(gameport);
GET_TIME(now);
now = get_time();
local_irq_restore(flags);

start = now;
Expand All @@ -249,24 +274,24 @@ static int analog_cooked_read(struct analog_port *port)

local_irq_disable();
this = gameport_read(gameport) & port->mask;
GET_TIME(now);
now = get_time();
local_irq_restore(flags);

if ((last ^ this) && (DELTA(loop, now) < loopout)) {
if ((last ^ this) && (delta(loop, now) < loopout)) {
data[i] = last ^ this;
time[i] = now;
i++;
}

} while (this && (i < 4) && (DELTA(start, now) < timeout));
} while (this && (i < 4) && (delta(start, now) < timeout));

this <<= 4;

for (--i; i >= 0; i--) {
this |= data[i];
for (j = 0; j < 4; j++)
if (data[i] & (1 << j))
port->axes[j] = (DELTA(start, time[i]) << ANALOG_FUZZ_BITS) / port->loop;
port->axes[j] = (delta(start, time[i]) << ANALOG_FUZZ_BITS) / port->loop;
}

return -(this != port->mask);
Expand Down Expand Up @@ -365,31 +390,39 @@ static void analog_close(struct input_dev *dev)
static void analog_calibrate_timer(struct analog_port *port)
{
struct gameport *gameport = port->gameport;
unsigned int i, t, tx, t1, t2, t3;
unsigned int i, t, tx;
u64 t1, t2, t3;
unsigned long flags;

local_irq_save(flags);
GET_TIME(t1);
if (use_ktime) {
port->speed = 1000000;
} else {
local_irq_save(flags);
t1 = get_time();
#ifdef FAKE_TIME
analog_faketime += 830;
analog_faketime += 830;
#endif
mdelay(1);
GET_TIME(t2);
GET_TIME(t3);
local_irq_restore(flags);
mdelay(1);
t2 = get_time();
t3 = get_time();
local_irq_restore(flags);

port->speed = DELTA(t1, t2) - DELTA(t2, t3);
port->speed = delta(t1, t2) - delta(t2, t3);
}

tx = ~0;

for (i = 0; i < 50; i++) {
local_irq_save(flags);
GET_TIME(t1);
for (t = 0; t < 50; t++) { gameport_read(gameport); GET_TIME(t2); }
GET_TIME(t3);
t1 = get_time();
for (t = 0; t < 50; t++) {
gameport_read(gameport);
t2 = get_time();
}
t3 = get_time();
local_irq_restore(flags);
udelay(i);
t = DELTA(t1, t2) - DELTA(t2, t3);
t = delta(t1, t2) - delta(t2, t3);
if (t < tx) tx = t;
}

Expand Down

0 comments on commit 76460a7

Please sign in to comment.