/** @file PS/2 Mouse 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 "Ps2Mouse.h" #include "CommPs2.h" /// /// DriverBinding Protocol Instance /// EFI_DRIVER_BINDING_PROTOCOL gPS2MouseDriver = { PS2MouseDriverSupported, PS2MouseDriverStart, PS2MouseDriverStop, 0xa, NULL, NULL }; /** Test to see if this driver supports ControllerHandle. Any ControllerHandle than contains a IsaIo protocol can be supported. @param This Protocol instance pointer. @param ControllerHandle Handle of device to test @param RemainingDevicePath Optional parameter use to pick a specific child device to start. @retval EFI_SUCCESS This driver supports this device @retval EFI_ALREADY_STARTED This driver is already running on this device @retval other This driver does not support this device **/ EFI_STATUS EFIAPI PS2MouseDriverSupported ( 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; } switch (Acpi->HID) { case EISA_PNP_ID (0xF03): // // Microsoft PS/2 style mouse // case EISA_PNP_ID (0xF13): // // PS/2 Port for PS/2-style Mice // break; case EISA_PNP_ID (0x303): // // IBM Enhanced (101/102-key, PS/2 mouse support) // if (Acpi->UID == 1) { break; } default: return EFI_UNSUPPORTED; break; } // // 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; } /** Start this driver on ControllerHandle by opening a Sio protocol, creating PS2_MOUSE_DEV device and install gEfiSimplePointerProtocolGuid finally. @param This Protocol instance pointer. @param ControllerHandle Handle of device to bind driver to @param RemainingDevicePath Optional parameter use to pick a specific child device to start. @retval EFI_SUCCESS This driver is added to ControllerHandle @retval EFI_ALREADY_STARTED This driver is already running on ControllerHandle @retval other This driver does not support this device **/ EFI_STATUS EFIAPI PS2MouseDriverStart ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE Controller, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath ) { EFI_STATUS Status; EFI_STATUS EmptyStatus; EFI_SIO_PROTOCOL *Sio; PS2_MOUSE_DEV *MouseDev; UINT8 Data; EFI_TPL OldTpl; EFI_STATUS_CODE_VALUE StatusCode; EFI_DEVICE_PATH_PROTOCOL *DevicePath; StatusCode = 0; // // Open the device path protocol // 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_MOUSE | 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; } // // Raise TPL to avoid keyboard operation impact // OldTpl = gBS->RaiseTPL (TPL_NOTIFY); // // Allocate private data // MouseDev = AllocateZeroPool(sizeof (PS2_MOUSE_DEV)); if (MouseDev == NULL) { Status = EFI_OUT_OF_RESOURCES; goto ErrorExit; } // // Setup the device instance // MouseDev->Signature = PS2_MOUSE_DEV_SIGNATURE; MouseDev->Handle = Controller; MouseDev->SampleRate = SampleRate20; MouseDev->Resolution = MouseResolution4; MouseDev->Scaling = Scaling1; MouseDev->DataPackageSize = 3; MouseDev->DevicePath = DevicePath; // // Resolution = 4 counts/mm // MouseDev->Mode.ResolutionX = 4; MouseDev->Mode.ResolutionY = 4; MouseDev->Mode.LeftButton = TRUE; MouseDev->Mode.RightButton = TRUE; MouseDev->SimplePointerProtocol.Reset = MouseReset; MouseDev->SimplePointerProtocol.GetState = MouseGetState; MouseDev->SimplePointerProtocol.Mode = &(MouseDev->Mode); // // Initialize keyboard controller if necessary // REPORT_STATUS_CODE_WITH_DEVICE_PATH ( EFI_PROGRESS_CODE, EFI_PERIPHERAL_MOUSE | EFI_P_MOUSE_PC_SELF_TEST, DevicePath ); Data = IoRead8 (KBC_CMD_STS_PORT); // // Fix for random hangs in System waiting for the Key if no KBC is present in BIOS. // if ((Data & (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_MOUSE | EFI_P_EC_NOT_DETECTED; goto ErrorExit; } if ((Data & KBC_SYSF) != KBC_SYSF) { Status = KbcSelfTest (); if (EFI_ERROR(Status)) { StatusCode = EFI_PERIPHERAL_MOUSE | EFI_P_EC_CONTROLLER_ERROR; goto ErrorExit; } } KbcEnableAux (); REPORT_STATUS_CODE_WITH_DEVICE_PATH ( EFI_PROGRESS_CODE, EFI_PERIPHERAL_MOUSE | EFI_P_PC_PRESENCE_DETECT, DevicePath ); // // Reset the mouse // Status = MouseDev->SimplePointerProtocol.Reset ( &MouseDev->SimplePointerProtocol, FeaturePcdGet (PcdPs2MouseExtendedVerification) ); if (EFI_ERROR(Status)) { // // mouse not connected // Status = EFI_SUCCESS; StatusCode = EFI_PERIPHERAL_MOUSE | EFI_P_EC_NOT_DETECTED; goto ErrorExit; } REPORT_STATUS_CODE_WITH_DEVICE_PATH ( EFI_PROGRESS_CODE, EFI_PERIPHERAL_MOUSE | EFI_P_PC_DETECTED, DevicePath ); // // Setup the WaitForKey event // Status = gBS->CreateEvent ( EVT_NOTIFY_WAIT, TPL_NOTIFY, MouseWaitForInput, MouseDev, &((MouseDev->SimplePointerProtocol).WaitForInput) ); if (EFI_ERROR(Status)) { Status = EFI_OUT_OF_RESOURCES; goto ErrorExit; } // // Setup a periodic timer, used to poll mouse state // Status = gBS->CreateEvent ( EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_NOTIFY, PollMouse, MouseDev, &MouseDev->TimerEvent ); if (EFI_ERROR(Status)) { Status = EFI_OUT_OF_RESOURCES; goto ErrorExit; } // // Start timer to poll mouse (100 samples per second) // Status = gBS->SetTimer (MouseDev->TimerEvent, TimerPeriodic, 100000); if (EFI_ERROR(Status)) { Status = EFI_OUT_OF_RESOURCES; goto ErrorExit; } MouseDev->ControllerNameTable = NULL; AddUnicodeString2 ( "eng", gPs2MouseComponentName.SupportedLanguages, &MouseDev->ControllerNameTable, L"PS/2 Mouse Device", TRUE ); AddUnicodeString2 ( "en", gPs2MouseComponentName2.SupportedLanguages, &MouseDev->ControllerNameTable, L"PS/2 Mouse Device", FALSE ); // // Install protocol interfaces for the mouse device. // Status = gBS->InstallMultipleProtocolInterfaces ( &Controller, &gEfiSimplePointerProtocolGuid, &MouseDev->SimplePointerProtocol, NULL ); if (EFI_ERROR(Status)) { goto ErrorExit; } gBS->RestoreTPL (OldTpl); return Status; ErrorExit: if (Status != EFI_DEVICE_ERROR) { KbcDisableAux (); } if (StatusCode != 0) { REPORT_STATUS_CODE_WITH_DEVICE_PATH ( EFI_ERROR_CODE | EFI_ERROR_MINOR, StatusCode, DevicePath ); } if ((MouseDev != NULL) && (MouseDev->SimplePointerProtocol.WaitForInput != NULL)) { gBS->CloseEvent (MouseDev->SimplePointerProtocol.WaitForInput); } if ((MouseDev != NULL) && (MouseDev->TimerEvent != NULL)) { gBS->CloseEvent (MouseDev->TimerEvent); } if ((MouseDev != NULL) && (MouseDev->ControllerNameTable != NULL)) { FreeUnicodeStringTable (MouseDev->ControllerNameTable); } if (Status != EFI_DEVICE_ERROR) { // // Since there will be no timer handler for mouse input any more, // exhaust input data just in case there is still mouse data left // EmptyStatus = EFI_SUCCESS; while (!EFI_ERROR(EmptyStatus)) { EmptyStatus = In8042Data (&Data); } } if (MouseDev != NULL) { FreePool(MouseDev); } gBS->CloseProtocol ( Controller, &gEfiDevicePathProtocolGuid, This->DriverBindingHandle, Controller ); gBS->CloseProtocol ( Controller, &gEfiSioProtocolGuid, This->DriverBindingHandle, Controller ); gBS->RestoreTPL (OldTpl); 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 PS2MouseDriverStop ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE Controller, IN UINTN NumberOfChildren, IN EFI_HANDLE *ChildHandleBuffer ) { EFI_STATUS Status; EFI_SIMPLE_POINTER_PROTOCOL *SimplePointerProtocol; PS2_MOUSE_DEV *MouseDev; UINT8 Data; Status = gBS->OpenProtocol ( Controller, &gEfiSimplePointerProtocolGuid, (VOID **) &SimplePointerProtocol, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (EFI_ERROR(Status)) { return EFI_SUCCESS; } MouseDev = PS2_MOUSE_DEV_FROM_THIS (SimplePointerProtocol); // // Report that the keyboard is being disabled // REPORT_STATUS_CODE_WITH_DEVICE_PATH ( EFI_PROGRESS_CODE, EFI_PERIPHERAL_MOUSE | EFI_P_PC_DISABLE, MouseDev->DevicePath ); Status = gBS->UninstallProtocolInterface ( Controller, &gEfiSimplePointerProtocolGuid, &MouseDev->SimplePointerProtocol ); if (EFI_ERROR(Status)) { return Status; } // // Cancel mouse data polling timer, close timer event // gBS->SetTimer (MouseDev->TimerEvent, TimerCancel, 0); gBS->CloseEvent (MouseDev->TimerEvent); // // Since there will be no timer handler for mouse input any more, // exhaust input data just in case there is still mouse data left // Status = EFI_SUCCESS; while (!EFI_ERROR(Status)) { Status = In8042Data (&Data); } gBS->CloseEvent (MouseDev->SimplePointerProtocol.WaitForInput); FreeUnicodeStringTable (MouseDev->ControllerNameTable); FreePool(MouseDev); gBS->CloseProtocol ( Controller, &gEfiDevicePathProtocolGuid, This->DriverBindingHandle, Controller ); gBS->CloseProtocol ( Controller, &gEfiSioProtocolGuid, This->DriverBindingHandle, Controller ); return EFI_SUCCESS; } /** Reset the Mouse and do BAT test for it, if ExtendedVerification is TRUE and there is a mouse device connected to system. @param This - Pointer of simple pointer Protocol. @param ExtendedVerification - Whether configure mouse parameters. True: do; FALSE: skip. @retval EFI_SUCCESS - The command byte is written successfully. @retval EFI_DEVICE_ERROR - Errors occurred during resetting keyboard. **/ EFI_STATUS EFIAPI MouseReset ( IN EFI_SIMPLE_POINTER_PROTOCOL *This, IN BOOLEAN ExtendedVerification ) { EFI_STATUS Status; PS2_MOUSE_DEV *MouseDev; EFI_TPL OldTpl; BOOLEAN KeyboardEnable; UINT8 Data; MouseDev = PS2_MOUSE_DEV_FROM_THIS (This); // // Report reset progress code // REPORT_STATUS_CODE_WITH_DEVICE_PATH ( EFI_PROGRESS_CODE, EFI_PERIPHERAL_MOUSE | EFI_P_PC_RESET, MouseDev->DevicePath ); KeyboardEnable = FALSE; // // Raise TPL to avoid keyboard operation impact // OldTpl = gBS->RaiseTPL (TPL_NOTIFY); ZeroMem (&MouseDev->State, sizeof (EFI_SIMPLE_POINTER_STATE)); MouseDev->StateChanged = FALSE; // // Exhaust input data // Status = EFI_SUCCESS; while (!EFI_ERROR(Status)) { Status = In8042Data (&Data); } CheckKbStatus (&KeyboardEnable); KbcDisableKb (); // // if there's data block on KBC data port, read it out // if ((IoRead8 (KBC_CMD_STS_PORT) & KBC_OUTB) == KBC_OUTB) { IoRead8 (KBC_DATA_PORT); } Status = EFI_SUCCESS; // // The PS2 mouse driver reset behavior is always successfully return no matter wheater or not there is mouse connected to system. // This behavior is needed by performance speed. The following mouse command only succeessfully finish when mouse device is // connected to system, so if PS2 mouse device not connect to system or user not ask for, we skip the mouse configuration and enabling // if (ExtendedVerification && CheckMouseConnect (MouseDev)) { // // Send mouse reset command and set mouse default configure // Status = PS2MouseReset (); if (EFI_ERROR(Status)) { Status = EFI_DEVICE_ERROR; goto Exit; } Status = PS2MouseSetSampleRate (MouseDev->SampleRate); if (EFI_ERROR(Status)) { Status = EFI_DEVICE_ERROR; goto Exit; } Status = PS2MouseSetResolution (MouseDev->Resolution); if (EFI_ERROR(Status)) { Status = EFI_DEVICE_ERROR; goto Exit; } Status = PS2MouseSetScaling (MouseDev->Scaling); if (EFI_ERROR(Status)) { Status = EFI_DEVICE_ERROR; goto Exit; } Status = PS2MouseEnable (); if (EFI_ERROR(Status)) { Status = EFI_DEVICE_ERROR; goto Exit; } } Exit: gBS->RestoreTPL (OldTpl); if (KeyboardEnable) { KbcEnableKb (); } return Status; } /** Check whether there is Ps/2 mouse device in system @param MouseDev - Mouse Private Data Structure @retval TRUE - Keyboard in System. @retval FALSE - Keyboard not in System. **/ BOOLEAN CheckMouseConnect ( IN PS2_MOUSE_DEV *MouseDev ) { EFI_STATUS Status; Status = PS2MouseEnable (); if (!EFI_ERROR(Status)) { return TRUE; } return FALSE; } /** Get and Clear mouse status. @param This - Pointer of simple pointer Protocol. @param State - Output buffer holding status. @retval EFI_INVALID_PARAMETER Output buffer is invalid. @retval EFI_NOT_READY Mouse is not changed status yet. @retval EFI_SUCCESS Mouse status is changed and get successful. **/ EFI_STATUS EFIAPI MouseGetState ( IN EFI_SIMPLE_POINTER_PROTOCOL *This, IN OUT EFI_SIMPLE_POINTER_STATE *State ) { PS2_MOUSE_DEV *MouseDev; EFI_TPL OldTpl; MouseDev = PS2_MOUSE_DEV_FROM_THIS (This); if (State == NULL) { return EFI_INVALID_PARAMETER; } if (!MouseDev->StateChanged) { return EFI_NOT_READY; } OldTpl = gBS->RaiseTPL (TPL_NOTIFY); CopyMem(State, &(MouseDev->State), sizeof (EFI_SIMPLE_POINTER_STATE)); // // clear mouse state // MouseDev->State.RelativeMovementX = 0; MouseDev->State.RelativeMovementY = 0; MouseDev->State.RelativeMovementZ = 0; MouseDev->StateChanged = FALSE; gBS->RestoreTPL (OldTpl); return EFI_SUCCESS; } /** Event notification function for SIMPLE_POINTER.WaitForInput event. Signal the event if there is input from mouse. @param Event event object @param Context event context **/ VOID EFIAPI MouseWaitForInput ( IN EFI_EVENT Event, IN VOID *Context ) { PS2_MOUSE_DEV *MouseDev; MouseDev = (PS2_MOUSE_DEV *) Context; // // Someone is waiting on the mouse event, if there's // input from mouse, signal the event // if (MouseDev->StateChanged) { gBS->SignalEvent (Event); } } /** Event notification function for TimerEvent event. If mouse device is connected to system, try to get the mouse packet data. @param Event - TimerEvent in PS2_MOUSE_DEV @param Context - Pointer to PS2_MOUSE_DEV structure **/ VOID EFIAPI PollMouse ( IN EFI_EVENT Event, IN VOID *Context ) { PS2_MOUSE_DEV *MouseDev; MouseDev = (PS2_MOUSE_DEV *) Context; // // Polling mouse packet data // PS2MouseGetPacket (MouseDev); } /** The user Entry Point for module Ps2Mouse. The user code starts with this function. @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 InitializePs2Mouse( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; // // Install driver model protocol(s). // Status = EfiLibInstallDriverBindingComponentName2 ( ImageHandle, SystemTable, &gPS2MouseDriver, ImageHandle, &gPs2MouseComponentName, &gPs2MouseComponentName2 ); ASSERT_EFI_ERROR(Status); return Status; }