svn commit: r305706 - in head: etc/mtree include sys/conf sys/dev/evdev
Oleksandr Tymoshenko
gonzo at FreeBSD.org
Sun Sep 11 18:56:40 UTC 2016
Author: gonzo
Date: Sun Sep 11 18:56:38 2016
New Revision: 305706
URL: https://svnweb.freebsd.org/changeset/base/305706
Log:
Add evdev protocol implementation
evdev is a generic input event interface compatible with Linux
evdev API at ioctl level. It allows using unmodified (apart from
header name) input evdev drivers in Xorg, Wayland, Qt.
This commit has only generic kernel API. evdev support for individual
hardware drivers like ukbd, ums, atkbd, etc. will be committed later.
Project was started by Jakub Klama as part of GSoC 2014. Jakub's
evdev implementation was later used as a base, updated and finished
by Vladimir Kondratiev.
Submitted by: Vladimir Kondratiev <wulf at cicgroup.ru>
Reviewed by: adrian, hans
Differential Revision: https://reviews.freebsd.org/D6998
Added:
head/sys/dev/evdev/
head/sys/dev/evdev/cdev.c (contents, props changed)
head/sys/dev/evdev/evdev.c (contents, props changed)
head/sys/dev/evdev/evdev.h (contents, props changed)
head/sys/dev/evdev/evdev_mt.c (contents, props changed)
head/sys/dev/evdev/evdev_private.h (contents, props changed)
head/sys/dev/evdev/evdev_utils.c (contents, props changed)
head/sys/dev/evdev/input-event-codes.h (contents, props changed)
head/sys/dev/evdev/input.h (contents, props changed)
head/sys/dev/evdev/uinput.c (contents, props changed)
head/sys/dev/evdev/uinput.h (contents, props changed)
Modified:
head/etc/mtree/BSD.include.dist
head/include/Makefile
head/sys/conf/NOTES
head/sys/conf/files
head/sys/conf/options
Modified: head/etc/mtree/BSD.include.dist
==============================================================================
--- head/etc/mtree/BSD.include.dist Sun Sep 11 18:15:34 2016 (r305705)
+++ head/etc/mtree/BSD.include.dist Sun Sep 11 18:56:38 2016 (r305706)
@@ -110,6 +110,8 @@
..
ciss
..
+ evdev
+ ..
filemon
..
firewire
Modified: head/include/Makefile
==============================================================================
--- head/include/Makefile Sun Sep 11 18:15:34 2016 (r305705)
+++ head/include/Makefile Sun Sep 11 18:56:38 2016 (r305706)
@@ -154,7 +154,7 @@ copies: .PHONY .META
done; \
fi
.endfor
-.for i in ${LDIRS} ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/bktr:Ndev/nand:Ndev/pci} ${LSUBSUBDIRS}
+.for i in ${LDIRS} ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/bktr:Ndev/evdev:Ndev/nand:Ndev/pci} ${LSUBSUBDIRS}
cd ${.CURDIR}/../sys; \
${INSTALL} -C ${TAG_ARGS} -o ${BINOWN} -g ${BINGRP} -m 444 $i/*.h \
${DESTDIR}${INCLUDEDIR}/$i
@@ -177,6 +177,13 @@ copies: .PHONY .META
${INSTALL} -C ${TAG_ARGS} -o ${BINOWN} -g ${BINGRP} -m 444 nand_dev.h \
${DESTDIR}${INCLUDEDIR}/dev/nand
.endif
+ cd ${.CURDIR}/../sys/dev/evdev; \
+ ${INSTALL} -C -o ${BINOWN} -g ${BINGRP} -m 444 input.h \
+ ${DESTDIR}${INCLUDEDIR}/dev/evdev; \
+ ${INSTALL} -C -o ${BINOWN} -g ${BINGRP} -m 444 input-event-codes.h \
+ ${DESTDIR}${INCLUDEDIR}/dev/evdev; \
+ ${INSTALL} -C -o ${BINOWN} -g ${BINGRP} -m 444 uinput.h \
+ ${DESTDIR}${INCLUDEDIR}/dev/evdev
cd ${.CURDIR}/../sys/dev/pci; \
${INSTALL} -C ${TAG_ARGS} -o ${BINOWN} -g ${BINGRP} -m 444 pcireg.h \
${DESTDIR}${INCLUDEDIR}/dev/pci
@@ -238,7 +245,7 @@ symlinks: .PHONY .META
${INSTALL_SYMLINK} ${TAG_ARGS} ../../../sys/$i/$$h ${DESTDIR}${INCLUDEDIR}/$i; \
done
.endfor
-.for i in ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/bktr:Ndev/nand:Ndev/pci}
+.for i in ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/bktr:Ndev/evdev:Ndev/nand:Ndev/pci}
cd ${.CURDIR}/../sys/$i; \
for h in *.h; do \
${INSTALL_SYMLINK} ${TAG_ARGS} ../../../../sys/$i/$$h ${DESTDIR}${INCLUDEDIR}/$i; \
@@ -266,6 +273,11 @@ symlinks: .PHONY .META
${DESTDIR}${INCLUDEDIR}/dev/nand; \
done
.endif
+ cd ${.CURDIR}/../sys/dev/evdev; \
+ for h in input.h input-event-codes.h uinput.h; do \
+ ln -fs ../../../../sys/dev/evdev/$$h \
+ ${DESTDIR}${INCLUDEDIR}/dev/evdev; \
+ done
cd ${.CURDIR}/../sys/dev/pci; \
for h in pcireg.h; do \
${INSTALL_SYMLINK} ${TAG_ARGS} ../../../../sys/dev/pci/$$h \
Modified: head/sys/conf/NOTES
==============================================================================
--- head/sys/conf/NOTES Sun Sep 11 18:15:34 2016 (r305705)
+++ head/sys/conf/NOTES Sun Sep 11 18:56:38 2016 (r305706)
@@ -3052,3 +3052,8 @@ options GZIO
# BHND(4) drivers
options BHND_LOGLEVEL # Logging threshold level
+
+# evdev interface
+options EVDEV
+options EVDEV_DEBUG
+options UINPUT_DEBUG
Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files Sun Sep 11 18:15:34 2016 (r305705)
+++ head/sys/conf/files Sun Sep 11 18:56:38 2016 (r305706)
@@ -1501,6 +1501,11 @@ dev/etherswitch/ip17x/ip17x_vlans.c opti
dev/etherswitch/miiproxy.c optional miiproxy
dev/etherswitch/rtl8366/rtl8366rb.c optional rtl8366rb
dev/etherswitch/ukswitch/ukswitch.c optional ukswitch
+dev/evdev/cdev.c optional evdev
+dev/evdev/evdev.c optional evdev
+dev/evdev/evdev_mt.c optional evdev
+dev/evdev/evdev_utils.c optional evdev
+dev/evdev/uinput.c optional evdev uinput
dev/ex/if_ex.c optional ex
dev/ex/if_ex_isa.c optional ex isa
dev/ex/if_ex_pccard.c optional ex pccard
Modified: head/sys/conf/options
==============================================================================
--- head/sys/conf/options Sun Sep 11 18:15:34 2016 (r305705)
+++ head/sys/conf/options Sun Sep 11 18:56:38 2016 (r305706)
@@ -987,3 +987,8 @@ BHND_LOGLEVEL opt_global.h
# GPIO and child devices
GPIO_SPI_DEBUG opt_gpio.h
+
+# evdev protocol support
+EVDEV opt_evdev.h
+EVDEV_DEBUG opt_evdev.h
+UINPUT_DEBUG opt_evdev.h
Added: head/sys/dev/evdev/cdev.c
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ head/sys/dev/evdev/cdev.c Sun Sep 11 18:56:38 2016 (r305706)
@@ -0,0 +1,860 @@
+/*-
+ * Copyright (c) 2014 Jakub Wojciech Klama <jceel at FreeBSD.org>
+ * Copyright (c) 2015-2016 Vladimir Kondratyev <wulf at 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);
+}
Added: head/sys/dev/evdev/evdev.c
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ head/sys/dev/evdev/evdev.c Sun Sep 11 18:56:38 2016 (r305706)
@@ -0,0 +1,917 @@
+/*-
+ * Copyright (c) 2014 Jakub Wojciech Klama <jceel at FreeBSD.org>
+ * Copyright (c) 2015-2016 Vladimir Kondratyev <wulf at 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/systm.h>
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/bitstring.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
More information about the svn-src-all
mailing list