aboutsummaryrefslogtreecommitdiff
path: root/src/evdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/evdev.c')
-rw-r--r--src/evdev.c268
1 files changed, 247 insertions, 21 deletions
diff --git a/src/evdev.c b/src/evdev.c
index a857db3..c0fe244 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -73,6 +73,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
@@ -96,6 +97,9 @@ static const char *evdevDefaults[] = {
NULL
};
+static int EvdevOn(DeviceIntPtr);
+static int EvdevCacheCompare(InputInfoPtr pInfo, Bool compare);
+
static void
SetXkbOption(InputInfoPtr pInfo, char *name, char **option)
{
@@ -154,6 +158,55 @@ PostKbdEvent(InputInfoPtr pInfo, struct input_event *ev, int value)
xf86PostKeyboardEvent(pInfo->dev, code, value);
}
+/**
+ * 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)
{
@@ -173,6 +226,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;
}
@@ -316,8 +378,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
@@ -836,6 +896,56 @@ 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);
+ }
+ pEvdev->flags |= EVDEV_INITIALIZED;
+ device->public.on = TRUE;
+ }
+
+ return Success;
+}
+
+
static int
EvdevProc(DeviceIntPtr device, int what)
{
@@ -851,30 +961,31 @@ 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);
- 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;
}
@@ -904,6 +1015,116 @@ EvdevConvert(InputInfoPtr pInfo, int first, int num, int v0, int v1, int v2,
return TRUE;
}
+/**
+ * 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)
@@ -1060,11 +1281,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);
@@ -1072,6 +1294,8 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
return NULL;
}
+ pEvdev->reopen_attempts = xf86SetIntOption(pInfo->options, "ReopenAttempts", 10);
+
pEvdev->noXkb = noXkbExtension;
/* parse the XKB options during kbd setup */
@@ -1081,6 +1305,8 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
return NULL;
}
+ EvdevCacheCompare(pInfo, FALSE); /* cache device data */
+
return pInfo;
}