/*
** File: keyboard.c
** Description: ReactOS keyboard driver
** Current version: 0.0.4
** Last modified: 28 May 1998
**
 * 31/08/98: Changed to interrupt driven model
 * 29/06/98: NT interface by David Welch (welch@mcmail.com)
 * 
** Written by Victor Kirhenshtein (sauros@iname.com)
** Initial attempt by Jason Filby
**
** Some parts of this code are based on Linux keyboard driver
**    written by Johan Myreen.
**
**
** KNOWN BUGS:
**
**   * Repeat counter isn't supported
**
*/

#include <ddk/ntddk.h>
#include <internal/mmhal.h>
#include <internal/halio.h>
#include <string.h>
#include <internal/string.h>
#include <defines.h>

#define NDEBUG
#include <internal/debug.h>

#include "keyboard.h"

/*
 * Driver data
 */

static KEY_EVENT_RECORD kbdBuffer[KBD_BUFFER_SIZE];
static int bufHead,bufTail;
static int keysInBuffer;
static int extKey;
static BYTE ledStatus;
static BYTE capsDown,numDown,scrollDown;
static DWORD ctrlKeyState;
static KSPIN_LOCK kbdBufferLock;
static PKINTERRUPT KbdInterrupt;
static KDPC KbdDpc;

/*
 * PURPOSE: Current irp being processed
 */
static PIRP CurrentIrp;

/*
 * PURPOSE: Number of keys that have been read into the current irp's buffer
 */
static ULONG KeysRead;
static ULONG KeysRequired;

/*
 * Virtual key codes table
 *
 * Comments:
 *   * PrtSc = VK_PRINT
 *   * Alt+PrtSc (SysRq) = VK_EXECUTE
 *  * Alt = VK_MENU
 */

static const WORD vkTable[128]=
{
   /* 00 - 07 */ 0, VK_ESCAPE, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6,
   /* 08 - 0F */ VK_7, VK_8, VK_9, VK_0, 189, 187, VK_BACK, VK_TAB,
   /* 10 - 17 */ VK_Q, VK_W, VK_E, VK_R, VK_T, VK_Y, VK_U, VK_I,
   /* 18 - 1F */ VK_O, VK_P, 219, 221, VK_RETURN, VK_CONTROL, VK_A, VK_S,
   /* 20 - 27 */ VK_D, VK_F, VK_G, VK_H, VK_J, VK_K, VK_L, 186,
   /* 28 - 2F */ 222, 192, VK_SHIFT, 220, VK_Z, VK_X, VK_C, VK_V,
   /* 30 - 37 */ VK_B, VK_N, VK_M, 188, 190, 191, VK_SHIFT, VK_MULTIPLY,
   /* 38 - 3F */ VK_MENU, VK_SPACE, VK_CAPITAL, VK_F1, VK_F2, VK_F3, VK_F4, VK_F5,
   /* 40 - 47 */ VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, VK_NUMLOCK, VK_SCROLL, VK_HOME,
   /* 48 - 4F */ VK_UP, VK_PRIOR, VK_SUBTRACT, VK_LEFT, VK_CLEAR, VK_RIGHT, VK_ADD, VK_END,
   /* 50 - 57 */ VK_DOWN, VK_NEXT, VK_INSERT, VK_DELETE, VK_EXECUTE, 0, 0, VK_F11,
   /* 58 - 5F */ VK_F12, 0, 0, 91, 92, 93, 0, 0,
   /* 60 - 67 */ 0, 0, 0, 0, 0, 0, 0, 0,
   /* 68 - 6F */ 0, 0, 0, 0, 0, 0, 0, 0,
   /* 70 - 77 */ 0, 0, 0, 0, 0, 0, 0, 0,
   /* 78 - 7F */ 0, 0, 0, 0, 0, 0, 0, VK_PAUSE
};
static const WORD vkKeypadTable[13]=	/* 47 - 53 */
{
   VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_SUBTRACT,
   VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_ADD,
   VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD0, VK_DECIMAL
};


