/** @file PS/2 Keyboard driver. Routines that interacts with callers, conforming to EFI driver model Copyright (c) 2006 - 2016, Intel Corporation. All rights reserved.
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; }