root/sys/dev/evdev/uinput.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/conf.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/poll.h>
#include <sys/proc.h>
#include <sys/selinfo.h>
#include <sys/systm.h>
#include <sys/sx.h>
#include <sys/uio.h>

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

#ifdef UINPUT_DEBUG
#define debugf(state, fmt, args...)     printf("uinput: " fmt "\n", ##args)
#else
#define debugf(state, fmt, args...)
#endif

#define UINPUT_BUFFER_SIZE      16

#define UINPUT_LOCK(state)              sx_xlock(&(state)->ucs_lock)
#define UINPUT_UNLOCK(state)            sx_unlock(&(state)->ucs_lock)
#define UINPUT_LOCK_ASSERT(state)       sx_assert(&(state)->ucs_lock, SA_LOCKED)
#define UINPUT_EMPTYQ(state) \
    ((state)->ucs_buffer_head == (state)->ucs_buffer_tail)

enum uinput_state
{
        UINPUT_NEW = 0,
        UINPUT_CONFIGURED,
        UINPUT_RUNNING
};

static evdev_event_t    uinput_ev_event;

static d_open_t         uinput_open;
static d_read_t         uinput_read;
static d_write_t        uinput_write;
static d_ioctl_t        uinput_ioctl;
static d_poll_t         uinput_poll;
static d_kqfilter_t     uinput_kqfilter;
static void uinput_dtor(void *);

static int uinput_kqread(struct knote *kn, long hint);
static void uinput_kqdetach(struct knote *kn);

static struct cdevsw uinput_cdevsw = {
        .d_version = D_VERSION,
        .d_open = uinput_open,
        .d_read = uinput_read,
        .d_write = uinput_write,
        .d_ioctl = uinput_ioctl,
        .d_poll = uinput_poll,
        .d_kqfilter = uinput_kqfilter,
        .d_name = "uinput",
};

static struct cdev *uinput_cdev;

static const struct evdev_methods uinput_ev_methods = {
        .ev_open = NULL,
        .ev_close = NULL,
        .ev_event = uinput_ev_event,
};

static const struct filterops uinput_filterops = {
        .f_isfd = 1,
        .f_attach = NULL,
        .f_detach = uinput_kqdetach,
        .f_event = uinput_kqread,
        .f_copy = knote_triv_copy,
};

struct uinput_cdev_state
{
        enum uinput_state       ucs_state;
        struct evdev_dev *      ucs_evdev;
        struct sx               ucs_lock;
        size_t                  ucs_buffer_head;
        size_t                  ucs_buffer_tail;
        struct selinfo          ucs_selp;
        bool                    ucs_blocked;
        bool                    ucs_selected;
        struct input_event      ucs_buffer[UINPUT_BUFFER_SIZE];
};

static void uinput_enqueue_event(struct uinput_cdev_state *, uint16_t,
    uint16_t, int32_t);
static int uinput_setup_provider(struct uinput_cdev_state *,
    struct uinput_user_dev *);
static int uinput_cdev_create(void);
static void uinput_notify(struct uinput_cdev_state *);

static void
uinput_knllock(void *arg)
{
        struct sx *sx = arg;

        sx_xlock(sx);
}

static void
uinput_knlunlock(void *arg)
{
        struct sx *sx = arg;

        sx_unlock(sx);
}

static void
uinput_knl_assert_lock(void *arg, int what)
{

        if (what == LA_LOCKED)
                sx_assert((struct sx*)arg, SA_XLOCKED);
        else
                sx_assert((struct sx*)arg, SA_UNLOCKED);
}

static void
uinput_ev_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
    int32_t value)
{
        struct uinput_cdev_state *state = evdev_get_softc(evdev);

        if (type == EV_LED)
                evdev_push_event(evdev, type, code, value);

        UINPUT_LOCK(state);
        if (state->ucs_state == UINPUT_RUNNING) {
                uinput_enqueue_event(state, type, code, value);
                uinput_notify(state);
        }
        UINPUT_UNLOCK(state);
}

