/*
 * Keyboard driver
 * Copyright (c) 2001,2004 David H. Hovemeyer <daveho@cs.umd.edu>
 * $Revision: 1.15 $
 * 
 * This is free software.  You are permitted to use,
 * redistribute, and modify it as specified in the file "COPYING".
 */

/*
 * Information sources:
 * - Chapter 8 of _The Undocumented PC_, 2nd ed, by Frank van Gilluwe,
 *   ISBN 0-201-47950-8.
 * - Pages 400-409 of _The Programmers PC Sourcebook_, by Thom Hogan,
 *   ISBN 1-55615-118-7.
 */

/*
 * Credits:
 * - Peter Gnodde <peter@pcswebdesign.nl> added support for
 *   the CTRL and ALT modifiers
 */

/* nspring added partial e0 (101-key, right control) stuff */

/*
 * TODO list:
 * - Right now we're assuming an 83-key keyboard.
 *   Should add support for 101+ keyboards.
 * - Should toggle keyboard LEDs.
 */

#include <geekos/kthread.h>
#include <geekos/kassert.h>
#include <geekos/screen.h>
#include <geekos/irq.h>
#include <geekos/io.h>
#include <geekos/keyboard.h>
#include <geekos/int.h>

#define DEBUG_KEYBOARD(x...)
// #define DEBUG_KEYBOARD(x...) Print("KBD: " x)

/* ----------------------------------------------------------------------
 * Private data and functions
 * ---------------------------------------------------------------------- */

/*
 * Current shift state.
 */
#define LEFT_SHIFT  0x01
#define RIGHT_SHIFT 0x02
#define LEFT_CTRL   0x04
#define RIGHT_CTRL  0x08
#define LEFT_ALT    0x10
#define RIGHT_ALT   0x20
#define SHIFT_MASK  (LEFT_SHIFT | RIGHT_SHIFT)
#define CTRL_MASK   (LEFT_CTRL | RIGHT_CTRL)
#define ALT_MASK    (LEFT_ALT | RIGHT_ALT)
static unsigned s_shiftState = 0;

/*
 * Queue for keycodes, in case they arrive faster than consumer
 * can deal with them.
 */
#define QUEUE_SIZE 256
#define QUEUE_MASK 0xff
#define NEXT(index) (((index) + 1) & QUEUE_MASK)
static Keycode s_queue[QUEUE_SIZE];
static int s_queueHead, s_queueTail;
/* for accessing the queue */
static Spin_Lock_t s_kbdQueueLock;

/*
 * Wait queue for thread(s) waiting for keyboard events.
 */
static struct Thread_Queue s_keyboardWaitQueue;

/*
 * Translate from scan code to key code, when shift is not pressed.
 */
static const Keycode s_scanTableNoShift[] = {
    KEY_UNKNOWN, ASCII_ESC, '1', '2',   /* 0x00 - 0x03 */
    '3', '4', '5', '6',         /* 0x04 - 0x07 */
    '7', '8', '9', '0',         /* 0x08 - 0x0B */
    '-', '=', ASCII_BS, '\t',   /* 0x0C - 0x0F */
    'q', 'w', 'e', 'r',         /* 0x10 - 0x13 */
    't', 'y', 'u', 'i',         /* 0x14 - 0x17 */
    'o', 'p', '[', ']',         /* 0x18 - 0x1B */
    '\r', KEY_LCTRL, 'a', 's',  /* 0x1C - 0x1F */
    'd', 'f', 'g', 'h',         /* 0x20 - 0x23 */
    'j', 'k', 'l', ';',         /* 0x24 - 0x27 */
    '\'', '`', KEY_LSHIFT, '\\',        /* 0x28 - 0x2B */
    'z', 'x', 'c', 'v',         /* 0x2C - 0x2F */
    'b', 'n', 'm', ',',         /* 0x30 - 0x33 */
    '.', '/', KEY_RSHIFT, KEY_PRINTSCRN,        /* 0x34 - 0x37 */
    KEY_LALT, ' ', KEY_CAPSLOCK, KEY_F1,        /* 0x38 - 0x3B */
    KEY_F2, KEY_F3, KEY_F4, KEY_F5,     /* 0x3C - 0x3F */
    KEY_F6, KEY_F7, KEY_F8, KEY_F9,     /* 0x40 - 0x43 */
    KEY_F10, KEY_NUMLOCK, KEY_SCRLOCK, KEY_KPHOME,      /* 0x44 - 0x47 */
    KEY_KPUP, KEY_KPPGUP, KEY_KPMINUS, KEY_KPLEFT,      /* 0x48 - 0x4B */
    KEY_KPCENTER, KEY_KPRIGHT, KEY_KPPLUS, KEY_KPEND,   /* 0x4C - 0x4F */
    KEY_KPDOWN, KEY_KPPGDN, KEY_KPINSERT, KEY_KPDEL,    /* 0x50 - 0x53 */
    KEY_SYSREQ, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,  /* 0x54 - 0x57 */
};

