之前研究过Shell下如何控制USB 键盘上的 LED【参考1】,这次研究如何实现 PS2 键盘的 LED 控制。
和 USB 键盘一样,PS2 键盘同样需要实现Shell 下键盘 LED 的同步。意思是如果系统中有一个 USB 键盘和一个PS2键盘,当USB键盘 Num 键按下后,键盘LED状态也要同步到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) & 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) & 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,
&gEfiSimpleTextInputExProtocolGuid,
NULL,
&HandleCount,
&DevicePathHandleBuffer);
//
//Open SimpleTextInputEx Protocol on each device
//
for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++)
{
Status = gBS->HandleProtocol(
DevicePathHandleBuffer[HandleIndex],
&gEfiSimpleTextInputExProtocolGuid,
(VOID**)&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 代码,所以必须在实体机上运行)
工作的视频:
参考:
- https://www.lab-z.com/stu120uk/ UEFI 下控制USB键盘 LED
- http://www-ug.eecg.toronto.edu/msl/nios_devices/datasheets/PS2%20Keyboard%20Protocol.htm The PS/2 Keyboard Interface
- https://wiki.osdev.org/PS/2_Keyboard PS/2 Keyboard