root/sys/dev/evdev/cdev.c
/*-
 * Copyright (c) 2014 Jakub Wojciech Klama <jceel@FreeBSD.org>
 * Copyright (c) 2015-2016 Vladimir Kondratyev <wulf@FreeBSD.org>
 * 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.
 */

#include "opt_evdev.h"

#include <sys/param.h>
#include <sys/bitstring.h>
#include <sys/conf.h>
#include <sys/epoch.h>
#include <sys/filio.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/poll.h>
#include <sys/proc.h>
#include <sys/selinfo.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/uio.h>

#include <dev/evdev/evdev.h>
#include <dev/evdev/evdev_private.h>
#include <dev/evdev/input.h>

#ifdef COMPAT_FREEBSD32
#include <sys/mount.h>
#include <sys/sysent.h>
#include <compat/freebsd32/freebsd32.h>
struct input_event32 {
        struct timeval32        time;
        uint16_t                type;
        uint16_t                code;
        int32_t                 value;
};
#endif

#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,
    struct thread *);
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 const struct filterops evdev_cdev_filterops = {
        .f_isfd = 1,
        .f_attach = NULL,
        .f_detach = evdev_kqdetach,
        .f_event = evdev_kqread,
        .f_copy = knote_triv_copy,
};

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);

        ret = EVDEV_LIST_LOCK_SIG(evdev);
        if (ret != 0)
                goto out;
        /* Avoid race with evdev_unregister */
        if (dev->si_drv1 == NULL)
                ret = ENODEV;
        else
                ret = evdev_register_client(evdev, client);
        EVDEV_LIST_UNLOCK(evdev);
out:
        if (ret == 0)
                ret = devfs_set_cdevpriv(client, evdev_dtor);
        else
                client->ec_revoked = true;

        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_LIST_LOCK(client->ec_evdev);
        if (!client->ec_revoked)
                evdev_dispose_client(client->ec_evdev, client);
        EVDEV_LIST_UNLOCK(client->ec_evdev);

        if (client->ec_evdev->ev_lock_type != EV_LOCK_MTX)
                epoch_wait_preempt(INPUT_EPOCH);
        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;
        union {
                struct input_event t;
#ifdef COMPAT_FREEBSD32
                struct input_event32 t32;
#endif
        } event;
        struct input_event *head;
        size_t evsize;
        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);

#ifdef COMPAT_FREEBSD32
        if (SV_CURPROC_FLAG(SV_ILP32))
                evsize = sizeof(struct input_event32);
        else