static void
uinput_enqueue_event(struct uinput_cdev_state *state, uint16_t type,
    uint16_t code, int32_t value)
{
        size_t head, tail;

        UINPUT_LOCK_ASSERT(state);

        head = state->ucs_buffer_head;
        tail = (state->ucs_buffer_tail + 1) % UINPUT_BUFFER_SIZE;

        microtime(&state->ucs_buffer[tail].time);
        state->ucs_buffer[tail].type = type;
        state->ucs_buffer[tail].code = code;
        state->ucs_buffer[tail].value = value;
        state->ucs_buffer_tail = tail;

        /* If queue is full remove oldest event */
        if (tail == head) {
                debugf(state, "state %p: buffer overflow", state);

                head = (head + 1) % UINPUT_BUFFER_SIZE;
                state->ucs_buffer_head = head;
        }
}

static int
uinput_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
        struct uinput_cdev_state *state;

        state = malloc(sizeof(struct uinput_cdev_state), M_EVDEV,
            M_WAITOK | M_ZERO);
        state->ucs_evdev = evdev_alloc();

        sx_init(&state->ucs_lock, "uinput");
        knlist_init(&state->ucs_selp.si_note, &state->ucs_lock, uinput_knllock,
            uinput_knlunlock, uinput_knl_assert_lock);

        devfs_set_cdevpriv(state, uinput_dtor);
        return (0);
}

static void
uinput_dtor(void *data)
{
        struct uinput_cdev_state *state = (struct uinput_cdev_state *)data;

        evdev_free(state->ucs_evdev);

        knlist_clear(&state->ucs_selp.si_note, 0);
        seldrain(&state->ucs_selp);
        knlist_destroy(&state->ucs_selp.si_note);
        sx_destroy(&state->ucs_lock);
        free(data, M_EVDEV);
}

static int
uinput_read(struct cdev *dev, struct uio *uio, int ioflag)
{
        struct uinput_cdev_state *state;
        struct input_event *event;
        int remaining, ret;

        ret = devfs_get_cdevpriv((void **)&state);
        if (ret != 0)
                return (ret);

        debugf(state, "read %zd bytes by thread %d", uio->uio_resid,
            uio->uio_td->td_tid);

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

        UINPUT_LOCK(state);

        if (state->ucs_state != UINPUT_RUNNING)
                ret = EINVAL;

        if (ret == 0 && UINPUT_EMPTYQ(state)) {
                if (ioflag & O_NONBLOCK)
                        ret = EWOULDBLOCK;
                else {
                        if (remaining != 0) {
                                state->ucs_blocked = true;
                                ret = sx_sleep(state, &state->ucs_lock,
                                    PCATCH, "uiread", 0);
                        }
                }
        }

        while (ret == 0 && !UINPUT_EMPTYQ(state) && remaining > 0) {
                event = &state->ucs_buffer[state->ucs_buffer_head];
                state->ucs_buffer_head = (state->ucs_buffer_head + 1) %
                    UINPUT_BUFFER_SIZE;
                remaining--;
                ret = uiomove(event, sizeof(struct input_event), uio);
        }

        UINPUT_UNLOCK(state);

        return (ret);
}

