summaryrefslogtreecommitdiffstats
path: root/sys/dev/evdev/cdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/evdev/cdev.c')
-rw-r--r--sys/dev/evdev/cdev.c860
1 files changed, 860 insertions, 0 deletions
diff --git a/sys/dev/evdev/cdev.c b/sys/dev/evdev/cdev.c
new file mode 100644
index 0000000..fb81ace
--- /dev/null
+++ b/sys/dev/evdev/cdev.c
@@ -0,0 +1,860 @@
+/*-
+ * Copyright (c) 2014 Jakub Wojciech Klama <jceel@FreeBSD.org>
+ * Copyright (c) 2015-2016 Vladimir Kondratyev <wulf@cicgroup.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include "opt_evdev.h"
+
+#include <sys/types.h>
+#include <sys/bitstring.h>
+#include <sys/systm.h>
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/proc.h>
+#include <sys/poll.h>
+#include <sys/filio.h>
+#include <sys/fcntl.h>
+#include <sys/selinfo.h>
+#include <sys/malloc.h>
+#include <sys/time.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#include <dev/evdev/evdev_private.h>
+
+#ifdef EVDEV_DEBUG
+#define debugf(client, fmt, args...) printf("evdev cdev: "fmt"\n", ##args)
+#else
+#define debugf(client, fmt, args...)
+#endif
+
+#define DEF_RING_REPORTS 8
+
+static d_open_t evdev_open;
+static d_read_t evdev_read;
+static d_write_t evdev_write;
+static d_ioctl_t evdev_ioctl;
+static d_poll_t evdev_poll;
+static d_kqfilter_t evdev_kqfilter;
+
+static int evdev_kqread(struct knote *kn, long hint);
+static void evdev_kqdetach(struct knote *kn);
+static void evdev_dtor(void *);
+static int evdev_ioctl_eviocgbit(struct evdev_dev *, int, int, caddr_t);
+static void evdev_client_filter_queue(struct evdev_client *, uint16_t);
+
+static struct cdevsw evdev_cdevsw = {
+ .d_version = D_VERSION,
+ .d_open = evdev_open,
+ .d_read = evdev_read,
+ .d_write = evdev_write,
+ .d_ioctl = evdev_ioctl,
+ .d_poll = evdev_poll,
+ .d_kqfilter = evdev_kqfilter,
+ .d_name = "evdev",
+};
+
+static struct filterops evdev_cdev_filterops = {
+ .f_isfd = 1,
+ .f_attach = NULL,
+ .f_detach = evdev_kqdetach,
+ .f_event = evdev_kqread,
+};
+
+static int
+evdev_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+ struct evdev_dev *evdev = dev->si_drv1;
+ struct evdev_client *client;
+ size_t buffer_size;
+ int ret;
+
+ if (evdev == NULL)
+ return (ENODEV);
+
+ /* Initialize client structure */
+ buffer_size = evdev->ev_report_size * DEF_RING_REPORTS;
+ client = malloc(offsetof(struct evdev_client, ec_buffer) +
+ sizeof(struct input_event) * buffer_size,
+ M_EVDEV, M_WAITOK | M_ZERO);
+
+ /* Initialize ring buffer */
+ client->ec_buffer_size = buffer_size;
+ client->ec_buffer_head = 0;
+ client->ec_buffer_tail = 0;
+ client->ec_buffer_ready = 0;
+
+ client->ec_evdev = evdev;
+ mtx_init(&client->ec_buffer_mtx, "evclient", "evdev", MTX_DEF);
+ knlist_init_mtx(&client->ec_selp.si_note, &client->ec_buffer_mtx);
+
+ /* Avoid race with evdev_unregister */
+ EVDEV_LOCK(evdev);
+ if (dev->si_drv1 == NULL)
+ ret = ENODEV;
+ else
+ ret = evdev_register_client(evdev, client);
+
+ if (ret != 0)
+ evdev_revoke_client(client);
+ /*
+ * Unlock evdev here because non-sleepable lock held
+ * while calling devfs_set_cdevpriv upsets WITNESS
+ */
+ EVDEV_UNLOCK(evdev);
+
+ if (!ret)
+ ret = devfs_set_cdevpriv(client, evdev_dtor);
+
+ if (ret != 0) {
+ debugf(client, "cannot register evdev client");
+ evdev_dtor(client);
+ }
+
+ return (ret);
+}
+
+static void
+evdev_dtor(void *data)
+{
+ struct evdev_client *client = (struct evdev_client *)data;
+
+ EVDEV_LOCK(client->ec_evdev);
+ if (!client->ec_revoked)
+ evdev_dispose_client(client->ec_evdev, client);
+ EVDEV_UNLOCK(client->ec_evdev);
+
+ knlist_clear(&client->ec_selp.si_note, 0);
+ seldrain(&client->ec_selp);
+ knlist_destroy(&client->ec_selp.si_note);
+ funsetown(&client->ec_sigio);
+ mtx_destroy(&client->ec_buffer_mtx);
+ free(client, M_EVDEV);
+}
+
+static int
+evdev_read(struct cdev *dev, struct uio *uio, int ioflag)
+{
+ struct evdev_client *client;
+ struct input_event *event;
+ int ret = 0;
+ int remaining;
+
+ ret = devfs_get_cdevpriv((void **)&client);
+ if (ret != 0)
+ return (ret);
+
+ debugf(client, "read %zd bytes by thread %d", uio->uio_resid,
+ uio->uio_td->td_tid);
+
+ if (client->ec_revoked)
+ return (ENODEV);
+
+ /* Zero-sized reads are allowed for error checking */
+ if (uio->uio_resid != 0 && uio->uio_resid < sizeof(struct input_event))
+ return (EINVAL);
+
+ remaining = uio->uio_resid / sizeof(struct input_event);
+
+ EVDEV_CLIENT_LOCKQ(client);
+
+ if (EVDEV_CLIENT_EMPTYQ(client)) {
+ if (ioflag & O_NONBLOCK)
+ ret = EWOULDBLOCK;
+ else {
+ if (remaining != 0) {
+ client->ec_blocked = true;
+ ret = mtx_sleep(client, &client->ec_buffer_mtx,
+ PCATCH, "evread", 0);
+ }
+ }
+ }
+
+ while (ret == 0 && !EVDEV_CLIENT_EMPTYQ(client) && remaining > 0) {
+ event = &client->ec_buffer[client->ec_buffer_head];
+ client->ec_buffer_head =
+ (client->ec_buffer_head + 1) % client->ec_buffer_size;
+ remaining--;
+
+ EVDEV_CLIENT_UNLOCKQ(client);
+ ret = uiomove(event, sizeof(struct input_event), uio);
+ EVDEV_CLIENT_LOCKQ(client);
+ }
+
+ EVDEV_CLIENT_UNLOCKQ(client);
+
+ return (ret);
+}
+
+static int
+evdev_write(struct cdev *dev, struct uio *uio, int ioflag)
+{
+ struct evdev_dev *evdev = dev->si_drv1;
+ struct evdev_client *client;
+ struct input_event event;
+ int ret = 0;
+
+ ret = devfs_get_cdevpriv((void **)&client);
+ if (ret != 0)
+ return (ret);
+
+ debugf(client, "write %zd bytes by thread %d", uio->uio_resid,
+ uio->uio_td->td_tid);
+
+ if (client->ec_revoked || evdev == NULL)
+ return (ENODEV);
+
+ if (uio->uio_resid % sizeof(struct input_event) != 0) {
+ debugf(client, "write size not multiple of input_event size");
+ return (EINVAL);
+ }
+
+ while (uio->uio_resid > 0 && ret == 0) {
+ ret = uiomove(&event, sizeof(struct input_event), uio);
+ if (ret == 0)
+ ret = evdev_inject_event(evdev, event.type, event.code,
+ event.value);
+ }
+
+ return (ret);
+}
+
+static int
+evdev_poll(struct cdev *dev, int events, struct thread *td)
+{
+ struct evdev_client *client;
+ int ret;
+ int revents = 0;
+
+ ret = devfs_get_cdevpriv((void **)&client);
+ if (ret != 0)
+ return (POLLNVAL);
+
+ debugf(client, "poll by thread %d", td->td_tid);
+
+ if (client->ec_revoked)
+ return (POLLHUP);
+
+ if (events & (POLLIN | POLLRDNORM)) {
+ EVDEV_CLIENT_LOCKQ(client);
+ if (!EVDEV_CLIENT_EMPTYQ(client))
+ revents = events & (POLLIN | POLLRDNORM);
+ else {
+ client->ec_selected = true;
+ selrecord(td, &client->ec_selp);
+ }
+ EVDEV_CLIENT_UNLOCKQ(client);
+ }
+
+ return (revents);
+}
+
+static int
+evdev_kqfilter(struct cdev *dev, struct knote *kn)
+{
+ struct evdev_client *client;
+ int ret;
+
+ ret = devfs_get_cdevpriv((void **)&client);
+ if (ret != 0)
+ return (ret);
+
+ if (client->ec_revoked)
+ return (ENODEV);
+
+ switch(kn->kn_filter) {
+ case EVFILT_READ:
+ kn->kn_fop = &evdev_cdev_filterops;
+ break;
+ default:
+ return(EINVAL);
+ }
+ kn->kn_hook = (caddr_t)client;
+
+ knlist_add(&client->ec_selp.si_note, kn, 0);
+ return (0);
+}
+
+static int
+evdev_kqread(struct knote *kn, long hint)
+{
+ struct evdev_client *client;
+ int ret;
+
+ client = (struct evdev_client *)kn->kn_hook;
+
+ EVDEV_CLIENT_LOCKQ_ASSERT(client);
+
+ if (client->ec_revoked) {
+ kn->kn_flags |= EV_EOF;
+ ret = 1;
+ } else {
+ kn->kn_data = EVDEV_CLIENT_SIZEQ(client) *
+ sizeof(struct input_event);
+ ret = !EVDEV_CLIENT_EMPTYQ(client);
+ }
+ return (ret);
+}
+
+static void
+evdev_kqdetach(struct knote *kn)
+{
+ struct evdev_client *client;
+
+ client = (struct evdev_client *)kn->kn_hook;
+ knlist_remove(&client->ec_selp.si_note, kn, 0);
+}
+
+static int
+evdev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
+ struct thread *td)
+{
+ struct evdev_dev *evdev = dev->si_drv1;
+ struct evdev_client *client;
+ struct input_keymap_entry *ke;
+ int ret, len, limit, type_num;
+ uint32_t code;
+ size_t nvalues;
+
+ ret = devfs_get_cdevpriv((void **)&client);
+ if (ret != 0)
+ return (ret);
+
+ if (client->ec_revoked || evdev == NULL)
+ return (ENODEV);
+
+ /* file I/O ioctl handling */
+ switch (cmd) {
+ case FIOSETOWN:
+ return (fsetown(*(int *)data, &client->ec_sigio));
+
+ case FIOGETOWN:
+ *(int *)data = fgetown(&client->ec_sigio);
+ return (0);
+
+ case FIONBIO:
+ return (0);
+
+ case FIOASYNC:
+ if (*(int *)data)
+ client->ec_async = true;
+ else
+ client->ec_async = false;
+
+ return (0);
+
+ case FIONREAD:
+ EVDEV_CLIENT_LOCKQ(client);
+ *(int *)data =
+ EVDEV_CLIENT_SIZEQ(client) * sizeof(struct input_event);
+ EVDEV_CLIENT_UNLOCKQ(client);
+ return (0);
+ }
+
+ len = IOCPARM_LEN(cmd);
+ debugf(client, "ioctl called: cmd=0x%08lx, data=%p", cmd, data);
+
+ /* evdev fixed-length ioctls handling */
+ switch (cmd) {
+ case EVIOCGVERSION:
+ *(int *)data = EV_VERSION;
+ return (0);
+
+ case EVIOCGID:
+ debugf(client, "EVIOCGID: bus=%d vendor=0x%04x product=0x%04x",
+ evdev->ev_id.bustype, evdev->ev_id.vendor,
+ evdev->ev_id.product);
+ memcpy(data, &evdev->ev_id, sizeof(struct input_id));
+ return (0);
+
+ case EVIOCGREP:
+ if (!evdev_event_supported(evdev, EV_REP))
+ return (ENOTSUP);
+
+ memcpy(data, evdev->ev_rep, sizeof(evdev->ev_rep));
+ return (0);
+
+ case EVIOCSREP:
+ if (!evdev_event_supported(evdev, EV_REP))
+ return (ENOTSUP);
+
+ evdev_inject_event(evdev, EV_REP, REP_DELAY, ((int *)data)[0]);
+ evdev_inject_event(evdev, EV_REP, REP_PERIOD,
+ ((int *)data)[1]);
+ return (0);
+
+ case EVIOCGKEYCODE:
+ /* Fake unsupported ioctl */
+ return (0);
+
+ case EVIOCGKEYCODE_V2:
+ if (evdev->ev_methods == NULL ||
+ evdev->ev_methods->ev_get_keycode == NULL)
+ return (ENOTSUP);
+
+ ke = (struct input_keymap_entry *)data;
+ evdev->ev_methods->ev_get_keycode(evdev, evdev->ev_softc, ke);
+ return (0);
+
+ case EVIOCSKEYCODE:
+ /* Fake unsupported ioctl */
+ return (0);
+
+ case EVIOCSKEYCODE_V2:
+ if (evdev->ev_methods == NULL ||
+ evdev->ev_methods->ev_set_keycode == NULL)
+ return (ENOTSUP);
+
+ ke = (struct input_keymap_entry *)data;
+ evdev->ev_methods->ev_set_keycode(evdev, evdev->ev_softc, ke);
+ return (0);
+
+ case EVIOCGABS(0) ... EVIOCGABS(ABS_MAX):
+ if (evdev->ev_absinfo == NULL)
+ return (EINVAL);
+
+ memcpy(data, &evdev->ev_absinfo[cmd - EVIOCGABS(0)],
+ sizeof(struct input_absinfo));
+ return (0);
+
+ case EVIOCSABS(0) ... EVIOCSABS(ABS_MAX):
+ if (evdev->ev_absinfo == NULL)
+ return (EINVAL);
+
+ code = cmd - EVIOCSABS(0);
+ /* mt-slot number can not be changed */
+ if (code == ABS_MT_SLOT)
+ return (EINVAL);
+
+ EVDEV_LOCK(evdev);
+ evdev_set_absinfo(evdev, code, (struct input_absinfo *)data);
+ EVDEV_UNLOCK(evdev);
+ return (0);
+
+ case EVIOCSFF:
+ case EVIOCRMFF:
+ case EVIOCGEFFECTS:
+ /* Fake unsupported ioctls */
+ return (0);
+
+ case EVIOCGRAB:
+ EVDEV_LOCK(evdev);
+ if (*(int *)data)
+ ret = evdev_grab_client(evdev, client);
+ else
+ ret = evdev_release_client(evdev, client);
+ EVDEV_UNLOCK(evdev);
+ return (ret);
+
+ case EVIOCREVOKE:
+ if (*(int *)data != 0)
+ return (EINVAL);
+
+ EVDEV_LOCK(evdev);
+ if (dev->si_drv1 != NULL && !client->ec_revoked) {
+ evdev_dispose_client(evdev, client);
+ evdev_revoke_client(client);
+ }
+ EVDEV_UNLOCK(evdev);
+ return (0);
+
+ case EVIOCSCLOCKID:
+ switch (*(int *)data) {
+ case CLOCK_REALTIME:
+ client->ec_clock_id = EV_CLOCK_REALTIME;
+ return (0);
+ case CLOCK_MONOTONIC:
+ client->ec_clock_id = EV_CLOCK_MONOTONIC;
+ return (0);
+ default:
+ return (EINVAL);
+ }
+ }
+
+ /* evdev variable-length ioctls handling */
+ switch (IOCBASECMD(cmd)) {
+ case EVIOCGNAME(0):
+ strlcpy(data, evdev->ev_name, len);
+ return (0);
+
+ case EVIOCGPHYS(0):
+ if (evdev->ev_shortname[0] == 0)
+ return (ENOENT);
+
+ strlcpy(data, evdev->ev_shortname, len);
+ return (0);
+
+ case EVIOCGUNIQ(0):
+ if (evdev->ev_serial[0] == 0)
+ return (ENOENT);
+
+ strlcpy(data, evdev->ev_serial, len);
+ return (0);
+
+ case EVIOCGPROP(0):
+ limit = MIN(len, bitstr_size(INPUT_PROP_CNT));
+ memcpy(data, evdev->ev_prop_flags, limit);
+ return (0);
+
+ case EVIOCGMTSLOTS(0):
+ if (evdev->ev_mt == NULL)
+ return (EINVAL);
+ if (len < sizeof(uint32_t))
+ return (EINVAL);
+ code = *(uint32_t *)data;
+ if (!ABS_IS_MT(code))
+ return (EINVAL);
+
+ nvalues =
+ MIN(len / sizeof(int32_t) - 1, MAXIMAL_MT_SLOT(evdev) + 1);
+ for (int i = 0; i < nvalues; i++)
+ ((int32_t *)data)[i + 1] =
+ evdev_get_mt_value(evdev, i, code);
+ return (0);
+
+ case EVIOCGKEY(0):
+ limit = MIN(len, bitstr_size(KEY_CNT));
+ EVDEV_LOCK(evdev);
+ evdev_client_filter_queue(client, EV_KEY);
+ memcpy(data, evdev->ev_key_states, limit);
+ EVDEV_UNLOCK(evdev);
+ return (0);
+
+ case EVIOCGLED(0):
+ limit = MIN(len, bitstr_size(LED_CNT));
+ EVDEV_LOCK(evdev);
+ evdev_client_filter_queue(client, EV_LED);
+ memcpy(data, evdev->ev_led_states, limit);
+ EVDEV_UNLOCK(evdev);
+ return (0);
+
+ case EVIOCGSND(0):
+ limit = MIN(len, bitstr_size(SND_CNT));
+ EVDEV_LOCK(evdev);
+ evdev_client_filter_queue(client, EV_SND);
+ memcpy(data, evdev->ev_snd_states, limit);
+ EVDEV_UNLOCK(evdev);
+ return (0);
+
+ case EVIOCGSW(0):
+ limit = MIN(len, bitstr_size(SW_CNT));
+ EVDEV_LOCK(evdev);
+ evdev_client_filter_queue(client, EV_SW);
+ memcpy(data, evdev->ev_sw_states, limit);
+ EVDEV_UNLOCK(evdev);
+ return (0);
+
+ case EVIOCGBIT(0, 0) ... EVIOCGBIT(EV_MAX, 0):
+ type_num = IOCBASECMD(cmd) - EVIOCGBIT(0, 0);
+ debugf(client, "EVIOCGBIT(%d): data=%p, len=%d", type_num,
+ data, len);
+ return (evdev_ioctl_eviocgbit(evdev, type_num, len, data));
+ }
+
+ return (EINVAL);
+}
+
+static int
+evdev_ioctl_eviocgbit(struct evdev_dev *evdev, int type, int len, caddr_t data)
+{
+ unsigned long *bitmap;
+ int limit;
+
+ switch (type) {
+ case 0:
+ bitmap = evdev->ev_type_flags;
+ limit = EV_CNT;
+ break;
+ case EV_KEY:
+ bitmap = evdev->ev_key_flags;
+ limit = KEY_CNT;
+ break;
+ case EV_REL:
+ bitmap = evdev->ev_rel_flags;
+ limit = REL_CNT;
+ break;
+ case EV_ABS:
+ bitmap = evdev->ev_abs_flags;
+ limit = ABS_CNT;
+ break;
+ case EV_MSC:
+ bitmap = evdev->ev_msc_flags;
+ limit = MSC_CNT;
+ break;
+ case EV_LED:
+ bitmap = evdev->ev_led_flags;
+ limit = LED_CNT;
+ break;
+ case EV_SND:
+ bitmap = evdev->ev_snd_flags;
+ limit = SND_CNT;
+ break;
+ case EV_SW:
+ bitmap = evdev->ev_sw_flags;
+ limit = SW_CNT;
+ break;
+ case EV_FF:
+ /*
+ * We don't support EV_FF now, so let's
+ * just fake it returning only zeros.
+ */
+ bzero(data, len);
+ return (0);
+ default:
+ return (ENOTTY);
+ }
+
+ /*
+ * Clear ioctl data buffer in case it's bigger than
+ * bitmap size
+ */
+ bzero(data, len);
+
+ limit = bitstr_size(limit);
+ len = MIN(limit, len);
+ memcpy(data, bitmap, len);
+ return (0);
+}
+
+void
+evdev_revoke_client(struct evdev_client *client)
+{
+
+ EVDEV_LOCK_ASSERT(client->ec_evdev);
+
+ client->ec_revoked = true;
+}
+
+void
+evdev_notify_event(struct evdev_client *client)
+{
+
+ EVDEV_CLIENT_LOCKQ_ASSERT(client);
+
+ if (client->ec_blocked) {
+ client->ec_blocked = false;
+ wakeup(client);
+ }
+ if (client->ec_selected) {
+ client->ec_selected = false;
+ selwakeup(&client->ec_selp);
+ }
+ KNOTE_LOCKED(&client->ec_selp.si_note, 0);
+
+ if (client->ec_async && client->ec_sigio != NULL)
+ pgsigio(&client->ec_sigio, SIGIO, 0);
+}
+
+int
+evdev_cdev_create(struct evdev_dev *evdev)
+{
+ struct make_dev_args mda;
+ int ret, unit = 0;
+
+ make_dev_args_init(&mda);
+ mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
+ mda.mda_devsw = &evdev_cdevsw;
+ mda.mda_uid = UID_ROOT;
+ mda.mda_gid = GID_WHEEL;
+ mda.mda_mode = 0600;
+ mda.mda_si_drv1 = evdev;
+
+ /* Try to coexist with cuse-backed input/event devices */
+ while ((ret = make_dev_s(&mda, &evdev->ev_cdev, "input/event%d", unit))
+ == EEXIST)
+ unit++;
+
+ if (ret == 0)
+ evdev->ev_unit = unit;
+
+ return (ret);
+}
+
+int
+evdev_cdev_destroy(struct evdev_dev *evdev)
+{
+
+ destroy_dev(evdev->ev_cdev);
+ return (0);
+}
+
+static void
+evdev_client_gettime(struct evdev_client *client, struct timeval *tv)
+{
+
+ switch (client->ec_clock_id) {
+ case EV_CLOCK_BOOTTIME:
+ /*
+ * XXX: FreeBSD does not support true POSIX monotonic clock.
+ * So aliase EV_CLOCK_BOOTTIME to EV_CLOCK_MONOTONIC.
+ */
+ case EV_CLOCK_MONOTONIC:
+ microuptime(tv);
+ break;
+
+ case EV_CLOCK_REALTIME:
+ default:
+ microtime(tv);
+ break;
+ }
+}
+
+void
+evdev_client_push(struct evdev_client *client, uint16_t type, uint16_t code,
+ int32_t value)
+{
+ struct timeval time;
+ size_t count, head, tail, ready;
+
+ EVDEV_CLIENT_LOCKQ_ASSERT(client);
+ head = client->ec_buffer_head;
+ tail = client->ec_buffer_tail;
+ ready = client->ec_buffer_ready;
+ count = client->ec_buffer_size;
+
+ /* If queue is full drop its content and place SYN_DROPPED event */
+ if ((tail + 1) % count == head) {
+ debugf(client, "client %p: buffer overflow", client);
+
+ head = (tail + count - 1) % count;
+ client->ec_buffer[head] = (struct input_event) {
+ .type = EV_SYN,
+ .code = SYN_DROPPED,
+ .value = 0
+ };
+ /*
+ * XXX: Here is a small race window from now till the end of
+ * report. The queue is empty but client has been already
+ * notified of data readyness. Can be fixed in two ways:
+ * 1. Implement bulk insert so queue lock would not be dropped
+ * till the SYN_REPORT event.
+ * 2. Insert SYN_REPORT just now and skip remaining events
+ */
+ client->ec_buffer_head = head;
+ client->ec_buffer_ready = head;
+ }
+
+ client->ec_buffer[tail].type = type;
+ client->ec_buffer[tail].code = code;
+ client->ec_buffer[tail].value = value;
+ client->ec_buffer_tail = (tail + 1) % count;
+
+ /* Allow users to read events only after report has been completed */
+ if (type == EV_SYN && code == SYN_REPORT) {
+ evdev_client_gettime(client, &time);
+ for (; ready != client->ec_buffer_tail;
+ ready = (ready + 1) % count)
+ client->ec_buffer[ready].time = time;
+ client->ec_buffer_ready = client->ec_buffer_tail;
+ }
+}
+
+void
+evdev_client_dumpqueue(struct evdev_client *client)
+{
+ struct input_event *event;
+ size_t i, head, tail, ready, size;
+
+ head = client->ec_buffer_head;
+ tail = client->ec_buffer_tail;
+ ready = client->ec_buffer_ready;
+ size = client->ec_buffer_size;
+
+ printf("evdev client: %p\n", client);
+ printf("event queue: head=%zu ready=%zu tail=%zu size=%zu\n",
+ head, ready, tail, size);
+
+ printf("queue contents:\n");
+
+ for (i = 0; i < size; i++) {
+ event = &client->ec_buffer[i];
+ printf("%zu: ", i);
+
+ if (i < head || i > tail)
+ printf("unused\n");
+ else
+ printf("type=%d code=%d value=%d ", event->type,
+ event->code, event->value);
+
+ if (i == head)
+ printf("<- head\n");
+ else if (i == tail)
+ printf("<- tail\n");
+ else if (i == ready)
+ printf("<- ready\n");
+ else
+ printf("\n");
+ }
+}
+
+static void
+evdev_client_filter_queue(struct evdev_client *client, uint16_t type)
+{
+ struct input_event *event;
+ size_t head, tail, count, i;
+ bool last_was_syn = false;
+
+ EVDEV_CLIENT_LOCKQ(client);
+
+ i = head = client->ec_buffer_head;
+ tail = client->ec_buffer_tail;
+ count = client->ec_buffer_size;
+ client->ec_buffer_ready = client->ec_buffer_tail;
+
+ while (i != client->ec_buffer_tail) {
+ event = &client->ec_buffer[i];
+ i = (i + 1) % count;
+
+ /* Skip event of given type */
+ if (event->type == type)
+ continue;
+
+ /* Remove empty SYN_REPORT events */
+ if (event->type == EV_SYN && event->code == SYN_REPORT) {
+ if (last_was_syn)
+ continue;
+ else
+ client->ec_buffer_ready = (tail + 1) % count;
+ }
+
+ /* Rewrite entry */
+ memcpy(&client->ec_buffer[tail], event,
+ sizeof(struct input_event));
+
+ last_was_syn = (event->type == EV_SYN &&
+ event->code == SYN_REPORT);
+
+ tail = (tail + 1) % count;
+ }
+
+ client->ec_buffer_head = i;
+ client->ec_buffer_tail = tail;
+
+ EVDEV_CLIENT_UNLOCKQ(client);
+}
OpenPOWER on IntegriCloud