之前在写UEFI程序的时候如果想在终端打印信息,或者接收用户输入的时候调用SystemTable下的ConIn/ConOut protocol就ok了。

//向终端设备输出字符
Status = gST->ConOut->OutputString (gST->ConOut,L"hehe");
//接收输入设备的信息  EFI_INPUT_KEY Key;
Status = gST->ConIn->ReadKeyStorke (gST->ConIn,&Key);

这样就可以把信息发送到自己主机所有接着的输出终端上/或者读取所有的输入设备。但是当时有个疑问就是用户如何通过一个读取接口把所有的输入设备的输入内容读出来? 于是开始看spec 找到相关的介绍,那么下面就一起开始学习一下这个UEFI的feature —–Console Splitter


Console Splitter

可以参考Beyond BIOS 中关于Console Splitter 的具体介绍,这里就不贴原文了,只是简单的解释一下Console Splitter 的基本原理。就是UEFI 为了系统有多个输入/输出设备那么它提供了一种splitter/merging 的一种机制。简单地讲就是Console Splitter 把自己安装在gST 这个system table 上并且作为一个主要的Console 设备。那么这个Splitter driver 它会虚拟三个设备virtual ConIn , virtual ConOut, virtual ErrOut, 并且给这个安装相应的protocol。它会不停地检测这些输入输出设备,这些输入输出设备是由相应ConOut ,ConIn, ErrOut 这几个变量指定(热插拔设备除外),其实ConOut, ConIn, ErrOut,就是存放了指定设备的PATH,关于设备PATH之后博客会介绍。

如图片所示,Console Splitter 监视这些输入输出设备。

下面开始讲解一下这个Splitter driver的原理和实践过程。

(edk2/MdeModulePkg/Univeral/Console/ConSplitterDxe)

这里只贴有ConIn 的code, 而ConOut,ErrOut大同小异就不贴code
EFI_STATUS
EFIAPI
ConSplitterDriverEntry(IN EFI_HANDLE           ImageHandle,IN EFI_SYSTEM_TABLE     *SystemTable)
{EFI_STATUS              Status;//// Install driver model protocol(s).//Status = EfiLibInstallDriverBindingComponentName2 (ImageHandle,SystemTable,&gConSplitterConInDriverBinding,ImageHandle,&gConSplitterConInComponentName,&gConSplitterConInComponentName2);ASSERT_EFI_ERROR (Status);Status = ConSplitterTextInConstructor (&mConIn);if (!EFI_ERROR (Status)) {Status = gBS->InstallMultipleProtocolInterfaces (&mConIn.VirtualHandle,&gEfiSimpleTextInProtocolGuid,&mConIn.TextIn,&gEfiSimpleTextInputExProtocolGuid,&mConIn.TextInEx,&gEfiSimplePointerProtocolGuid,&mConIn.SimplePointer,&gEfiAbsolutePointerProtocolGuid,&mConIn.AbsolutePointer,NULL);if (!EFI_ERROR (Status)) {//// Update the EFI System Table with new virtual console// and update the pointer to Simple Text Input protocol.//gST->ConsoleInHandle  = mConIn.VirtualHandle;gST->ConIn            = &mConIn.TextIn;}}
  这就是Splitter driver的入口函数,首先在这个UEFI driver的Image上安装UefiDriverBindingProtocol。构造一个vistual ConIn device,然后在这个device 的handl上安装上TextIn ,TextInEx, SimplePointer, AbsolutePointer 相应的protocol. 看一下ConSpliterConInDriverBinding 的 Support 函数
EFI_STATUS
EFIAPI
ConSplitterConInDriverBindingSupported (IN  EFI_DRIVER_BINDING_PROTOCOL     *This,IN  EFI_HANDLE                      ControllerHandle,IN  EFI_DEVICE_PATH_PROTOCOL        *RemainingDevicePath)
{return ConSplitterSupported (This,ControllerHandle,&gEfiConsoleInDeviceGuid);
}
EFI_STATUS
ConSplitterSupported (IN  EFI_DRIVER_BINDING_PROTOCOL     *This,IN  EFI_HANDLE                      ControllerHandle,IN  EFI_GUID                        *Guid)
{EFI_STATUS  Status;VOID        *Instance;//// Make sure the Console Splitter does not attempt to attach to itself//if (ControllerHandle == mConIn.VirtualHandle  ||ControllerHandle == mConOut.VirtualHandle ||ControllerHandle == mStdErr.VirtualHandle) {return EFI_UNSUPPORTED;}//// Check to see whether the specific protocol could be opened BY_DRIVER//Status = gBS->OpenProtocol (ControllerHandle,Guid,&Instance,This->DriverBindingHandle,ControllerHandle,EFI_OPEN_PROTOCOL_BY_DRIVER);if (EFI_ERROR (Status)) {return Status;}gBS->CloseProtocol (ControllerHandle,Guid,This->DriverBindingHandle,ControllerHandle);return EFI_SUCCESS;
}

简单地分析这个函数,这个函数是测试所有的controller handles是否安装了gEfiConsoleInDeviceGuid, 如果support 函数返回success,然后就会调用start 函数。它会检测所有的controller handles 如果这些handles 上有安装device path 并且这个device 的path 和 ConIn 上文提到的变量它是一个mutilate instance path, 如果有匹配其中一个instance 的path 那么就会给这个controller安装这个guid, 热插拔的输入设备不会判断它的path 是否匹配ConIn 中的paths)。那么假设有一个设备它满足这support 函数的要求,所以Start 函数会被调用,下面看看start 函数。

