From owner-svn-src-head@freebsd.org Sun Sep 11 18:56:40 2016 Return-Path: Delivered-To: svn-src-head@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 3CC9FBD5F12; Sun, 11 Sep 2016 18:56:40 +0000 (UTC) (envelope-from gonzo@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id DC6F13FE; Sun, 11 Sep 2016 18:56:39 +0000 (UTC) (envelope-from gonzo@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id u8BIudpI023563; Sun, 11 Sep 2016 18:56:39 GMT (envelope-from gonzo@FreeBSD.org) Received: (from gonzo@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id u8BIucU3023555; Sun, 11 Sep 2016 18:56:38 GMT (envelope-from gonzo@FreeBSD.org) Message-Id: <201609111856.u8BIucU3023555@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: gonzo set sender to gonzo@FreeBSD.org using -f From: Oleksandr Tymoshenko Date: Sun, 11 Sep 2016 18:56:38 +0000 (UTC) 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 X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 11 Sep 2016 18:56:40 -0000 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 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 + * Copyright (c) 2015-2016 Vladimir Kondratyev + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 + * Copyright (c) 2015-2016 Vladimir Kondratyev + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***