Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 11 Sep 2016 18:56:38 +0000 (UTC)
From:      Oleksandr Tymoshenko <gonzo@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r305706 - in head: etc/mtree include sys/conf sys/dev/evdev
Message-ID:  <201609111856.u8BIucU3023555@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
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@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@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);
+}

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@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/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 ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201609111856.u8BIucU3023555>