xserver-multidpi/os/inputthread.c

471 lines
12 KiB
C
Raw Normal View History

/* inputthread.c -- Threaded generation of input events.
*
* Copyright © 2007-2008 Tiago Vignatti <vignatti at freedesktop org>
* Copyright © 2010 Nokia
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Fernando Carrijo <fcarrijo at freedesktop org>
* Tiago Vignatti <vignatti at freedesktop org>
*/
#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <X11/Xpoll.h>
#include "inputstr.h"
#include "opaque.h"
#include "osdep.h"
#if INPUTTHREAD
Bool InputThreadEnable = TRUE;
/**
* An input device as seen by the threaded input facility
*/
typedef struct _InputThreadDevice {
struct xorg_list node;
NotifyFdProcPtr readInputProc;
void *readInputArgs;
int fd;
} InputThreadDevice;
/**
* The threaded input facility.
*
* For now, we have one instance for all input devices.
*/
typedef struct {
pthread_t thread;
struct xorg_list devs;
fd_set fds;
int readPipe;
int writePipe;
} InputThreadInfo;
static InputThreadInfo *inputThreadInfo;
static int hotplugPipeRead = -1;
static int hotplugPipeWrite = -1;
static int input_mutex_count;
#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
static pthread_mutex_t input_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
#else
static pthread_mutex_t input_mutex;
static Bool input_mutex_initialized;
#endif
void
input_lock(void)
{
#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
if (!input_mutex_initialized) {
pthread_mutexattr_t mutex_attr;
input_mutex_initialized = TRUE;
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&input_mutex, &mutex_attr);
}
#endif
pthread_mutex_lock(&input_mutex);
++input_mutex_count;
}
void
input_unlock(void)
{
--input_mutex_count;
pthread_mutex_unlock(&input_mutex);
}
void
input_force_unlock(void)
{
if (pthread_mutex_trylock(&input_mutex) == 0) {
/* unlock +1 times for the trylock */
while (input_mutex_count-- >= 0)
pthread_mutex_unlock(&input_mutex);
}
}
/**
* Notify a thread about the availability of new asynchronously enqueued input
* events.
*
* @see WaitForSomething()
*/
static void
InputThreadFillPipe(int writeHead)
{
int ret;
char byte = 0;
fd_set writePipe;
FD_ZERO(&writePipe);
while (1) {
ret = write(writeHead, &byte, 1);
if (!ret)
FatalError("input-thread: write() returned 0");
if (ret > 0)
break;
if (errno != EAGAIN)
FatalError("input-thread: filling pipe");
DebugF("input-thread: pipe full\n");
FD_SET(writeHead, &writePipe);
Select(writeHead + 1, NULL, &writePipe, NULL, NULL);
}
}
/**
* Consume eventual notifications left by a thread.
*
* @see WaitForSomething()
* @see InputThreadFillPipe()
*/
static int
InputThreadReadPipe(int readHead)
{
int ret, array[10];
ret = read(readHead, &array, sizeof(array));
if (ret >= 0)
return ret;
if (errno != EAGAIN)
FatalError("input-thread: draining pipe (%d)", errno);
return 1;
}
/**
* Register an input device in the threaded input facility
*
* @param fd File descriptor which identifies the input device
* @param readInputProc Procedure used to read input from the device
* @param readInputArgs Arguments to be consumed by the above procedure
*
* return 1 if success; 0 otherwise.
*/
int
InputThreadRegisterDev(int fd,
NotifyFdProcPtr readInputProc,
void *readInputArgs)
{
InputThreadDevice *dev;
if (!inputThreadInfo)
return SetNotifyFd(fd, readInputProc, X_NOTIFY_READ, readInputArgs);
dev = calloc(1, sizeof(InputThreadDevice));
if (dev == NULL) {
DebugF("input-thread: could not register device\n");
return 0;
}
dev->fd = fd;
dev->readInputProc = readInputProc;
dev->readInputArgs = readInputArgs;
xorg_list_add(&dev->node, &inputThreadInfo->devs);
FD_SET(fd, &inputThreadInfo->fds);
InputThreadFillPipe(hotplugPipeWrite);
DebugF("input-thread: registered device %d\n", fd);
return 1;
}
/**
* Unregister a device in the threaded input facility
*
* @param fd File descriptor which identifies the input device
*
* @return 1 if success; 0 otherwise.
*/
int
InputThreadUnregisterDev(int fd)
{
InputThreadDevice *dev;
Bool found_device = FALSE;
/* return silently if input thread is already finished (e.g., at
* DisableDevice time, evdev tries to call this function again through
* xf86RemoveEnabledDevice) */
if (!inputThreadInfo) {
RemoveNotifyFd(fd);
return 1;
}
xorg_list_for_each_entry(dev, &inputThreadInfo->devs, node)
if (dev->fd == fd) {
found_device = TRUE;
break;
}
/* fd didn't match any registered device. */
if (!found_device)
return 0;
xorg_list_del(&dev->node);
FD_CLR(fd, &inputThreadInfo->fds);
free(dev);
InputThreadFillPipe(hotplugPipeWrite);
DebugF("input-thread: unregistered device: %d\n", fd);
return 1;
}
/**
* The workhorse of threaded input event generation.
*
* Or if you prefer: The WaitForSomething for input devices. :)
*
* Runs in parallel with the server main thread, listening to input devices in
* an endless loop. Whenever new input data is made available, calls the
* proper device driver's routines which are ultimately responsible for the
* generation of input events.
*
* @see InputThreadPreInit()
* @see InputThreadInit()
*/
static void*
InputThreadDoWork(void *arg)
{
fd_set readyFds;
InputThreadDevice *dev, *next;
sigset_t set;
/* Don't handle any signals on this thread */
sigfillset(&set);
pthread_sigmask(SIG_BLOCK, &set, NULL);
FD_ZERO(&readyFds);
while (1)
{
XFD_COPYSET(&inputThreadInfo->fds, &readyFds);
FD_SET(hotplugPipeRead, &readyFds);
DebugF("input-thread: %s waiting for devices\n", __func__);
if (Select(MAXSELECT, &readyFds, NULL, NULL, NULL) < 0) {
if (errno == EINVAL)
FatalError("input-thread: %s (%s)", __func__, strerror(errno));
else if (errno != EINTR)
ErrorF("input-thread: %s (%s)\n", __func__, strerror(errno));
}
DebugF("input-thread: %s generating events\n", __func__);
/* Call the device drivers to generate input events for us */
xorg_list_for_each_entry_safe(dev, next, &inputThreadInfo->devs, node) {
if (FD_ISSET(dev->fd, &readyFds) && dev->readInputProc) {
input_lock();
dev->readInputProc(dev->fd, X_NOTIFY_READ, dev->readInputArgs);
input_unlock();
}
}
/* Kick main thread to process the generated input events and drain
* events from hotplug pipe */
InputThreadFillPipe(inputThreadInfo->writePipe);
/* Empty pending input, shut down if the pipe has been closed */
if (FD_ISSET(hotplugPipeRead, &readyFds)) {
if (InputThreadReadPipe(hotplugPipeRead) == 0)
break;
}
}
return NULL;
}
static void
InputThreadNotifyPipe(int fd, int mask, void *data)
{
InputThreadReadPipe(fd);
}
/**
* Pre-initialize the facility used for threaded generation of input events
*
*/
void
InputThreadPreInit(void)
{
int fds[2], hotplugPipe[2];
if (!InputThreadEnable)
return;
if (pipe(fds) < 0)
FatalError("input-thread: could not create pipe");
if (pipe(hotplugPipe) < 0)
FatalError("input-thread: could not create pipe");
inputThreadInfo = malloc(sizeof(InputThreadInfo));
if (!inputThreadInfo)
FatalError("input-thread: could not allocate memory");
inputThreadInfo->thread = 0;
xorg_list_init(&inputThreadInfo->devs);
FD_ZERO(&inputThreadInfo->fds);
/* By making read head non-blocking, we ensure that while the main thread
* is busy servicing client requests, the dedicated input thread can work
* in parallel.
*/
inputThreadInfo->readPipe = fds[0];
fcntl(inputThreadInfo->readPipe, F_SETFL, O_NONBLOCK | O_CLOEXEC);
SetNotifyFd(inputThreadInfo->readPipe, InputThreadNotifyPipe, X_NOTIFY_READ, NULL);
inputThreadInfo->writePipe = fds[1];
hotplugPipeRead = hotplugPipe[0];
fcntl(hotplugPipeRead, F_SETFL, O_NONBLOCK | O_CLOEXEC);
hotplugPipeWrite = hotplugPipe[1];
}
/**
* Start the threaded generation of input events. This routine complements what
* was previously done by InputThreadPreInit(), being only responsible for
* creating the dedicated input thread.
*
*/
void
InputThreadInit(void)
{
pthread_attr_t attr;
/* If the driver hasn't asked for input thread support by calling
* InputThreadPreInit, then do nothing here
*/
if (!inputThreadInfo)
return;
pthread_attr_init(&attr);
/* For OSes that differentiate between processes and threads, the following
* lines have sense. Linux uses the 1:1 thread model. The scheduler handles
* every thread as a normal process. Therefore this probably has no meaning
* if we are under Linux.
*/
if (pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM) != 0)
ErrorF("input-thread: error setting thread scope\n");
DebugF("input-thread: creating thread\n");
pthread_create(&inputThreadInfo->thread, &attr,
&InputThreadDoWork, NULL);
pthread_attr_destroy (&attr);
}
/**
* Stop the threaded generation of input events
*
* This function is supposed to be called at server shutdown time only.
*/
void
InputThreadFini(void)
{
InputThreadDevice *dev, *next;
if (!inputThreadInfo)
return;
/* Close the pipe to get the input thread to shut down */
close(hotplugPipeWrite);
pthread_join(inputThreadInfo->thread, NULL);
xorg_list_for_each_entry_safe(dev, next, &inputThreadInfo->devs, node) {
FD_CLR(dev->fd, &inputThreadInfo->fds);
free(dev);
}
xorg_list_init(&inputThreadInfo->devs);
FD_ZERO(&inputThreadInfo->fds);
RemoveNotifyFd(inputThreadInfo->readPipe);
close(inputThreadInfo->readPipe);
close(inputThreadInfo->writePipe);
inputThreadInfo->readPipe = -1;
inputThreadInfo->writePipe = -1;
close(hotplugPipeRead);
hotplugPipeRead = -1;
hotplugPipeWrite = -1;
free(inputThreadInfo);
inputThreadInfo = NULL;
}
int xthread_sigmask(int how, const sigset_t *set, sigset_t *oldset)
{
return pthread_sigmask(how, set, oldset);
}
#else /* INPUTTHREAD */
Bool InputThreadEnable = FALSE;
void input_lock(void) {}
void input_unlock(void) {}
void input_force_unlock(void) {}
void InputThreadPreInit(void) {}
void InputThreadInit(void) {}
void InputThreadFini(void) {}
int InputThreadRegisterDev(int fd,
NotifyFdProcPtr readInputProc,
void *readInputArgs)
{
return SetNotifyFd(fd, readInputProc, X_NOTIFY_READ, readInputArgs);
}
extern int InputThreadUnregisterDev(int fd)
{
RemoveNotifyFd(fd);
return 1;
}
int xthread_sigmask(int how, const sigset_t *set, sigset_t *oldset)
{
return sigprocmask(how, set, oldset);
}
#endif