UEFI开发探索12 – Oprom测试板

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

今天拿到了测试板,可以把UEFI Oprom写到硬件里面去了。手有点痒,想写一个程序来试试。

测试用的板子非常简单,去除了所有实际的硬件控制元器件,只留下了WCH366和一个128K的ROM。不过按照沁恒电子的资料,WCH366只支持64K的寻址,而且还不能同时寻址,意味着我们只有32K的空间可以写代码。

做了十个,足够我折腾很长时间了。

图1 测试用的小板卡
继续阅读“UEFI开发探索12 – Oprom测试板”

2,917 total views, 2 views today

UEFI开发探索11 – 鼠标前传

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

这不是写鼠标的历史,而是记录在很久以前,在Legacy BIOS下,我写鼠标驱动(BIOS/DOS下运行)的过程。

我对底层编写程序,从参加工作的时候就非常着迷。当时接手隔离卡5.0的开发维护,主体产品已经完成,我一直思考:怎么才能让产品和别家的不同呢?

介绍下背景知识:隔离卡V5.0是公司第一款采用PCI Oprom开发的产品,插上就可以出界面。省去了用户安装软件的过程,一推出就大受欢迎。我们采用的是沁恒电子的WCH365,代码也参考他们的DEMO开发的。很快,对手使用同样的芯片,有了同样的方案。

就是在这种情况下,我希望找到新的竞争点。突然想到,市场上所有的PCI Option ROM上的界面,都是支持键盘的,没有支持鼠标的。这不就是一个切入点吗?如图,这是当时的隔离卡。我工作的笔记本没带回来,图是从网上找到的,这么多年,没想到还能找到。

图1 易思克隔离卡v5.0

有了想法,执行起来也不容易。

问题1: 鼠标肯定是在图形模式下运行的,怎么无缝的将鼠标显示在目前的界面?
问题2: 针对鼠标的驱动代码量不能大,ROM只有32K,现在出货的产品已经有21.5K,留给鼠标驱动的就没有多少空间,必须控制代码量;
问题3: 我知道DOS下有int33h可以控制鼠标,也有大量的例子可以参考。可惜我们是在BIOS上运行的,无法调用;

继续阅读“UEFI开发探索11 – 鼠标前传”

1,697 total views, 3 views today

UEFI开发探索10 – 再论键盘

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

突然想起一句诗:戍客望边色,思归多苦颜。也许是天气原因,也许是出差,心情总是不好。

上海的天空,灰蒙蒙的。一眼望去,都是淡墨色的乌云,不沉重,也透不过阳光。窗外的风很小,茂盛的树叶一动不动。除了传来的汽车声音,几乎要怀疑时间停止了一样。

没有阳光的天气,让人心情比较压抑。

继续阅读“UEFI开发探索10 – 再论键盘”

2,240 total views, 2 views today

UEFI开发探索09 – 图形显示02

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

上次对UEFI的图形模式有了一定的了解,根据得到的信息,我准备用1024×768的分辨率来运行程序。

这个分辨率我比较熟悉,以前的代码(legacy bios下)可以复用。在Vesa标准中,有多个模式是这个分辨率的(我最常用的是105h)。如图:

图1 VESA graphics

那么UEFI的这个分辨率对应的是哪个模式呢(UEFI 下的模式索引值为3)?虽然都是从EDID中读出来的,不过经过了UEFI 的重组,我也搞不清到底是对应哪个。从blt的操作方式来看,可能是118h;不过似乎其他的模式也能类似操作。

算了,不纠结了,什么时候去跟下代码就知道了。

继续阅读“UEFI开发探索09 – 图形显示02”

2,083 total views, 2 views today

UEFI开发探索08 – PE/COFF

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

前几天有人问起我,对UEFI的格式文件的理解。

嗯,我基本上快把PE格式忘光光了,那是很多年前,搞反汇编,混看雪学院时搞的事情。当时花了几个月,专门学习软件加密、加壳、扰乱反汇编等等。然后,又花了几周,破解了几个dll,扔给工程师去用,就再也没有接触过了。

闲言少叙。既然忘记了,就花点时间去回忆一下吧。上午的会议开完,下午有两个小时空档,找找以前的日志和资料。

继续阅读“UEFI开发探索08 – PE/COFF”

3,440 total views, 2 views today

UEFI开发探索07 – 关于SMBUS的开发故事

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

这篇博客曾经发表在高端调试的论坛上,我把它转移到这里来了。UEFI访问SMBUS设备,是我计划中要写的,这篇就作为其前言吧,其中没有任何与UEFI相关的内容。

当时我已经开发完了C850F320上的smbus访问代码(作为smbus device)、Legacy BIOS下的smbus访问代码(在dos下调试,运行于OptionRom上),期望在windows上开发一个驱动,能通过smbus直接访问C850F320。这能让目前产品的价格降低20元,以当时的出货量,一年的成本可以节省40w,很可观的。