/*
 * ASCII translation tables
 */

static const BYTE asciiTable1[10]=
{
   ')','!','@','#','$','%','^','&','*','('
};
static const BYTE asciiTable2[16]=
{
   '0','1','2','3','4','5','6','7','8','9','*','+',0,'-','.','/'
};
static const BYTE asciiTable3[37]=
{
   ';','=',',','-','.','/','`', 0, 0, 0, 0, 0, 0, 0, 0,
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
   '[', '\\', ']', '\''
};
static const BYTE asciiTable4[37]=
{
   ':','+','<','_','>','?','~', 0, 0, 0, 0, 0, 0, 0, 0,
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
   '{', '|', '}', '"'
};


/*
 * Write data to keyboard
 */

static void KbdWrite(int addr,BYTE data)
{
   BYTE status;

   do
   {
      status=inb_p(KBD_CTRL_PORT);     // Wait until input buffer empty
   } while(status & KBD_IBF);
   outb_p(addr,data);
}


/*
 * Read data from port 0x60
 */

static int KbdReadData(void)
{
   int i;
   BYTE status,data;

   i=500000;
   do
   {
      status=inb_p(KBD_CTRL_PORT);
      if (!(status & KBD_OBF))      // Check if data available
         continue;
      data=inb_p(KBD_DATA_PORT);
      if (status & (KBD_GTO | KBD_PERR))  // Check for timeout error
         continue;
      return data;
   } while(--i);
   return -1;     // Timed out
}


/*
 * Set keyboard LED's
 */

static void SetKeyboardLEDs(BYTE status)
{
   KbdWrite(KBD_DATA_PORT,0xED);
   if (KbdReadData()!=KBD_ACK)      // Error
      return;
   KbdWrite(KBD_DATA_PORT,status);
   KbdReadData();
}


/*
 * Process scan code
 */

static void ProcessScanCode(BYTE scanCode,BOOL isDown)
{
   switch(scanCode)
   {
      case 0x1D:  // Ctrl
         if (extKey)
         {
            if (isDown)
               ctrlKeyState|=RIGHT_CTRL_PRESSED;
            else
               ctrlKeyState&=~RIGHT_CTRL_PRESSED;
         }
         else
         {
            if (isDown)
               ctrlKeyState|=LEFT_CTRL_PRESSED;
            else
               ctrlKeyState&=~LEFT_CTRL_PRESSED;
         }
         break;
      case 0x2A:  // Left shift
      case 0x36:  // Right shift
         if (isDown)
            ctrlKeyState|=SHIFT_PRESSED;
         else
            ctrlKeyState&=~SHIFT_PRESSED;
         break;
      case 0x38:  // Alt
         if (extKey)
         {
            if (isDown)
               ctrlKeyState|=RIGHT_ALT_PRESSED;
            else
               ctrlKeyState&=~RIGHT_ALT_PRESSED;
         }
         else
         {
            if (isDown)
               ctrlKeyState|=LEFT_ALT_PRESSED;
            else
               ctrlKeyState&=~LEFT_ALT_PRESSED;
         }
         break;
      case 0x3A:  // CapsLock
         if (ctrlKeyState & CTRL_PRESSED)
            break;
         if (isDown)
         {
            if (!capsDown)
            {
               capsDown=1;
               if (ctrlKeyState & CAPSLOCK_ON)
               {
                  ledStatus&=~KBD_LED_CAPS;
                  ctrlKeyState&=~CAPSLOCK_ON;
               }
               else
               {
                  ledStatus|=KBD_LED_CAPS;
                  ctrlKeyState|=CAPSLOCK_ON;
               }
               SetKeyboardLEDs(ledStatus);
            }
         }
         else
         {
            capsDown=0;
         }
         break;
      case 0x45:  // NumLock
         if (ctrlKeyState & CTRL_PRESSED)
            break;
         if (isDown)
         {
            if (!numDown)
            {
               numDown=1;
               if (ctrlKeyState & NUMLOCK_ON)
               {
                  ledStatus&=~KBD_LED_NUM;
                  ctrlKeyState&=~NUMLOCK_ON;
               }
               else
               {
                  ledStatus|=KBD_LED_NUM;
                  ctrlKeyState|=NUMLOCK_ON;
               }
               SetKeyboardLEDs(ledStatus);
            }
         }
         else
         {
            numDown=0;
         }
         break;
      case 0x46:  // ScrollLock
         if (ctrlKeyState & CTRL_PRESSED)
            break;
         if (isDown)
         {
            if (!scrollDown)
            {
               scrollDown=1;
               if (ctrlKeyState & SCROLLLOCK_ON)
               {
                  ledStatus&=~KBD_LED_SCROLL;
                  ctrlKeyState&=~SCROLLLOCK_ON;
               }
               else
               {
                  ledStatus|=KBD_LED_SCROLL;
                  ctrlKeyState|=SCROLLLOCK_ON;
               }
               SetKeyboardLEDs(ledStatus);
            }
         }
         else
         {
            scrollDown=0;
         }
         break;
      default:
         break;
   }
}


