UEFI开发探索33 – 再谈串口通信

UEFI系列博客的第20篇中,曾经尝试过构建串口通信的代码。发送串口数据在当时已经实现了,可是没有找到如何判断是否有可读的数据,读取串口没有成功。偶尔能读到串口数据,只能证明读函数起作用了。

在多年的开发经历中,我曾经开发过各种MCU的串口代码,也在DOS和Legacy BIOS下开发过。读取串口,基本都是使用中断(或者配合DMA)方式。Windows下的串口,虽然表面是Windows的消息机制,驱动层还是使用了中断。

开发过一款串口通信的隔离卡,我构建了Legacy BIOS下的底层串口和单片机的串口程序:

图1 Legacy BIOS:串口通信

虽然寄存器众多,不过核心思想还是不变:设置串口参数,设定中断处理程序,然后建立读写的函数。

可是,UEFI下只提供了Event的方式,而SerialIO Protocol中并没有对应的事件函数(类似键盘的WaitForKey)。该怎么解决这个问题?

这个礼拜一直在出差,翻来覆去都在思考这个问题。

1 调试大法

最开始的想法,从OVMFPkg入手。

在第20篇中,我曾经用自制的工具pipetool,配合虚拟机,与虚拟机内运行的SecMain.exe建立了串口通信。宿主机使用命名管道,虚拟机内使用串口。

这就证明,Nt32Pkg内部是支持串口读写的,而且直接用来输出调试信息。而且Nt32Pkg代码开源,可以一段段跟踪。OvpmfPkg理论上应该也是一样的。

世事不尽如人意。如图:

图2 试图跟踪OvmfPkg

Qemu搭建的调试环境直接报错。也许是串口模拟没有做好导致,总之,没法继续调试了。

而OvmfPkg的结构我还没有弄清楚,想定位到我想要的代码,还需要很多知识的补充。

这就意味着,逆向学习这条路,目前是走不通的。还是走正向的道路,仔细研究研究UEFI Spec,自己来构建吧。

2 Serial IO Protocol的GetControl

我仔细读了UEFI Spec,唯一能使用的只有GetControl中提供的几个标志。

图3 UEFI Spec: GetControl

从SerialIo.h中知道,与读相关的标志有6个,分别为EFI_SERIAL_CLEAR_TO_SEND、EFI_SERIAL_DATA_SET_READY、EFI_SERIAL_RING_INDICATE、EFI_SERIAL_CARRIER_DETECT、EFI_SERIAL_INPUT_BUFFER_EMPTY、EFI_SERIAL_OUTPUT_BUFFER_EMPTY。

我写了测试程序,把这些位的值全部打印出来。最终把怀疑点集中在EFI_SERIAL_INPUT_BUFFER_EMPTY位上,当有数据可以读取的时候,此位会变为1。

对照《PC技术内幕》这本古老的神书,上述的位,也可以与串口的信号对应起来。我手上只有电子书,很不清晰。等回南京后,拿纸质书一个个再研究下,估计会理解更为深刻。

3 构建代码

因为没有中断机制,只能采用循环查询的方式或者使用Event建立定时查询。为了编程方便,我没有使用Event,直接循环查询。

图4 构建代码

代码的逻辑很直接,发送两次字符串以测试串口通信正常。然后进入循环,当接收到的数据第一个为’Q’时,退出循环;否则不断去查询EFI_SERIAL_INPUT_BUFFER_EMPTY是否为0,也即是否有数据需要读。

之后只要接收数据即可。

需要注意的是,我是使用虚拟机配合PipeTool的方式来测试,所以对串口参数都没有进行设置,直接使用了缺省参数。而在实际编程中,还是要设定波特率、停止位等参数的。

另一个需要注意的是,必须考虑复数串口的情况。比如,在我的测试中,虚拟机实际上使用了串口2来进行通信。我在代码中设定了一个长度为256的数组来保存枚举到的串口,并将工作用的串口指针指向了串口2。

4 运行

编译之后,按照UEFI开发探索20的介绍,搭建测试环境。

图5 运行

程序运行得不错,能够将从宿主机发过来的信息抓到,打印出来了。

还有一个小问题,偶尔会出现发过来的数据,没有接收到。当退出程序后,那些数据又在TianoCore的模拟器上打印出来了(模拟环境也使用此串口输出信息)。

从测试的信息,我猜测UDK中的串口应该是使用FIFO方式在运作。至于这个问题,在实际环境中是否仍存在,我是存疑的。

待之后更多了解OvmfPkg,找到对应代码的位置,再来看看吧。

百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
文件在 23 SerialPort-RW 下

319 total views, 2 views today

发表评论

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