root/src/add-ons/kernel/debugger/usb_keyboard/usb_keyboard.cpp
/*
 * Copyright 2009-2011, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Lotz <mmlr@mlotz.ch>
 */

#include <debug.h>
#include <debugger_keymaps.h>

static bool sUseUSBKeyboard = false;
static uint8 sUSBTransferData[64];
static uint8 sLastTransferData[64];
static size_t sUSBTransferLength = 0;
static void *sUSBPipe = NULL;

// simple ring buffer
static int sBufferedChars[32];
static uint8 sBufferSize = sizeof(sBufferedChars) / sizeof(sBufferedChars[0]);
static uint8 sBufferedCharCount = 0;
static uint8 sBufferWriteIndex = 0;
static uint8 sBufferReadIndex = 0;

#define MODIFIER_CONTROL        0x01
#define MODIFIER_SHIFT          0x02
#define MODIFIER_ALT            0x04

static uint32 sModifierTable[] = {
        MODIFIER_CONTROL,
        MODIFIER_SHIFT,
        MODIFIER_ALT,
        0,
        MODIFIER_CONTROL,
        MODIFIER_SHIFT,
        MODIFIER_ALT,
        0
};

static uint8 sKeyTable[] = {
        0,      // ERROR
        0,      // ERROR
        0,      // ERROR
        0,      // ERROR
        30,     // A
        48,     // B
        46,     // C
        32,     // D
        18,     // E
        33,     // F
        34,     // G
        35,     // H
        23,     // I
        36,     // J
        37,     // K
        38,     // L
        50,     // M
        49,     // N
        24,     // O
        25,     // P
        16,     // Q
        19,     // R
        31,     // S
        20,     // T
        22,     // U
        47,     // V
        17,     // W
        45,     // X
        21,     // Y
        44,     // Z
        2,      // 1
        3,      // 2
        4,      // 3
        5,      // 4
        6,      // 5
        7,      // 6
        8,      // 7
        9,      // 8
        10,     // 9
        11,     // 0
        28,     // enter
        1,      // Esc
        14,     // Backspace
        15,     // Tab
        57,     // Space
        12,     // -
        13,     // =
        26,     // [
        27,     // ]
        43,     // backslash
        80,     // backslash
        39,     // ;
        40,     // '
        41,     // `
        51,     // ,
        52,     // .
        53,     // /
        0,      // Caps
        0,      // F1
        0,      // F2
        0,      // F3
        0,      // F4
        0,      // F5
        0,      // F6
        0,      // F7
        0,      // F8
        0,      // F9
        0,      // F10
        0,      // F11
        0,      // F12
        0,      // PrintScreen
        0,      // Scroll Lock
        0,      // Pause (0x7f with Ctrl)
        0,      // Insert
        0x80 | 'H',     // Home
        0x80 | '5',     // Page up
        0x80 | '3',     // Delete
        0x80 | 'F',     // End
        0x80 | '6',     // Page down
        0x80 | 'C',     // Right arrow
        0x80 | 'D',     // Left arrow
        0x80 | 'B',     // Down arrow
        0x80 | 'A',     // Up arrow
        0,      // Num Lock
        53,     // Pad /
        55,     // Pad *
        12,     // Pad -
        54,     // Pad +
        28,     // Pad Enter
        2,      // Pad 1
        3,      // Pad 2
        4,      // Pad 3
        5,      // Pad 4
        6,      // Pad 5
        7,      // Pad 6
        8,      // Pad 7
        9,      // Pad 8
        10,     // Pad 9
        11,     // Pad 0
        52,     // Pad .
        86,     // <
        0,      // Menu
        0,      // Power
        13      // Pad =
};

static size_t sKeyTableSize = sizeof(sKeyTable) / sizeof(sKeyTable[0]);


static void
enter_debugger(void)
{
        if (!has_debugger_command("get_usb_keyboard_config")
                || !has_debugger_command("get_usb_pipe_for_id")
                || !has_debugger_command("usb_process_transfer")) {
                return;
        }

        unset_debug_variable("_usbPipe");
        unset_debug_variable("_usbReportSize");

        evaluate_debug_command("get_usb_keyboard_config");
        sUSBTransferLength = get_debug_variable("_usbReportSize", 0);
        if (sUSBTransferLength == 0 || sUSBTransferLength > sizeof(sUSBTransferData))
                return;

        evaluate_debug_command("get_usb_pipe_for_id");
        sUSBPipe = (void *)get_debug_variable("_usbPipe", 0);
        if (sUSBPipe == NULL)
                return;

        sUseUSBKeyboard = true;
}