EFI_STATUS
EFIAPI
ConSplitterStdErrDriverBindingStart (IN  EFI_DRIVER_BINDING_PROTOCOL     *This,IN  EFI_HANDLE                      ControllerHandle,IN  EFI_DEVICE_PATH_PROTOCOL        *RemainingDevicePath)
{EFI_STATUS                       Status;EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *TextOut;//// Start ConSplitter on ControllerHandle, and create the virtual// agrogated console device on first call Start for a StandardError handle.//Status = ConSplitterStart (This,ControllerHandle,mStdErr.VirtualHandle,&gEfiStandardErrorDeviceGuid,&gEfiSimpleTextOutProtocolGuid,(VOID **) &TextOut);if (EFI_ERROR (Status)) {return Status;}
}
EFI_STATUS
ConSplitterStart (IN  EFI_DRIVER_BINDING_PROTOCOL     *This,IN  EFI_HANDLE                      ControllerHandle,IN  EFI_HANDLE                      ConSplitterVirtualHandle,IN  EFI_GUID                        *DeviceGuid,IN  EFI_GUID                        *InterfaceGuid,OUT VOID                            **Interface)
{EFI_STATUS  Status;VOID        *Instance;//// Check to see whether the ControllerHandle has the DeviceGuid on it.//Status = gBS->OpenProtocol (ControllerHandle,DeviceGuid,&Instance,This->DriverBindingHandle,ControllerHandle,EFI_OPEN_PROTOCOL_BY_DRIVER);if (EFI_ERROR (Status)) {return Status;}//// Open the Parent Handle for the child.//Status = gBS->OpenProtocol (ControllerHandle,DeviceGuid,&Instance,This->DriverBindingHandle,ConSplitterVirtualHandle,EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER);if (EFI_ERROR (Status)) {goto Err;}//// Open InterfaceGuid on the virtul handle.//Status =  gBS->OpenProtocol (ControllerHandle,InterfaceGuid,Interface,This->DriverBindingHandle,ConSplitterVirtualHandle,EFI_OPEN_PROTOCOL_GET_PROTOCOL);if (!EFI_ERROR (Status)) {return EFI_SUCCESS;}//// close the DeviceGuid on ConSplitter VirtualHandle.//gBS->CloseProtocol (ControllerHandle,DeviceGuid,This->DriverBindingHandle,ConSplitterVirtualHandle);Err://// close the DeviceGuid on ControllerHandle.//gBS->CloseProtocol (ControllerHandle,DeviceGuid,This->DriverBindingHandle,ControllerHandle);return Status;
}

在ConSplitterStart函数里,它主要是返回一个接口,这个接口就每个device上各自的SimpleInProtocol 的接口。所以这个函数我们主要看

Status = gBS->OpenProtocol (ControllerHandle,InterfaceGuid,Interface,This->DriverBindingHandle,ConSplitterVirtualHandle,EFI_OPEN_PROTOCOL_GET_PROTOCOL);

其实OpenProtocol 可以这么理解DriverBindingHandle 就是这个driver 和 ConSplitterVirtualHandle 它俩打开了位ControllerHandle也就是device的handle上的SimpleInProtocol 这个protocol的接口。所以返回的Interface就是device上独自protocol接口。EFI_OPEN_PROTOCOL_GET_PROTOCOL,只是记录了ConSplitterVirtualHandle和ControllerHandle是子与父的关系。具体关于OpenProtocol请参考UEFI spec

接下来driver就会调用ConSplitterTextInAddDevic(&mConIn, TextIn); 把这个接口存放在一个数组里。如果有多个device那么这个数组就是存放的每个device的SimpleTextInputProtocol 的实例。