/*
 * Translate virtual key code to ASCII
 */

static BYTE VirtualToAscii(WORD keyCode,BOOL isDown)
{
   if ((ctrlKeyState & ALT_PRESSED)&&(ctrlKeyState & CTRL_PRESSED))
      return 0;		// Ctrl+Alt+char always 0
   if ((!isDown)&&(ctrlKeyState & ALT_PRESSED))
      return 0;		// Alt+char is 0 when key is released

   if (ctrlKeyState & CTRL_PRESSED)
   {
      if ((keyCode>=VK_A)&&(keyCode<=VK_Z))
         return keyCode-VK_A+1;
      switch(keyCode)
      {
         case VK_SPACE:
            return ' ';
         case VK_BACK:
            return 127;
         case VK_RETURN:
            return 10;                     
         case 219:                      /* [ */
            if (ctrlKeyState & SHIFT_PRESSED)
               return 0;
            return 27;
         case 220:         /* \ */
            if (ctrlKeyState & SHIFT_PRESSED)
               return 0;
            return 28;
         case 221:         /* ] */
            if (ctrlKeyState & SHIFT_PRESSED)
               return 0;
             return 29;
         default:
            return 0;
      }
   }

   if ((keyCode>=VK_A)&&(keyCode<=VK_Z))
   {
      if (ctrlKeyState & CAPSLOCK_ON)
         if (ctrlKeyState & SHIFT_PRESSED)
            return keyCode-VK_A+'a';
         else
            return keyCode-VK_A+'A';
      else
         if (ctrlKeyState & SHIFT_PRESSED)
            return keyCode-VK_A+'A';
         else
            return keyCode-VK_A+'a';
   }

   if ((keyCode>=VK_0)&&(keyCode<=VK_9))
   {
      if (ctrlKeyState & SHIFT_PRESSED)
         return asciiTable1[keyCode-VK_0];
      else
         return keyCode-VK_0+'0';
   }

   if ((keyCode>=VK_NUMPAD0)&&(keyCode<=VK_DIVIDE))
      return asciiTable2[keyCode-VK_NUMPAD0];

   if ((keyCode>=186)&&(keyCode<=222))
   {
      if (ctrlKeyState & SHIFT_PRESSED)
         return asciiTable4[keyCode-186];
      else
         return asciiTable3[keyCode-186];
   }

   switch(keyCode)
   {
      case VK_SPACE:
         return ' ';
      case VK_RETURN:
         return '\n';
      case VK_BACK:
         return 8;
      case VK_TAB:
         return 9;
   }
   return 0;
}


/*
 * Translate scan code to virtual key code
 */

