mirror of
https://github.com/opnsense/src.git
synced 2026-02-17 09:39:26 -05:00
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
710 lines
16 KiB
C
710 lines
16 KiB
C
/*-
|
|
* 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/fcntl.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/module.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/selinfo.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/sx.h>
|
|
|
|
#include <dev/evdev/input.h>
|
|
#include <dev/evdev/uinput.h>
|
|
#include <dev/evdev/evdev.h>
|
|
#include <dev/evdev/evdev_private.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 struct evdev_methods uinput_ev_methods = {
|
|
.ev_open = NULL,
|
|
.ev_close = NULL,
|
|
.ev_event = uinput_ev_event,
|
|
};
|
|
|
|
static struct filterops uinput_filterops = {
|
|
.f_isfd = 1,
|
|
.f_attach = NULL,
|
|
.f_detach = uinput_kqdetach,
|
|
.f_event = uinput_kqread,
|
|
};
|
|
|
|
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_locked(void *arg)
|
|
{
|
|
|
|
sx_assert((struct sx*)arg, SA_XLOCKED);
|
|
}
|
|
|
|
static void
|
|
uinput_knl_assert_unlocked(void *arg)
|
|
{
|
|
|
|
sx_assert((struct sx*)arg, SA_UNLOCKED);
|
|
}
|
|
|
|
static void
|
|
uinput_ev_event(struct evdev_dev *evdev, void *softc, uint16_t type,
|
|
uint16_t code, int32_t value)
|
|
{
|
|
struct uinput_cdev_state *state = softc;
|
|
|
|
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_locked,
|
|
uinput_knl_assert_unlocked);
|
|
|
|
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_register(state->ucs_evdev);
|
|
state->ucs_state = UINPUT_RUNNING;
|
|
return (0);
|
|
|
|
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 || uabs->code < 0)
|
|
return (EINVAL);
|
|
|
|
evdev_support_abs(state->ucs_evdev, uabs->code,
|
|
uabs->absinfo.value, uabs->absinfo.minimum,
|
|
uabs->absinfo.maximum, uabs->absinfo.fuzz,
|
|
uabs->absinfo.flat, uabs->absinfo.resolution);
|
|
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_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);
|