在ConSplitterDriverEntry function 里我们发现了
gST->ConsoleInHandle = mConIn.VirtualHandle ;
gST->ConIn = &mConIn.TextIn ;
所以前文开头提到的我们在App里读取输入设备的信息调用的统一接口就mConIn.TextIn 这个protocol提供的接口。那么接下来就来看看这些接口的实现:
(ReadKeyStroke 对应的就是ConSplitterTextInReadKeyStroke)
ConSplitterTextInReadKeyStroke里我们主要看ConSplitterTextInPrivateReadKeyStroke(Private, Key)在这个函数里我们看到一个for 循环它就是会调用各个device提供的接口。

for (Index = 0; Index < Private->CurrentNumberOfConsoles; Index++){Status = Private->TextInList[Index]->ReadKeyStroke (Private->TextInList[Index],&CurrentKey);
}

所以以上可以理解就是这个splitter In driver 查询所有的input device 如果找到就把device 上SimpleTextInputProtocol实现的接口统一放在一个数组里,当在应用程序去调用gST->ConIn->ReadKeyStroke的时候这个driver就会把所有device上的ReadKeyStroke函数都循环调用一遍。这个就是简单的流程,但是我们分析还没有结束,因为在读取的用户输入的时候我们必须把当前程序给阻塞住,来等待用户输入。所以一般UEFI 程序写读取输入信息的时候是这样:

EFI_STATUS
WaitForKeyStroke(IN OUT EFI_INPUT_KEY  *Key)
{EFI_STATUS  Status;UINTN       Index;while (TRUE) {Status = gST->ConIn->ReadKeyStroke(gST->ConIn, Key);if (!EFI_ERROR(Status)) {break;}if (Status != EFI_NOT_READY) {continue;}gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index);}return Status;
}

我们看到这个函数会调用gBS->WaitForEvent 它会把当前程序阻塞住不往下执行直到gST->ConIn->WaitForKey 这个event 被signal 起来才会往下执行。所以接下来,我们可以看一下这个event 在什么时候被signal起来。Trace code 我们发现这个event 是在ConSplitterTextInConstructor 这function 里被注册的

  Status = gBS->CreateEvent (EVT_NOTIFY_WAIT,TPL_NOTIFY,ConSplitterTextInWaitForKey,   ---callbackConInPrivate,    &ConInPrivate->TextIn.WaitForKey ---Event);

这里创建的是一个 EVT_NOTIFY_WAIT 类型的event,这个类型的event 意思就是当这个event 被WaitForEvent 或者 CheckEvent 的时候如果当时event没有被signal 起来的时候它的callback 就会调用,打算你二者有点区别,用WaitForEvent的时候如果这个event没有signal 的时候callback会一直被调用直到event被signal,而用CheckEvent 的时候callback 就会被调用一次。有兴趣可以看一下,WaitForEvent 内部就一个while循环然后不停的调用CheckEvent。
因为我们在应用程序里调用了 gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index); 所以ConSplitterTextInWaitForKey 这个function 就会不停地跑

