/*++ Copyright (c) 2012 Minoca Corp. All Rights Reserved Module Name: i8042.c Abstract: This module implements the Intel 8042 keyboard/mouse controller driver. Author: Evan Green 20-Dec-2012 Environment: Kernel --*/ // // ------------------------------------------------------------------- Includes // #include #include "i8042.h" // // --------------------------------------------------------------------- Macros // // // These macros read from and write to the control, status, and data registers. // #define WRITE_CONTROL_REGISTER(_Device, _Value) \ HlIoPortOutByte(_Device->ControlPort, _Value) #define READ_STATUS_REGISTER(_Device) HlIoPortInByte(_Device->ControlPort) #define WRITE_DATA_REGISTER(_Device, _Value) \ HlIoPortOutByte(_Device->DataPort, _Value) #define READ_DATA_REGISTER(_Device) HlIoPortInByte(_Device->DataPort) // // This macro spins waiting for the last keyboard command to finish. // #define WAIT_FOR_INPUT_BUFFER(_Device) \ while ((READ_STATUS_REGISTER(_Device) & \ I8042_STATUS_INPUT_BUFFER_FULL) != 0) { \ \ NOTHING; \ } // // This macro determines if data is available to be received from the device. // #define IS_DATA_AVAILABLE(_Device) \ ((READ_STATUS_REGISTER(_Device) & I8042_STATUS_OUTPUT_BUFFER_FULL) != 0) // // ---------------------------------------------------------------- Definitions // // // Define the size of the device keyboard buffer size. // #define I8042_BUFFER_SIZE 256 // // Define the bits in the 8042 status register. // #define I8042_STATUS_OUTPUT_BUFFER_FULL 0x01 #define I8042_STATUS_INPUT_BUFFER_FULL 0x02 #define I8042_STATUS_SELF_TEST_COMPLETE 0x04 #define I8042_STATUS_LAST_WRITE_COMMAND 0x08 #define I8042_STATUS_KEYBOARD_UNLOCK 0x10 #define I8042_STATUS_DATA_FROM_MOUSE 0x20 #define I8042_STATUS_TIMEOUT 0x40 #define I8042_STATUS_PARITY_ERROR 0x80 // // Define bits in the 8042 command byte register. // #define I8042_COMMAND_BYTE_KEYBOARD_INTERRUPT_ENABLED 0x01 #define I8042_COMMAND_BYTE_MOUSE_INTERRUPT_ENABLED 0x02 #define I8042_COMMAND_BYTE_SYSTEM_FLAG 0x04 #define I8042_COMMAND_BYTE_PCAT_INHIBIT 0x08 #define I8042_COMMAND_BYTE_KEYBOARD_DISABLED 0x10 #define I8042_COMMAND_BYTE_MOUSE_DISABLED 0x20 #define I8042_COMMAND_BYTE_TRANSLATION_ENABLED 0x40 // // Define the known device identifiers that this driver responds to. // #define KEYBOARD_HARDWARE_IDENTIFIER "PNP0303" #define MOUSE_HARDWARE_IDENTIFIER "PNP0F13" // // Define the allocation tag used by this driver. // #define I8042_ALLOCATION_TAG 0x32343869 // '248i' // // Define the amount of time to allow the keyboard to reset, in microseconds. // #define I8042_RESET_DELAY 10000 // // ------------------------------------------------------ Data Type Definitions // /*++ Structure Description: This structure stores context about a device driven by the i8042 driver. Members: IsMouse - Stores a boolean indicating whether the device is a mouse (TRUE) or a keyboard (FALSE). ControlPort - Stores the I/O port number of the 8042 control port. DataPort - Stores the I/O port number of the 8042 data port. InterruptVector - Stores the interrupt vector that this interrupt comes in on. InterruptLine - Stores the interrupt line that the interrupt comes in on. InterruptResourcesFound - Stores a boolean indicating whether or not the interrupt vector and interrupt line fields are valid. InterruptHandle - Stores the handle for the connected interrupt. UserInputDeviceHandle - Stores the handle returned by the User Input library. InterruptLock - Stores a spinlock synchronizing access to the device with the interrupt service routine. ReadLock - Stores a pointer to a queued lock that serializes read access to the data buffer. ReadIndex - Stores the index of the next byte to read out of the data buffer. WriteIndex - Stores the index of the next byte to write to the data buffer. DataBuffer - Stores the buffer of keys coming out of the controller. --*/ typedef struct _I8042_DEVICE { BOOL IsMouse; USHORT ControlPort; USHORT DataPort; ULONGLONG InterruptVector; ULONGLONG InterruptLine; BOOL InterruptResourcesFound; HANDLE InterruptHandle; HANDLE UserInputDeviceHandle; KSPIN_LOCK InterruptLock; PQUEUED_LOCK ReadLock; volatile ULONG ReadIndex; volatile ULONG WriteIndex; volatile UCHAR DataBuffer[I8042_BUFFER_SIZE]; } I8042_DEVICE, *PI8042_DEVICE; // // ----------------------------------------------- Internal Function Prototypes // KSTATUS I8042AddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ); VOID I8042DispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID I8042DispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID I8042DispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID I8042DispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID I8042DispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); INTERRUPT_STATUS I8042InterruptService ( PVOID Context ); INTERRUPT_STATUS I8042InterruptServiceWorker ( PVOID Parameter ); KSTATUS I8042pProcessResourceRequirements ( PIRP Irp, PI8042_DEVICE Device ); KSTATUS I8042pStartDevice ( PIRP Irp, PI8042_DEVICE Device ); KSTATUS I8042pEnableDevice ( PVOID OsDevice, PI8042_DEVICE Device ); KSTATUS I8042pSetLedState ( PVOID Device, PVOID DeviceContext, ULONG LedState ); UCHAR I8042pReadCommandByte ( PI8042_DEVICE Device ); VOID I8042pWriteCommandByte ( PI8042_DEVICE Device, UCHAR Value ); KSTATUS I8042pSendKeyboardCommand ( PI8042_DEVICE Device, UCHAR Command, UCHAR Parameter ); KSTATUS I8042pSendCommand ( PI8042_DEVICE Device, UCHAR Command ); KSTATUS I8042pReceiveResponse ( PI8042_DEVICE Device, PUCHAR Data ); // // -------------------------------------------------------------------- Globals // PDRIVER I8042Driver = NULL; // // ------------------------------------------------------------------ Functions // KSTATUS DriverEntry ( PDRIVER Driver ) /*++ Routine Description: This routine is the entry point for the i8042 driver. It registers its other dispatch functions, and performs driver-wide initialization. Arguments: Driver - Supplies a pointer to the driver object. Return Value: STATUS_SUCCESS on success. Failure code on error. --*/ { DRIVER_FUNCTION_TABLE FunctionTable; KSTATUS Status; I8042Driver = Driver; RtlZeroMemory(&FunctionTable, sizeof(DRIVER_FUNCTION_TABLE)); FunctionTable.Version = DRIVER_FUNCTION_TABLE_VERSION; FunctionTable.AddDevice = I8042AddDevice; FunctionTable.DispatchStateChange = I8042DispatchStateChange; FunctionTable.DispatchOpen = I8042DispatchOpen; FunctionTable.DispatchClose = I8042DispatchClose; FunctionTable.DispatchIo = I8042DispatchIo; FunctionTable.DispatchSystemControl = I8042DispatchSystemControl; Status = IoRegisterDriverFunctions(Driver, &FunctionTable); return Status; } // // --------------------------------------------------------- Internal Functions // KSTATUS I8042AddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ) /*++ Routine Description: This routine is called when a device is detected for which the i8042 driver acts as the function driver. The driver will attach itself to the stack. Arguments: Driver - Supplies a pointer to the driver being called. DeviceId - Supplies a pointer to a string with the device ID. ClassId - Supplies a pointer to a string containing the device's class ID. CompatibleIds - Supplies a pointer to a string containing device IDs that would be compatible with this device. DeviceToken - Supplies an opaque token that the driver can use to identify the device in the system. This token should be used when attaching to the stack. Return Value: STATUS_SUCCESS on success. Failure code if the driver was unsuccessful in attaching itself. --*/ { BOOL DeviceIdsAreEqual; BOOL DeviceIsMouse; BOOL MatchesCompatibleId; BOOL MatchFound; PI8042_DEVICE NewDevice; KSTATUS Status; DeviceIsMouse = FALSE; MatchFound = FALSE; DeviceIdsAreEqual = IoAreDeviceIdsEqual(DeviceId, KEYBOARD_HARDWARE_IDENTIFIER); MatchesCompatibleId = IoIsDeviceIdInCompatibleIdList(KEYBOARD_HARDWARE_IDENTIFIER, DeviceToken); if ((DeviceIdsAreEqual != FALSE) || (MatchesCompatibleId != FALSE)) { MatchFound = TRUE; } else { // // Attempt to match against the mouse ID. // DeviceIdsAreEqual = IoAreDeviceIdsEqual(DeviceId, MOUSE_HARDWARE_IDENTIFIER); MatchesCompatibleId = IoIsDeviceIdInCompatibleIdList(MOUSE_HARDWARE_IDENTIFIER, DeviceToken); if ((DeviceIdsAreEqual != FALSE) || (MatchesCompatibleId != FALSE)) { MatchFound = TRUE; DeviceIsMouse = TRUE; } } // // If there is no match, return now. // if (MatchFound == FALSE) { return STATUS_SUCCESS; } // // there is a match, create the device context and attach to the device. // NewDevice = MmAllocateNonPagedPool(sizeof(I8042_DEVICE), I8042_ALLOCATION_TAG); if (NewDevice == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(NewDevice, sizeof(I8042_DEVICE)); KeInitializeSpinLock(&(NewDevice->InterruptLock)); NewDevice->InterruptHandle = INVALID_HANDLE; NewDevice->UserInputDeviceHandle = INVALID_HANDLE; NewDevice->IsMouse = DeviceIsMouse; NewDevice->ReadLock = KeCreateQueuedLock(); if (NewDevice->ReadLock == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto AddDeviceEnd; } Status = IoAttachDriverToDevice(Driver, DeviceToken, NewDevice); if (!KSUCCESS(Status)) { goto AddDeviceEnd; } AddDeviceEnd: if (!KSUCCESS(Status)) { if (NewDevice != NULL) { if (NewDevice->ReadLock != NULL) { KeDestroyQueuedLock(NewDevice->ReadLock); } MmFreeNonPagedPool(NewDevice); } } return Status; } VOID I8042DispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles State Change IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { PI8042_DEVICE Device; KSTATUS Status; ASSERT(Irp->MajorCode == IrpMajorStateChange); Device = (PI8042_DEVICE)DeviceContext; switch (Irp->MinorCode) { case IrpMinorQueryResources: // // On the way up, filter the resource requirements to add interrupt // vectors to any lines. // if (Irp->Direction == IrpUp) { Status = I8042pProcessResourceRequirements(Irp, Device); if (!KSUCCESS(Status)) { IoCompleteIrp(I8042Driver, Irp, Status); } } break; case IrpMinorStartDevice: // // Attempt to fire the thing up if the bus has already started it. // if (Irp->Direction == IrpUp) { Status = I8042pStartDevice(Irp, Device); if (!KSUCCESS(Status)) { IoCompleteIrp(I8042Driver, Irp, Status); } } break; // // For all other IRPs, do nothing. // default: break; } return; } VOID I8042DispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles Open IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID I8042DispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles Close IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID I8042DispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles I/O IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID I8042DispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles System Control IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { ASSERT(Irp->MajorCode == IrpMajorSystemControl); // // Do no processing on any IRPs. Let them flow. // return; } INTERRUPT_STATUS I8042InterruptService ( PVOID Context ) /*++ Routine Description: This routine implements the 8042 keyboard controller interrupt service routine. Arguments: Context - Supplies the context pointer given to the system when the interrupt was connected. In this case, this points to the 8042 device context. Return Value: Interrupt status. --*/ { UCHAR Byte; PI8042_DEVICE Device; INTERRUPT_STATUS InterruptStatus; ULONG WriteIndex; Device = (PI8042_DEVICE)Context; InterruptStatus = InterruptStatusNotClaimed; // // Check to see if there is data waiting. // if ((READ_STATUS_REGISTER(Device) & I8042_STATUS_OUTPUT_BUFFER_FULL) != 0) { // // There was data here, so most likely it was this device interrupting. // InterruptStatus = InterruptStatusClaimed; // // Read the bytes out of the controller. // KeAcquireSpinLock(&(Device->InterruptLock)); WriteIndex = Device->WriteIndex; while ((READ_STATUS_REGISTER(Device) & I8042_STATUS_OUTPUT_BUFFER_FULL) != 0) { Byte = READ_DATA_REGISTER(Device); if (((WriteIndex + 1) % I8042_BUFFER_SIZE) != Device->ReadIndex) { Device->DataBuffer[WriteIndex] = Byte; // // Advance the write index. // if ((WriteIndex + 1) == I8042_BUFFER_SIZE) { WriteIndex = 0; } else { WriteIndex += 1; } } else { RtlDebugPrint("I8042: Buffer overflow, losing byte %02X\n", Byte); } } // // Save the new write index now that everything's out. // Device->WriteIndex = WriteIndex; KeReleaseSpinLock(&(Device->InterruptLock)); } return InterruptStatus; } INTERRUPT_STATUS I8042InterruptServiceWorker ( PVOID Parameter ) /*++ Routine Description: This routine processes interrupts for the e100 controller at low level. Arguments: Parameter - Supplies an optional parameter passed in by the creator of the work item. Return Value: Interrupt status. --*/ { UCHAR Byte; UCHAR Code1; UCHAR Code2; UCHAR Code3; PI8042_DEVICE Device; USER_INPUT_EVENT Event; BOOL KeyUp; ULONG ReadIndex; Code1 = 0; Code2 = 0; Code3 = 0; Device = (PI8042_DEVICE)Parameter; ASSERT(KeGetRunLevel() == RunLevelLow); RtlZeroMemory(&Event, sizeof(USER_INPUT_EVENT)); // // Pull as much data out of the buffer as there is. // KeAcquireQueuedLock(Device->ReadLock); ReadIndex = Device->ReadIndex; while (ReadIndex != Device->WriteIndex) { Byte = Device->DataBuffer[ReadIndex]; ReadIndex += 1; if (ReadIndex == I8042_BUFFER_SIZE) { ReadIndex = 0; } // // If the first byte read was the extended 2 code, then another 2 bytes // should be coming in. Get those bytes. // if (Code1 == SCAN_CODE_1_EXTENDED_2_CODE) { if (Code2 == 0) { Code2 = Byte; continue; } Code3 = Byte; // // If the first byte read was the extended (1) code, then another byte // should be coming in. Get that byte. // } else if (Code1 == SCAN_CODE_1_EXTENDED_CODE) { Code2 = Byte; } else { Code1 = Byte; if ((Code1 == SCAN_CODE_1_EXTENDED_CODE) || (Code1 == SCAN_CODE_1_EXTENDED_2_CODE)) { continue; } } // // Get the specifics of the event. // Event.U.Key = I8042ConvertScanCodeToKey(Code1, Code2, Code3, &KeyUp); if (Event.U.Key != KeyboardKeyInvalid) { if (KeyUp != FALSE) { Event.EventType = UserInputEventKeyUp; } else { Event.EventType = UserInputEventKeyDown; } // // Log the event. // InReportInputEvent(Device->UserInputDeviceHandle, &Event); } // // A full key combination was read, move the read index forward. // Device->ReadIndex = ReadIndex; Code1 = 0; Code2 = 0; } KeReleaseQueuedLock(Device->ReadLock); return InterruptStatusClaimed; } KSTATUS I8042pProcessResourceRequirements ( PIRP Irp, PI8042_DEVICE Device ) /*++ Routine Description: This routine filters through the resource requirements presented by the bus. It adds an interrupt vector requirement for any interrupt line requested. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this 8042 controller device. Return Value: Status code. --*/ { PRESOURCE_CONFIGURATION_LIST Requirements; KSTATUS Status; RESOURCE_REQUIREMENT VectorRequirement; ASSERT((Irp->MajorCode == IrpMajorStateChange) && (Irp->MinorCode == IrpMinorQueryResources)); // // Initialize a nice interrupt vector requirement in preparation. // RtlZeroMemory(&VectorRequirement, sizeof(RESOURCE_REQUIREMENT)); VectorRequirement.Type = ResourceTypeInterruptVector; VectorRequirement.Minimum = 0; VectorRequirement.Maximum = -1; VectorRequirement.Length = 1; // // Loop through all configuration lists, creating a vector for each line. // Requirements = Irp->U.QueryResources.ResourceRequirements; Status = IoCreateAndAddInterruptVectorsForLines(Requirements, &VectorRequirement); if (!KSUCCESS(Status)) { goto ProcessResourceRequirementsEnd; } ProcessResourceRequirementsEnd: return Status; } KSTATUS I8042pStartDevice ( PIRP Irp, PI8042_DEVICE Device ) /*++ Routine Description: This routine starts up the 8042 controller. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this 8042 controller device. Return Value: Status code. --*/ { PRESOURCE_ALLOCATION Allocation; PRESOURCE_ALLOCATION_LIST AllocationList; IO_CONNECT_INTERRUPT_PARAMETERS Connect; BOOL ControlFound; BOOL DataFound; PRESOURCE_ALLOCATION LineAllocation; KSTATUS Status; ControlFound = FALSE; DataFound = FALSE; // // If there are no resources, then return success but don't start anything. // AllocationList = Irp->U.StartDevice.ProcessorLocalResources; if (AllocationList == NULL) { Status = STATUS_SUCCESS; goto StartDeviceEnd; } // // Loop through the allocated resources to get the control and data ports, // and the interrupt. // Allocation = IoGetNextResourceAllocation(AllocationList, NULL); while (Allocation != NULL) { if (Allocation->Type == ResourceTypeIoPort) { ASSERT(Allocation->Length == 1); ASSERT(Allocation->Allocation <= 0xFFFF); // // Assume the first resource is the data port. // if (DataFound == FALSE) { Device->DataPort = (USHORT)Allocation->Allocation; DataFound = TRUE; // // The second resource must be the control port. // } else if (ControlFound == FALSE) { Device->ControlPort = (USHORT)Allocation->Allocation; ControlFound = TRUE; } // // If the resource is an interrupt vector, then it should have an // owning interrupt line allocation. // } else if (Allocation->Type == ResourceTypeInterruptVector) { // // Currently only one interrupt resource is expected. // ASSERT(Device->InterruptResourcesFound == FALSE); ASSERT(Allocation->OwningAllocation != NULL); // // Save the line and vector number. // LineAllocation = Allocation->OwningAllocation; Device->InterruptLine = LineAllocation->Allocation; Device->InterruptVector = Allocation->Allocation; Device->InterruptResourcesFound = TRUE; } // // Get the next allocation in the list. // Allocation = IoGetNextResourceAllocation(AllocationList, Allocation); } // // Fail if both ports were not found. // if ((DataFound == FALSE) || (ControlFound == FALSE)) { Status = STATUS_INVALID_CONFIGURATION; goto StartDeviceEnd; } // // Fire up the device. // Status = I8042pEnableDevice(Irp->Device, Device); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } // // Attempt to connect the interrupt. // ASSERT(Device->InterruptHandle == INVALID_HANDLE); RtlZeroMemory(&Connect, sizeof(IO_CONNECT_INTERRUPT_PARAMETERS)); Connect.Version = IO_CONNECT_INTERRUPT_PARAMETERS_VERSION; Connect.Device = Irp->Device; Connect.LineNumber = Device->InterruptLine; Connect.Vector = Device->InterruptVector; Connect.InterruptServiceRoutine = I8042InterruptService; Connect.LowLevelServiceRoutine = I8042InterruptServiceWorker; Connect.Context = Device; Connect.Interrupt = &(Device->InterruptHandle); Status = IoConnectInterrupt(&Connect); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } StartDeviceEnd: if (!KSUCCESS(Status)) { Device->InterruptResourcesFound = FALSE; if (Device->InterruptHandle != INVALID_HANDLE) { IoDisconnectInterrupt(Device->InterruptHandle); Device->InterruptHandle = INVALID_HANDLE; } if (Device->UserInputDeviceHandle != INVALID_HANDLE) { InDestroyInputDevice(Device->UserInputDeviceHandle); Device->UserInputDeviceHandle = INVALID_HANDLE; } } return Status; } KSTATUS I8042pEnableDevice ( PVOID OsDevice, PI8042_DEVICE Device ) /*++ Routine Description: This routine enables the given 8042 device. Arguments: OsDevice - Supplies a pointer to the OS device token. Device - Supplies a pointer to this 8042 controller device. Return Value: Status code. --*/ { UCHAR CommandByte; USER_INPUT_DEVICE_DESCRIPTION Description; UCHAR Response; KSTATUS Status; BOOL TwoPorts; if (Device->IsMouse != FALSE) { // // Mice are not currently supported. // return STATUS_NOT_IMPLEMENTED; } else { // // Disable both ports. // I8042pSendCommand(Device, I8042_COMMAND_DISABLE_KEYBOARD); I8042pSendCommand(Device, I8042_COMMAND_DISABLE_MOUSE_PORT); // // Flush any leftover data out. // while ((READ_STATUS_REGISTER(Device) & I8042_STATUS_OUTPUT_BUFFER_FULL) != 0) { READ_DATA_REGISTER(Device); } // // Enable the keyboard in the command byte. Disable the interrupt // for now during setup. // CommandByte = I8042pReadCommandByte(Device); CommandByte &= ~(I8042_COMMAND_BYTE_KEYBOARD_DISABLED | I8042_COMMAND_BYTE_PCAT_INHIBIT | I8042_COMMAND_BYTE_KEYBOARD_INTERRUPT_ENABLED | I8042_COMMAND_BYTE_MOUSE_INTERRUPT_ENABLED); I8042pWriteCommandByte(Device, CommandByte); // // Send a self test to the controller itself, and verify that it // passes. // I8042pSendCommand(Device, I8042_COMMAND_SELF_TEST); HlBusySpin(I8042_RESET_DELAY); Status = I8042pReceiveResponse(Device, &Response); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } if (Response != I8042_SELF_TEST_SUCCESS) { RtlDebugPrint("i8042: Received %x to keyboard reset instead of " "expected %x.\n", Response, I8042_SELF_TEST_SUCCESS); Status = STATUS_DEVICE_IO_ERROR; goto EnableDeviceEnd; } // // Determine if there are two ports. Enable the mouse port, and the // "data from mouse" bit in the status should clear. // TwoPorts = FALSE; I8042pSendCommand(Device, I8042_COMMAND_ENABLE_MOUSE_PORT); if ((READ_STATUS_REGISTER(Device) & I8042_STATUS_DATA_FROM_MOUSE) == 0) { TwoPorts = TRUE; } I8042pSendCommand(Device, I8042_COMMAND_DISABLE_MOUSE_PORT); // // Test the ports. // I8042pSendCommand(Device, I8042_COMMAND_INTERFACE_TEST); Status = I8042pReceiveResponse(Device, &Response); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } if (Response != I8042_PORT_TEST_SUCCESS) { Status = STATUS_DEVICE_IO_ERROR; goto EnableDeviceEnd; } if (TwoPorts != FALSE) { I8042pSendCommand(Device, I8042_COMMAND_TEST_MOUSE_PORT); Status = I8042pReceiveResponse(Device, &Response); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } if (Response != I8042_PORT_TEST_SUCCESS) { Status = STATUS_DEVICE_IO_ERROR; goto EnableDeviceEnd; } } // // Enable the ports. // I8042pSendCommand(Device, I8042_COMMAND_ENABLE_KEYBOARD); if (TwoPorts != FALSE) { I8042pSendCommand(Device, I8042_COMMAND_ENABLE_MOUSE_PORT); } // // Reset the keyboard. // Status = I8042pSendKeyboardCommand(Device, KEYBOARD_COMMAND_RESET, KEYBOARD_COMMAND_NO_PARAMETER); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } // // Read the BAT (Basic Assurance Test) code that the keyboard sends // when it finishes resetting. // Status = I8042pReceiveResponse(Device, &Response); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } if (Response != KEYBOARD_BAT_PASS) { goto EnableDeviceEnd; } // // Set the typematic rate/delay on the keyboard. Start by sending the // command. // Status = I8042pSendKeyboardCommand(Device, KEYBOARD_COMMAND_SET_TYPEMATIC, DEFAULT_TYPEMATIC_VALUE); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } // // Enable the keyboard. // Status = I8042pSendKeyboardCommand(Device, KEYBOARD_COMMAND_ENABLE, KEYBOARD_COMMAND_NO_PARAMETER); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } // // Create the user input handle if not already done. // if (Device->UserInputDeviceHandle == INVALID_HANDLE) { Description.Device = OsDevice; Description.DeviceContext = Device; Description.Type = UserInputDeviceKeyboard; Description.InterfaceVersion = USER_INPUT_KEYBOARD_DEVICE_INTERFACE_VERSION; Description.U.KeyboardInterface.SetLedState = I8042pSetLedState; Device->UserInputDeviceHandle = InRegisterInputDevice(&Description); if (Device->UserInputDeviceHandle == INVALID_HANDLE) { Status = STATUS_UNSUCCESSFUL; goto EnableDeviceEnd; } } // // Enable the keyboard interrupt. // CommandByte |= I8042_COMMAND_BYTE_KEYBOARD_INTERRUPT_ENABLED; I8042pWriteCommandByte(Device, CommandByte); } EnableDeviceEnd: return Status; } KSTATUS I8042pSetLedState ( PVOID Device, PVOID DeviceContext, ULONG LedState ) /*++ Routine Description: This routine sets a keyboard's LED state (e.g. Number lock, Caps lock and scroll lock). The state is absolute; the desired state for each LED must be supplied. Arguments: Device - Supplies a pointer to the OS device representing the user input device. DeviceContext - Supplies the opaque device context supplied in the device description upon registration with the user input library. LedState - Supplies a bitmask of flags describing the desired LED state. See USER_INPUT_KEYBOARD_LED_* for definition. Return Value: Status code. --*/ { PI8042_DEVICE I8042Device; UCHAR KeyboardLedState; KSTATUS Status; I8042Device = (PI8042_DEVICE)DeviceContext; // // Convert the LED state to the proper format. // KeyboardLedState = 0; if ((LedState & USER_INPUT_KEYBOARD_LED_SCROLL_LOCK) != 0) { KeyboardLedState |= KEYBOARD_LED_SCROLL_LOCK; } if ((LedState & USER_INPUT_KEYBOARD_LED_NUM_LOCK) != 0) { KeyboardLedState |= KEYBOARD_LED_NUM_LOCK; } if ((LedState & USER_INPUT_KEYBOARD_LED_CAPS_LOCK) != 0) { KeyboardLedState |= KEYBOARD_LED_CAPS_LOCK; } Status = I8042pSendKeyboardCommand(I8042Device, KEYBOARD_COMMAND_SET_LEDS, KeyboardLedState); return Status; } UCHAR I8042pReadCommandByte ( PI8042_DEVICE Device ) /*++ Routine Description: This routine reads the contents of the command byte in the 8042 keyboard controller. Arguments: Device - Supplies a pointer to this 8042 controller device. Return Value: Status code. --*/ { I8042pSendCommand(Device, I8042_COMMAND_READ_COMMAND_BYTE); return READ_DATA_REGISTER(Device); } VOID I8042pWriteCommandByte ( PI8042_DEVICE Device, UCHAR Value ) /*++ Routine Description: This routine reads the contents of the command byte in the 8042 keyboard controller. Arguments: Device - Supplies a pointer to this 8042 controller device. Value - Supplies the value to write to the command register. Return Value: Status code. --*/ { KSTATUS Status; Status = I8042pSendCommand(Device, I8042_COMMAND_WRITE_COMMAND_BYTE); if (KSUCCESS(Status)) { WRITE_DATA_REGISTER(Device, Value); } return; } KSTATUS I8042pSendKeyboardCommand ( PI8042_DEVICE Device, UCHAR Command, UCHAR Parameter ) /*++ Routine Description: This routine sends a command byte to the keyboard itself (not the keyboard controller) and checks the return status byte. Arguments: Device - Supplies a pointer to this 8042 controller device. Command - Supplies the command to write to the keyboard. Parameter - Supplies an additional byte to send. Set this to 0xFF to skip sending this byte. Return Value: Status code indicating whether the keyboard succeeded or failed the command. --*/ { UCHAR KeyboardResult; KSTATUS Status; WAIT_FOR_INPUT_BUFFER(Device); WRITE_DATA_REGISTER(Device, Command); if (Parameter != KEYBOARD_COMMAND_NO_PARAMETER) { WAIT_FOR_INPUT_BUFFER(Device); WRITE_DATA_REGISTER(Device, Parameter); } Status = I8042pReceiveResponse(Device, &KeyboardResult); if (!KSUCCESS(Status)) { return Status; } if (KeyboardResult == KEYBOARD_STATUS_ACKNOWLEDGE) { return STATUS_SUCCESS; } if (KeyboardResult == KEYBOARD_STATUS_RESEND) { return STATUS_NOT_READY; } if (KeyboardResult == KEYBOARD_STATUS_OVERRUN) { return STATUS_BUFFER_OVERRUN; } return STATUS_DEVICE_IO_ERROR; } KSTATUS I8042pSendCommand ( PI8042_DEVICE Device, UCHAR Command ) /*++ Routine Description: This routine sends a command to the PS/2 controller (not the device connected to it). Arguments: Device - Supplies a pointer to this 8042 controller device. Command - Supplies the command to write to the controller. Return Value: Status code indicating whether the keyboard succeeded or failed the command. --*/ { WAIT_FOR_INPUT_BUFFER(Device); WRITE_CONTROL_REGISTER(Device, Command); WAIT_FOR_INPUT_BUFFER(Device); return STATUS_SUCCESS; } KSTATUS I8042pReceiveResponse ( PI8042_DEVICE Device, PUCHAR Data ) /*++ Routine Description: This routine receives a byte from the data port, with a timeout. Arguments: Device - Supplies a pointer to this 8042 controller device. Data - Supplies a pointer where the data will be returned on success. Return Value: Status code. --*/ { KSTATUS StatusCode; UCHAR StatusRegister; ULONGLONG Timeout; Timeout = KeGetRecentTimeCounter() + ((HlQueryTimeCounterFrequency() * I8042_COMMAND_TIMEOUT) / MILLISECONDS_PER_SECOND); StatusCode = STATUS_TIMEOUT; do { StatusRegister = READ_STATUS_REGISTER(Device); if ((StatusRegister & I8042_STATUS_TIMEOUT) != 0) { StatusCode = STATUS_TIMEOUT; break; } else if ((StatusRegister & I8042_STATUS_PARITY_ERROR) != 0) { StatusCode = STATUS_PARITY_ERROR; break; } else if ((StatusRegister & I8042_STATUS_OUTPUT_BUFFER_FULL) != 0) { *Data = READ_DATA_REGISTER(Device); StatusCode = STATUS_SUCCESS; break; } } while (KeGetRecentTimeCounter() <= Timeout); return StatusCode; }