static int
uinput_write(struct cdev *dev, struct uio *uio, int ioflag)
{
        struct uinput_cdev_state *state;
        struct uinput_user_dev userdev;
        struct input_event event;
        int ret = 0;

        ret = devfs_get_cdevpriv((void **)&state);
        if (ret != 0)
                return (ret);

        debugf(state, "write %zd bytes by thread %d", uio->uio_resid,
            uio->uio_td->td_tid);

        UINPUT_LOCK(state);

        if (state->ucs_state != UINPUT_RUNNING) {
                /* Process written struct uinput_user_dev */
                if (uio->uio_resid != sizeof(struct uinput_user_dev)) {
                        debugf(state, "write size not multiple of "
                            "struct uinput_user_dev size");
                        ret = EINVAL;
                } else {
                        ret = uiomove(&userdev, sizeof(struct uinput_user_dev),
                            uio);
                        if (ret == 0)
                                uinput_setup_provider(state, &userdev);
                }
        } else {
                /* Process written event */
                if (uio->uio_resid % sizeof(struct input_event) != 0) {
                        debugf(state, "write size not multiple of "
                            "struct input_event size");
                        ret = EINVAL;
                }

                while (ret == 0 && uio->uio_resid > 0) {
                        uiomove(&event, sizeof(struct input_event), uio);
                        ret = evdev_push_event(state->ucs_evdev, event.type,
                            event.code, event.value);
                }
        }

        UINPUT_UNLOCK(state);

        return (ret);
}

static int
uinput_setup_dev(struct uinput_cdev_state *state, struct input_id *id,
    char *name, uint32_t ff_effects_max)
{

        if (name[0] == 0)
                return (EINVAL);

        evdev_set_name(state->ucs_evdev, name);
        evdev_set_id(state->ucs_evdev, id->bustype, id->vendor, id->product,
            id->version);
        state->ucs_state = UINPUT_CONFIGURED;

        return (0);
}

static int
uinput_setup_provider(struct uinput_cdev_state *state,
    struct uinput_user_dev *udev)
{
        struct input_absinfo absinfo;
        int i, ret;

        debugf(state, "setup_provider called, udev=%p", udev);

        ret = uinput_setup_dev(state, &udev->id, udev->name,
            udev->ff_effects_max);
        if (ret)
                return (ret);

        bzero(&absinfo, sizeof(struct input_absinfo));
        for (i = 0; i < ABS_CNT; i++) {
                if (!bit_test(state->ucs_evdev->ev_abs_flags, i))
                        continue;

                absinfo.minimum = udev->absmin[i];
                absinfo.maximum = udev->absmax[i];
                absinfo.fuzz = udev->absfuzz[i];
                absinfo.flat = udev->absflat[i];
                evdev_set_absinfo(state->ucs_evdev, i, &absinfo);
        }

        return (0);
}

static int
uinput_poll(struct cdev *dev, int events, struct thread *td)
{
        struct uinput_cdev_state *state;
        int revents = 0;

        if (devfs_get_cdevpriv((void **)&state) != 0)
                return (POLLNVAL);

        debugf(state, "poll by thread %d", td->td_tid);

        /* Always allow write */
        if (events & (POLLOUT | POLLWRNORM))
                revents |= (events & (POLLOUT | POLLWRNORM));

        if (events & (POLLIN | POLLRDNORM)) {
                UINPUT_LOCK(state);
                if (!UINPUT_EMPTYQ(state))
                        revents = events & (POLLIN | POLLRDNORM);
                else {
                        state->ucs_selected = true;
                        selrecord(td, &state->ucs_selp);
                }
                UINPUT_UNLOCK(state);
        }

        return (revents);
}

static int
uinput_kqfilter(struct cdev *dev, struct knote *kn)
{
        struct uinput_cdev_state *state;
        int ret;

        ret = devfs_get_cdevpriv((void **)&state);
        if (ret != 0)
                return (ret);

        switch(kn->kn_filter) {
        case EVFILT_READ:
                kn->kn_fop = &uinput_filterops;
                break;
        default:
                return(EINVAL);
        }
        kn->kn_hook = (caddr_t)state;

        knlist_add(&state->ucs_selp.si_note, kn, 0);
        return (0);
}

static int
uinput_kqread(struct knote *kn, long hint)
{
        struct uinput_cdev_state *state;
        int ret;

        state = (struct uinput_cdev_state *)kn->kn_hook;

        UINPUT_LOCK_ASSERT(state);

        ret = !UINPUT_EMPTYQ(state);
        return (ret);
}

