root/src/add-ons/kernel/bus_managers/ps2/ps2_service.cpp
/*
 * Copyright 2005-2007 Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * PS/2 bus manager
 *
 * Authors (in chronological order):
 *              Marcus Overhagen (marcus@overhagen.de)
 */


#include "ps2_service.h"

#include "packet_buffer.h"


#define DEBUG_PUBLISHING
#ifdef DEBUG_PUBLISHING
#       include <stdlib.h>
#       include <debug.h>
#endif


//#define TRACE_PS2_SERVICE
#ifdef TRACE_PS2_SERVICE
#       define TRACE(x...) dprintf(x)
#else
#       define TRACE(x...)
#endif


typedef struct {
        uint32          id;
        ps2_dev *       dev;
} ps2_service_cmd;

enum {
        PS2_SERVICE_NOTIFY_DEVICE_ADDED = 1,
        PS2_SERVICE_NOTIFY_DEVICE_REPUBLISH,
        PS2_SERVICE_NOTIFY_DEVICE_REMOVED,
};


static sem_id                   sServiceSem;
static thread_id                sServiceThread;
static volatile bool    sServiceTerminate;
static packet_buffer *  sServiceCmdBuffer;


void
ps2_service_notify_device_added(ps2_dev *dev)
{
        ps2_service_cmd cmd;

        TRACE("ps2: ps2_service_notify_device_added %s\n", dev->name);

        cmd.id = PS2_SERVICE_NOTIFY_DEVICE_ADDED;
        cmd.dev = dev;

        packet_buffer_write(sServiceCmdBuffer, (const uint8 *)&cmd, sizeof(cmd));
        release_sem_etc(sServiceSem, 1, B_DO_NOT_RESCHEDULE);

        TRACE("ps2: ps2_service_notify_device_added done\n");
}


void
ps2_service_notify_device_republish(ps2_dev *dev)
{
        ps2_service_cmd cmd;

        TRACE("ps2: ps2_service_notify_device_republish %s\n", dev->name);

        cmd.id = PS2_SERVICE_NOTIFY_DEVICE_REPUBLISH;
        cmd.dev = dev;

        packet_buffer_write(sServiceCmdBuffer, (const uint8 *)&cmd, sizeof(cmd));
        release_sem_etc(sServiceSem, 1, B_DO_NOT_RESCHEDULE);

        TRACE("ps2: ps2_service_notify_device_republish done\n");
}


void
ps2_service_notify_device_removed(ps2_dev *dev)
{
        ps2_service_cmd cmd;

        TRACE("ps2: ps2_service_notify_device_removed %s\n", dev->name);

        cmd.id = PS2_SERVICE_NOTIFY_DEVICE_REMOVED;
        cmd.dev = dev;

        packet_buffer_write(sServiceCmdBuffer, (const uint8 *)&cmd, sizeof(cmd));
        release_sem_etc(sServiceSem, 1, B_DO_NOT_RESCHEDULE);

        TRACE("ps2: ps2_service_notify_device_removed done\n");
}


static int32
ps2_service_thread(void *arg)
{
        TRACE("ps2: ps2_service_thread started\n");

        for (;;) {
                status_t status;
                status = acquire_sem_etc(sServiceSem, 1, B_CAN_INTERRUPT, 0);
                if (sServiceTerminate)
                        break;
                if (status == B_OK) {

                        // process service commands
                        ps2_service_cmd cmd;
                        packet_buffer_read(sServiceCmdBuffer, (uint8 *)&cmd, sizeof(cmd));
                        switch (cmd.id) {
                                case PS2_SERVICE_NOTIFY_DEVICE_ADDED:
                                        TRACE("ps2: PS2_SERVICE_NOTIFY_DEVICE_ADDED %s\n", cmd.dev->name);
                                        ps2_dev_publish(cmd.dev);
                                        break;

                                case PS2_SERVICE_NOTIFY_DEVICE_REPUBLISH:
                                        TRACE("ps2: PS2_SERVICE_NOTIFY_DEVICE_REPUBLISH %s\n", cmd.dev->name);
                                        ps2_dev_unpublish(cmd.dev);
                                        snooze(1500000);
                                        ps2_dev_publish(cmd.dev);
                                        break;

                                case PS2_SERVICE_NOTIFY_DEVICE_REMOVED:
                                        TRACE("ps2: PS2_SERVICE_NOTIFY_DEVICE_REMOVED %s\n", cmd.dev->name);
                                        ps2_dev_unpublish(cmd.dev);
                                        break;

                                default:
                                        TRACE("ps2: PS2_SERVICE: unknown id %" B_PRIu32 "\n", cmd.id);
                                        break;
                        }
                } else {
                        INFO("ps2: ps2_service_thread: Error, status 0x%08" B_PRIx32 ", "
                                "terminating\n", status);
                        break;
                }
        }
        return 0;
}


#ifdef DEBUG_PUBLISHING
static int
ps2_republish(int argc, char **argv)
{
        int dev = PS2_DEVICE_KEYB;
        if (argc == 2)
                dev = strtoul(argv[1], NULL, 0);
        if (dev < 0 || dev >= PS2_DEVICE_COUNT)
                dev = PS2_DEVICE_KEYB;
        ps2_service_notify_device_republish(&ps2_device[dev]);
        return 0;
}
#endif


status_t
ps2_service_init(void)
{
        TRACE("ps2: ps2_service_init\n");
        sServiceCmdBuffer = create_packet_buffer(sizeof(ps2_service_cmd) * 50);
        if (sServiceCmdBuffer == NULL)
                goto err1;
        sServiceSem = create_sem(0, "ps2 service");
        if (sServiceSem < B_OK)
                goto err2;
        sServiceThread = spawn_kernel_thread(ps2_service_thread, "ps2 service", 20, NULL);
        if (sServiceThread < B_OK)
                goto err3;
        sServiceTerminate = false;
        resume_thread(sServiceThread);

#ifdef DEBUG_PUBLISHING
        add_debugger_command("ps2republish", &ps2_republish, "republish a ps2 device (0-3 mouse, 4 keyb (default))");
#endif

        TRACE("ps2: ps2_service_init done\n");
        return B_OK;

err3:
        delete_sem(sServiceSem);
err2:
        delete_packet_buffer(sServiceCmdBuffer);
err1:
        TRACE("ps2: ps2_service_init failed\n");
        return B_ERROR;
}


void
ps2_service_exit(void)
{
        TRACE("ps2: ps2_service_exit enter\n");
        sServiceTerminate = true;
        release_sem(sServiceSem);
        wait_for_thread(sServiceThread, NULL);
        delete_sem(sServiceSem);
        delete_packet_buffer(sServiceCmdBuffer);

#ifdef DEBUG_PUBLISHING
        remove_debugger_command("ps2republish", &ps2_republish);
#endif
        TRACE("ps2: ps2_service_exit done\n");
}