/* 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>
 */

#include <dix-config.h>

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include "dix/input_priv.h"
#include "os/ddx_priv.h"
#include "os/log_priv.h"
#include "os/ossock.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 && ossock_wouldblock(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);

    ddxInputThreadInit();

    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 = calloc(1, 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);
    input_force_unlock();
    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)
{
#ifdef HAVE_SIGPROCMASK
    return sigprocmask(how, set, oldset);
#else
    return 0;
#endif
}

#endif
