叶汉能,姚茂群,赵武锋
(1.杭州师范大学信息科学与工程学院,浙江杭州310036;2.浙江大学信息与电子工程学系,浙江杭州310027)
引导装载程序是嵌入式系统底层的引导软件,主要作用是引导嵌入式操作系统。Linux建议引导装载程序最基本的功能包括:初始化RAM,初始化一个串口,检测处理器类型,建立并向内核传递参数列表,引导内核[1]。但以U-Boot为代表的通用型引导装载程序的功能已远远超过了这些,它们扩展了多种其他功能,可以方便地在不同开发平台上进行移植[2],降低了开发难度。闪存是目前嵌入式系统中最常用的非易失性存储器件[3],因为其读写方式的特殊性,需要相应驱动的支持。通用型引导装载程序需要支持多种类型的闪存,因此通用的闪存驱动非常有价值,而目前常用的通用型引导装载程序缺乏明确、通用的闪存驱动[4],在移植时通常要针对特定闪存芯片进行修改。本文基于内存技术设备(Memory Technology Device,MTD)的设计思想,提出一种适合通用型引导装载程序的闪存驱动设计方案。
闪存是目前嵌入式系统中的主流存储器件,主要有NOR Flash和NAND Flash两种类型。因此通用型引导装载程序一般需要在移植时方便地支持这两种类型的闪存。
NOR Flash分别提供地址总线和数据总线,读操作类似于随机存取存储器,程序可在片内执行。为了避免每个闪存设备都要不同的驱动,闪存制造商提出了公共闪存接口标准,使得系统软件可方便地查询闪存内的参数。NAND Flash为大容量内存的实现提供了良好的解决方案[5],它共用地址线与数据线,内部数据以块为单位存储,因此无法像NOR Flash一样简单地进行数据的随机访问。读写操作可以页为单位进行,但擦除操作必须以块为单位。
Linux的MTD设备是基于闪存抽象出来的,主要目的是为存储器件设计通用的子系统,在硬件驱动和文件系统间提供通用的接口[6,7],通过该子系统存储设备驱动的开发更加简单。
传统的Unix系统只识别块设备和字符设备,字符设备的访问以字节流的方式进行,块设备以块为单位进行。闪存并不符合两者要求,它以块为单位访问,但与块设备不同的是它区分写和擦除。MTD设备专门针对闪存的特点而产生。块设备和MTD设备的对比如表1所示。
表1 块设备和MTD设备对比
MTD技术将从面向文件系统的接口到底层硬件的基本操作之间的内容分为4层:设备节点、MTD设备层、MTD原始设备层和硬件驱动层[7]。MTD层的结构图如图1所示。
图1 MTD层结构图
对MTD设备的操作,首先需要确定设备号,以此来确定操作的MTD设备对象。Linux中建立了MTD字符设备节点和MTD块设备节点,因此通过设备节点层可访问对应的MTD设备。
MTD设备层分成字符设备和块设备[8]。节点层的节点号对应相应的设备层设备,每一个设备层设备又对应相应的原始设备层设备。MTD字符设备注册了一系列统一的文件操作函数,如open、read、write等,这些函数基于原始设备层的基本操作实现。MTD块设备则定义了结构mtdblk_dev来对自身进行描述,并通过一个mtdblks数组管理mtdblk_dev。
MTD为每一个原始设备定义结构体mtd_info来描述其操作函数及信息。所有原始设备的mtd_info结构体用mtd_table指向的结构体数组统一管理。原始设备与具体设备的映射由结构体map_info描述,它包括了指向驱动程序结构体mtd_chip_driver的指针。
硬件驱动层负责驱动具体的闪存设备,为上层提供不同闪存设备最基本的操作。
参照Linux中MTD技术的设计思路,本驱动方案主要考虑4点:驱动结构的分层,各层包括的内容及相互间关系的确定,读、写、擦除等函数的建立,初始化过程的实现。
(1)驱动结构的分层
通用型引导装载程序中闪存的主要操作有读、写、擦除等,不需要抽象出字符设备和块设备。因此本方案层次划分为设备层、原始设备层和硬件驱动层。
(2)各层中的主要内容及相互关系
设备层为上层应用提供了统一的操作接口:read()、write()、erase()等,这些函数根据特定的原始设备信息调用原始设备层结构体mtd_info中相应的函数实现。
原始设备层主要为每个Flash设备抽象出一个原始设备,定义原始设备结构体mtd_info来组织原始设备对应的信息和操作函数。该结构体的主要成员如下:
mtd_info结构体中type域指示了该原始设备层设备的类型,函数指针(如*read、*write等)根据该类型指向不同设备在原始设备层的操作函数。
mtd_info结构体的priv指针指向该原始设备层设备对应的具体闪存芯片的硬件驱动层结构体,如NAND Flash的结构体主要成员如下:
硬件驱动层结构体中的函数指针指向具体闪存的最基本操作函数。
(3)读、写、擦除等函数的建立
设备层的通用接口函数调用原始设备层的读、写、擦除等函数实现,而这些原始设备层的函数基于硬件驱动层的基本操作函数实现。设备驱动层的基本操作函数则需要根据具体闪存芯片,采用专门的访问方式来实现。
(4)驱动的初始化
图2 驱动框架及主要结构体
初始化过程由初始化函数完成。针对闪存参数的初始化,本驱动以结构体的方式为每种被支持的闪存封装特定参数,并将所有这些封装后的参数结构体组成结构体数组,作为一张闪存信息表。根据闪存标准接口设计扫描函数,用于在初始化时读取闪存内固化的参数,或者读取制造商ID并与信息表对照,得到相应的闪存参数,完成驱动中闪存参数的初始化。这种方法可以使得驱动支持的芯片在初始化时被自动识别,增加了驱动的灵活性和通用性。
本文驱动方案的框架以及主要结构体之间的关系如图2所示。
根据本驱动设计方案开发引导装载程序,并分别在2套开发平台下进行了验证:平台1中CPU为Samsung公司的以ARM920T为内核的S3C2440芯片,闪存芯片为Samsung公司的K9F1208 NAND Flash;平台2中CPU为Samsung公司的以ARM920T为内核的S3C2410芯片,闪存芯片为Intel公司的28F128J3A NOR Flash。采用GDB和JTAG为基础的工具链进行调试,关键代码处单步执行。调试结果证明驱动能自动识别不同芯片,并对相应芯片提供良好的支持。引导装载程序的闪存驱动程序在平台1上调试过程中函数的调用流程如图3所示。
图3 驱动程序函数调用流程图
常用的引导装载程序在使用时一般在要针对系统中具体的闪存芯片进行相应的配置和修改,编译后往往只支持同类型的个别芯片。本方案设计的驱动提供自动识别闪存芯片的能力,按照实际闪存芯片完成驱动的初始化,在不进行重新配置的前提下,扩展了对不同种类闪存芯片的支持。由于引导装载程序功能的扩展,高层模块需要涉及更多的策略,因此本方案运用Linux中MTD技术的设计思想,结合通用型引导装载程序的特点,进行了合理的分层,从而对高层屏蔽了低层实现的细节。该方案结构清晰,通用性好,易于升级和维护。
[1] Wolfgang Rominger.Intelligent sensor data logging utilizing Linux[EB/OL].http://www.imp-ortwro.at/romeo/images/Publications/abschlussarbeit.pdf,2011 - 01 - 05.
[2] 王亚刚.嵌入式Bootloader机制的分析与移植[J].计算机工程,2010,36(6):267-269.
[3] 高冰,杨名利,沈毅,等.基于ARM9的恒压恒速双缸泵控制系统[J].计算机工程,2009,35(12):211-213.
[4] 刘浩,邹雪城,李鹏,等.无线传感器网络终端引导程序设计[J].计算机工程,2010,36(2):33-35.
[5] 王立峰,胡善清,刘峰,等.基于闪存的高速海量存储模块设计[J].计算机工程,2011,37(7):255-257.
[6] 韦斯,丁志刚,张伟宏.Linux下UBI子系统的研究与应用[J].计算机应用与软件,2010,27(10):68-71.
[7] 唐卫明.大容量NAND闪存存储管理研究[D].长沙:国防科学技术大学,2009:32-42.
[8] Woodhouse D.Memory Technology Device(MTD)Subsystem for Linux[EB/OL].http://www.linux-mtd.infradead.org/doc/general.html,2008 - 10 - 17.