diff options
-rw-r--r-- | man/evdev.man | 5 | ||||
-rw-r--r-- | src/evdev.c | 276 | ||||
-rw-r--r-- | src/evdev.h | 16 |
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 */ |