static void
uinput_kqdetach(struct knote *kn)
{
        struct uinput_cdev_state *state;

        state = (struct uinput_cdev_state *)kn->kn_hook;
        knlist_remove(&state->ucs_selp.si_note, kn, 0);
}

static void
uinput_notify(struct uinput_cdev_state *state)
{

        UINPUT_LOCK_ASSERT(state);

        if (state->ucs_blocked) {
                state->ucs_blocked = false;
                wakeup(state);
        }
        if (state->ucs_selected) {
                state->ucs_selected = false;
                selwakeup(&state->ucs_selp);
        }
        KNOTE_LOCKED(&state->ucs_selp.si_note, 0);
}

static int
uinput_ioctl_sub(struct uinput_cdev_state *state, u_long cmd, caddr_t data)
{
        struct uinput_setup *us;
        struct uinput_abs_setup *uabs;
        int ret, len, intdata;
        char buf[NAMELEN];

        UINPUT_LOCK_ASSERT(state);

        len = IOCPARM_LEN(cmd);
        if ((cmd & IOC_DIRMASK) == IOC_VOID && len == sizeof(int))
                intdata = *(int *)data;

        switch (IOCBASECMD(cmd)) {
        case UI_GET_SYSNAME(0):
                if (state->ucs_state != UINPUT_RUNNING)
                        return (ENOENT);
                if (len == 0)
                        return (EINVAL);
                snprintf(data, len, "event%d", state->ucs_evdev->ev_unit);
                return (0);
        }

        switch (cmd) {
        case UI_DEV_CREATE:
                if (state->ucs_state != UINPUT_CONFIGURED)
                        return (EINVAL);

                evdev_set_methods(state->ucs_evdev, state, &uinput_ev_methods);
                evdev_set_flag(state->ucs_evdev, EVDEV_FLAG_SOFTREPEAT);
                evdev_set_flag(state->ucs_evdev, EVDEV_FLAG_MT_KEEPID);
                ret = evdev_register(state->ucs_evdev);
                if (ret == 0)
                        state->ucs_state = UINPUT_RUNNING;
                return (ret);

        case UI_DEV_DESTROY:
                if (state->ucs_state != UINPUT_RUNNING)
                        return (0);

                evdev_unregister(state->ucs_evdev);
                bzero(state->ucs_evdev, sizeof(struct evdev_dev));
                state->ucs_state = UINPUT_NEW;
                return (0);

        case UI_DEV_SETUP:
                if (state->ucs_state == UINPUT_RUNNING)
                        return (EINVAL);

                us = (struct uinput_setup *)data;
                return (uinput_setup_dev(state, &us->id, us->name,
                    us->ff_effects_max));

        case UI_ABS_SETUP:
                if (state->ucs_state == UINPUT_RUNNING)
                        return (EINVAL);

                uabs = (struct uinput_abs_setup *)data;
                if (uabs->code > ABS_MAX)
                        return (EINVAL);

                evdev_set_abs_bit(state->ucs_evdev, uabs->code);
                evdev_set_absinfo(state->ucs_evdev, uabs->code,
                    &uabs->absinfo);
                return (0);

        case UI_SET_EVBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > EV_MAX || intdata < 0)
                        return (EINVAL);
                evdev_support_event(state->ucs_evdev, intdata);
                return (0);

        case UI_SET_KEYBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > KEY_MAX || intdata < 0)
                        return (EINVAL);
                evdev_support_key(state->ucs_evdev, intdata);
                return (0);

        case UI_SET_RELBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > REL_MAX || intdata < 0)
                        return (EINVAL);
                evdev_support_rel(state->ucs_evdev, intdata);
                return (0);

        case UI_SET_ABSBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > ABS_MAX || intdata < 0)
                        return (EINVAL);
                evdev_set_abs_bit(state->ucs_evdev, intdata);
                return (0);

        case UI_SET_MSCBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > MSC_MAX || intdata < 0)
                        return (EINVAL);
                evdev_support_msc(state->ucs_evdev, intdata);
                return (0);

        case UI_SET_LEDBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > LED_MAX || intdata < 0)
                        return (EINVAL);
                evdev_support_led(state->ucs_evdev, intdata);
                return (0);

        case UI_SET_SNDBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > SND_MAX || intdata < 0)
                        return (EINVAL);
                evdev_support_snd(state->ucs_evdev, intdata);
                return (0);

        case UI_SET_FFBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > FF_MAX || intdata < 0)
                        return (EINVAL);
                /* Fake unsupported ioctl */
                return (0);

        case UI_SET_PHYS:
                if (state->ucs_state == UINPUT_RUNNING)
                        return (EINVAL);
                ret = copyinstr(*(void **)data, buf, sizeof(buf), NULL);
                /* Linux returns EINVAL when string does not fit the buffer */
                if (ret == ENAMETOOLONG)
                        ret = EINVAL;
                if (ret != 0)
                        return (ret);
                evdev_set_phys(state->ucs_evdev, buf);
                return (0);

        case UI_SET_BSDUNIQ:
                if (state->ucs_state == UINPUT_RUNNING)
                        return (EINVAL);
                ret = copyinstr(*(void **)data, buf, sizeof(buf), NULL);
                if (ret != 0)
                        return (ret);
                evdev_set_serial(state->ucs_evdev, buf);
                return (0);

        case UI_SET_SWBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > SW_MAX || intdata < 0)
                        return (EINVAL);
                evdev_support_sw(state->ucs_evdev, intdata);
                return (0);

        case UI_SET_PROPBIT:
                if (state->ucs_state == UINPUT_RUNNING ||
                    intdata > INPUT_PROP_MAX || intdata < 0)
                        return (EINVAL);
                evdev_support_prop(state->ucs_evdev, intdata);
                return (0);

        case UI_BEGIN_FF_UPLOAD:
        case UI_END_FF_UPLOAD:
        case UI_BEGIN_FF_ERASE:
        case UI_END_FF_ERASE:
                if (state->ucs_state == UINPUT_RUNNING)
                        return (EINVAL);
                /* Fake unsupported ioctl */
                return (0);

        case UI_GET_VERSION:
                *(unsigned int *)data = UINPUT_VERSION;
                return (0);
        }

        return (EINVAL);
}

