summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@redhat.com>2008-08-26 14:33:40 +0930
committerPeter Hutterer <peter.hutterer@redhat.com>2008-09-04 18:33:39 +0930
commit9930477cbeb4acfd070ae70894d13ffabfc347b8 (patch)
treea9f58016b8ad839c7ea3752981dea561f87c611b
parent4509ec1daf8a03b261c1fa8aa48b5def3f336aed (diff)
Attempt to re-open devices on read errors.
Coming back from resume may leave us with a file descriptor that can be opened but fails on the first read (ENODEV). In this case, try to open the device until it becomes available or until the predefined count expires. To be safe, we cache the information from the device and compare against it when we re-open. This way we ensure that if the topology changes under us, we don't open a completely different device. If a device has changed, we disable it. Adds option "ReopenAttempts" <int>
-rw-r--r--man/evdev.man5
-rw-r--r--src/evdev.c276
-rw-r--r--src/evdev.h16
3 files changed, 272 insertions, 25 deletions
diff --git a/man/evdev.man b/man/evdev.man
index 91894dd..98268c8 100644
--- a/man/evdev.man
+++ b/man/evdev.man
@@ -141,6 +141,11 @@ emulation mode. Button number
is mapped to the negative Y axis motion and button number
.I N2
is mapped to the positive Y axis motion. Default: "4 5"
+.TP 7
+.BI "Option \*qReopenAttempts\*q \*q" integer \*q
+Number of reopen attempts after a read error occurs on the device (e.g. after
+waking up from suspend). In between each attempt is a 100ms wait. Default: 10.
+
.SH AUTHORS
Kristian Høgsberg.
.SH "SEE ALSO"
diff --git a/src/evdev.c b/src/evdev.c
index f4efd74..5a58a8f 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -74,6 +74,7 @@
#define EVDEV_RELATIVE_EVENTS (1 << 2)
#define EVDEV_ABSOLUTE_EVENTS (1 << 3)
#define EVDEV_TOUCHPAD (1 << 4)
+#define EVDEV_INITIALIZED (1 << 5) /* WheelInit etc. called already? */
#define MIN_KEYCODE 8
#define GLYPHS_PER_KEY 2
@@ -112,6 +113,9 @@ static PropHandler evdevPropHandlers[] =
#endif
+static int EvdevOn(DeviceIntPtr);
+static int EvdevCacheCompare(InputInfoPtr pInfo, Bool compare);
+
static void
SetXkbOption(InputInfoPtr pInfo, char *name, char **option)
{
@@ -195,6 +199,55 @@ static Bool EvdevGetProperty(DeviceIntPtr dev,
}
#endif
+/**
+ * Coming back from resume may leave us with a file descriptor that can be
+ * opened but fails on the first read (ENODEV).
+ * In this case, try to open the device until it becomes available or until
+ * the predefined count expires.
+ */
+static CARD32
+EvdevReopenTimer(OsTimerPtr timer, CARD32 time, pointer arg)
+{
+ InputInfoPtr pInfo = (InputInfoPtr)arg;
+ EvdevPtr pEvdev = pInfo->private;
+
+ do {
+ pInfo->fd = open(pEvdev->device, O_RDWR, 0);
+ } while (pInfo->fd < 0 && errno == EINTR);
+
+ if (pInfo->fd != -1)
+ {
+ pEvdev->reopen_left = 0;
+
+ if (EvdevCacheCompare(pInfo, TRUE) == Success)
+ {
+ xf86Msg(X_INFO, "%s: Device reopened after %d attempts.\n", pInfo->name,
+ pEvdev->reopen_attempts - pEvdev->reopen_left);
+ EvdevOn(pInfo->dev);
+ } else
+ {
+ xf86Msg(X_ERROR, "%s: Device has changed - disabling.\n",
+ pInfo->name);
+ DisableDevice(pInfo->dev);
+ close(pInfo->fd);
+ pInfo->fd = -1;
+ }
+ return 0;
+ }
+
+ pEvdev->reopen_left--;
+
+ if (!pEvdev->reopen_left)
+ {
+ xf86Msg(X_ERROR, "%s: Failed to reopen device after %d attempts.\n",
+ pInfo->name, pEvdev->reopen_attempts);
+ DisableDevice(pInfo->dev);
+ return 0;
+ }
+
+ return 100; /* come back in 100 ms */
+}
+
static void
EvdevReadInput(InputInfoPtr pInfo)
{
@@ -215,6 +268,15 @@ EvdevReadInput(InputInfoPtr pInfo)
/* The kernel promises that we always only read a complete
* event, so len != sizeof ev is an error. */
xf86Msg(X_ERROR, "%s: Read error: %s\n", pInfo->name, strerror(errno));
+
+ if (errno == ENODEV) /* May happen after resume */
+ {
+ xf86RemoveEnabledDevice(pInfo);
+ close(pInfo->fd);
+ pInfo->fd = -1;
+ pEvdev->reopen_left = pEvdev->reopen_attempts;
+ pEvdev->reopen_timer = TimerSet(NULL, 0, 100, EvdevReopenTimer, pInfo);
+ }
break;
}
@@ -345,8 +407,6 @@ EvdevReadInput(InputInfoPtr pInfo)
}
}
-#define LONG_BITS (sizeof(long) * 8)
-#define NBITS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
#define TestBit(bit, array) (array[(bit) / LONG_BITS]) & (1 << ((bit) % LONG_BITS))
static void
@@ -933,6 +993,58 @@ EvdevInit(DeviceIntPtr device)
return Success;
}
+/**
+ * Init all extras (wheel emulation, etc.) and grab the device.
+ *
+ * Coming from a resume, the grab may fail with ENODEV. In this case, we set a
+ * timer to wake up and try to reopen the device later.
+ */
+static int
+EvdevOn(DeviceIntPtr device)
+{
+ InputInfoPtr pInfo;
+ EvdevPtr pEvdev;
+ int rc = 0;
+
+ pInfo = device->public.devicePrivate;
+ pEvdev = pInfo->private;
+
+ if (pInfo->fd != -1 && !pEvdev->kernel24 &&
+ (rc = ioctl(pInfo->fd, EVIOCGRAB, (void *)1)))
+ {
+ xf86Msg(X_WARNING, "%s: Grab failed (%s)\n", pInfo->name,
+ strerror(errno));
+
+ /* ENODEV - device has disappeared after resume */
+ if (rc && errno == ENODEV)
+ {
+ close(pInfo->fd);
+ pInfo->fd = -1;
+ }
+ }
+
+ if (pInfo->fd == -1)
+ {
+ pEvdev->reopen_left = pEvdev->reopen_attempts;
+ pEvdev->reopen_timer = TimerSet(NULL, 0, 100, EvdevReopenTimer, pInfo);
+ } else
+ {
+ xf86AddEnabledDevice(pInfo);
+ if ((pEvdev->flags & EVDEV_BUTTON_EVENTS) &&
+ !(pEvdev->flags & EVDEV_INITIALIZED))
+ {
+ EvdevMBEmuPreInit(pInfo);
+ EvdevWheelEmuPreInit(pInfo);
+ EvdevDragLockInit(pInfo);
+ }
+ pEvdev->flags |= EVDEV_INITIALIZED;
+ device->public.on = TRUE;
+ }
+
+ return Success;
+}
+
+
static int
EvdevProc(DeviceIntPtr device, int what)
{
@@ -948,40 +1060,149 @@ EvdevProc(DeviceIntPtr device, int what)
return EvdevInit(device);
case DEVICE_ON:
- if (!pEvdev->kernel24 && ioctl(pInfo->fd, EVIOCGRAB, (void *)1))
- xf86Msg(X_WARNING, "%s: Grab failed (%s)\n", pInfo->name,
- strerror(errno));
- if (errno != ENODEV)
- {
- xf86AddEnabledDevice(pInfo);
- if (pEvdev->flags & EVDEV_BUTTON_EVENTS)
- {
- EvdevMBEmuPreInit(pInfo);
- EvdevWheelEmuPreInit(pInfo);
- EvdevDragLockInit(pInfo);
- }
- device->public.on = TRUE;
- }
- break;
+ return EvdevOn(device);
case DEVICE_OFF:
- if (!pEvdev->kernel24 && ioctl(pInfo->fd, EVIOCGRAB, (void *)0))
- xf86Msg(X_WARNING, "%s: Release failed (%s)\n", pInfo->name,
- strerror(errno));
- xf86RemoveEnabledDevice(pInfo);
- EvdevMBEmuFinalize(pInfo);
+ if (pInfo->fd != -1)
+ {
+ if (!pEvdev->kernel24 && ioctl(pInfo->fd, EVIOCGRAB, (void *)0))
+ xf86Msg(X_WARNING, "%s: Release failed (%s)\n", pInfo->name,
+ strerror(errno));
+ xf86RemoveEnabledDevice(pInfo);
+ }
+ if (pEvdev->flags & EVDEV_INITIALIZED)
+ EvdevMBEmuFinalize(pInfo);
+ pEvdev->flags &= ~EVDEV_INITIALIZED;
device->public.on = FALSE;
+ if (pEvdev->reopen_timer)
+ {
+ TimerFree(pEvdev->reopen_timer);
+ pEvdev->reopen_timer = NULL;
+ }
break;
case DEVICE_CLOSE:
xf86Msg(X_INFO, "%s: Close\n", pInfo->name);
- close(pInfo->fd);
+ if (pInfo->fd != -1)
+ close(pInfo->fd);
break;
}
return Success;
}
+/**
+ * Get as much information as we can from the fd and cache it.
+ * If compare is True, then the information retrieved will be compared to the
+ * one already cached. If the information does not match, then this function
+ * returns an error.
+ *
+ * @return Success if the information was cached, or !Success otherwise.
+ */
+static int
+EvdevCacheCompare(InputInfoPtr pInfo, Bool compare)
+{
+ EvdevPtr pEvdev = pInfo->private;
+ int i;
+
+ char name[1024] = {0};
+ long bitmask[NBITS(EV_MAX)] = {0};
+ long key_bitmask[NBITS(KEY_MAX)] = {0};
+ long rel_bitmask[NBITS(REL_MAX)] = {0};
+ long abs_bitmask[NBITS(ABS_MAX)] = {0};
+ long led_bitmask[NBITS(LED_MAX)] = {0};
+ struct input_absinfo absinfo[ABS_MAX];
+
+ if (ioctl(pInfo->fd,
+ EVIOCGNAME(sizeof(name) - 1), name) < 0) {
+ xf86Msg(X_ERROR, "ioctl EVIOCGNAME failed: %s\n", strerror(errno));
+ goto error;
+ }
+
+ if (compare && strcmp(pEvdev->name, name))
+ goto error;
+
+ if (ioctl(pInfo->fd,
+ EVIOCGBIT(0, sizeof(bitmask)), bitmask) < 0) {
+ xf86Msg(X_ERROR, "ioctl EVIOCGNAME failed: %s\n", strerror(errno));
+ goto error;
+ }
+
+ if (compare && memcmp(pEvdev->bitmask, bitmask, sizeof(bitmask)))
+ goto error;
+
+
+ if (ioctl(pInfo->fd,
+ EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) < 0) {
+ xf86Msg(X_ERROR, "ioctl EVIOCGBIT failed: %s\n", strerror(errno));
+ goto error;
+ }
+
+ if (compare && memcmp(pEvdev->rel_bitmask, rel_bitmask, sizeof(rel_bitmask)))
+ goto error;
+
+ if (ioctl(pInfo->fd,
+ EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) < 0) {
+ xf86Msg(X_ERROR, "ioctl EVIOCGBIT failed: %s\n", strerror(errno));
+ goto error;
+ }
+
+ if (compare && memcmp(pEvdev->abs_bitmask, abs_bitmask, sizeof(abs_bitmask)))
+ goto error;
+
+ if (ioctl(pInfo->fd,
+ EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) < 0) {
+ xf86Msg(X_ERROR, "ioctl EVIOCGBIT failed: %s\n", strerror(errno));
+ goto error;
+ }
+
+ if (compare && memcmp(pEvdev->key_bitmask, key_bitmask, sizeof(key_bitmask)))
+ goto error;
+
+ if (ioctl(pInfo->fd,
+ EVIOCGBIT(EV_LED, sizeof(led_bitmask)), led_bitmask) < 0) {
+ xf86Msg(X_ERROR, "ioctl EVIOCGBIT failed: %s\n", strerror(errno));
+ goto error;
+ }
+
+ if (compare && memcmp(pEvdev->led_bitmask, led_bitmask, sizeof(led_bitmask)))
+ goto error;
+
+ memset(absinfo, 0, sizeof(absinfo));
+
+ for (i = 0; i < ABS_MAX; i++)
+ {
+ if (TestBit(i, abs_bitmask))
+ {
+ if (ioctl(pInfo->fd, EVIOCGABS(i), &absinfo[i]) < 0) {
+ xf86Msg(X_ERROR, "ioctl EVIOCGABS failed: %s\n", strerror(errno));
+ goto error;
+ }
+ }
+ }
+
+ if (compare && memcmp(pEvdev->absinfo, absinfo, sizeof(absinfo)))
+ goto error;
+
+ /* cache info */
+ if (!compare)
+ {
+ strcpy(pEvdev->name, name);
+ memcpy(pEvdev->bitmask, bitmask, sizeof(bitmask));
+ memcpy(pEvdev->key_bitmask, key_bitmask, sizeof(key_bitmask));
+ memcpy(pEvdev->rel_bitmask, rel_bitmask, sizeof(rel_bitmask));
+ memcpy(pEvdev->abs_bitmask, abs_bitmask, sizeof(abs_bitmask));
+ memcpy(pEvdev->led_bitmask, led_bitmask, sizeof(led_bitmask));
+ memcpy(pEvdev->absinfo, absinfo, sizeof(absinfo));
+ }
+
+ return Success;
+
+error:
+ return !Success;
+
+}
+
static int
EvdevProbe(InputInfoPtr pInfo)
{
@@ -1147,11 +1368,12 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
return NULL;
}
+ pEvdev->device = device;
+
xf86Msg(deviceFrom, "%s: Device: \"%s\"\n", pInfo->name, device);
do {
pInfo->fd = open(device, O_RDWR, 0);
- }
- while (pInfo->fd < 0 && errno == EINTR);
+ } while (pInfo->fd < 0 && errno == EINTR);
if (pInfo->fd < 0) {
xf86Msg(X_ERROR, "Unable to open evdev device \"%s\".\n", device);
@@ -1159,6 +1381,8 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
return NULL;
}
+ pEvdev->reopen_attempts = xf86SetIntOption(pInfo->options, "ReopenAttempts", 10);
+
EvdevInitButtonMapping(pInfo);
pEvdev->noXkb = noXkbExtension;
@@ -1170,6 +1394,8 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
return NULL;
}
+ EvdevCacheCompare(pInfo, FALSE); /* cache device data */
+
return pInfo;
}
diff --git a/src/evdev.h b/src/evdev.h
index 6c59dad..eca049e 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -46,6 +46,8 @@
#define HAVE_PROPERTIES 1
#endif
+#define LONG_BITS (sizeof(long) * 8)
+#define NBITS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
/* axis specific data for wheel emulation */
typedef struct {
@@ -55,6 +57,7 @@ typedef struct {
} WheelAxis, *WheelAxisPtr;
typedef struct {
+ const char *device;
int kernel24;
int screen;
int min_x, min_y, max_x, max_y;
@@ -100,6 +103,19 @@ typedef struct {
} emulateWheel;
unsigned char btnmap[32]; /* config-file specified button mapping */
+
+ int reopen_attempts; /* max attempts to re-open after read failure */
+ int reopen_left; /* number of attempts left to re-open the device */
+ OsTimerPtr reopen_timer;
+
+ /* Cached info from device. */
+ char name[1024];
+ long bitmask[NBITS(EV_MAX)];
+ long key_bitmask[NBITS(KEY_MAX)];
+ long rel_bitmask[NBITS(REL_MAX)];
+ long abs_bitmask[NBITS(ABS_MAX)];
+ long led_bitmask[NBITS(LED_MAX)];
+ struct input_absinfo absinfo[ABS_MAX];
} EvdevRec, *EvdevPtr;
/* Middle Button emulation */