static WORD ScanToVirtual(BYTE scanCode)
{
   if ((scanCode>=0x47)&&(scanCode<=0x53)&&(ctrlKeyState & NUMLOCK_ON)&&
       (!extKey)&&(!(ctrlKeyState & SHIFT_PRESSED)))
      return vkKeypadTable[scanCode-0x47];
   if ((scanCode==0x35)&&(extKey))		// Gray divide
      return VK_DIVIDE;
   if ((scanCode==0x37)&&(extKey))     // Print screen
      return VK_PRINT;
   return vkTable[scanCode];
}


/*
 * Keyboard IRQ handler
 */

static VOID KbdDpcRoutine(PKDPC Dpc,
			  PVOID DeferredContext,
			  PVOID SystemArgument1,
			  PVOID SystemArgument2)
{
   PIRP Irp = (PIRP)SystemArgument2;
   PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)SystemArgument1;
   
   CHECKPOINT;
   DPRINT("KbdDpcRoutine(DeviceObject %x, Irp %x)\n",
	    DeviceObject,Irp);
   Irp->IoStatus.Status = STATUS_SUCCESS;
   Irp->IoStatus.Information = 0;
   IoCompleteRequest(Irp,IO_NO_INCREMENT);
   IoStartNextPacket(DeviceObject,FALSE);
}

static BOOLEAN KeyboardHandler(PKINTERRUPT Interrupt, PVOID Context)
{
   BYTE thisKey;
   BOOL isDown;
   static BYTE lastKey;

   CHECKPOINT;

   // Read scan code
   thisKey=inb_p(KBD_DATA_PORT);
   if ((thisKey==0xE0)||(thisKey==0xE1))   // Extended key
   {
      extKey=1;         // Wait for next byte
      lastKey=thisKey;
      return FALSE;
   }

   isDown=!(thisKey & 0x80);
   thisKey&=0x7F;

   // The keyboard maintains its own internal caps lock and num lock
   // statuses.  In caps lock mode E0 AA precedes make code and
   // E0 2A follow break code.  In num lock mode, E0 2A precedes
   // make code and E0 AA follow break code.  We maintain our own caps lock
   // and num lock statuses, so we will just ignore these.
   // Some keyboards have L-Shift/R-Shift modes instead of caps lock
   // mode.  If right shift pressed, E0 B6 / E0 36 pairs generated.
   if (extKey & ((thisKey==0x2A)||(thisKey==0x36)))
   {
      extKey=0;
      return FALSE;
   }

   // Check for PAUSE sequence
   if (extKey && (lastKey==0xE1))
   {
      if (thisKey==0x1D)
         lastKey=0xFF;     // Sequence is OK
      else
         extKey=0;
      return FALSE;
   }
   if (extKey && (lastKey==0xFF))
   {
      if (thisKey!=0x45)
      {
         extKey=0;         // Bad sequence
         return FALSE;
      }
      thisKey=0x7F;        // Pseudo-code for PAUSE
   }

   ProcessScanCode(thisKey,isDown);

//   DbgPrint("Key: %c\n",VirtualToAscii(ScanToVirtual(thisKey),isDown));
//   DbgPrint("Key: %x\n",ScanToVirtual(thisKey));
   if (ScanToVirtual(thisKey)==0x2a)
     {
	KeBugCheck(0);
     }

   if (CurrentIrp!=NULL)
     {
	KEY_EVENT_RECORD* rec = (KEY_EVENT_RECORD *)
	  CurrentIrp->AssociatedIrp.SystemBuffer;
	PIO_STACK_LOCATION stk = IoGetCurrentIrpStackLocation(CurrentIrp);
	
	CHECKPOINT;
	
	rec[KeysRead].bKeyDown=isDown;
	rec[KeysRead].wRepeatCount=1;
	rec[KeysRead].wVirtualKeyCode=ScanToVirtual(thisKey);
	rec[KeysRead].wVirtualScanCode=thisKey;
        rec[KeysRead].uChar.AsciiChar=VirtualToAscii(rec->wVirtualKeyCode,isDown);
	rec[KeysRead].dwControlKeyState=ctrlKeyState;
	if (extKey)
	  {
	     rec[KeysRead].dwControlKeyState|=ENHANCED_KEY;
	  }
	KeysRead++;
	DPRINT("KeysRequired %d KeysRead %x\n",KeysRequired,KeysRead);
	if (KeysRead==KeysRequired)
	  {
	     KeInsertQueueDpc(&KbdDpc,stk->DeviceObject,CurrentIrp);
	     CurrentIrp=NULL;
	  }
	CHECKPOINT;
	return TRUE;
     }
   
   // Buffer is full ?
   if (keysInBuffer==KBD_BUFFER_SIZE)      // Buffer is full
   {
      extKey=0;
      return FALSE;
   }
   kbdBuffer[bufHead].bKeyDown=isDown;
   kbdBuffer[bufHead].wRepeatCount=1;
   kbdBuffer[bufHead].wVirtualKeyCode=ScanToVirtual(thisKey);
   kbdBuffer[bufHead].wVirtualScanCode=thisKey;
   kbdBuffer[bufHead].uChar.UnicodeChar=0;
   // kbdBuffer[bufHead].uChar.AsciiChar=TranslateScanCode(thisKey);
   kbdBuffer[bufHead].uChar.AsciiChar=VirtualToAscii(kbdBuffer[bufHead].wVirtualKeyCode,isDown);
   kbdBuffer[bufHead].dwControlKeyState=ctrlKeyState;
   if (extKey)
      kbdBuffer[bufHead].dwControlKeyState|=ENHANCED_KEY;
   bufHead++;
   bufHead&=KBD_WRAP_MASK;    // Modulo KBD_BUFFER_SIZE
   keysInBuffer++;
   extKey=0;

   return TRUE;
}


