562 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			562 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
| /* 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 "inputstr.h"
 | |
| #include "opaque.h"
 | |
| #include "osdep.h"
 | |
| 
 | |
| #if INPUTTHREAD
 | |
| 
 | |
| Bool InputThreadEnable = TRUE;
 | |
| 
 | |
| /**
 | |
|  * An input device as seen by the threaded input facility
 | |
|  */
 | |
| 
 | |
| typedef enum _InputDeviceState {
 | |
|     device_state_added,
 | |
|     device_state_running,
 | |
|     device_state_removed
 | |
| } InputDeviceState;
 | |
| 
 | |
| typedef struct _InputThreadDevice {
 | |
|     struct xorg_list node;
 | |
|     NotifyFdProcPtr readInputProc;
 | |
|     void *readInputArgs;
 | |
|     int fd;
 | |
|     InputDeviceState state;
 | |
| } InputThreadDevice;
 | |
| 
 | |
| /**
 | |
|  * The threaded input facility.
 | |
|  *
 | |
|  * For now, we have one instance for all input devices.
 | |
|  */
 | |
| typedef struct {
 | |
|     pthread_t thread;
 | |
|     struct xorg_list devs;
 | |
|     struct ospoll *fds;
 | |
|     int readPipe;
 | |
|     int writePipe;
 | |
|     Bool changed;
 | |
|     Bool running;
 | |
| } 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
 | |
| 
 | |
| int
 | |
| in_input_thread(void)
 | |
| {
 | |
|     return inputThreadInfo &&
 | |
|            pthread_equal(pthread_self(), inputThreadInfo->thread);
 | |
| }
 | |
| 
 | |