我把这篇博客拷贝过来了,绝不是因为今天上午要开会,没时间写了。说不是就不是!

以下为原文:

继续阅读“UEFI开发探索07 – 关于SMBUS的开发故事”

6,099 total views, 3 views today

光伏云项目杂记02 – 构建Ringbuff(环形缓冲)1

在做这个项目的时候,遇到一个问题。我们使用的STMF103C8T6,资源很可怜,看看:

图1 STM32F103C8T6的参数

72M的频率,内存只有20K。而这个芯片需要做的事情:

1 3个串口的收发;
2 3个灯的控制,让用户通过灯的闪烁判断处于什么状态,方便维护;
3 GPRS模块的协议管理;
4 与逆变器端的协议管理;
5 看门狗的任务处理;
6  服务器端协议的处理,包含应用端口和配置端口;

以FreeRTOS为架构,估计用了近20个任务来完成这些工作。每个任务给的堆栈是0.5K,加上串口需要的缓冲区,一番操作下来,内存基本用光光。一起开发的兄弟,暂且称为烟枪吧(抽烟很快,1分钟一根,当得起这个称呼)。每天嚷嚷,说内存严重不够用,不能让客户再提新的需求了。虽然通过map文件计算了每个任务所用的栈空间,尽可能不浪费。不过再改的话,程序就直接溢出了,问题出在哪都不知道。

我很同意他的看法,也很同情我们的处境。可是,客户又不是我家的,他想提要求,我又控制不住。何况有些要求还是很中肯的,咱们都是讲道理的,对吧?

那怎么办?

分析源代码,我盯上了串口处理的这段代码。三个串口,除去串口接收发送所需要建立的任务外,每个串口还用了256字节的缓冲区。另外,程序写得耦合性比较差,估计换个芯片,所有代码都得重写。

程序逻辑是这样的:

  1. 将缓冲区分为两段,A段128字节,B段128字节;
  2. 串口接收启用DMA传输,不占用CPU资源;
  3. 当触发串口接收DMA中断,中断处理程序将缓冲区指向A段,接收数据;
  4. 接收完毕后缓冲区指向B段;
  5. 有一个任务专门监听,发现有数据立即取走;
  6. 新的数据来了,DMA接收中断处理程序用B段缓冲区取走数据,收完后缓冲区指针再指向A段。

也就是说,当监听任务在处理数据的时候,DMA接收中断使用了另外一段缓冲区,大家互不干涉(当然程序中也利用了FreeRTOS的互斥量来实现)。

这样实现其实没有太大问题,因为精心计算了串口和DMA的处理时间。接收串口的数据越长,越不会出现问题。

问题就出在小数据上。如果串口接收大量的小数据,频繁的触发中断,CPU就有可能丢失数据了。(实际运行中没有出现过,连续发送1个字节的情况,在客户环境中可认为概率是0)

烟枪和我商量了一段时间,我们决定使用ringbuffer(环形缓冲)来实现。他把这个任务轻飘飘的交给我了,调他的板子去了。

没办法,开始干活吧。 Ringbuffer的基本原理,我大概了解。维基百科中也有说明:https://en.wikipedia.org/wiki/Circular_buffer

A 24-byte keyboard circular buffer. When the write pointer is about to reach the read pointer – because the microprocessor is not responding, the buffer will stop recording keystrokes and – in some computers – a beep will be played.

我能用的资源有限。必须实现原有的串口接收,不能丢数据;同时还有提供尽可能简单的接口,供应用层的函数调用。

因此,我选择使用Contiki中的代码,用它的核心思想来构建这个架构。当时估计错误,我不觉得能有多难,本以为一周左右能搞定。没想到对芯片的DMA以及串口不那么熟悉,整整花了我三周时间。

这是一段痛并快乐的回忆。

3,476 total views, no views today

UEFI开发探索06 – 图形显示01

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

到了我最喜欢的环节了,图形显示!

我曾经写过不少关于图形显示的博客和论文,特别是在Legacy BIOS以及嵌入式设备中的显示。PC的显示一直都遵循着VESA的标准,在底层的访问还是比较一致。虽然在多年的开发中,也在一些主板上出现过奇怪的现象(一次而已。Award某款主板,设置显示模式的时候,花屏。在调用设置显示模式的函数前,多压几次堆栈,问题又消失了。)。但当年写的核心显示代码,运行了近10年了,也没出现过显示的问题。

这是我曾翻烂过的参考书:

图1 参考书

还有这些…

