[PATCH evdev 2/2] Add SYN_DROPPED handling #59702
Chung-yih Wang
cywang at chromium.org
Tue Jan 22 01:50:01 PST 2013
If an evdev client cannot consume evdev events in its queue fast enough, the
evdev kernel driver will enqueue a SYN_DROPPED event and clear the queue
once the client's queue is full. The result is that the X driver will be out
of sync with respect to the kernel driver state. The patch tries to handle the
SYN_DROPPED event by retrieving the kernel driver's state. Retrieving this
state is inherently non-atomic, since it requires a sequence of ioctls. We use
a simple before and after time stamping approach to deal with the race
condition between partially syncing state and any potentially stale events that
arrive during synchronization.
Signed-off-by: Chung-yih Wang <cywang at chromium.org>
---
src/evdev.c | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/evdev.h | 17 ++++
2 files changed, 298 insertions(+), 8 deletions(-)
diff --git a/src/evdev.c b/src/evdev.c
index 6564cd0..96f5aa0 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -128,6 +128,8 @@ static void EvdevSetCalibration(InputInfoPtr pInfo, int num_calibration, int cal
static int EvdevOpenDevice(InputInfoPtr pInfo);
static void EvdevCloseDevice(InputInfoPtr pInfo);
+static int EvdevInjectEvent(InputInfoPtr pInfo, uint16_t type,
+ uint16_t code, int32_t value);
static void EvdevInitAxesLabels(EvdevPtr pEvdev, int mode, int natoms, Atom *atoms);
static void EvdevInitOneAxisLabel(EvdevPtr pEvdev, int axis,
const char **labels, int label_idx, Atom *atoms);
@@ -135,6 +137,18 @@ static void EvdevInitButtonLabels(EvdevPtr pEvdev, int natoms, Atom *atoms);
static void EvdevInitProperty(DeviceIntPtr dev);
static int EvdevSetProperty(DeviceIntPtr dev, Atom atom,
XIPropertyValuePtr val, BOOL checkonly);
+static void EvdevSyncState(InputInfoPtr pInfo);
+static void EvdevGetKernelTime(struct timeval *current_time,
+ BOOL use_monotonic);
+static int EvdevKeyStateSync(InputInfoPtr pInfo);
+static int EvdevAbsAxesSync(InputInfoPtr pInfo);
+static int EvdevInjectTrackingIdChangeEvents(InputInfoPtr pInfo, int slot_index,
+ int curr_tid);
+static int EvdevCheckAbsMtAxesChange(InputInfoPtr pInfo, MTSlotInfo *slots);
+static int EvdevGetAllSlotVals(InputInfoPtr pInfo, MTSlotInfo *slots);
+static int EvdevAbsMtStateSync(InputInfoPtr pInfo);
+static int EvdevAbsStateSync(InputInfoPtr pInfo);
+
static Atom prop_product_id;
static Atom prop_invert;
static Atom prop_calibration;
@@ -208,6 +222,11 @@ static inline void EvdevSetBit(unsigned long *array, int bit)
array[bit / LONG_BITS] |= (1LL << (bit % LONG_BITS));
}
+static inline void EvdevClearBit(unsigned long *array, int bit)
+{
+ array[bit / LONG_BITS] &= ~(1LL << (bit % LONG_BITS));
+}
+
static int
EvdevGetMajorMinor(InputInfoPtr pInfo)
{
@@ -649,6 +668,11 @@ EvdevProcessButtonEvent(InputInfoPtr pInfo, struct input_event *ev)
/* Get the signed value, earlier kernels had this as unsigned */
value = ev->value;
+ if (ev->value)
+ EvdevSetBit(pEvdev->key_state_bitmask, ev->code);
+ else
+ EvdevClearBit(pEvdev->key_state_bitmask, ev->code);
+
/* Handle drag lock */
if (EvdevDragLockFilterEvent(pInfo, button, value))
return;
@@ -734,7 +758,6 @@ EvdevProcessTouch(InputInfoPtr pInfo)
else
type = XI_TouchUpdate;
-
EvdevQueueTouchEvent(pInfo, pEvdev->cur_slot, pEvdev->mt_mask, type);
pEvdev->slot_state = SLOTSTATE_EMPTY;
@@ -779,6 +802,7 @@ EvdevProcessTouchEvent(InputInfoPtr pInfo, struct input_event *ev)
if (pEvdev->slot_state == SLOTSTATE_EMPTY)
pEvdev->slot_state = SLOTSTATE_UPDATE;
if (ev->code == ABS_MT_TRACKING_ID) {
+ pEvdev->cached_tid[slot_index] = ev->value;
if (ev->value >= 0) {
pEvdev->slot_state = SLOTSTATE_OPEN;
@@ -993,7 +1017,7 @@ static void EvdevPostQueuedEvents(InputInfoPtr pInfo, int num_v, int first_v,
* Take the synchronization input event and process it accordingly; the motion
* notify events are sent first, then any button/key press/release events.
*/
-static void
+static BOOL
EvdevProcessSyncEvent(InputInfoPtr pInfo, struct input_event *ev)
{
int i;
@@ -1001,6 +1025,11 @@ EvdevProcessSyncEvent(InputInfoPtr pInfo, struct input_event *ev)
int v[MAX_VALUATORS] = {};
EvdevPtr pEvdev = pInfo->private;
+ if (ev->code == SYN_DROPPED) {
+ xf86IDrvMsg(pInfo, X_INFO, "+++ SYN_DROPPED +++\n");
+ return TRUE;
+ }
+
EvdevProcessProximityState(pInfo);
EvdevProcessValuators(pInfo);
@@ -1028,16 +1057,20 @@ EvdevProcessSyncEvent(InputInfoPtr pInfo, struct input_event *ev)
pEvdev->abs_queued = 0;
pEvdev->rel_queued = 0;
pEvdev->prox_queued = 0;
-
+ return FALSE;
}
/**
* Process the events from the device; nothing is actually posted to the server
- * until an EV_SYN event is received.
+ * until an EV_SYN event is received. As the SYN_DROPPED event indicates that the
+ * state of evdev driver will be out of sync with the event queue, additional
+ * handling is required for processing the SYN_DROPPED event. The function returns
+ * TRUE if an SYN_DROPPED event is received, false otherwise.
*/
-static void
+static BOOL
EvdevProcessEvent(InputInfoPtr pInfo, struct input_event *ev)
{
+ BOOL syn_dropped = FALSE;
switch (ev->type) {
case EV_REL:
EvdevProcessRelativeMotionEvent(pInfo, ev);
@@ -1049,9 +1082,10 @@ EvdevProcessEvent(InputInfoPtr pInfo, struct input_event *ev)
EvdevProcessKeyEvent(pInfo, ev);
break;
case EV_SYN:
- EvdevProcessSyncEvent(pInfo, ev);
+ syn_dropped = EvdevProcessSyncEvent(pInfo, ev);
break;
}
+ return syn_dropped;
}
#undef ABS_X_VALUE
@@ -1082,6 +1116,227 @@ EvdevFreeMasks(EvdevPtr pEvdev)
#endif
}
+static void
+EvdevGetKernelTime(struct timeval *current_time, BOOL use_monotonic) {
+ struct timespec now;
+ clockid_t clockid = (use_monotonic) ? CLOCK_MONOTONIC : CLOCK_REALTIME;
+
+ clock_gettime(clockid, &now);
+ current_time->tv_sec = now.tv_sec;
+ current_time->tv_usec = now.tv_nsec / 1000;
+}
+
+static int
+EvdevInjectEvent(InputInfoPtr pInfo, uint16_t type, uint16_t code,
+ int32_t value) {
+ EvdevPtr pEvdev = pInfo->private;
+ struct input_event ev;
+
+ ev.type = type;
+ ev.code = code;
+ ev.value = value;
+ EvdevGetKernelTime(&ev.time, pEvdev->is_monotonic);
+ /* Inject the event by processing it */
+ EvdevProcessEvent(pInfo, &ev);
+ return 1;
+}
+
+static int
+EvdevKeyStateSync(InputInfoPtr pInfo) {
+ EvdevPtr pEvdev = pInfo->private;
+ unsigned long key_state_bitmask[NLONGS(KEY_CNT)];
+ int i, ev_count = 0;
+ int len = sizeof(key_state_bitmask);
+
+ if (ioctl(pInfo->fd, EVIOCGKEY(len), key_state_bitmask) < 0) {
+ xf86IDrvMsg(pInfo, X_ERROR,
+ "ioctl EVIOCGKEY failed: %s\n", strerror(errno));
+ return !Success;
+ }
+ for (i = 0; i < KEY_CNT; i++) {
+ int orig_value, current_value;
+ if (!EvdevBitIsSet(pEvdev->key_bitmask, i))
+ continue;
+ orig_value = EvdevBitIsSet(pEvdev->key_state_bitmask, i);
+ current_value = EvdevBitIsSet(key_state_bitmask, i);
+ if (current_value == orig_value)
+ continue;
+ ev_count += EvdevInjectEvent(pInfo, EV_KEY, i, current_value);
+ }
+ return ev_count;
+}
+
+static int
+EvdevAbsAxesSync(InputInfoPtr pInfo) {
+ EvdevPtr device = pInfo->private;
+ struct input_absinfo absinfo;
+ int i, ev_count = 0;
+
+ /* Sync all ABS_ axes excluding ABS_MT_ axes */
+ for (i = ABS_X; i < ABS_MAX; i++) {
+ if (i > ABS_MT_SLOT && i < _ABS_MT_LAST)
+ continue;
+ if (!EvdevBitIsSet(device->abs_bitmask, i))
+ continue;
+ if (ioctl(pInfo->fd, EVIOCGABS(i), &absinfo) < 0) {
+ xf86IDrvMsg(pInfo, X_ERROR, "ioctl EVIOCGABS(%zu) failed: %s\n",
+ i, strerror(errno));
+ } else if (absinfo.value != device->absinfo[i].value) {
+ ev_count += EvdevInjectEvent(pInfo, EV_ABS, i, absinfo.value);
+ }
+ }
+ return ev_count;
+}
+
+static int
+EvdevInjectTrackingIdChangeEvents(InputInfoPtr pInfo, int slot_index,
+ int curr_tid)
+{
+ EvdevPtr device = pInfo->private;
+ int orig_tid = device->cached_tid[slot_index];
+ int ev_count = 0;
+
+ if (curr_tid != orig_tid) {
+ ev_count += EvdevInjectEvent(pInfo, EV_ABS, ABS_MT_SLOT, slot_index);
+ /* The finger id is changed, remove the original finger then */
+ if (orig_tid != -1) {
+ ev_count += EvdevInjectEvent(pInfo, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ if (curr_tid != -1)
+ ev_count += EvdevInjectEvent(pInfo, EV_SYN, SYN_REPORT, 0);
+ }
+ if (curr_tid != -1)
+ ev_count += EvdevInjectEvent(pInfo, EV_ABS,
+ ABS_MT_TRACKING_ID, curr_tid);
+ }
+ return ev_count;
+}
+
+static int
+EvdevCheckAbsMtAxesChange(InputInfoPtr pInfo, MTSlotInfo *slots)
+{
+ EvdevPtr device = pInfo->private;
+ int i, j, ev_count = 0;
+
+ for (i = 0; i < num_slots(device); i++) {
+ int curr_tid = slots[ABS_MT_TRACKING_ID - _ABS_MT_FIRST].values[i];
+ ev_count += EvdevInjectTrackingIdChangeEvents(pInfo, i, curr_tid);
+ if (curr_tid == -1)
+ continue;
+ for (j = _ABS_MT_FIRST; j < _ABS_MT_LAST; j++) {
+ int axis = j - _ABS_MT_FIRST;
+ int map, orig_value, curr_value;
+ if (j == ABS_MT_TRACKING_ID)
+ continue;
+ if (!EvdevBitIsSet(device->abs_bitmask, j))
+ continue;
+ if (((map = device->axis_map[j]) == -1) &&
+ (j != ABS_MT_TRACKING_ID))
+ continue;
+ orig_value = valuator_mask_get(device->vals, map);
+ curr_value = slots[axis].values[i];
+ if (orig_value == curr_value)
+ continue;
+ if (i != device->cur_slot)
+ ev_count += EvdevInjectEvent(pInfo, EV_ABS, ABS_MT_SLOT, i);
+ ev_count += EvdevInjectEvent(pInfo, EV_ABS, j, curr_value);
+ }
+ }
+
+ return ev_count;
+}
+
+static int
+EvdevGetAllSlotVals(InputInfoPtr pInfo, MTSlotInfo *slots)
+{
+ EvdevPtr device = pInfo->private;
+ int i;
+
+ /* Retrieve current ABS_MT_ axes for all slots */
+ for (i = _ABS_MT_FIRST; i < _ABS_MT_LAST; i++) {
+ MTSlotInfo *req = &slots[i - _ABS_MT_FIRST];
+ if (!EvdevBitIsSet(device->abs_bitmask, i))
+ continue;
+ req->code = i;
+ if (ioctl(pInfo->fd, EVIOCGMTSLOTS((sizeof(*req))), req) < 0) {
+ xf86IDrvMsg(pInfo, X_ERROR,
+ "ioctl EVIOCGMTSLOTS(req.code=%d) failed: %s\n",
+ req->code, strerror(errno));
+ return !Success;
+ }
+ }
+
+ return Success;
+}
+
+static int
+EvdevAbsMtStateSync(InputInfoPtr pInfo) {
+ EvdevPtr device = pInfo->private;
+ MTSlotInfo slots[_ABS_MT_LAST - _ABS_MT_FIRST + 1];
+ int ev_count = 0;
+
+ /* Get all current slots axes, then check if there is any update required */
+ if (EvdevGetAllSlotVals(pInfo, slots) == Success)
+ ev_count = EvdevCheckAbsMtAxesChange(pInfo, slots);
+
+ return ev_count;
+}
+
+static int
+EvdevAbsStateSync(InputInfoPtr pInfo) {
+ EvdevPtr device = pInfo->private;
+ int i, ev_count;
+
+ /* Sync all ABS_ axes first */
+ ev_count = EvdevAbsAxesSync(pInfo);
+
+ /* Sync ABS_MT_ axes for all slots if exists */
+ if (device->num_mt_vals) {
+ ev_count += EvdevAbsMtStateSync(pInfo);
+ /* Set the current slot id then */
+ if (EvdevBitIsSet(device->abs_bitmask, ABS_MT_SLOT))
+ ev_count += EvdevAbsAxesSync(pInfo);
+ }
+
+ return ev_count;
+}
+
+/**
+ * Synchronize the current state with kernel evdev driver.
+ */
+static void
+EvdevSyncState(InputInfoPtr pInfo)
+{
+ int ev_count = 0;
+ EvdevPtr origin = pInfo->private;
+
+ EvdevGetKernelTime(&origin->before_sync_time, origin->is_monotonic);
+
+ ev_count = EvdevKeyStateSync(pInfo);
+
+ /*
+ * TODO: sync all led, switch and sound states as well. We probably need
+ * to post events out actively if the new states are different from the
+ * cached ones.
+ */
+
+ ev_count += EvdevAbsStateSync(pInfo); /* sync abs value/limits */
+
+ /*
+ * Push SYN_REPORT event out if there is any event injected
+ * during the state synchronization.
+ */
+ if (ev_count)
+ ev_count += EvdevInjectEvent(pInfo, EV_SYN, SYN_REPORT, 0);
+
+ EvdevGetKernelTime(&origin->after_sync_time, origin->is_monotonic);
+
+ xf86IDrvMsg(pInfo, X_INFO, "Sync_State: before %ld.%ld after %ld.%ld\n",
+ origin->before_sync_time.tv_sec,
+ origin->before_sync_time.tv_usec,
+ origin->after_sync_time.tv_sec,
+ origin->after_sync_time.tv_usec);
+}
+
/* just a magic number to reduce the number of reads */
#define NUM_EVENTS 16
@@ -1090,6 +1345,7 @@ EvdevReadInput(InputInfoPtr pInfo)
{
struct input_event ev[NUM_EVENTS];
int i, len = sizeof(ev);
+ BOOL sync_evdev_state = FALSE;
while (len == sizeof(ev))
{
@@ -1120,9 +1376,23 @@ EvdevReadInput(InputInfoPtr pInfo)
break;
}
- for (i = 0; i < len/sizeof(ev[0]); i++)
- EvdevProcessEvent(pInfo, &ev[i]);
+ for (i = 0; i < len/sizeof(ev[0]); i++) {
+ if (sync_evdev_state)
+ break;
+ if (timercmp(&ev[i].time, &pEvdev->before_sync_time, <)) {
+ /* Ignore events before last sync time */
+ continue;
+ } else if (timercmp(&ev[i].time, &pEvdev->after_sync_time, >)) {
+ /* Event_Process returns TRUE if SYN_DROPPED detected */
+ sync_evdev_state = EvdevProcessEvent(pInfo, &ev[i]);
+ } else {
+ /* If the event occurred during sync, then sync again */
+ sync_evdev_state = TRUE;
+ }
+ }
}
+ if (sync_evdev_state)
+ EvdevSyncState(pInfo);
}
static void
@@ -1308,6 +1578,7 @@ EvdevAddAbsValuatorClass(DeviceIntPtr device)
}
for (i = 0; i < num_slots(pEvdev); i++) {
+ pEvdev->cached_tid[i] = -1;
pEvdev->last_mt_vals[i] = valuator_mask_new(num_mt_axes_total);
if (!pEvdev->last_mt_vals[i]) {
xf86IDrvMsg(pInfo, X_ERROR,
@@ -1833,7 +2104,9 @@ EvdevOn(DeviceIntPtr device)
Evdev3BEmuOn(pInfo);
pEvdev->flags |= EVDEV_INITIALIZED;
device->public.on = TRUE;
+ pEvdev->slot_state = SLOTSTATE_EMPTY;
+ EvdevSyncState(pInfo);
return Success;
}
diff --git a/src/evdev.h b/src/evdev.h
index 58a3fa3..35a4627 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -101,6 +101,10 @@
/* Number of longs needed to hold the given number of bits */
#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
+#define _ABS_MT_FIRST ABS_MT_TOUCH_MAJOR
+#define _ABS_MT_LAST ABS_MT_DISTANCE
+#define _ABS_MT_CNT (_ABS_MT_LAST - _ABS_MT_FIRST + 1)
+
/* Function key mode */
enum fkeymode {
FKEYMODE_UNKNOWN = 0,
@@ -251,8 +255,21 @@ typedef struct {
EventQueueRec queue[EVDEV_MAXQUEUE];
enum fkeymode fkeymode;
+
+ /* Sync timestamps */
+ unsigned long key_state_bitmask[NLONGS(KEY_CNT)];
+ struct timeval before_sync_time;
+ struct timeval after_sync_time;
+ int32_t cached_tid[64];
} EvdevRec, *EvdevPtr;
+#define MAX_SLOT_COUNT 64
+
+typedef struct {
+ uint32_t code;
+ int32_t values[MAX_SLOT_COUNT];
+} MTSlotInfo, *MTSlotInfoPtr;
+
/* Event posting functions */
void EvdevQueueKbdEvent(InputInfoPtr pInfo, struct input_event *ev, int value);
void EvdevQueueButtonEvent(InputInfoPtr pInfo, int button, int value);
--
1.7.7.3
More information about the xorg-devel
mailing list