| 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) {
 | |
|         input_mutex_count++;
 | |
|         /* unlock +1 times for the trylock */
 | |
|         while (input_mutex_count > 0)
 | |
|             input_unlock();
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Notify a thread about the availability of new asynchronously enqueued input
 | |
|  * events.
 | |
|  *
 | |
|  * @see WaitForSomething()
 | |
|  */
 | |
| static void
 | |
| InputThreadFillPipe(int writeHead)
 | |
| {
 | |
|     int ret;
 | |
|     char byte = 0;
 | |
| 
 | |
|     do {
 | |
|         ret = write(writeHead, &byte, 1);
 | |
|     } while (ret < 0 && ETEST(errno));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
| }
 | |
| 
 | |
| static void
 | |
| InputReady(int fd, int xevents, void *data)
 | |
| {
 | |
|     InputThreadDevice *dev = data;
 | |
| 
 | |
|     input_lock();
 | |
|     if (dev->state == device_state_running)
 | |
|         dev->readInputProc(fd, xevents, dev->readInputArgs);
 | |
|     input_unlock();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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, *old;
 | |
| 
 | |
|     if (!inputThreadInfo)
 | |
|         return SetNotifyFd(fd, readInputProc, X_NOTIFY_READ, readInputArgs);
 | |
| 
 | |
|     input_lock();
 | |
| 
 | |
|     dev = NULL;
 | |
|     xorg_list_for_each_entry(old, &inputThreadInfo->devs, node) {
 | |
|         if (old->fd == fd && old->state != device_state_removed) {
 | |
|             dev = old;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (dev) {
 | |
|         dev->readInputProc = readInputProc;
 | |
|         dev->readInputArgs = readInputArgs;
 | |
|     } else {
 | |
|         dev = calloc(1, sizeof(InputThreadDevice));
 | |
|         if (dev == NULL) {
 | |
|             DebugF("input-thread: could not register device\n");
 | |
|             input_unlock();
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         dev->fd = fd;
 | |
|         dev->readInputProc = readInputProc;
 | |
|         dev->readInputArgs = readInputArgs;
 | |
|         dev->state = device_state_added;
 | |
| 
 | |
|         /* Do not prepend, so that any dev->state == device_state_removed
 | |
|          * with the same dev->fd get processed first. */
 | |
|         xorg_list_append(&dev->node, &inputThreadInfo->devs);
 | |
|     }
 | |
| 
 | |
|     inputThreadInfo->changed = TRUE;
 | |
| 
 | |
|     input_unlock();
 | |
| 
 | |
|     DebugF("input-thread: registered device %d\n", fd);
 | |
|     InputThreadFillPipe(hotplugPipeWrite);
 | |
| 
 | |
|     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;
 | |
|     }
 | |
| 
 | |
|     input_lock();
 | |
|     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) {
 | |
|         input_unlock();
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     dev->state = device_state_removed;
 | |
|     inputThreadInfo->changed = TRUE;
 | |
| 
 | |
|     input_unlock();
 | |
| 
 | |
|     InputThreadFillPipe(hotplugPipeWrite);
 | |
|     DebugF("input-thread: unregistered device: %d\n", fd);
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| static void
 | |
| InputThreadPipeNotify(int fd, int revents, void *data)
 | |
| {
 | |
|     /* Empty pending input, shut down if the pipe has been closed */
 | |
|     if (InputThreadReadPipe(hotplugPipeRead) == 0) {
 | |
|         inputThreadInfo->running = FALSE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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)
 | |
| {
 | |
|     sigset_t set;
 | |
| 
 | |
|     /* Don't handle any signals on this thread */
 | |
|     sigfillset(&set);
 | |
|     pthread_sigmask(SIG_BLOCK, &set, NULL);
 | |
| 
 | |
|     inputThreadInfo->running = TRUE;
 | |
| 
 | |
| #if defined(HAVE_PTHREAD_SETNAME_NP_WITH_TID)
 | |
|     pthread_setname_np (pthread_self(), "InputThread");
 | |
| #elif defined(HAVE_PTHREAD_SETNAME_NP_WITHOUT_TID)
 | |
|     pthread_setname_np ("InputThread");
 | |
| #endif
 | |
| 
 | |
|     ospoll_add(inputThreadInfo->fds, hotplugPipeRead,
 | |
|                ospoll_trigger_level,
 | |
|                InputThreadPipeNotify,
 | |
|                NULL);
 | |
|     ospoll_listen(inputThreadInfo->fds, hotplugPipeRead, X_NOTIFY_READ);
 | |
| 
 | |
|     while (inputThreadInfo->running)
 | |
|     {
 | |
|         DebugF("input-thread: %s waiting for devices\n", __func__);
 | |
| 
 | |
|         /* Check for hotplug changes and modify the ospoll structure to suit */
 | |
|         if (inputThreadInfo->changed) {
 | |
|             InputThreadDevice *dev, *tmp;
 | |
| 
 | |
|             input_lock();
 | |
|             inputThreadInfo->changed = FALSE;
 | |
|             xorg_list_for_each_entry_safe(dev, tmp, &inputThreadInfo->devs, node) {
 | |
|                 switch (dev->state) {
 | |
|                 case device_state_added:
 | |
|                     ospoll_add(inputThreadInfo->fds, dev->fd,
 | |
|                                ospoll_trigger_level,
 | |
|                                InputReady,
 | |
|                                dev);
 | |
|                     ospoll_listen(inputThreadInfo->fds, dev->fd, X_NOTIFY_READ);
 | |
|                     dev->state = device_state_running;
 | |
|                     break;
 | |
|                 case device_state_running:
 | |
|                     break;
 | |
|                 case device_state_removed:
 | |
|                     ospoll_remove(inputThreadInfo->fds, dev->fd);
 | |
|                     xorg_list_del(&dev->node);
 | |
|                     free(dev);
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             input_unlock();
 | |
|         }
 | |
| 
 | |
|         if (ospoll_wait(inputThreadInfo->fds, -1) < 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));
 | |
|         }
 | |
| 
 | |
|         /* Kick main thread to process the generated input events and drain
 | |
|          * events from hotplug pipe */
 | |
|         InputThreadFillPipe(inputThreadInfo->writePipe);
 | |
|     }
 | |
| 
 | |
|     ospoll_remove(inputThreadInfo->fds, hotplugPipeRead);
 | |
| 
 | |
|     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];
 | |
|     int flags;
 | |
| 
 | |
|     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->changed = FALSE;
 | |
| 
 | |
|     inputThreadInfo->thread = 0;
 | |
|     xorg_list_init(&inputThreadInfo->devs);
 | |
|     inputThreadInfo->fds = ospoll_create();
 | |
| 
 | |
|     /* 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);
 | |
|     flags = fcntl(inputThreadInfo->readPipe, F_GETFD);
 | |
|     if (flags != -1) {
 | |
|         flags |= FD_CLOEXEC;
 | |
|         (void)fcntl(inputThreadInfo->readPipe, F_SETFD, &flags);
 | |
|     }
 | |
|     SetNotifyFd(inputThreadInfo->readPipe, InputThreadNotifyPipe, X_NOTIFY_READ, NULL);
 | |
| 
 | |
|     inputThreadInfo->writePipe = fds[1];
 | |
| 
 | |
|     hotplugPipeRead = hotplugPipe[0];
 | |
|     fcntl(hotplugPipeRead, F_SETFL, O_NONBLOCK);
 | |
|     flags = fcntl(hotplugPipeRead, F_GETFD);
 | |
|     if (flags != -1) {
 | |
|         flags |= FD_CLOEXEC;
 | |
|         (void)fcntl(hotplugPipeRead, F_SETFD, &flags);
 | |
|     }
 | |
|     hotplugPipeWrite = hotplugPipe[1];
 | |
| 
 | |
| #ifndef __linux__ /* Linux does not deal well with renaming the main thread */
 | |
| #if defined(HAVE_PTHREAD_SETNAME_NP_WITH_TID)
 | |
|     pthread_setname_np (pthread_self(), "MainThread");
 | |
| #elif defined(HAVE_PTHREAD_SETNAME_NP_WITHOUT_TID)
 | |
|     pthread_setname_np ("MainThread");
 | |
| #endif
 | |
| #endif
 | |
| 
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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) {
 | |
|         ospoll_remove(inputThreadInfo->fds, dev->fd);
 | |
|         free(dev);
 | |
|     }
 | |
|     xorg_list_init(&inputThreadInfo->devs);
 | |
|     ospoll_destroy(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 in_input_thread(void) { return 0; }
 | |
| 
 | |
| 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
 |