//
// Initialize keyboard
//
static void KeyboardConnectInterrupt(void)
{
   ULONG MappedIrq;
   KIRQL Dirql;
   KAFFINITY Affinity;
   NTSTATUS Status;
   
   MappedIrq = HalGetInterruptVector(Internal,
				     0,
				     0,
				     KEYBOARD_IRQ,
				     &Dirql,
				     &Affinity);
   Status = IoConnectInterrupt(&KbdInterrupt,
			       KeyboardHandler,
			       NULL,
			       NULL,
			       MappedIrq,
			       Dirql,
			       Dirql,
			       0,
			       FALSE,
			       Affinity,
			       FALSE);
}

static int InitializeKeyboard(void)
{
   // Initialize variables
   bufHead=0;
   bufTail=0;
   keysInBuffer=0;
   ledStatus=0;
   capsDown=0;
   numDown=0;
   scrollDown=0;
   ctrlKeyState=0;
   extKey=0;

   KeyboardConnectInterrupt();
   KeInitializeDpc(&KbdDpc,KbdDpcRoutine,NULL);
   return 0;
}


/*
 * Read data from keyboard buffer
 */

BOOLEAN KbdSynchronizeRoutine(PVOID Context)
{
   PIRP Irp = (PIRP)Context;
   KEY_EVENT_RECORD* rec = (KEY_EVENT_RECORD *)
     Irp->AssociatedIrp.SystemBuffer;
   PIO_STACK_LOCATION stk = IoGetCurrentIrpStackLocation(Irp);
   ULONG NrToRead = stk->Parameters.Read.Length/sizeof(KEY_EVENT_RECORD);
   int i;

   DPRINT("NrToRead %d keysInBuffer %d\n",NrToRead,keysInBuffer);
   NrToRead = min(NrToRead,keysInBuffer);

   DPRINT("NrToRead %d stk->Parameters.Read.Length %d\n",
	  NrToRead,stk->Parameters.Read.Length);
   DPRINT("sizeof(KEY_EVENT_RECORD) %d\n",sizeof(KEY_EVENT_RECORD));
   for (i=0;i<NrToRead;i++)
     {
	memcpy(&rec[i],&kbdBuffer[bufTail],sizeof(KEY_EVENT_RECORD));
	bufTail++;
	bufTail&=KBD_WRAP_MASK;
	keysInBuffer--;
    }
   if ((stk->Parameters.Read.Length/sizeof(KEY_EVENT_RECORD))==NrToRead)
     {
	return(TRUE);
     }

   KeysRequired=stk->Parameters.Read.Length/sizeof(KEY_EVENT_RECORD);
   KeysRead=NrToRead;
   CurrentIrp=Irp;

   return(FALSE);
}