以及《最新VESA SVGA图形图像编程秘技》 李军著、《C语言游戏编程从入门到精通》,还有图像格式,主要是BMP、PCX和jpeg。哦,还有一本雷军(没错,我就是当年用过这本书,才成为米粉的)和求伯君的《深入DOS编程》。

写得最扎实的是《IBM PC的原理及应用》以及《PC技术内幕》,想了解底层显示原理的可以参考一下。虽然我觉得这些知识有点过时了,但对理解还是有帮助的。

继续阅读“UEFI开发探索06 – 图形显示01”

3,156 total views, no views today

UEFI开发探索05 – 搭环境遇到了问题

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

这两天妻子去阜宁,孩子没有带过去。我只能在陪孩子之外,腾出一点点时间,来搭建UDK2017/UDK2018的编译环境。

大部分的时间都在装VS,试了两个版本,VS2013和VS2015。我不知道是不是我的版本有问题,总是不成功。Ubuntu16.04的也没有安装成功,让人颇为恼火。总计来回折腾了12个虚拟机,真是不甘心。

下周开始又没有太多时间了,几个案子都在催着完成,有大量的工作要去做。两个专利也还没写,前期的资料都还没收集完。思来想去,我仍旧以UDK2010来编译这些代码吧。主要的目的是熟悉这些API和UEFI的架构,我目前没有开发BIOS,太细节的暂时不必理会。

插一句,为了方便调试,我请同事做了一些带有PCI-E扩展ROM的卡,主芯片是CH366,下周应该可以到我手上了。对于进入操作系统之前,可以跑自己的程序,我一直很热衷:总想脱离操作系统做点什么坏事。→_→

比如针对操作系统核心的几个文件ntldr.dll,gdi32.dll等,使用Oprom对其认证,防止篡改。或者和杀毒软件配合,进入OS前做些事情。整个硬盘都可以控制,可以预先做很多动作。

又扯远了,回到搭建编译环境的事情吧。

我主要使用Vmware搭建虚拟机,开发用的操作系统为win10。中间的各种小问题就不说了,基本上都解决了。主要是两个问题,没有找到解决办法。

1. VS2013+UDK2017,VS2013全部安装,WDK8.0和WDK8.1的目录都有。编译Nt32Pkg无法通过。如图1。

图1 UDK2017+VS2013,编译NT32Pkg报错

2. VS2015+UDK2018,Nt32Pkg编译通过,AppPkg编译不通过。本系列Blog中的“UEFI开发探索03-环境搭建2”曾经描述过安装过程。编译AppPkg的报错如下:

图2 UDK2018+VS2015 编译AppPkg报错

这段时间以把程序编译运行为主,就用UDK2010,尽快把博客写完。和张佩去年就约定要把Foxdisk和UEFI的开发记录下来,一直到现在还没搞定,甚是惭愧。

搭建环境的问题,留待日后吧,看是否能凑些边角料的时间来找找原因。O__O

如果哪位朋友知道怎么回事,也望不吝赐教,感激不尽。:)

1,960 total views, 1 views today

UEFI开发探索04 – 与键盘的互动

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

我当年学习UEFI的最终目标是实现UEFI Oprom,写入PCI-E ROM,作为隔离卡的底层软件运行。中间的一个目标是编写UEFI driver/application,与bios一起编译,同样是作为底层软件,与另外一款卡通讯。当时是与联想共同开发了一款安全隔离计算机,中间目标就是为这款产品而设的。

最后两个目标都实现了,当年刚做的时候还是比较辛苦的。第一个面临的问题就是使用哪种程序入口。有三种可选:UefiMain,main,ShellAppMain。

参照当时能找到的资料,《Beyond BIOS》、《Harnessing the UEFI Shell》以及UEFI spec(当年看的是2.3,现在都到2.9了),还有bios之家、网友的博客,终于摸到了一些门径。

图1 曾经使用过的参考资料

当年的我并不了解这些程序入口的区别,是最近写博客的时候看《UEFI原理与编程》时才有所了解(终于有本中文版的参考资料了!感谢作者戴正华,有机会真想拜见)。

我的理解,main和ShellAppMain肯定是不能用在Option ROM上的。那么多的链接库,一是没法放进去,二是即便可以放进去,工作量也太大了。简单来说,就如同我之前开发的Legacy Option ROM一样,只能调用int 10h,int 13h,int 16h等这些bios中断,想调用int 33h这些DOS中断,你只能把整个DOS弄进去(更别提那个时候可能BIOS都还没准备好,放进去也运行不了)。

不过,使用main的程序入口,可以很方便的进行Shell下的测试。所以在实现这第一个程序的时候,我选择了这种编译方式。

如图,使用的是以下的API(UEFI spec 2.8 Page431):

图2 访问键盘所用API