#define SCAN_TABLE_SIZE (sizeof(s_scanTableNoShift) / sizeof(Keycode))

/*
 * Translate from scan code to key code, when shift *is* pressed.
 * Keep this in sync with the unshifted table above!
 * They must be the same size.
 */
static const Keycode s_scanTableWithShift[] = {
    KEY_UNKNOWN, ASCII_ESC, '!', '@',   /* 0x00 - 0x03 */
    '#', '$', '%', '^',         /* 0x04 - 0x07 */
    '&', '*', '(', ')',         /* 0x08 - 0x0B */
    '_', '+', ASCII_BS, '\t',   /* 0x0C - 0x0F */
    'Q', 'W', 'E', 'R',         /* 0x10 - 0x13 */
    'T', 'Y', 'U', 'I',         /* 0x14 - 0x17 */
    'O', 'P', '{', '}',         /* 0x18 - 0x1B */
    '\r', KEY_LCTRL, 'A', 'S',  /* 0x1C - 0x1F */
    'D', 'F', 'G', 'H',         /* 0x20 - 0x23 */
    'J', 'K', 'L', ':',         /* 0x24 - 0x27 */
    '"', '~', KEY_LSHIFT, '|',  /* 0x28 - 0x2B */
    'Z', 'X', 'C', 'V',         /* 0x2C - 0x2F */
    'B', 'N', 'M', '<',         /* 0x30 - 0x33 */
    '>', '?', KEY_RSHIFT, KEY_PRINTSCRN,        /* 0x34 - 0x37 */
    KEY_LALT, ' ', KEY_CAPSLOCK, KEY_F1,        /* 0x38 - 0x3B */
    KEY_F2, KEY_F3, KEY_F4, KEY_F5,     /* 0x3C - 0x3F */
    KEY_F6, KEY_F7, KEY_F8, KEY_F9,     /* 0x40 - 0x43 */
    KEY_F10, KEY_NUMLOCK, KEY_SCRLOCK, KEY_KPHOME,      /* 0x44 - 0x47 */
    KEY_KPUP, KEY_KPPGUP, KEY_KPMINUS, KEY_KPLEFT,      /* 0x48 - 0x4B */
    KEY_KPCENTER, KEY_KPRIGHT, KEY_KPPLUS, KEY_KPEND,   /* 0x4C - 0x4F */
    KEY_KPDOWN, KEY_KPPGDN, KEY_KPINSERT, KEY_KPDEL,    /* 0x50 - 0x53 */
    KEY_SYSREQ, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,  /* 0x54 - 0x57 */
};