static void
exit_debugger(void)
{
        if (sUseUSBKeyboard) {
                // make sure a possibly pending transfer is canceled
                set_debug_variable("_usbPipe", (uint64)sUSBPipe);
                evaluate_debug_command("usb_process_transfer cancel");
                sUseUSBKeyboard = false;
        }
}


static void
write_key(int key)
{
        sBufferedChars[sBufferWriteIndex++] = key;
        sBufferWriteIndex %= sBufferSize;
        sBufferedCharCount++;
}


static int
debugger_getchar(void)
{
        if (!sUseUSBKeyboard)
                return -1;

        if (sBufferedCharCount == 0) {
                set_debug_variable("_usbPipe", (uint64)sUSBPipe);
                set_debug_variable("_usbTransferData", (uint64)sUSBTransferData);
                set_debug_variable("_usbTransferLength", (uint64)sUSBTransferLength);

                status_t status = evaluate_debug_command("usb_process_transfer");
                if (status == B_DEV_PENDING)
                        return -1;

                if (status != B_OK) {
                        // try clearing a possibly set halt due to toggle mismatches
                        evaluate_debug_command("usb_clear_stall");
                        return -1;
                }

                bool phantomState = true;
                for (size_t i = 2; i < sUSBTransferLength; i++) {
                        if (sUSBTransferData[i] != 0x01) {
                                phantomState = false;
                                break;
                        }
                }

                if (phantomState)
                        return -1;

                uint8 modifiers = 0;
                for (uint32 i = 0; i < 8; i++) {
                        if (sUSBTransferData[0] & (1 << i))
                                modifiers |= sModifierTable[i];
                }

                uint8 *current = sUSBTransferData;
                uint8 *compare = sLastTransferData;
                for (uint32 i = 2; i < sUSBTransferLength; i++) {
                        if (current[i] == 0x00 || current[i] == 0x01)
                                continue;

                        bool found = false;
                        for (uint32 j = 2; j < sUSBTransferLength; j++) {
                                if (compare[j] == current[i]) {
                                        found = true;
                                        break;
                                }
                        }

                        if (found)
                                continue;

                        if (current[i] >= sKeyTableSize)
                                continue;

                        int result = -1;
                        uint8 key = sKeyTable[current[i]];
                        if (key & 0x80) {
                                write_key(27);
                                write_key('[');

                                key &= ~0x80;
                                write_key(key);

                                if (key == '5' || key == '6' || key == '3')
                                        write_key('~');

                                continue;
                        } else if (modifiers & MODIFIER_CONTROL) {
                                char c = kShiftedKeymap[key];
                                if (c >= 'A' && c <= 'Z')
                                        result = 0x1f & c;
                        } else if (modifiers & MODIFIER_ALT)
                                result = kAltedKeymap[key];
                        else if (modifiers & MODIFIER_SHIFT)
                                result = kShiftedKeymap[key];
                        else
                                result = kUnshiftedKeymap[key];

                        if (result < 0)
                                continue;

                        write_key(result);
                }

                for (uint32 i = 0; i < sUSBTransferLength; i++)
                        sLastTransferData[i] = sUSBTransferData[i];
        }

        if (sBufferedCharCount == 0)
                return -1;

        int result = sBufferedChars[sBufferReadIndex++];
        sBufferReadIndex %= sBufferSize;
        sBufferedCharCount--;
        return result;
}


static status_t
std_ops(int32 op, ...)
{
        if (op == B_MODULE_INIT || op == B_MODULE_UNINIT)
                return B_OK;

        return B_BAD_VALUE;
}


static struct debugger_module_info sModuleInfo = {
        {
                "debugger/usb_keyboard/v1",
                0,
                &std_ops
        },

        &enter_debugger,
        &exit_debugger,
        NULL,
        &debugger_getchar
};

module_info *modules[] = {
        (module_info *)&sModuleInfo,
        NULL
};