API还是很好理解的,看EFI_KEY_DATA的数据结构,与int 16h 0号功能返回的是一样的。我不知道UEFI是怎么处理硬件中断9的,估计也是封装在了这些API中。

程序编写相对比较容易,gBS和gST是预置的,直接使用就可以了。(代码在文末提供下载地址)根据API的说明,我编写了一个获取键盘输入的函数GetKeyEx,将用户输入的键盘信息保存下来。

上述的工作相对比较简单,有点麻烦的是准备编译。使用main函数的工程模块需要在AppPkg环境下才能成功编译。

为方便后续的开发,我在根目录下建立了一个\_LuoApp的文件夹。在\AppPkg\AppPkg.dsc中添加相应的inf文件,指明要编译的代码,如图所示。

图3 建立编译用的目录

(20190512 20:13 Robin: 这次的编译仍旧使用了UDK2010的环境。我试图搭建UDK2017或UDK2018,都不成功。有的NT32Pkg可以编译,但是AppPkg无法编译通过;有的则连Nt32Pkg都编译不通过。VS2013和VS2015都试过了,怀疑是我VS版本有些问题。)

修改target.txt,设置为如下参数:

ACTIVE_PLATFORM       = AppPkg/AppPkg.dsc
TARGET_ARCH           = IA32
TOOL_CHAIN_TAG        = VS2008

打开Visual Studio 2008 command,编译:

C:\MyEorkSPace>edksetup.bat
C:\MyEorkSPace>build
或者build -p AppPkg\AppPkg.dsc -a IA32 -t VS2008 (不需要修改target.txt了)。

生成的文件为c:\myWorkSpace\Build\AppPkg\DEBUG_VS2008\IA32\_LuoApp\Luo2\Luo2\OUTPUT\Luo2.efi

将其拷贝至之前编译好的NT32下(主要是可以使用SecMain.exe):

c:\myWorkSpace\Build\NT32\DEBUG_VS2008\IA32\

运行SecMain.exe,测试Luo2.exe:

图4 编译好的键盘处理程序

代码放在百度云上,对照看下,逻辑还是比较简单的,将用户的按键信息打印出来。这个程序是以main入口函数来编写的,另外也编写了一个UefiMain入口函数的程序,功能一样,也在云端。

至此,实现了键盘的访问。如需要在实际机器上运行,只要修改编译参数即可。

百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23

键盘访问的代码在 01 ReadKeyEx下。

2,796 total views, 1 views today

UEFI开发探索03 – 环境搭建2

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

从上次写完博客,过去了一个多月。中间到深圳出差,解决一个硬盘加密的项目,时间就这么过去了。等回过神来,发现写博客的计划又往后拖了。

下定决心,我想在这周搞定编译环境。

以前我是在XP下,使用UDK2010来编译的,比如这个:

图1 VS2008+UDK2010开发的uefi application

我原本的计划是用Ubuntu 16来搭建环境。最近正习惯在上面用vim和vscode编程,戒掉用了多年的UE和sublime。尝试了一天,总是遇到各种问题,特别是到现在也没有也没搞明白,GCC在安装过程中不知怎么变为了7.x。

这么下去,这周的计划又完成不了。我回到了用windows安装的老路,预备安装UDK2018。

还是折腾了一天,搞定了。把安装步骤和中途遇到的问题记录下来吧。

这是参考用的几个网址:

https://github.com/tianocore/edk2/releases

https://github.com/tianocore/tianocore.github.io/wiki/UDK2018-How-to-Build#how-to-build-windows-system

先吐槽一下,文档写得并不友好,我的时间全花在了编译Base Toosls上。实际上可以不用自己去编译Base Tools,直接下载就可以了。地址如下:

https://github.com/tianocore/edk2-BaseTools-win32

我是在读UDK2017的文档UDK2017 How to Build时发现的。

安装步骤(Win10下安装):

1. 安装 VS2015。我安装了图中所示的项,Python Tools for Visual Studio我觉得应该是不需要的,当时顺手选上了。

图2 VS2015的安装

2. 安装Python 2.7.16,同时选择将Python加入Path(安装的时候需要选择);

3. 安装Nasm 2.12.01,注意选择安装路径为C:\Nasm。在我的电脑-属性-高级系统设置-高级-环境变量…-系统变量-Path中添加此路径;

4. 安装Openssl工具和ASL工具(应该不需要,这是用来生成Base Tools用的);

5. 解压edk2-vUDK2018.zip到C:\MyWorkspace;

6. 添加系统变量PYTHON_HOME=c:\Python27;(补充一句,添加编译工具路径以及系统变量,可以在edksetup.bat中添加,比如:
Path = c:\nasm;%path%
Set PYTHON_HOME=c:\python27
这样就不需要像步骤3和6一样操作了,对我这种强迫症比较合适);

