diff options
Diffstat (limited to 'os/inputthread.c')
-rw-r--r-- | os/inputthread.c | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/os/inputthread.c b/os/inputthread.c new file mode 100644 index 000000000..b6bbf3509 --- /dev/null +++ b/os/inputthread.c @@ -0,0 +1,470 @@ +/* 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 |