提取CMOS中的时间信息在Shell中以固定格式显示并一直刷新
x86架构中有两种类型的IO,一种是内存映射的IO(即MMIO,Memory-mapped IO),另一种是端口映射IO(Port-mapped IO)。读取CMOS信息就是使用的端口映射IO,其中,端口0x70是用来设置CMOS中的数据地址,端口0x71是用来读取端口0x70设置的CMOS数据地址中的数据。
简单的说,0x70端口写入的是数据地址,从0x71端口读取的是数据,是从0x70端口写入的地址中读取的数据。
下面是从CMOS中读取系统时间,并按年/月/日 周几 时:分:秒显示,并一直刷新屏幕。
.c文件:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/TimerLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/IoLib.h>
#define CMOS_INDEX 0x70
#define CMOS_DATA 0x71
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
UINT32 flag = 0;
//定义秒、分、时、周几、日、月、年并初始化
UINT8 second = 0;
UINT8 minute = 0;
UINT8 hour = 0;
UINT8 weekday = 0;
UINT8 date = 0;
UINT8 month = 0;
UINT8 year = 0;
while (flag < 100) {
//写入数据地址并读取,将结果返回给定义好的变量
IoWrite8 (CMOS_INDEX, 0x00);
second = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x02);
minute = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x04);
hour = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x06);
weekday = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x07);
date = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x08);
month = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x09);
year = IoRead8 (CMOS_DATA);
Print (L"%02x/%02x/%02x %02x %02x:%02x:%02x\n", year,month,date,weekday,hour,minute,second); //用十六进制显示,之前用其他格式显示老出错
MicroSecondDelay(1000*1000); //每隔一秒显示一次
gST->ConOut->ClearScreen(gST->ConOut); //清屏
flag++;
}
return EFI_SUCCESS;
}
.inf文件:
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = Cmoswls
FILE_GUID = E4F3B912-F453-4AE8-B732-B8737229AB7A
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = ShellCEntryLib
[Sources]
CMOSwls.c
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
[LibraryClasses]
UefiLib
IoLib
TimerLib
ShellCEntryLib
UefiBootServicesTableLib
先说写代码时遇到的错误,读取CMOS信息并显示没有问题,出问题的是调用延时函数MicroSecondDelay时老是提示找不到对应的库。
错误信息如下:
build.py…
d:\myworkspace\edk2\ShellPkg\ShellPkg.dsc(…): error 4000: Instance of library class [TimerLib] is not found
in [d:\myworkspace\edk2\ShellPkg\WLS\CMOS\CMOSwls.inf] [X64]
consumed by module [d:\myworkspace\edk2\ShellPkg\WLS\CMOS\CMOSwls.inf]
之前一直以为调用某个函数只要写好头文件,并在.inf文件中的[LibraryClasses]下加上Lib就好了,但有的Lib还需要在包含你.c和.inf的Pkg下的.dsc文件里加上你需要的Lib信息,我的.c和.inf是在ShellPkg下,所以在ShellPkg.dsc里的[LibraryClasses.common]中加入了TimerLib | MdePkg\Library\SecPeiDxeTimerLibCpu\SecPeiDxeTimerLibCpu.inf这一句。TimerLib 是要用到的库,MdePkg\Library\SecPeiDxeTimerLibCpu\SecPeiDxeTimerLibCpu.inf是和TimerLib 对应的.inf文件,SecPeiDxeTimerLibCpu.inf里的.c文件DxeCoreTimerLib.c中包含了MicroSecondDelay函数。
在虚拟机上运行可以成功,在windows的模拟环境中会卡退,在物理机中加刷新屏幕函数就会很卡,体验不到屏幕刷新的效果。
这是在虚拟机下拍的运行结果:
Cmos
技术含量不高,后续会加上一些其他功能。
2021/8/26更新
这次更新解决了在物理机中刷新屏幕卡顿的问题,新加了检测到任意按键程序退出功能。原先每秒的延时功能由MicroSecondDelay改为定时器事件,当一秒内未检测到按键则刷新屏幕。
代码:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/TimerLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/IoLib.h>
#define CMOS_INDEX 0x70
#define CMOS_DATA 0x71
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
EFI_STATUS Status;
EFI_EVENT Events[2] = {0};
UINTN EventIndex = 0;
UINT8 second = 0;
UINT8 minute = 0;
UINT8 hour = 0;
UINT8 weekday = 0;
UINT8 date = 0;
UINT8 month = 0;
UINT8 year = 0;
//Events数组的0号事件为键盘事件
Events[0] = gST->ConIn->WaitForKey;
//生成定时器事件,并将事件对象赋值给Events数组的1号元素
Status = gBS->CreateEvent(EVT_TIMER , TPL_CALLBACK, (EFI_EVENT_NOTIFY)NULL, (VOID*)NULL, &Events[1]);
//设置定时器属性,1秒(10 * 1000 * 1000 * 100ns = 1s)钟后到期
Status = gBS->SetTimer(Events[1],TimerPeriodic , 10 * 1000 * 1000);
while (1) {
IoWrite8 (CMOS_INDEX, 0x00);
second = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x02);
minute = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x04);
hour = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x06);
weekday = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x07);
date = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x08);
month = IoRead8 (CMOS_DATA);
IoWrite8 (CMOS_INDEX, 0x09);
year = IoRead8 (CMOS_DATA);
Print (L"%02x/%02x/%02x %02x %02x:%02x:%02x\n", year,month,date,weekday,hour,minute,second);
//等待Events数组钟任一事件发生,EventIndex返回触发的事件在Events中的位置
Status = gBS->WaitForEvent (2, Events, &EventIndex);
if(EFI_SUCCESS == Status)
{
if(EventIndex == 0){
//键盘有输入
break;
}else if(EventIndex == 1){
// 1 秒内无键盘操作则刷新屏幕
gST->ConOut->ClearScreen(gST->ConOut);
continue;
}
}
}
Status = gBS->CloseEvent(Events[1]);
return Status;
}
这次的测试结果是在物理机上运行的(用U盘做的UEFI Shell环境,2021/8/27下午录制):
Cmos加按键定时器