Input: joystick - use ktime for measuring timing
authorTakashi Iwai <tiwai@suse.de>
Thu, 11 Sep 2014 17:28:30 +0000 (10:28 -0700)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Fri, 12 Sep 2014 06:41:18 +0000 (23:41 -0700)
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>
drivers/input/gameport/gameport.c
drivers/input/joystick/analog.c

index 24c41ba7d4e01dcf852050d3f1d8ef27b5f0a22f..e29c04e2aff4ea1ae4e570be4fa677cb9e014fb3 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/workqueue.h>
 #include <linux/sched.h>       /* HZ */
 #include <linux/mutex.h>
+#include <linux/timekeeping.h>
 
 /*#include <asm/io.h>*/
 
@@ -30,6 +31,10 @@ 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.
@@ -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__)
 
@@ -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);
 
index ab0fdcd36e18d7746ce7f8d5e41c51b5dfae9304..4284080e481d323bc028ea5b4a10d82cb595b66a 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/gameport.h>
 #include <linux/jiffies.h>
 #include <linux/timex.h>
+#include <linux/timekeeping.h>
 
 #define DRIVER_DESC    "Analog joystick and gamepad driver"
 
@@ -43,6 +44,10 @@ 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.
  */
@@ -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.
  */
@@ -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;
@@ -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;
@@ -249,16 +274,16 @@ 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;
 
@@ -266,7 +291,7 @@ static int analog_cooked_read(struct analog_port *port)
                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);
@@ -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;
        }