VOID KbdStartIo(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
   PIO_STACK_LOCATION stk = IoGetCurrentIrpStackLocation(Irp);

   DPRINT("KeyboardStartIo(DeviceObject %x Irp %x)\n",DeviceObject,Irp);

   DPRINT("stk->Parameters.Read.Length %d\n",stk->Parameters.Read.Length);
   DPRINT("KeysRequired %d\n",KeysRequired);     
}

NTSTATUS KbdDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
   PIO_STACK_LOCATION stk = IoGetCurrentIrpStackLocation(Irp);
   NTSTATUS Status;

   DPRINT("DeviceObject %x\n",DeviceObject);
   DPRINT("Irp %x\n",Irp);

   switch (stk->MajorFunction)
     {
      case IRP_MJ_CREATE:
      case IRP_MJ_CLOSE:
        Status = STATUS_SUCCESS;
	break;

      case IRP_MJ_READ:
        DPRINT("Handling Read request\n");
        if (KeSynchronizeExecution(KbdInterrupt,KbdSynchronizeRoutine,Irp))
           {
                Status = STATUS_SUCCESS;
           }
        else
           {
	      DPRINT("Queueing packet\n");
	      IoStartPacket(DeviceObject,Irp,NULL,NULL);
	      Status = STATUS_PENDING;
           }
	break;

      default:
        Status = STATUS_NOT_IMPLEMENTED;
	break;
     }

   if (Status==STATUS_PENDING)
     {
	DPRINT("Marking irp pending\n");
	IoMarkIrpPending(Irp);
     }
   else
     {
        Irp->IoStatus.Status = Status;
	Irp->IoStatus.Information = 0;
	IoCompleteRequest(Irp,IO_NO_INCREMENT);
     }
   DPRINT("Status %d\n",Status);
   return(Status);
}

/*
 * Module entry point
 */
STDCALL NTSTATUS 
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
   PDEVICE_OBJECT DeviceObject;
   ANSI_STRING adevice_name;
   UNICODE_STRING device_name;
   ANSI_STRING asymlink_name;
   UNICODE_STRING symlink_name;

   DbgPrint("Keyboard Driver 0.0.4\n");
   InitializeKeyboard();

   DriverObject->MajorFunction[IRP_MJ_CREATE] = KbdDispatch;
   DriverObject->MajorFunction[IRP_MJ_CLOSE] = KbdDispatch;
   DriverObject->MajorFunction[IRP_MJ_READ] = KbdDispatch;
   DriverObject->DriverStartIo = KbdStartIo;

   RtlInitAnsiString(&adevice_name,"\\Device\\Keyboard");
   RtlAnsiStringToUnicodeString(&device_name,&adevice_name,TRUE);
   IoCreateDevice(DriverObject,0,&device_name,FILE_DEVICE_KEYBOARD,0,
		  TRUE,&DeviceObject);
   DeviceObject->Flags = DO_BUFFERED_IO;

   RtlInitAnsiString(&asymlink_name,"\\??\\Keyboard");
   RtlAnsiStringToUnicodeString(&symlink_name,&asymlink_name,TRUE);
   IoCreateSymbolicLink(&symlink_name,&device_name);

   return(STATUS_SUCCESS);
}