/* incomplete (nspring added because his L_CTRL is an R_CTRL) */
static const Keycode s_scanTableE0[] = {
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x00 - 0x03 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x04 - 0x07 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x08 - 0x0B */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x0C - 0x0F */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x10 - 0x13 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x14 - 0x17 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x18 - 0x1B */
    KEY_UNKNOWN, KEY_RCTRL, KEY_UNKNOWN, KEY_UNKNOWN,   /* 0x1C - 0x1F */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x20 - 0x23 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x24 - 0x27 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x28 - 0x2B */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x2C - 0x2F */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x30 - 0x33 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x34 - 0x37 */
    KEY_RALT, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,    /* 0x38 - 0x3B */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x3C - 0x3F */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x40 - 0x43 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x44 - 0x47 */
    KEY_KPUP, KEY_KPPGUP, KEY_KPMINUS, KEY_KPLEFT,      /* 0x48 - 0x4B */
    KEY_KPCENTER, KEY_KPRIGHT, KEY_KPPLUS, KEY_KPEND,   /* 0x4C - 0x4F */
    KEY_KPDOWN, KEY_KPPGDN, KEY_KPINSERT, KEY_KPDEL,    /* 0x50 - 0x53 */
    KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, /* 0x54 - 0x57 */
};

static __inline__ bool Is_Queue_Empty(void) {
    return s_queueHead == s_queueTail;
}

static __inline__ bool Is_Queue_Full(void) {
    return NEXT(s_queueTail) == s_queueHead;
}

/*
 * Called from Keyboard_Interrupt_Handler, so interrupts are already
 * disabled. Safe to use raw Spin_Lock (no need for Spin_Lock_Irq_Save).
 */
static __inline__ void Enqueue_Keycode(Keycode keycode) {
    Spin_Lock(&s_kbdQueueLock);
    if(!Is_Queue_Full()) {
        s_queue[s_queueTail] = keycode;
        s_queueTail = NEXT(s_queueTail);
    }
    Spin_Unlock(&s_kbdQueueLock);
}

static __inline__ Keycode Dequeue_Keycode(void) {
    Keycode result;
    KASSERT(!Is_Queue_Empty());
    result = s_queue[s_queueHead];
    s_queueHead = NEXT(s_queueHead);
    return result;
}

/*
 * Handler for keyboard interrupts.
 */
void Keyboard_Interrupt_Handler(struct Interrupt_State *state) {
    uchar_t status, scanCode;
    unsigned flag = 0;
    bool release = false, shift;
    Keycode keycode;

    Begin_IRQ(state);

    status = In_Byte(KB_CMD);
    IO_Delay();

    if((status & KB_OUTPUT_FULL) != 0) {
        /* There is a byte available */
        scanCode = In_Byte(KB_DATA);
        IO_Delay();

        DEBUG_KEYBOARD("code=%x%s\n", scanCode,
                       (scanCode & 0x80) ? " [release]" : "");

        shift = ((s_shiftState & SHIFT_MASK) != 0);

        static const Keycode *scantable_using_current_modifiers;
        scantable_using_current_modifiers =
            shift ? s_scanTableWithShift : s_scanTableNoShift;
        if(scanCode == 0xe0) {
            /* e0 is a prefix for keypad arrows and right alt/ctrl. */
            scanCode = In_Byte(KB_DATA);
            IO_Delay();
            scantable_using_current_modifiers = s_scanTableE0;
            DEBUG_KEYBOARD("suffixcode=%x%s\n", scanCode,
                           (scanCode & 0x80) ? " [release]" : "");
        }

        if(scanCode & KB_KEY_RELEASE) {
            release = true;
            scanCode &= ~(KB_KEY_RELEASE);
        }

        if(scanCode >= SCAN_TABLE_SIZE) {
            Print("Unknown scan code: 0x%x\n", scanCode);
            goto done;
        }

        keycode = scantable_using_current_modifiers[scanCode];

        /* Update shift, control and alt state */
        switch (keycode) {
            case KEY_LSHIFT:
                flag = LEFT_SHIFT;
                break;
            case KEY_RSHIFT:
                flag = RIGHT_SHIFT;
                break;
            case KEY_LCTRL:
                flag = LEFT_CTRL;
                break;
            case KEY_RCTRL:
                flag = RIGHT_CTRL;
                break;
            case KEY_LALT:
                flag = LEFT_ALT;
                break;
            case KEY_RALT:
                flag = RIGHT_ALT;
                break;
            default:
                goto noflagchange;
        }

        if(release)
            s_shiftState &= ~(flag);
        else
            s_shiftState |= flag;

        /*
         * Shift, control and alt keys don't have to be
         * queued, flags will be set!
         */
        goto done;

      noflagchange:
        /* Format the new keycode */
        if(shift)
            keycode |= KEY_SHIFT_FLAG;
        if((s_shiftState & CTRL_MASK) != 0)
            keycode |= KEY_CTRL_FLAG;
        if((s_shiftState & ALT_MASK) != 0)
            keycode |= KEY_ALT_FLAG;
        if(release)
            keycode |= KEY_RELEASE_FLAG;

        /* Put the keycode in the buffer */
        Enqueue_Keycode(keycode);

        /* Wake up event consumers */
        Wake_Up(&s_keyboardWaitQueue);

        /*
         * Pick a new thread upon return from interrupt
         * (hopefully the one waiting for the keyboard event)
         */
        g_needReschedule[Get_CPU_ID()] = true;
    }

  done:
    End_IRQ(state);
}