7. 打开“VS2015 x64 Native Tools Command Prompt”,运行edksetup.bat –nt32,会生成/Conf下所需的一些文件; 8. 执行build -p NT32Pkg\Nt32Pkg.dsc -a IA32 -t VS2015x86 run,启动SecMain.exe。

8. 执行build -p NT32Pkg\Nt32Pkg.dsc -a IA32 -t VS2015x86 run,启动SecMain.exe。

图3 UDK2018的模拟器

我的安装过程并不顺利,遇到了两个问题,记录如下:

问题1:python编译中找不到cxFreeze的module,无法通过编译。

解决:cmd打开,输入pip install cx_freeze。(注意Path中要包含Python的路径,否则无法执行)。

问题2:提示 File “GenFds\GenFds.py”, line 24, in <module>
ValueError: Attempted relative import in non-package

解决:将路径BaseTools\Bin\Win32下GenFds.exe更名为GenFds.LABZ,并将BaseTools\BinWrappers\WindowsLike;添加到Path路径中。
(此问题的解决参照http://www.lab-z.com/udk2018coming/https://github.com/tzz1996/Blog/blob/master/compile_udk2018_in_windows.md,感谢!)

至此,终于可以继续探索UEFI了。明天开始将之前写过的例子一个个编译运行,加深理解。至于Ubuntu的开发环境,找时间再做吧。毕竟ubuntu更轻量级点,windows上光VS2015就占了12G。我都是在虚拟机下开发,这么多开发环境,空间严重不足了。

(20190512 20:40 Robin: Nt32Pkg是可以编译,可是AppPkg没有编译成功,目前没有找到原因,编译的问题在 UEFI开发探索05中列出,后面找时间再解决。暂时还是以我之前的UDK2010来编译吧)

5,140 total views, 1 views today

UEFI开发探索02 – 环境搭建1

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

开发初期的目的就是做出可以在pci rom上跑的Oprom,当然是在uefi bios下。我的计划大致如下:

1 搭建完整的编译环境,了解使用哪些库进行编译;

2 我的设备最常用的几种总线通讯方式:PCI总线、smbus总线和usb总线,如何通过这些总线访问设备,必须要解决;

3 图形显示,我最感兴趣的,首要解决的是如何画点。PC的架构还是一样的,硬件原理差不多,还是有页、显示模式等概念,不过uefi api把很多细节屏蔽了;

4 键盘输入,必不可少。鼠标的功能,试试看吧;

5 uefi下可以使用网络的功能,这很吸引人。以前为了加快产品测试,在dos下开发网络通信功能,最头疼的是要去找各种网卡的dos驱动,新的网卡基本都不怎么提供了。写这篇博客的时候,我还没去实现过,后面尝试一下;

6 硬盘访问;

7 编译一个可以放在pci rom上的oprom。以前的还原卡、硬盘加密等,都可以用这种方式来实现了。

计划大概就是如此,主线是做Oprom,支线是如何访问各种设备,以应用为目的,对uefi进行探索。

先看下我常用的两款设备,用来运行实际代码的。

图1 带有smbus与usb hid通讯功能的切换卡

图2 带PCI-E扩展ROM的切换卡
继续阅读“UEFI开发探索02 – 环境搭建1”

Foxdisk11-小字库显示汉字2

刚从大学毕业那会,对操作系统极其入迷,总想搞清楚底层是怎么运行的。其中最感兴趣的是图形的显示,BIOS对硬件的控制等。找了很多资料看,正好公司的一些项目上也需要用到,就这么磕磕碰碰的实现了各种显示的代码。

Foxdisk的代码中,Vesa.c和Vesa.h就包含了显示的所有信息。主要是遵循了vesa的标准进行显示,采用C嵌汇编的方式来编写。目前,几乎所有的机器都支持VESA标准,而在X86平台下编程就是基于此标准的。VESA标准在原有BIOS提供的API的基础上,提供了一组扩展BIOS功能调用。对于标准VGA下的模式,仍然可以使用基本BIOS调用,但对于扩展模式,其许多操作如模式设置及视频缓冲读写等,则只能通过VESA标准提供的扩展BIOS来实现。

在VESA标准下,视频缓冲是以分页映射的方式进行操作。其基窗口一般为在0xA0000处,每页为64K。整个视频缓冲都通过映射到此窗口而得以直接存取。一般的图形编程步骤如下:

  • 设置显示模式(如0x103为 800×600 256色的配置)
  • 设置颜色寄存器
  • 按照图形显示的原理编写画点、画线、画圆等基本函数。

过多的细节不用纠结,Vesa.c中提供了画点函数putpixel256()。把屏幕当做一个大画布,设置模式的时候知道这块画布多大,用画点函数绘制即可。Foxdisk使用的是模式0x105,也即1024×768,256色。想像一下,有张宽1024像素点,长768像素点的画布,有支可在画布任何坐标处画点的笔,应该是什么画都可以画出来了,包括汉字。

Foxdisk中显示汉字,大致可以分为两步:

  1. 提取需要的汉字字模,保存到程序内部;
  2. 调用Font.c中显示汉字的函数,将需要的汉字显示在屏幕上。

Vesa.c和Font.c中屏蔽了很多显示的细节,特别是对各种字体及字体大小的处理。在设计之初,考虑去兼容各种不同的字体,比如黑体、楷体、宋体等等。代码中针对不同的字体,提供了各种编译用的开关。实际程序中,主要用了16×16宋体和24×24楷体,从提取汉字库的批处理命令可以看出来。

在博客“Foxdisk09-工具篇”中,已经初略的介绍了提取汉字库的工具了。我是为了能自动针对所有用到汉字的代码,自动提取汉字字模,开发的这些小工具。下面具体介绍下如何提取字库。

所谓无字库技术,就是在程序中建立一个类似字库的字库数组来代替字库。程序中建立的字库某种意义上独立于程序中显示程序。也就是存在这样的可能:单独对字库数组进行压缩,在使用的时候重新解压。这个特性在嵌入式的应用中非常有用,可以节约不少的程序空间。

一般说来,提取汉字字库的步骤应该是这样的:

1) 对包含需要提取汉字的源文件进行文法分析,析取出需要提取字模的汉字