static int
uinput_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
    struct thread *td)
{
        struct uinput_cdev_state *state;
        int ret;

        ret = devfs_get_cdevpriv((void **)&state);
        if (ret != 0)
                return (ret);

        debugf(state, "ioctl called: cmd=0x%08lx, data=%p", cmd, data);

        UINPUT_LOCK(state);
        ret = uinput_ioctl_sub(state, cmd, data);
        UINPUT_UNLOCK(state);

        return (ret);
}

static int
uinput_cdev_create(void)
{
        struct make_dev_args mda;
        int ret;

        make_dev_args_init(&mda);
        mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
        mda.mda_devsw = &uinput_cdevsw;
        mda.mda_uid = UID_ROOT;
        mda.mda_gid = GID_WHEEL;
        mda.mda_mode = 0600;

        ret = make_dev_s(&mda, &uinput_cdev, "uinput");

        return (ret);
}

static int
uinput_cdev_destroy(void)
{

        destroy_dev(uinput_cdev);

        return (0);
}

static int
uinput_modevent(module_t mod __unused, int cmd, void *data)
{
        int ret = 0;

        switch (cmd) {
        case MOD_LOAD:
                ret = uinput_cdev_create();
                break;

        case MOD_UNLOAD:
                ret = uinput_cdev_destroy();
                break;

        case MOD_SHUTDOWN:
                break;

        default:
                ret = EINVAL;
                break;
        }

        return (ret);
}

DEV_MODULE(uinput, uinput_modevent, NULL);
MODULE_VERSION(uinput, 1);
MODULE_DEPEND(uinput, evdev, 1, 1, 1);