/* ----------------------------------------------------------------------
 * Public functions
 * ---------------------------------------------------------------------- */

void Init_Keyboard(void) {
    ushort_t irqMask;

    Print("Initializing keyboard...\n");

    /* Start out with no shift keys enabled. */
    s_shiftState = 0;

    /* Buffer is initially empty. */
    s_queueHead = s_queueTail = 0;

    /* Install interrupt handler */
    Install_IRQ(KB_IRQ, Keyboard_Interrupt_Handler);

    /* Enable IRQ1 (keyboard) */
    irqMask = Get_IRQ_Mask();
    irqMask &= ~(1 << KB_IRQ);
    Set_IRQ_Mask(irqMask);

}

/*
 * Poll for a key event.
 * Returns true if a key is available,
 * false if not.  If a key event is available,
 * it will be stored in the location pointed to
 * by keycode.
 */
bool Read_Key(Keycode * keycode) {
    bool result, iflag;

    /* Must disable interrupts before acquiring lock to prevent deadlock
     * with keyboard interrupt handler which also acquires this lock. */
    iflag = Spin_Lock_Irq_Save(&s_kbdQueueLock);
    result = !Is_Queue_Empty();
    if(result) {
        *keycode = Dequeue_Keycode();
    }
    Spin_Unlock_Irq_Restore(&s_kbdQueueLock, iflag);

    return result;
}

Keycode Get_Test_Input() {
    Keycode ret;
    ret = In_Byte(0x510);
    if(!ret)
        return KEY_UNKNOWN;

    return ret;
}

/*
 * Wait for a keycode to arrive.
 * Uses the keyboard wait queue to sleep until
 * a keycode arrives.
 */
Keycode Wait_For_Key(void) {
    bool gotKey, iflag;
    Keycode keycode = KEY_UNKNOWN;

    // see if we still have input from the qemu command line to hand off
    keycode = Get_Test_Input();
    if(keycode != KEY_UNKNOWN) {
        return keycode;
    }

    iflag = Spin_Lock_Irq_Save(&s_kbdQueueLock);
    do {
        gotKey = !Is_Queue_Empty();
        if(gotKey)
            keycode = Dequeue_Keycode();
        else {
            Spin_Unlock(&s_kbdQueueLock);
            Wait(&s_keyboardWaitQueue);
            Spin_Lock(&s_kbdQueueLock);
        }
    }
    while (!gotKey);
    Spin_Unlock_Irq_Restore(&s_kbdQueueLock, iflag);

    return keycode;
}