2) 对提取出的汉字再次分析,并去除重复的汉字打开相应的汉字库,提取需要的字模,并按规则形成新的字库数组

3) 对照上一篇博客的说明,设计16×16及24×24的汉字字模结构体。

struct       hzk16_typ{         /*  汉字字模结构体  */

  unsigned int code;

  unsigned int array[16];

};

struct       hzk24_typ{         /*  汉字字模结构体  */

  unsigned int code;

  unsigned char array[3*24];

};

最终会将字模以上述数据结构的方式存储,Foxdisk中存储生成的文件为HZTABLE.H和HZK24.H。

具体的提取代码可以参照ehz24.c和etrhz.c,这两个文件的代码量都在400行左右。核心工作在于分析指定的文件,从中提取出需要转换字模的汉字。

为了便于分析,也为了程序编写简单,对指定的文件是有要求的。必须保证在提取的文件中字符串中没有//和/* 字符,否则可能会提取错误。提取程序并没有去进行字符串内部的语义分析,否则要处理的情况太多了。 代码就不贴出来了,放不下。对照编程的想法,以及代码,还是比较容易看明白运作的机制的。

UEFI开发探索01-起篇

2013年的时候,和联想的双网安全隔离计算机项目进入了平稳阶段。经过了前几年的合作开发,几代不同的产品的磨合,Q45、G41、H61等,整个方案已经成熟。再开案的话,我觉得大部分问题都会集中在硬件方面,软件问题不会太多。

不过,实际上软件还留下一个问题需要想办法解决,那就是Option ROM对UEFI的支持。在2012年出货的产品中,因为时间比较紧,为了把Option ROM移植到UEFI下(联想2011年左右基本都转换为UEFI BIOS),我采用了一种比较省事的方法,用AMI的VBE来开发。主要是考虑到有问题可以去请教联想的BIOS工程师,这是种比较保险的做法。

因此,即便我了解intel提供了UDK可以直接进行开发,我仍旧走了上述的路线。当时的开发很难,从VBE新手到开发出符合要求的UEFI driver(实际上就是个option rom),花了我三个礼拜,像打了鸡血一样不眠不休的完成了。

在此要感谢当时还在Intel的Raymond,张银奎老师,帮我向intel bios部门咨询解决了显示的一个问题。以及我刚出生没多久的宝贝,因为她我才在北京待不住,用了最快的速度解决了这个项目问题,就为了早一刻回南京抱她。

所以,2013年的时候,摆在我面前的问题是,如何用intel UDK把Option ROM开发出来。一方面用在联想的合作项目上;另外一方面,把它用在公司的另外一个产品-隔离卡上。

随着UEFI越来越普及,未来肯定会只支持UEFI,当时隔离卡上面的Legacy oprom会无法运行的。几年过去了,这个论断终于实现了。现在是2019年,公司就是因为当时的这个技术设计,今年3月还没过完,半个月的销量,超过了去年同期一个月的销量了。我甚是欣慰。

完成这个目标的时间就比较充裕了,毕竟和联想的案子已经有一个可用的软件版本了(用VBE开发的),而隔离卡这边的需求还没到时候。

仿照之前学习底层开发的过程,我制定了一个小计划,从控制键盘、图形显示、鼠标显示、访问PCI端口、SMBUS通讯、USB访问、网络访问….这样的顺序开始尝试写代码。

