idev-evdev.c revision c93e5a62ff599528c3bf2a8656825403aaebe093
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <fcntl.h>
#include <inttypes.h>
#include <libevdev/libevdev.h>
#include <libudev.h>
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/sd-event.h>
#include <unistd.h>
#include "bus-util.h"
#include "hashmap.h"
#include "idev.h"
#include "idev-internal.h"
#include "macro.h"
#include "udev-util.h"
#include "util.h"
typedef struct idev_evdev idev_evdev;
typedef struct unmanaged_evdev unmanaged_evdev;
typedef struct managed_evdev managed_evdev;
struct idev_evdev {
struct idev_element element;
int fd;
};
struct unmanaged_evdev {
struct idev_evdev evdev;
char *devnode;
};
struct managed_evdev {
struct idev_evdev evdev;
};
#define unmanaged_evdev_from_element(_e) \
#define managed_evdev_from_element(_e) \
.fd = -1, \
})
static const idev_element_vtable unmanaged_evdev_vtable;
static const idev_element_vtable managed_evdev_vtable;
/*
* Virtual Evdev Element
* The virtual evdev element is the base class of all other evdev elements. It
* uses libevdev to access the kernel evdev API. It supports asynchronous
* access revocation, re-syncing if events got dropped and more.
* This element cannot be used by itself. There must be a wrapper around it
* which opens a file-descriptor and passes it to the virtual evdev element.
*/
/* @out must be at least of size IDEV_EVDEV_NAME_MAX */
}
.type = IDEV_DATA_EVDEV,
.evdev = {
},
};
}
/*
* On HUP, we close the current fd via idev_evdev_pause(). This drops
* the event-sources from the main-loop and effectively puts the
* element asleep. If the HUP is part of a hotplug-event, a following
* udev-notification will destroy the element. Otherwise, the HUP is
* either result of access-revokation or a serious error.
* For unmanaged devices, we should never receive HUP (except for
* unplug-events). But if we do, something went seriously wrong and we
* shouldn't try to be clever.
* Instead, we simply stay asleep and wait for the device to be
* disabled and then re-enabled (or closed and re-opened). This will
* re-open the device node and restart the device.
* For managed devices, a HUP usually means our device-access was
* revoked. In that case, we simply put the device asleep and wait for
* logind to notify us once the device is alive again. logind also
* passes us a new fd. Hence, we don't have to re-enable the device.
*
* Long story short: The only thing we have to do here, is close() the
* file-descriptor and remove it from the main-loop. Everything else is
* handled via additional events we receive.
*/
idev_evdev_pause(evdev, true);
}
struct input_event ev;
unsigned int flags;
int r, error = 0;
/*
* Read input-events via libevdev until the input-queue is drained. In
* case we're disabled, don't do anything. The input-queue might
* overflow, but we don't care as we have to resync after wake-up,
* anyway.
* TODO: libevdev should give us a hint how many events to read. We
* really want to avoid starvation, so we shouldn't read forever in
* case we cannot keep up with the kernel.
* TODO: Make sure libevdev always reports SYN_DROPPED to us, regardless
* whether any event was synced afterwards.
* TODO: Forward SYN_DROPPED to attached devices.
*/
while (e->enabled) {
/* immediately resync, even if in sync right now */
if (r < 0 && r != -EAGAIN) {
r = 0;
goto error;
} else if (r != LIBEVDEV_READ_STATUS_SYNC) {
log_debug("idev-evdev: %s/%s: cannot force resync: %d",
}
} else {
}
/* end of re-sync */
} else if (r == -EAGAIN) {
/* no data available */
break;
} else if (r < 0) {
/* read error */
goto error;
} else if (r == LIBEVDEV_READ_STATUS_SYNC) {
/* sync-event */
if (r != 0) {
error = r;
break;
}
} else {
/* start of sync */
}
} else {
/* normal event */
if (r != 0) {
error = r;
break;
}
}
}
if (error < 0)
log_debug("idev-evdev: %s/%s: error on data event: %s",
return error;
return r;
}
/* fetch data as long as EPOLLIN is signalled */
return idev_evdev_io(evdev);
return 0;
}
/*
* The idle-event is raised whenever we have to re-sync the libevdev
* state from the kernel. We simply call into idev_evdev_io() which
* flushes the state and re-syncs it if @unsync is set.
* State has to be synced whenever our view of the kernel device is
* out of date. This is the case when we open the device, if the
* kernel's receive buffer overflows, or on other exceptional
* situations. Events during re-syncs must be forwarded to the upper
* layers so they can update their view of the device. However, such
* events must only be handled passively, as they might be out-of-order
*/
return 0;
return idev_evdev_io(evdev);
}
}
}
}
int r, flags;
fd = -1;
return 0;
}
idev_evdev_pause(evdev, true);
r = fd_nonblock(fd, true);
if (r < 0)
return r;
r = fd_cloexec(fd, true);
if (r < 0)
return r;
if (flags < 0)
return r;
return -EACCES;
/*
* TODO: We *MUST* re-sync the device so we get a delta of the changed
* state while we didn't read events from the device. This works just
* fine with libevdev_change_fd(), however, libevdev_new_from_fd() (or
* libevdev_set_fd()) don't pass us events for the initial device
* state. So even if we force a re-sync, we will not get the delta for
* the initial device state.
* We really need to fix libevdev to support that!
*/
else
if (r < 0)
return r;
fd,
evdev);
if (r < 0)
return r;
evdev);
if (r < 0) {
return r;
}
}
fd = -1;
return 0;
}
return;
if (release) {
} else {
}
}
/*
* Unmanaged Evdev Element
* The unmanaged evdev element opens the evdev node for a given input device
* the device only if we really require it and releases it as soon as we're
* disabled or closed.
* The unmanaged element can be used in all situations where you have direct
* access to input device nodes. Unlike managed evdev elements, it can be used
* outside of user sessions and in emergency situations where logind is not
* available.
*/
static void unmanaged_evdev_resume(idev_element *e) {
int r, fd;
/*
* Unmanaged devices can be acquired on-demand. Therefore, don't
* acquire it unless someone opened the device *and* we're enabled.
*/
return;
if (fd < 0) {
if (fd < 0) {
log_debug("idev-evdev: %s/%s: cannot open node %s: %m",
return;
}
if (fd < 0) {
log_debug("idev-evdev: %s/%s: cannot open node %s: %m",
return;
}
e->readable = true;
e->writable = false;
} else {
e->readable = true;
e->writable = true;
}
}
if (r < 0)
log_debug("idev-evdev: %s/%s: cannot resume: %s",
}
static void unmanaged_evdev_pause(idev_element *e) {
/*
* Release the device if the device is disabled or there is no-one who
* opened it. This guarantees we stay only available if we're opened
* *and* enabled.
*/
}
char name[IDEV_EVDEV_NAME_MAX];
const char *devnode;
int r;
assert_return(s, -EINVAL);
return -ENODEV;
if (!eu)
return -ENOMEM;
return -ENOMEM;
r = idev_element_add(e, name);
if (r < 0)
return r;
if (out)
*out = e;
e = NULL;
return 0;
}
static void unmanaged_evdev_free(idev_element *e) {
}
static const idev_element_vtable unmanaged_evdev_vtable = {
};
/*
* Managed Evdev Element
* The managed evdev element uses systemd-logind to acquire evdev devices. This
* logind passes us a file-descriptor whenever our session is activated. Thus,
* we don't need access to the device node directly.
* Furthermore, whenever the session is put asleep, logind revokes the
* file-descriptor so we loose access to the device.
* Managed evdev elements should be preferred over unmanaged elements whenever
* you run inside a user session with exclusive device access.
*/
void *userdata,
idev_session *s = e->session;
log_debug("idev-evdev: %s/%s: TakeDevice failed: %s: %s",
return 0;
}
if (r < 0) {
return 0;
}
/* If the device is paused, ignore it; we will get the next fd via
* ResumeDevice signals. */
if (paused)
return 0;
if (fd < 0) {
return 0;
}
if (r < 0)
log_debug("idev-evdev: %s/%s: cannot resume: %s",
return 0;
}
static void managed_evdev_resume(idev_element *e) {
idev_session *s = e->session;
idev_context *c = s->context;
int r;
/*
* Acquiring managed devices is heavy, so do it only once we're
* enabled *and* opened by someone.
*/
return;
/* bail out if already pending */
return;
r = sd_bus_message_new_method_call(c->sysbus,
&m,
"org.freedesktop.login1",
s->path,
"org.freedesktop.login1.Session",
"TakeDevice");
if (r < 0)
goto error;
if (r < 0)
goto error;
r = sd_bus_call_async(c->sysbus,
m,
em,
0);
if (r < 0)
goto error;
return;
log_debug("idev-evdev: %s/%s: cannot send TakeDevice request: %s",
}
static void managed_evdev_pause(idev_element *e) {
idev_session *s = e->session;
idev_context *c = s->context;
int r;
/*
* Releasing managed devices is heavy. Once acquired, we get
* release it if disabled but opened. However, if a device is closed,
* then (even if we're actually enabled).
*/
return;
/*
* If TakeDevice() is pending or was successful, make sure to
* release the device again. We don't care for return-values,
* so send it without waiting or callbacks.
* If a failed TakeDevice() is pending, but someone else took
* the device on the same bus-connection, we might incorrectly
* release their device. This is an unlikely race, though.
* Furthermore, you really shouldn't have two users of the
* controller-API on the same session, on the same devices, *AND* on
* the same bus-connection. So we don't care for that race..
*/
return;
r = sd_bus_message_new_method_call(c->sysbus,
&m,
"org.freedesktop.login1",
s->path,
"org.freedesktop.login1.Session",
"ReleaseDevice");
if (r >= 0) {
if (r >= 0)
}
if (r < 0 && r != -ENOTCONN)
log_debug("idev-evdev: %s/%s: cannot send ReleaseDevice: %s",
}
void *userdata,
idev_session *s = e->session;
idev_context *c = s->context;
const char *mode;
int r;
/*
* We get PauseDevice() signals from logind whenever a device we
* number of the device and the mode of the operation.
* In case the event is not about our device, we ignore it. Otherwise,
* we treat it as asynchronous access-revocation (as if we got HUP on
* the device fd). Note that we might have already treated the HUP
* event via EPOLLHUP, whichever comes first.
*
* @mode can be one of the following:
* "pause": The device is about to be paused. We must react
* immediately and respond with PauseDeviceComplete(). Once
* we replied, logind will pause the device. Note that
* logind might apply any kind of timeout and force pause
* the device if we don't respond in a timely manner. In
* this case, we will receive a second PauseDevice event
* with @mode set to "force" (or similar).
* "force": The device was disabled forecfully by logind. Access is
* already revoked. This is just an asynchronous
* notification so we can put the device asleep (in case
* we didn't already notice the access revocation).
* "gone": This is like "force" but is sent if the device was
* paused due to a device-removal event.
*
* We always handle PauseDevice signals as "force" as we properly
* support asynchronous access revocation, anyway. But in case logind
* sent mode "pause", we also call PauseDeviceComplete() to immediately
* acknowledge the request.
*/
if (r < 0) {
log_debug("idev-evdev: %s/%s: erroneous PauseDevice signal",
return 0;
}
/* not our device? */
return 0;
/*
* Sending PauseDeviceComplete() is racy if logind triggers the
* timeout. That is, if we take too long and logind pauses the
* device by sending a forced PauseDevice, our
* PauseDeviceComplete call will be stray. That's fine, though.
* logind ignores such stray calls. Only if logind also sent a
* further PauseDevice() signal, it might match our call
* incorrectly to the newer PauseDevice(). That's fine, too, as
* we handle that event asynchronously, anyway. Therefore,
* whatever happens, we're fine. Yay!
*/
r = sd_bus_message_new_method_call(c->sysbus,
&m,
"org.freedesktop.login1",
s->path,
"org.freedesktop.login1.Session",
"PauseDeviceComplete");
if (r >= 0) {
if (r >= 0)
}
if (r < 0)
log_debug("idev-evdev: %s/%s: cannot send PauseDeviceComplete: %s",
}
return 0;
}
void *userdata,
idev_session *s = e->session;
int r, fd;
/*
* We get ResumeDevice signals whenever logind resumed a previously
* related device and a new file-descriptor for the freshly opened
* device-node.
* If the signal is not about our device, we simply ignore it.
* Otherwise, we take the file-descriptor and immediately resume the
* device.
*/
if (r < 0) {
log_debug("idev-evdev: %s/%s: erroneous ResumeDevice signal",
return 0;
}
/* not our device? */
return 0;
if (fd < 0) {
log_debug("idev-evdev: %s/%s: cannot duplicate evdev fd: %m",
return 0;
}
if (r < 0)
log_debug("idev-evdev: %s/%s: cannot resume: %s",
return 0;
}
idev_session *s = e->session;
idev_context *c = s->context;
int r;
"sender='org.freedesktop.login1',"
"interface='org.freedesktop.login1.Session',"
"member='PauseDevice',"
NULL);
if (!match)
return -ENOMEM;
r = sd_bus_add_match(c->sysbus,
em);
if (r < 0)
return r;
"sender='org.freedesktop.login1',"
"interface='org.freedesktop.login1.Session',"
"member='ResumeDevice',"
NULL);
if (!match)
return -ENOMEM;
r = sd_bus_add_match(c->sysbus,
em);
if (r < 0)
return r;
return 0;
}
char name[IDEV_EVDEV_NAME_MAX];
int r;
assert_return(s, -EINVAL);
if (devnum == 0)
return -ENODEV;
if (!em)
return -ENOMEM;
r = managed_evdev_setup_bus(em);
if (r < 0)
return r;
r = idev_element_add(e, name);
if (r < 0)
return r;
if (out)
*out = e;
e = NULL;
return 0;
}
static void managed_evdev_free(idev_element *e) {
}
static const idev_element_vtable managed_evdev_vtable = {
};
/*
* Generic Constructor
* Instead of relying on the caller to choose between managed and unmanaged
* evdev devices, the idev_evdev_new() constructor does that for you (by
* looking at s->managed).
*/
bool idev_is_evdev(idev_element *e) {
return e && (e->vtable == &unmanaged_evdev_vtable ||
e->vtable == &managed_evdev_vtable);
}
char name[IDEV_EVDEV_NAME_MAX];
assert_return(s, NULL);
return idev_find_element(s, name);
}
assert_return(s, -EINVAL);
}