Step to UEFI (214)Ps2键盘的LED 分析

之前研究过Shell下如何控制USB 键盘上的 LED【参考1】,这次研究如何实现 PS2 键盘的 LED 控制。

和 USB 键盘一样,PS2 键盘同样需要实现Shell 下键盘 LED 的同步。意思是如果系统中有一个 USB 键盘和一个PS2键盘,当USB键盘 Num 键按下后,键盘LED状态也要同步到PS2 键盘上。

PS2键盘

对应的代码可以在\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c 的 UpdateStatusLights 函数中看到。根据代码基本想法如下在这个头文件中有如下定义\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2Keyboard.h:

typedef struct {
  UINTN                               Signature;

  EFI_HANDLE                          Handle;
  EFI_SIMPLE_TEXT_INPUT_PROTOCOL      ConIn;
  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL   ConInEx;

  EFI_EVENT                           TimerEvent;

  UINT32                              DataRegisterAddress;
  UINT32                              StatusRegisterAddress;
  UINT32                              CommandRegisterAddress;

  BOOLEAN                             LeftCtrl;
  BOOLEAN                             RightCtrl;
  BOOLEAN                             LeftAlt;
  BOOLEAN                             RightAlt;
  BOOLEAN                             LeftShift;
  BOOLEAN                             RightShift;
  BOOLEAN                             LeftLogo;
  BOOLEAN                             RightLogo;
  BOOLEAN                             Menu;
  BOOLEAN                             SysReq;

  BOOLEAN                             CapsLock;
  BOOLEAN                             NumLock;
  BOOLEAN                             ScrollLock;

  BOOLEAN                             IsSupportPartialKey;
  //
  // Queue storing key scancodes
  //
  SCAN_CODE_QUEUE                     ScancodeQueue;
  EFI_KEY_QUEUE                       EfiKeyQueue;
  EFI_KEY_QUEUE                       EfiKeyQueueForNotify;

  //
  // Error state
  //
  BOOLEAN                             KeyboardErr;

  EFI_UNICODE_STRING_TABLE            *ControllerNameTable;

  EFI_DEVICE_PATH_PROTOCOL            *DevicePath;
  //
  // Notification Function List
  //
  LIST_ENTRY                          NotifyList;
  EFI_EVENT                           KeyNotifyProcessEvent;
} KEYBOARD_CONSOLE_IN_DEV;

先找到系统中的全部  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL ,检查 Signature 确定后再使用下面的宏即可获得KEYBOARD_CONSOLE_IN_DEV结构体

#define KEYBOARD_CONSOLE_IN_DEV_FROM_THIS(a)  CR (a, KEYBOARD_CONSOLE_IN_DEV, ConIn, KEYBOARD_CONSOLE_IN_DEV_SIGNATURE)
#define TEXT_INPUT_EX_KEYBOARD_CONSOLE_IN_DEV_FROM_THIS(a) \
  CR (a, \
      KEYBOARD_CONSOLE_IN_DEV, \
      ConInEx, \
      KEYBOARD_CONSOLE_IN_DEV_SIGNATURE \
      )

之后即可用UpdateStatusLights() 函数实现更改LED 状态。

最终完整代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/IoLib.h>

#include  "Ps2Keyboard.h"

extern EFI_BOOT_SERVICES        *gBS;
extern EFI_SYSTEM_TABLE         *gST;
extern EFI_RUNTIME_SERVICES     *gRT;

EFI_GUID gEfiSimpleTextInputExProtocolGuid = 
  {0xdd9e7534, 0x7762, 0x4698, 
      { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } };
                       
UINTN
EFIAPI
MicroSecondDelay (
  IN      UINTN                     MicroSeconds
  )
{
  gBS->Stall(MicroSeconds);
  return MicroSeconds;
}

/**
  Write data register.

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Data      value wanted to be written

**/
VOID
KeyWriteDataRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Data
  )
{
  IoWrite8 (ConsoleIn->DataRegisterAddress, Data);
}

/**
  Read data register .

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return return the value

**/
UINT8
KeyReadDataRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )

{
  return IoRead8 (ConsoleIn->DataRegisterAddress);
}

/**
  Read status register.

  @param ConsoleIn  Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return value in status register

**/
UINT8
KeyReadStatusRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )
{
  return IoRead8 (ConsoleIn->StatusRegisterAddress);
}

/**
  wait for a specific value to be presented on
  8042 Data register by keyboard and then read it,
  used in keyboard commands ack

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Value     the value wanted to be waited.

  @retval EFI_TIMEOUT Fail to get specific value in given time
  @retval EFI_SUCCESS Success to get specific value in given time.

**/
EFI_STATUS
KeyboardWaitForValue (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Value
  )
{
  UINT8   Data;
  UINT32  TimeOut;
  UINT32  SumTimeOut;
  UINT32  GotIt;

  GotIt       = 0;
  TimeOut     = 0;
  SumTimeOut  = 0;

  //
  // Make sure the initial value of 'Data' is different from 'Value'
  //
  Data = 0;
  if (Data == Value) {
    Data = 1;
  }
  //
  // Read from 8042 (multiple times if needed)
  // until the expected value appears
  // use SumTimeOut to control the iteration
  //
  while (1) {
    //
    // Perform a read
    //
    for (TimeOut = 0; TimeOut < KEYBOARD_TIMEOUT; TimeOut += 30) {
      if (KeyReadStatusRegister (ConsoleIn) &amp; 0x01) {
        Data = KeyReadDataRegister (ConsoleIn);
        break;
      }

      MicroSecondDelay (30);
    }

    SumTimeOut += TimeOut;

    if (Data == Value) {
      GotIt = 1;
      break;
    }

    if (SumTimeOut >= KEYBOARD_WAITFORVALUE_TIMEOUT) {
      break;
    }
  }
  //
  // Check results
  //
  if (GotIt == 1) {
    return EFI_SUCCESS;
  } else {
    return EFI_TIMEOUT;
  }

}