虽然因为各种原因,在完成了最初的目标(支持公司产品在UDK下的开发)后,有些工作没有继续了。但我觉得还是基本上把UEFI下的开发摸了个遍,即便要开发其他的产品,现有的代码也能用得很好。

图1 robin’s Uefi 代码列表

我写的博客虽然是从Foxdisk开始的,但在计划中,UEFI的开发就已经位列其中了。

今天终于如愿开始了,记录我挥汗编程的日日夜夜,也纪念我当时用来在家开发,如今已经退役的老朋友,它使用了“很古老” 的华硕945主板,跟了我那么多年,终于没有机会再使用它了。

是为开篇。

(原文地址:
http://yiiyee.cn/blog/2019/03/20/uefi%e5%bc%80%e5%8f%91%e6%8e%a2%e7%b4%a201-%e8%b5%b7%e7%af%87/ )

Foxdisk10-小字库显示汉字1

在以前的博客中曾经讨论过,设计的Foxdisk代码段和数据段总共只能128K。可是随便哪个汉字库轻轻松松都超过了100K,大的有近500K(我设计的时候只用了24×24以及16×16的字模),无论如何是不能包含在程序中的。唯一的办法,是将需要用的汉字字模提取出来,实现小字库的汉字显示。

当然,如果改变Foxdisk的设计方法,比如采用很久以前用过的一种技术:在硬盘的末端存一个裁剪的DOS或者linux,用OS来支持。现在遇到的存储空间不够啊、想实现的功能难以实现啊,都能解决。并且还能复用大量的DOS/Linux的软件,还是很不错的。不过,这就失去了我当时设计的初衷了,本来就是用来学习底层知识的一个小项目,用这种方法则屏蔽了大量的实现细节,所以我就放弃了。不过,在公司早期的一款产品中(卖了好几年,技术支持巨难,后来被我停掉了)是用了这种方法的,有机会也开个博客仔细探讨下。

文字显示主要包括文字的读取和显示两个步骤。西方文字显示完全可以集成在一个ROM内,但是中文字太多,通常都要用到字库。中文字库有两大类型:点阵式字库和矢量字库。

点阵式字库是通过将中文字看成一个个点组成的二维阵列来实现显示;矢量字库则通过对文字每个笔划的起始点和结束点的记录来完成文字显示。很明显,点阵字如果要放大文字将会出现明显的不平滑现象;而矢量字无论字的大小都可以保证字体圆滑。但是由于点阵字理解简单、便于编程等原因,在很多领域点阵字库成为了主流。

当前只针对点阵字库进行讨论研究,并针对研究的各字体库进行分析,阐述其在编程上的一些区别。在此基础上,提供了无字库方案的实现过程及代码。 点阵字的显示原理其实很简单。在规定大小的区域格内,如16×16、24×24等,将需要显示的字的对应格填充即可。字库内对应的格则以1表示,没有填充的以0表示。如图所示:

图1 英文字模图样

这是英文字模的图样,而中文字模的图样是这样的:

图2 中文字模的图样

图中的字模信息就存储在相应的字库中,而程序员就可以按照显示原理在显示器上以点位的方式显示字符。

每个英文字母都是由一个ASCII码组成的,而中文字是扩展ASCII码组成的。也就是说一个中文字是由左半边和右半边两部分的信息组成的。字库中的文字一般是按区位码的形式存放的,大体原理相同,但是在实际组织的时候有些细微的差别。[1]

本文所讨论的字库有HZK16(16×16汉字库)、HZK24S(24×24宋体)、HZK24H(24×24黑体)、HZK24F(24×24仿宋)、HZK24K(24×24楷体)、HZK24T(24×24汉字全角字符)。

对于24×24以上的汉字字库,比如48×48汉字字库,其存储方式与24×24 的相同,只是大小不同,简单修改就可适用,以下将作详细说明。

HZK16的中汉字存储可以用公式表示如下:

  • 区码 = 文字左半边信息(扩展ASCII码) – 0xA0
  • 位码 = 文字右半边信息 – 0xA0
  • 在字库中的地址 = [(区码-1) *94 + (位码-1)] * 32

其中1、2步减去0xA0的原因是中文字符用到的扩展ASCII码是从0xA0之后开始计算的,94的来源是字库中每个区最多存放94个中文字点阵。另外,因为HZK16是16×16字体,每个字由32个字节组成(16位x16)。[1]

24×24字体库中文字库与 16×16字体库的组织有些不同。HZK24T中已经存放了汉字的全角字符,其余的各种字体的汉字库16区之前是独立在此的。也就是说,对HZK24S、HZK24H、HZK24F和HZK24K,公式如下:

  • 区码 = 文字左半边信息(扩展ASCII码) – 0xA0
  • 位码 = 文字右半边信息 – 0xA0
  • 在字库中的地址 = [(区码-1- 15) *94 + (位码-1)] * 72

注意在第三步要减去15,前15区的字符存放在HZK24T中了。72表示24×24字库中汉字是按72字节存储的(24位x24)。

对于更大的字体,如32×32、48×48等,也是按照这样的方式存储。只是在第3步的计算中,32×32字体库中汉字占用 128字节,48×48字体库中汉字占用288字节。

汉字的点阵结构介绍完了,后面就面临着怎么提取字模以及显示的问题。实际上,在工具篇中已经有所涉及提取的问题了,下面两篇博客中详细描述。

光伏云项目杂记01—modbus串口丢包了

和深圳某家公司合作光伏云的项目,我们主要做数据传输模块,对方负责开发云端,光伏逆变器外采。针对不同的应用场景,做了三款产品。来来回回用了一段时间,反响还不错,和其他同类产品相比还比较稳定。图一是其中一款用得较多的数据传输模块。

图1 物联网透传设备-gprs dtu

前一段时间现场的工程师报了一个问题,处理过程比较有趣,就记录了下来。

数据传输模块(也即数据透传模块,简称DTU)安装在逆变器上,通过串口把逆变器的数据接收过来,DTU通过GPRS(2.5G)转发给服务器。DTU最重要的是不能断网,任何情况下导致断网都必须重新连接后台,保证数据的连续性。

工程师报告,使用组态软件连接DTU(串口连接),把数据发送到服务器。数据是按包的方式定时发送的,每过5分钟会有一个包丢失。有多个设备通过组态软件连接DTU,也就是说有很多包丢失了。

接到报告后,我开始着手搭建环境,模拟现场。工程师使用的是modbus slave,我找他要来相应的版本软件。DTU与一台小的Mini台式机相连,在我自己的阿里云上运行自己写的网络调试工具。

数据包一个个发出去,几个小时下来,没有出现丢包的现象。把modbus slave换成串口调试助手,也没有出现丢包现象。我把数据包调大,大概200字节一次发送,这次出现了。

联系后台软件工程师,用他的云平台配合modslave以及DTU配合测试。其中DTU与台式机通过USB转串的设备连接,后面也尝试直接用串口线连接。经过20小时的测试,现象不大一致。使用USB转串设备进行测试的,仍旧有拆包现象;使用原生串口的,没有发现拆包现象。不过,对比了服务器的后台记录,基本没有拆包,也有命令包直接就丢失了。

百思不得其解。要后台的工程师配合我,协调起来很麻烦。我回到阿里云,用自己的网络软件模拟后台。同时把modbus slave撤了,用自己以前的代码写了个串口调试的工具,继续测试。这回,不管是usb转串的还是原生串口连接,都没有出现丢包拆包。光伏云项目中,数据获取是有后端服务器软件发起的,由其发送命令包,通过DTU传送给设备,设备再通过DTU发给服务器。在测试中还发现一个现象,如果服务器发送命令包向设备要求数据,时间频率如果设为每5秒发一次,丢包现象还会出现;设置为30秒一次,则机会没有出现丢包过。

看起来有好几个问题混杂在一起,产生了这样奇怪的现象。首先,排除DTU本身固件的问题,DTU中采用了DMA机制,以单片机的处理速度,这点串口数据不应该会处理不了。最大的问题应该有两个:

1 modbus slave和串口调试助手在串口读写的编程中,有哪个地方没有处理好,导致了拆包的现象;

2 至于丢包现象,和服务器端的软件有关。工程师可能习惯了平常的internet,没有考虑到GPRS的网络稳定性远低于有线或者wifi网络。不能过于频繁的去塞数据。

分析完了,寻找证据。第二个问题好解决,请后端工程师改下通讯频率就行了,回头一起测试。 至于第一个问题,只能反汇编modbus slave了。很幸运,用IDA反汇编后,我读了半小时代码,找到了问题所在,如图2。

图2 用IDA反汇编modbus slave

问题出在SetCommTimeouts函数。定位到SetCommTimeouts函数的调用位置,其前三个参数分别为ReadIntervalTimeout=0xffffffff;ReadTotalTimeoutMultiplier=0x00; ReadTotalTimeoutConstant=0x00。也就是说,modbus slave读取一次缓冲区,读操作就完成了。

这样操作是有点问题的,对照我自己的代码,把三个参数分别改为:0x64,0x0A,0x3E8。这些修改是把exe当做文件编辑的,相当于在字节码上直接修改了。

Modbus slave的软件hack完了,重新搭建环境。4个小时后,测试结果显示,修改后的modbus slave没有出现拆包丢包现象。