VOID
EFIAPI
ConSplitterTextInWaitForKey (IN  EFI_EVENT                       Event,IN  VOID                            *Context)
{EFI_STATUS                    Status;TEXT_IN_SPLITTER_PRIVATE_DATA *Private;UINTN                         Index;Private = (TEXT_IN_SPLITTER_PRIVATE_DATA *) Context;if (Private->KeyEventSignalState) {//// If KeyEventSignalState is flagged before, and not cleared by Reset() or ReadKeyStroke()//gBS->SignalEvent (Event);return ;}//// If any physical console input device has key input, signal the event.//
for(Index = 0; Index < Private->CurrentNumberOfConsoles; Index++){Status=gBS->CheckEvent (Private->TextInList[Index]->WaitForKey);if (!EFI_ERROR (Status)) {gBS->SignalEvent (Event);Private->KeyEventSignalState = TRUE;}}}

我们可以看到它里面有一个for 循环,还是去检查这个数组里记录的input device 他们的waitforkey 的event, 如果它们之中有Event被signal起来的时候它们这个函数就会调用gBS->SignalEvent (Event) 来把自身的Event给signal 起来,所以这个callback 就不会被继续调用了。那么各自device的event其实是它们相应driver 写好的,当有信息输入的时候它们各自的event就会被signal起来,我们我们这里调用CheckEvent 的时候就会返回True,我们的callback就会结束继续调用。

所以到这里我们把Console Splitter 中关于ConIn的function就分析完了,其他的几个driver 基本原理是一样的。

下一篇我们来实践一下,要不然看一遍就是走马观花。下篇,我们会自己写一个虚拟的Input device 然后通过binding 这个 Console Splitter driver ,我们就可以在应用程序里读取这些输入信息。

引用了 Beyond BIOS Second Editon 中关于Splitter 中的内容

UEFI Console Splitter相关推荐

  1. 著名ai换脸网站_AI如何从著名的死去艺术家那里删除新音乐

    著名ai换脸网站 Love it or hate it, the new Star Wars trilogy made history. Forget about film critics - I'm ...

  2. 用树莓派搭建公网个人下载平台aria2-pro,推荐6个优质种子资源站

    很早zhaoolee就想搭个人下载站,趁着今年国庆时间充裕,我把下载站搭建到了树莓派上,并对公网开放:在任何地点,我只需通过网页提交下载任务,家中的树莓派就会自动把我需要的资源,日夜不间断地下载到我的 ...

  3. UEFI Application

    UEFI Application是一种EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION类型的EFI Image.os loader 是一种特殊的application,执行完成后 ...

  4. UEFI原理与编程实践--UEFI工程模块文件

    标准应用程序工程模块 该模块是其他应用程序工程模块的基础,也是UEFI中常见的一种应用程序工程模块,标准应用程序工程模块,UefiMain就是这个模块的入口函数 EFI_STATUS EFIAPI U ...

  5. golang第三方日志包seelog配置文件详解

    开发任何项目,都离不开日志,配好自己的项目日志输出,往往是开发项目的前提.在golang中,seelog应该是比较有名的日志处理包了,功能非常强大,seelog官方文档 一.seelog主要功能 下面 ...

  6. udk开发-稀里糊涂

    一.EDK2简介 1.EDK2工作流 ​ 二.EDK2 Packages 1.Packages介绍 ​ EDK2 Packages是一个容器,其中包含一组模块及模块的相关定义.每个Package是一个 ...

  7. go第三方日志系统-seelog-Basic sections

    https://github.com/cihub/seelog 文档学习:https://github.com/cihub/seelog/wiki 1.安装: go get github.com/ci ...

  8. SMBIOS Table

    这里我借鉴了其他的一些博客 https://blog.csdn.net/zhangliang19950813/article/details/105842364 https://www.lab-z.c ...

  9. EDK II Module Writers Guide下

    三.常见UEFI Module类型 1.UEFI APP ​ UEFI Application是EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION类型的EFI Image. UEF ...

最新文章

  1. java查询mysql装载bean_jsp与javabean链接mysql数据库并查询数据表的简单实例源码
  2. Linux下getopt函数的使用
  3. linux下有关phy的命令,linux – 如何为Debian安装b43-lpphy-installer?
  4. NSRunLoop详解
  5. java Random.nextInt()方法
  6. OpenGL伽玛校正测试
  7. java循环控制_Java - 循环控制(Loop Control)
  8. 【JS新手教程】LODOP打印复选框选中的任务或页数
  9. MacOS 12.0.X系统提示“未能装载磁盘映像,错误代码为109”的临时解决方法
  10. 多路径配置udev_多路径multipath配置,udev绑定
  11. error: ‘VPX_IMG_FMT_RGB32’ undeclared (first use in this function); did you mean ‘VPX_IMG_FMT_NV12’?
  12. 载入java VM时出错216_Android6.0中oat文件的加载过程
  13. Linux+Nginx+SpringBoot+War环境下websocket部署遇到的问题
  14. EasyChair提交会议论文的方法简介
  15. 【短视频运营】短视频制作流程 ( 视频存稿 | 写脚本 | 拍摄收音 | 提词器 | 后期剪辑 | 前测工具 | 检查违禁词 )
  16. RocketMQ 内存优化
  17. CLM陆面过程模式实践技术应用
  18. 【边做项目边学Android】手机安全卫士10-设置向导之绑定SIM卡
  19. alibaba.jym.item.external.goods.batch.offsale( 交易猫外部商家批量下架商品接口 )
  20. 靠追热点出圈,网易传媒打造“爆款制造机2.0”

热门文章

  1. Babel转码器(ES6)
  2. 微信小程序内跳转公众号
  3. 基于区块链的去中心化抗量子密钥管理系统
  4. ISO3834国际焊接认证简介
  5. 《复联4》在中国首映的 阴谋
  6. 怎样防止苹果系统更新_苹果xs换过原装屏幕可以更新14系统吗?
  7. 迁移学习系列--领域泛化
  8. 克服焦虑--图解JVM内存模型和JVM线程模型
  9. 万年历日程提醒c语言,Android 日历 万年历 源代码(支持日程提醒)
  10. 华为HarmonyOS鸿蒙2.0系统安装谷歌框架play商店GMS,mate30/40系列,p40,p50,nova5 6 7 8,荣耀30/40/50/magic3/9x