#endif
                evsize = sizeof(struct input_event);

        /* Zero-sized reads are allowed for error checking */
        if (uio->uio_resid != 0 && uio->uio_resid < evsize)
                return (EINVAL);

        remaining = uio->uio_resid / evsize;

        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);
                                if (ret == 0 && client->ec_revoked)
                                        ret = ENODEV;
                        }
                }
        }

        while (ret == 0 && !EVDEV_CLIENT_EMPTYQ(client) && remaining > 0) {
                head = client->ec_buffer + client->ec_buffer_head;
#ifdef COMPAT_FREEBSD32
                if (SV_CURPROC_FLAG(SV_ILP32)) {
                        bzero(&event.t32, sizeof(struct input_event32));
                        TV_CP(*head, event.t32, time);
                        CP(*head, event.t32, type);
                        CP(*head, event.t32, code);
                        CP(*head, event.t32, value);
                } else
#endif
                        bcopy(head, &event.t, evsize);

                client->ec_buffer_head =
                    (client->ec_buffer_head + 1) % client->ec_buffer_size;
                remaining--;

                EVDEV_CLIENT_UNLOCKQ(client);
                ret = uiomove(&event, evsize, 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;
        union {
                struct input_event t;
#ifdef COMPAT_FREEBSD32
                struct input_event32 t32;
#endif
        } event;
        size_t evsize;
        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);

#ifdef COMPAT_FREEBSD32
        if (SV_CURPROC_FLAG(SV_ILP32))
                evsize = sizeof(struct input_event32);
        else
#endif
                evsize = sizeof(struct input_event);

        if (uio->uio_resid % evsize != 0) {
                debugf(client, "write size not multiple of input_event size");
                return (EINVAL);
        }

        while (uio->uio_resid > 0 && ret == 0) {
                ret = uiomove(&event, evsize, uio);
                if (ret == 0) {
#ifdef COMPAT_FREEBSD32
                        if (SV_CURPROC_FLAG(SV_ILP32))
                                ret = evdev_inject_event(evdev, event.t32.type,
                                    event.t32.code, event.t32.value);
                        else
#endif
                                ret = evdev_inject_event(evdev, event.t.type,
                                    event.t.code, event.t.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;
        struct epoch_tracker et;
        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);

        /*
         * Fix evdev state corrupted with discarding of kdb events.
         * EVIOCGKEY and EVIOCGLED ioctls can suffer from this.
         */
        if (evdev->ev_kdb_active) {
                EVDEV_LOCK(evdev);
                if (evdev->ev_kdb_active) {
                        evdev->ev_kdb_active = false;
                        if (evdev->ev_lock_type == EV_LOCK_EXT_EPOCH)
                                epoch_enter_preempt(INPUT_EPOCH, &et);
                        evdev_restore_after_kdb(evdev);
                        if (evdev->ev_lock_type == EV_LOCK_EXT_EPOCH)
                                epoch_exit_preempt(INPUT_EPOCH, &et);
                }
                EVDEV_UNLOCK(evdev);
        }

        /* 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, 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, 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_LIST_LOCK(evdev);
                if (dev->si_drv1 != NULL && !client->ec_revoked) {
                        evdev_dispose_client(evdev, client);
                        evdev_revoke_client(client);
                }
                EVDEV_LIST_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):
                /* Linux evdev does not terminate truncated strings with 0 */
                limit = MIN(strlen(evdev->ev_name) + 1, len);
                memcpy(data, evdev->ev_name, limit);
                td->td_retval[0] = limit;
                return (0);

        case EVIOCGPHYS(0):
                if (evdev->ev_shortname[0] == 0)
                        return (ENOENT);

                limit = MIN(strlen(evdev->ev_shortname) + 1, len);
                memcpy(data, evdev->ev_shortname, limit);
                td->td_retval[0] = limit;
                return (0);

        case EVIOCGUNIQ(0):
                if (evdev->ev_serial[0] == 0)
                        return (ENOENT);

                limit = MIN(strlen(evdev->ev_serial) + 1, len);
                memcpy(data, evdev->ev_serial, limit);
                td->td_retval[0] = limit;
                return (0);

        case EVIOCGPROP(0):
                limit = MIN(len, bitstr_size(INPUT_PROP_CNT));
                memcpy(data, evdev->ev_prop_flags, limit);
                td->td_retval[0] = limit;
                return (0);

        case EVIOCGMTSLOTS(0):
                /* EVIOCGMTSLOTS always returns 0 on success */
                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_mt_get_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);
                td->td_retval[0] = limit;
                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);
                td->td_retval[0] = limit;
                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);
                td->td_retval[0] = limit;
                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);
                td->td_retval[0] = limit;
                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, td));
        }

        return (EINVAL);
}

static int
evdev_ioctl_eviocgbit(struct evdev_dev *evdev, int type, int len, caddr_t data,
    struct thread *td)
{
        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);
                td->td_retval[0] = 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);
        td->td_retval[0] = len;
        return (0);
}

void
evdev_revoke_client(struct evdev_client *client)
{

        EVDEV_LIST_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 = evdev->ev_cdev_uid;
        mda.mda_gid = evdev->ev_cdev_gid;
        mda.mda_mode = evdev->ev_cdev_mode;
        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);
}