/** @file | |
PS/2 Keyboard driver. Routines that interacts with callers, | |
conforming to EFI driver model | |
Copyright (c) 2006 - 2016, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "Ps2Keyboard.h" | |
// | |
// Function prototypes | |
// | |
/** | |
Test controller is a keyboard Controller. | |
@param This Pointer of EFI_DRIVER_BINDING_PROTOCOL | |
@param Controller driver's controller | |
@param RemainingDevicePath children device path | |
@retval EFI_UNSUPPORTED controller is not floppy disk | |
@retval EFI_SUCCESS controller is floppy disk | |
**/ | |
EFI_STATUS | |
EFIAPI | |
KbdControllerDriverSupported ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE Controller, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath | |
); | |
/** | |
Create KEYBOARD_CONSOLE_IN_DEV instance on controller. | |
@param This Pointer of EFI_DRIVER_BINDING_PROTOCOL | |
@param Controller driver controller handle | |
@param RemainingDevicePath Children's device path | |
@retval whether success to create floppy control instance. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
KbdControllerDriverStart ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE Controller, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath | |
); | |
/** | |
Stop this driver on ControllerHandle. Support stopping any child handles | |
created by this driver. | |
@param This Protocol instance pointer. | |
@param ControllerHandle Handle of device to stop driver on | |
@param NumberOfChildren Number of Handles in ChildHandleBuffer. If number of | |
children is zero stop the entire bus driver. | |
@param ChildHandleBuffer List of Child Handles to Stop. | |
@retval EFI_SUCCESS This driver is removed ControllerHandle | |
@retval other This driver was not removed from this device | |
**/ | |
EFI_STATUS | |
EFIAPI | |
KbdControllerDriverStop ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE Controller, | |
IN UINTN NumberOfChildren, | |
IN EFI_HANDLE *ChildHandleBuffer | |
); | |
/** | |
Free the waiting key notify list. | |
@param ListHead Pointer to list head | |
@retval EFI_INVALID_PARAMETER ListHead is NULL | |
@retval EFI_SUCCESS Success to free NotifyList | |
**/ | |
EFI_STATUS | |
KbdFreeNotifyList ( | |
IN OUT LIST_ENTRY *ListHead | |
); | |
// | |
// DriverBinding Protocol Instance | |
// | |
EFI_DRIVER_BINDING_PROTOCOL gKeyboardControllerDriver = { | |
KbdControllerDriverSupported, | |
KbdControllerDriverStart, | |
KbdControllerDriverStop, | |
0xa, | |
NULL, | |
NULL | |
}; | |
/** | |
Test controller is a keyboard Controller. | |
@param This Pointer of EFI_DRIVER_BINDING_PROTOCOL | |
@param Controller driver's controller | |
@param RemainingDevicePath children device path | |
@retval EFI_UNSUPPORTED controller is not floppy disk | |
@retval EFI_SUCCESS controller is floppy disk | |
**/ | |
EFI_STATUS | |
EFIAPI | |
KbdControllerDriverSupported ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE Controller, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_SIO_PROTOCOL *Sio; | |
EFI_DEVICE_PATH_PROTOCOL *DevicePath; | |
ACPI_HID_DEVICE_PATH *Acpi; | |
// | |
// Check whether the controller is keyboard. | |
// | |
Status = gBS->OpenProtocol ( | |
Controller, | |
&gEfiDevicePathProtocolGuid, | |
(VOID **)&DevicePath, | |
This->DriverBindingHandle, | |
Controller, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
do { | |
Acpi = (ACPI_HID_DEVICE_PATH *)DevicePath; | |
DevicePath = NextDevicePathNode (DevicePath); | |
} while (!IsDevicePathEnd (DevicePath)); | |
if ((DevicePathType (Acpi) != ACPI_DEVICE_PATH) || | |
((DevicePathSubType (Acpi) != ACPI_DP) && (DevicePathSubType (Acpi) != ACPI_EXTENDED_DP))) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
if ((Acpi->HID != EISA_PNP_ID (0x303)) || (Acpi->UID != 0)) { | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Open the IO Abstraction(s) needed to perform the supported test | |
// | |
Status = gBS->OpenProtocol ( | |
Controller, | |
&gEfiSioProtocolGuid, | |
(VOID **)&Sio, | |
This->DriverBindingHandle, | |
Controller, | |
EFI_OPEN_PROTOCOL_BY_DRIVER | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Close the I/O Abstraction(s) used to perform the supported test | |
// | |
gBS->CloseProtocol ( | |
Controller, | |
&gEfiSioProtocolGuid, | |
This->DriverBindingHandle, | |
Controller | |
); | |
return Status; | |
} | |
/** | |
Create KEYBOARD_CONSOLE_IN_DEV instance on controller. | |
@param This Pointer of EFI_DRIVER_BINDING_PROTOCOL | |
@param Controller driver controller handle | |
@param RemainingDevicePath Children's device path | |
@retval whether success to create floppy control instance. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
KbdControllerDriverStart ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE Controller, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_STATUS Status1; | |
EFI_SIO_PROTOCOL *Sio; | |
KEYBOARD_CONSOLE_IN_DEV *ConsoleIn; | |
UINT8 Data; | |
EFI_STATUS_CODE_VALUE StatusCode; | |
EFI_DEVICE_PATH_PROTOCOL *DevicePath; | |
StatusCode = 0; | |
Status = gBS->OpenProtocol ( | |
Controller, | |
&gEfiDevicePathProtocolGuid, | |
(VOID **)&DevicePath, | |
This->DriverBindingHandle, | |
Controller, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Report that the keyboard is being enabled | |
// | |
REPORT_STATUS_CODE_WITH_DEVICE_PATH ( | |
EFI_PROGRESS_CODE, | |
EFI_PERIPHERAL_KEYBOARD | EFI_P_PC_ENABLE, | |
DevicePath | |
); | |
// | |
// Get the ISA I/O Protocol on Controller's handle | |
// | |
Status = gBS->OpenProtocol ( | |
Controller, | |
&gEfiSioProtocolGuid, | |
(VOID **)&Sio, | |
This->DriverBindingHandle, | |
Controller, | |
EFI_OPEN_PROTOCOL_BY_DRIVER | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Allocate private data | |
// | |
ConsoleIn = AllocateZeroPool (sizeof (KEYBOARD_CONSOLE_IN_DEV)); | |
if (ConsoleIn == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_CONTROLLER_ERROR; | |
goto ErrorExit; | |
} | |
// | |
// Setup the device instance | |
// | |
ConsoleIn->Signature = KEYBOARD_CONSOLE_IN_DEV_SIGNATURE; | |
ConsoleIn->Handle = Controller; | |
(ConsoleIn->ConIn).Reset = KeyboardEfiReset; | |
(ConsoleIn->ConIn).ReadKeyStroke = KeyboardReadKeyStroke; | |
ConsoleIn->DataRegisterAddress = KEYBOARD_8042_DATA_REGISTER; | |
ConsoleIn->StatusRegisterAddress = KEYBOARD_8042_STATUS_REGISTER; | |
ConsoleIn->CommandRegisterAddress = KEYBOARD_8042_COMMAND_REGISTER; | |
ConsoleIn->DevicePath = DevicePath; | |
ConsoleIn->ConInEx.Reset = KeyboardEfiResetEx; | |
ConsoleIn->ConInEx.ReadKeyStrokeEx = KeyboardReadKeyStrokeEx; | |
ConsoleIn->ConInEx.SetState = KeyboardSetState; | |
ConsoleIn->ConInEx.RegisterKeyNotify = KeyboardRegisterKeyNotify; | |
ConsoleIn->ConInEx.UnregisterKeyNotify = KeyboardUnregisterKeyNotify; | |
InitializeListHead (&ConsoleIn->NotifyList); | |
// | |
// Fix for random hangs in System waiting for the Key if no KBC is present in BIOS. | |
// When KBC decode (IO port 0x60/0x64 decode) is not enabled, | |
// KeyboardRead will read back as 0xFF and return status is EFI_SUCCESS. | |
// So instead we read status register to detect after read if KBC decode is enabled. | |
// | |
// | |
// Return code is ignored on purpose. | |
// | |
if (!PcdGetBool (PcdFastPS2Detection)) { | |
KeyboardRead (ConsoleIn, &Data); | |
if ((KeyReadStatusRegister (ConsoleIn) & (KBC_PARE | KBC_TIM)) == (KBC_PARE | KBC_TIM)) { | |
// | |
// If nobody decodes KBC I/O port, it will read back as 0xFF. | |
// Check the Time-Out and Parity bit to see if it has an active KBC in system | |
// | |
Status = EFI_DEVICE_ERROR; | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_NOT_DETECTED; | |
goto ErrorExit; | |
} | |
} | |
// | |
// Setup the WaitForKey event | |
// | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_WAIT, | |
TPL_NOTIFY, | |
KeyboardWaitForKey, | |
ConsoleIn, | |
&((ConsoleIn->ConIn).WaitForKey) | |
); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_OUT_OF_RESOURCES; | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_CONTROLLER_ERROR; | |
goto ErrorExit; | |
} | |
// | |
// Setup the WaitForKeyEx event | |
// | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_WAIT, | |
TPL_NOTIFY, | |
KeyboardWaitForKeyEx, | |
ConsoleIn, | |
&(ConsoleIn->ConInEx.WaitForKeyEx) | |
); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_OUT_OF_RESOURCES; | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_CONTROLLER_ERROR; | |
goto ErrorExit; | |
} | |
// Setup a periodic timer, used for reading keystrokes at a fixed interval | |
// | |
Status = gBS->CreateEvent ( | |
EVT_TIMER | EVT_NOTIFY_SIGNAL, | |
TPL_NOTIFY, | |
KeyboardTimerHandler, | |
ConsoleIn, | |
&ConsoleIn->TimerEvent | |
); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_OUT_OF_RESOURCES; | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_CONTROLLER_ERROR; | |
goto ErrorExit; | |
} | |
Status = gBS->SetTimer ( | |
ConsoleIn->TimerEvent, | |
TimerPeriodic, | |
KEYBOARD_TIMER_INTERVAL | |
); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_OUT_OF_RESOURCES; | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_CONTROLLER_ERROR; | |
goto ErrorExit; | |
} | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
KeyNotifyProcessHandler, | |
ConsoleIn, | |
&ConsoleIn->KeyNotifyProcessEvent | |
); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_OUT_OF_RESOURCES; | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_CONTROLLER_ERROR; | |
goto ErrorExit; | |
} | |
REPORT_STATUS_CODE_WITH_DEVICE_PATH ( | |
EFI_PROGRESS_CODE, | |
EFI_PERIPHERAL_KEYBOARD | EFI_P_PC_PRESENCE_DETECT, | |
DevicePath | |
); | |
// | |
// Reset the keyboard device | |
// | |
Status = ConsoleIn->ConInEx.Reset (&ConsoleIn->ConInEx, FeaturePcdGet (PcdPs2KbdExtendedVerification)); | |
if (EFI_ERROR (Status)) { | |
Status = EFI_DEVICE_ERROR; | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_NOT_DETECTED; | |
goto ErrorExit; | |
} | |
REPORT_STATUS_CODE_WITH_DEVICE_PATH ( | |
EFI_PROGRESS_CODE, | |
EFI_PERIPHERAL_KEYBOARD | EFI_P_PC_DETECTED, | |
DevicePath | |
); | |
ConsoleIn->ControllerNameTable = NULL; | |
AddUnicodeString2 ( | |
"eng", | |
gPs2KeyboardComponentName.SupportedLanguages, | |
&ConsoleIn->ControllerNameTable, | |
L"PS/2 Keyboard Device", | |
TRUE | |
); | |
AddUnicodeString2 ( | |
"en", | |
gPs2KeyboardComponentName2.SupportedLanguages, | |
&ConsoleIn->ControllerNameTable, | |
L"PS/2 Keyboard Device", | |
FALSE | |
); | |
// | |
// Install protocol interfaces for the keyboard device. | |
// | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&Controller, | |
&gEfiSimpleTextInProtocolGuid, | |
&ConsoleIn->ConIn, | |
&gEfiSimpleTextInputExProtocolGuid, | |
&ConsoleIn->ConInEx, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
StatusCode = EFI_PERIPHERAL_KEYBOARD | EFI_P_EC_CONTROLLER_ERROR; | |
goto ErrorExit; | |
} | |
return Status; | |
ErrorExit: | |
// | |
// Report error code | |
// | |
if (StatusCode != 0) { | |
REPORT_STATUS_CODE_WITH_DEVICE_PATH ( | |
EFI_ERROR_CODE | EFI_ERROR_MINOR, | |
StatusCode, | |
DevicePath | |
); | |
} | |
if ((ConsoleIn != NULL) && (ConsoleIn->ConIn.WaitForKey != NULL)) { | |
gBS->CloseEvent (ConsoleIn->ConIn.WaitForKey); | |
} | |
if ((ConsoleIn != NULL) && (ConsoleIn->TimerEvent != NULL)) { | |
gBS->CloseEvent (ConsoleIn->TimerEvent); | |
} | |
if ((ConsoleIn != NULL) && (ConsoleIn->ConInEx.WaitForKeyEx != NULL)) { | |
gBS->CloseEvent (ConsoleIn->ConInEx.WaitForKeyEx); | |
} | |
if ((ConsoleIn != NULL) && (ConsoleIn->KeyNotifyProcessEvent != NULL)) { | |
gBS->CloseEvent (ConsoleIn->KeyNotifyProcessEvent); | |
} | |
KbdFreeNotifyList (&ConsoleIn->NotifyList); | |
if ((ConsoleIn != NULL) && (ConsoleIn->ControllerNameTable != NULL)) { | |
FreeUnicodeStringTable (ConsoleIn->ControllerNameTable); | |
} | |
// | |
// Since there will be no timer handler for keyboard input any more, | |
// exhaust input data just in case there is still keyboard data left | |
// | |
if (ConsoleIn != NULL) { | |
Status1 = EFI_SUCCESS; | |
while (!EFI_ERROR (Status1) && (Status != EFI_DEVICE_ERROR)) { | |
Status1 = KeyboardRead (ConsoleIn, &Data); | |
} | |
} | |
if (ConsoleIn != NULL) { | |
gBS->FreePool (ConsoleIn); | |
} | |
gBS->CloseProtocol ( | |
Controller, | |
&gEfiSioProtocolGuid, | |
This->DriverBindingHandle, | |
Controller | |
); | |
return Status; | |
} | |
/** | |
Stop this driver on ControllerHandle. Support stopping any child handles | |
created by this driver. | |
@param This Protocol instance pointer. | |
@param ControllerHandle Handle of device to stop driver on | |
@param NumberOfChildren Number of Handles in ChildHandleBuffer. If number of | |
children is zero stop the entire bus driver. | |
@param ChildHandleBuffer List of Child Handles to Stop. | |
@retval EFI_SUCCESS This driver is removed ControllerHandle | |
@retval other This driver was not removed from this device | |
**/ | |
EFI_STATUS | |
EFIAPI | |
KbdControllerDriverStop ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE Controller, | |
IN UINTN NumberOfChildren, | |
IN EFI_HANDLE *ChildHandleBuffer | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; | |
KEYBOARD_CONSOLE_IN_DEV *ConsoleIn; | |
UINT8 Data; | |
// | |
// Disable Keyboard | |
// | |
Status = gBS->OpenProtocol ( | |
Controller, | |
&gEfiSimpleTextInProtocolGuid, | |
(VOID **)&ConIn, | |
This->DriverBindingHandle, | |
Controller, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = gBS->OpenProtocol ( | |
Controller, | |
&gEfiSimpleTextInputExProtocolGuid, | |
NULL, | |
This->DriverBindingHandle, | |
Controller, | |
EFI_OPEN_PROTOCOL_TEST_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
ConsoleIn = KEYBOARD_CONSOLE_IN_DEV_FROM_THIS (ConIn); | |
// | |
// Report that the keyboard is being disabled | |
// | |
REPORT_STATUS_CODE_WITH_DEVICE_PATH ( | |
EFI_PROGRESS_CODE, | |
EFI_PERIPHERAL_KEYBOARD | EFI_P_PC_DISABLE, | |
ConsoleIn->DevicePath | |
); | |
if (ConsoleIn->TimerEvent != NULL) { | |
gBS->CloseEvent (ConsoleIn->TimerEvent); | |
ConsoleIn->TimerEvent = NULL; | |
} | |
// | |
// Since there will be no timer handler for keyboard input any more, | |
// exhaust input data just in case there is still keyboard data left | |
// | |
Status = EFI_SUCCESS; | |
while (!EFI_ERROR (Status)) { | |
Status = KeyboardRead (ConsoleIn, &Data); | |
} | |
// | |
// Uninstall the SimpleTextIn and SimpleTextInEx protocols | |
// | |
Status = gBS->UninstallMultipleProtocolInterfaces ( | |
Controller, | |
&gEfiSimpleTextInProtocolGuid, | |
&ConsoleIn->ConIn, | |
&gEfiSimpleTextInputExProtocolGuid, | |
&ConsoleIn->ConInEx, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
gBS->CloseProtocol ( | |
Controller, | |
&gEfiSioProtocolGuid, | |
This->DriverBindingHandle, | |
Controller | |
); | |
// | |
// Free other resources | |
// | |
if ((ConsoleIn->ConIn).WaitForKey != NULL) { | |
gBS->CloseEvent ((ConsoleIn->ConIn).WaitForKey); | |
(ConsoleIn->ConIn).WaitForKey = NULL; | |
} | |
if (ConsoleIn->ConInEx.WaitForKeyEx != NULL) { | |
gBS->CloseEvent (ConsoleIn->ConInEx.WaitForKeyEx); | |
ConsoleIn->ConInEx.WaitForKeyEx = NULL; | |
} | |
if (ConsoleIn->KeyNotifyProcessEvent != NULL) { | |
gBS->CloseEvent (ConsoleIn->KeyNotifyProcessEvent); | |
ConsoleIn->KeyNotifyProcessEvent = NULL; | |
} | |
KbdFreeNotifyList (&ConsoleIn->NotifyList); | |
FreeUnicodeStringTable (ConsoleIn->ControllerNameTable); | |
gBS->FreePool (ConsoleIn); | |
return EFI_SUCCESS; | |
} | |
/** | |
Free the waiting key notify list. | |
@param ListHead Pointer to list head | |
@retval EFI_INVALID_PARAMETER ListHead is NULL | |
@retval EFI_SUCCESS Success to free NotifyList | |
**/ | |
EFI_STATUS | |
KbdFreeNotifyList ( | |
IN OUT LIST_ENTRY *ListHead | |
) | |
{ | |
KEYBOARD_CONSOLE_IN_EX_NOTIFY *NotifyNode; | |
if (ListHead == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
while (!IsListEmpty (ListHead)) { | |
NotifyNode = CR ( | |
ListHead->ForwardLink, | |
KEYBOARD_CONSOLE_IN_EX_NOTIFY, | |
NotifyEntry, | |
KEYBOARD_CONSOLE_IN_EX_NOTIFY_SIGNATURE | |
); | |
RemoveEntryList (ListHead->ForwardLink); | |
gBS->FreePool (NotifyNode); | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
The module Entry Point for module Ps2Keyboard. | |
@param[in] ImageHandle The firmware allocated handle for the EFI image. | |
@param[in] SystemTable A pointer to the EFI System Table. | |
@retval EFI_SUCCESS The entry point is executed successfully. | |
@retval other Some error occurs when executing this entry point. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
InitializePs2Keyboard ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
// | |
// Install driver model protocol(s). | |
// | |
Status = EfiLibInstallDriverBindingComponentName2 ( | |
ImageHandle, | |
SystemTable, | |
&gKeyboardControllerDriver, | |
ImageHandle, | |
&gPs2KeyboardComponentName, | |
&gPs2KeyboardComponentName2 | |
); | |
ASSERT_EFI_ERROR (Status); | |
return Status; | |
} |