//
//\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c
//
/**
  write key to keyboard

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Data      value wanted to be written

  @retval EFI_TIMEOUT   The input buffer register is full for putting new value util timeout
  @retval EFI_SUCCESS   The new value is sucess put into input buffer register.

**/
EFI_STATUS
KeyboardWrite (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Data
  )
{
  UINT32  TimeOut;
  UINT32  RegEmptied;

  TimeOut     = 0;
  RegEmptied  = 0;

  //
  // wait for input buffer empty
  //
  for (TimeOut = 0; TimeOut < KEYBOARD_TIMEOUT; TimeOut += 30) {
    if ((KeyReadStatusRegister (ConsoleIn) &amp; 0x02) == 0) {
      RegEmptied = 1;
      break;
    }

    MicroSecondDelay (30);
  }

  if (RegEmptied == 0) {
    return EFI_TIMEOUT;
  }
  //
  // Write it
  //
  KeyWriteDataRegister (ConsoleIn, Data);

  return EFI_SUCCESS;
}

//
//\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c
//
/**
  Show keyboard status lights according to
  indicators in ConsoleIn.

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return status of updating keyboard register

**/
EFI_STATUS
UpdateStatusLights (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )
{
  EFI_STATUS  Status;
  UINT8       Command;

  //
  // Send keyboard command
  //
  Status = KeyboardWrite (ConsoleIn, 0xed);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  KeyboardWaitForValue (ConsoleIn, 0xfa);

  //
  // Light configuration
  //
  Command = 0;
  if (ConsoleIn->CapsLock) {
    Command |= 4;
  }

  if (ConsoleIn->NumLock) {
    Command |= 2;
  }

  if (ConsoleIn->ScrollLock) {
    Command |= 1;
  }

  Status = KeyboardWrite (ConsoleIn, Command);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  KeyboardWaitForValue (ConsoleIn, 0xfa);
  return Status;
}


int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS    Status;
  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL       *ConIn;
  KEYBOARD_CONSOLE_IN_DEV              *ConsoleIn;
  UINTN         HandleIndex, HandleCount;
  EFI_HANDLE    *DevicePathHandleBuffer = NULL;
  UINTN         i;
  //
  //Get all the Handles that have SimpleTextInputEx Protocol
  //
  Status = gBS->LocateHandleBuffer(
                  ByProtocol,
                  &amp;gEfiSimpleTextInputExProtocolGuid,
                  NULL,
                  &amp;HandleCount,
                  &amp;DevicePathHandleBuffer);
  //
  //Open SimpleTextInputEx Protocol on each device
  //
  for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) 
  { 
    Status = gBS->HandleProtocol(
                      DevicePathHandleBuffer[HandleIndex],
                      &amp;gEfiSimpleTextInputExProtocolGuid,
                      (VOID**)&amp;ConIn);
    if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Open ConIn fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }
    
    //Get KEYBOARD_CONSOLE_IN_DEV by SimpleTextInputEx Protocol    
    ConsoleIn = TEXT_INPUT_EX_KEYBOARD_CONSOLE_IN_DEV_FROM_THIS (ConIn);
    
    //Check the signature if it's what we want    
    if (ConsoleIn->Signature==KEYBOARD_CONSOLE_IN_DEV_SIGNATURE) {
            // Turn Off all LEDs
            ConsoleIn->CapsLock=0;
            ConsoleIn->NumLock=0;
            ConsoleIn->ScrollLock=0;
            UpdateStatusLights(ConsoleIn);
                    
            for (i=0;i<20;i++) {
                    ConsoleIn->NumLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->NumLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    
                    ConsoleIn->CapsLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->CapsLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    
                    ConsoleIn->ScrollLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->ScrollLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                }
   } //if (ConsoleIn->Signature
  } //for (HandleIndex = 0;

  gBS->FreePool(DevicePathHandleBuffer);  
  return Status;
}

完整代码和X64 EFI 下载(因为含有 IO 代码,所以必须在实体机上运行)

工作的视频:

参考:

  1. https://www.lab-z.com/stu120uk/  UEFI 下控制USB键盘 LED
  2. http://www-ug.eecg.toronto.edu/msl/nios_devices/datasheets/PS2%20Keyboard%20Protocol.htm The PS/2 Keyboard Interface
  3. https://wiki.osdev.org/PS/2_Keyboard PS/2 Keyboard

发表评论

电子邮件地址不会被公开。 必填项已用*标注