5.1. 硬件配置和组成 系统实现必须以实际的硬件为基础:当硬件改变时,局部可能要做适当的改变。实际系统硬件配置见表5.1:
 表5.1 硬件配置表 硬件组成与其它系统不同的是显示部分,存储卡,时钟部分,SIM卡,等。也包括了通用的字符设备和网络设备。 目前的显示屏是640*480,125DPI,16位色的TFT液晶屏。因为芯片支持VGA口,所以显示屏可以被换成将来的电子纸。(电子纸是一种超薄、超轻的显示屏,表面看起来与普通纸张十分相似,可以像报纸一样被折叠卷起,但实际上却有天壤之别。它上面涂有一种由无数微小的透明颗粒组成的电子墨水,颗粒直径只有人的头发丝的一半大小。这种微小颗粒内包含着黑色的染料和一些更为微小的白色粒子,染料使包裹着它的透明颗粒呈黑色,那些更为微小的白色粒子能够感应电荷而朝不同的方向运动,当它们集中向某一个方向运动时,就能使原本看起来呈黑色的颗粒的某一面变成白色。根据这一原理,当这种电子墨水被涂到纸、布或其它平面物体上后,人们只要适当地对它予以电击,就能使数以亿计的颗粒变幻颜色,从而根据人们的设定不断地改变所显现的图案和文字,这便是电子墨水的神奇功效。当然,电子墨水的颜色并不局限于黑白两色,只要调整颗粒内的染料和微型粒子的颜色,便能够使电子墨水展现出五彩缤纷的色彩和图案来[21])。 加入电子纸后,阅读器的显示质量将达到或超过普通的纸。手持阅读器势必被更多的人接受。 存储卡能存储大量书籍。其中,SD卡属于安全存储介质,可用内部硬件保证内容不被非法拷贝。读取卡中的内容需要密钥。 此外,晶振和电源控制部分:这一部分控制低功耗。当用户在阅读时,并且后台没有任何操作时,系统进入低功耗状态,电压、频率都降低,最大限度地节电。为了适应这一要求,硬件设备在设计时,采用双晶振:一个高频晶振,用于正常操作和运算,高频晶振还可以被分频,可以用软件对CPU和外设超频或降频;另一个是低频晶振,当系统空闲时,切换到低频。CPU的供电也可以调整。当空闲时,切换到低频后,电压也切到低压。部分设备用可以软件控制关掉电源,但有些设备不能关掉电源,比如用电路产生中断的设备,如果关了电源,就不会产生中断了。在硬件设计时,要考虑到阅读设备的特殊性。 最后,SIM卡:这是版权和本质安全的核心部分。软件加密无论多么复杂,但时间不长就会被黑客解密。相比之下,SIM卡的安全性就高多了,有软件硬件同时作用。它的算法和校验是靠其内部固化的程序完成的,外部无法更改。它靠硬件来保证当攻击超过限度时,用自毁的措施防止继续攻击。 Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(network device)三种。字符设备是指存取时没有缓存的设备。块设备的读写都有缓存来支持,并且块设备必须能够随机存取(random access),字符设备则没有这个要求。典型的字符设备包括鼠标,键盘,串行口等。块设备主要包括硬盘软盘设备,CD-ROM等。一个文件系统要安装进入操作系统必须在块设备上[22]。 网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。 WOLF Linux的驱动程序结构和其它Linux系统的驱动结构相同。但是,为了适应多媒体阅读平台的要求,驱动程序对于硬件的操作与PC机和其它嵌入式设备相比有特殊性。 5.2. BIOS BIOS由两个不同功能模块:一是引导WOLF Linux启动也即WOLF Boot Loader,二是设置底层硬件参数。引导启动指在系统加电后,首先执行BIOS程序,初始化端口和内存,然后,将控制权交给WOLF Linux。设置底层参数则是指设备的参数可以在启动前设定,比如触摸屏的校正参数,屏幕分辨率,颜色位数等等。 这两个模块可以在启动时按住某个按键(称之为切换键)来切换,类似于PC机启动时按DEL键就进入CMOS设置。在实际系统中,使用了GPIO口作为快捷按键,这是考虑到虽然有键盘接口,但是用户可能不插键盘。RESET后的第一行就判断切换键是否被按下,如果按下,则转入到设置模块,否则,进入引导模块: Reset_Handler ;--------------------------------------------------------------------------------------------------- ldr r1, =SA11x0_GpioCntlBase //GPIO寄存器地址 ldr r0, [r1] //寄存器间接寻址 and r0, r0, #2 //取BIT1的值 cmp r0,#2 //BIT1 是0吗?即切换键按下(高电平变低)否? bne WOLF_SETUP //不是1,切换键被按下则跳转到SETUP。 ;----------------------------------------------------------------------------------------------------- 程序 5.1 BIOS两个模块间切换 5.2.1 WOLF Boot Loader 处理器加电后执行的第一条指令必须位于0x0000h,而SA-1110要求中断向量表必须设置在从0地址开始,连续8×4字节的空间,分别是复位(RESET)、未定义指令错误、软件中断、预取指令错误、数据存取错误、IRQ、FIQ和一个保留的中断向量(见表5.2),因此BIOS的前八条指令必须为跳转指令。由于没有必要实现除RESET之外的所有中断服务子程序,故暂将这些函数编为死循环。在将控制权交给操作系统之后,操作系统内核将会重新初始化虚拟地址空间的中断向量表,并提供中断服务子程序。

表5.2中断向量表 [23]
当微处理器启动的时候,它从0x0000h处跳到预先设置的地址上执行指令。执行一些低水平的CPU初始化和其它硬件的配置。在PC上运行的Linux依靠PC的BIOS来提供这些配置和OS加载功能。在没有硬盘的嵌入式系统中,代码和数据大都固化在非挥发的存储器中,因此,为了使系统成为一个能够自举的独立系统,必须提供一个系统加载程序Boot Loader。一般的嵌入式Linux系统在加电后,Boot Loader将内核拷贝到RAM中,其中内核是压缩的。并把Ramdisk搬到RAM中,然后将控制权交给操作系统。操作系统自解压运行。 由于WOLF Linux独特的存储结构,所以,一般通用的嵌入式linux的Boot Loader不能引导WOLF Linux。它们的区别是WOLF Boot Loader不需要搬运内核和Ramdisk,因为WOLF Linux 为了节省内存资源并加快引导速度,内核是未经过压缩的二进制代码,并且实现了只读部分(代码和常量)和可写部分(数据)分离,所以Boot Loader不需要把内核拷贝到内存中(参见2.3.1)。只是把数据部分拷到内存中。此外,WOLF Linux采用了基于flash的文件系统,文件存放在flash上,所以也不需要拷贝ramdisk。WOLF Boot Loader的工作流程如图5.1:

CPU在两种情况下可以从RESET处执行:一种是硬件复位,属于正常启动;第二种是从低功耗,CPU从Boot Loader恢复现场,并继续执行。请看程序5.2 /* 检查是否是从低功耗下被唤醒的******************************************/ ldr r0, RST_BASE ldr r1, [r0, #RCSR] and r1, r1, #0x0f teq r1, #0x08 bne NORMAL_BOOT/* 如果不是唤醒,转正常启动程序*******************/ /如果是唤醒,******************************************************************/ /*系统主频从低频切到高频****************************************************/ WAKEUP: mov r1, #0x80 str r1, [r0,# HIGH_FREQ] //启动高频振荡 mov r1, #0x04 str r1,[r0,# FSYS_BASE] // 切换频率 /*向RCSR写1以清除RCSR ************************************************/ mov r1, #0x08 str r1, [r0, #RCSR] ; /* 处理电源管理状态寄存器 (PSSR)************* ****************************/ ldr r1, [r0, #PSSR] /* clear DH bit, brings out DRAM from self-refresh **************************/ orr r1, r1, #0x08 ldr r0, PWR_BASE ldr r1, [r0, #PSPR] mov pc, r1 //跳到睡眠前设定的恢复点。 NORMAL_BOOT: . . . /**********************************************************************************/ 程序 5.2 Boot Loader 引导启动 正常启动的情况下,先执行初始化模块:初始化内存控制器,以便访问Flash和SDRAM空间,按照系统主板上的存储器布局正确初始化存储器控制器,在系统主板上共有32M的Flash存储器和32M的SDRAM。Boot Loader并不初始化MMU(Memory Mangement Unit)虚拟空间,而把这一任务交给操作系统。初始化串口一,程序执行当中输出调试信息。同时也可以通过设置SA-1110上的LED来提示程序的运行情况。串口还用于从超级终端用xmodem协议传送内核和文件系统,并烧录到flash。把控制器和端口初始化完后,跳转到已经烧录好的内核的第一条语句执行,控制权交给内核。 如果是从低功耗状态返回,则首先把系统主频从低频(几个Hz),切到高频(CPU的正常主频),停止SDRAM自动刷新,恢复对SDRAM的控制,恢复CPU的当前状态,继续执行程序。 在编译Boot Loader以前,确定内核与文件系统在Flash中的位置和长度,记入Boot Loader,最后将Boot Loader编译成映像格式,而不是可执行文件格式,写入Flash Memory的第一块。 5.3. WOLF Linux内核实现 WOLF Linux适应了多媒体移动阅读的特性,在内核实现中需要对成本、本质安全、低功耗、速度和空间优化等许多与具体硬件和具体应用相关的考虑。 5.3.1. 程序和数据存储结构 WOLF Linux实现了从rom上运行内核,节省了大量动态内存,既省电又能降低成本。这种运行是靠特殊的存储结构来实现的,即前面讲过的类哈佛结构。这种存储结构是依靠把linux的内核分成只读代码部分和可读写的数据部分,并分开存储来实现的。 根据linux可用的系统资源和引导装载程序的功能,内核可以编译成 vmlinux、Image 或 zImage。vmlinux 和 zImage 之间的主要区别在于 vmlinux 是实际的(未压缩的)可执行文件,而 zImage 是或多或少包含相同信息的自解压压缩文件 — 只是压缩它以处理(通常是 Intel 强制的)640 KB 引导时间的限制。 为了让WOLF Linux从rom上直接运行,从以上分析显然可以看出,不能用压缩的文件格式,因为如果压缩存储在rom上,当WOLF Boot Loader引导,将控制权转过来,WOLF Linux必须先解压,而解压的代码不能写在只读的rom上,只能解到内存,就没有意义了。 Linux 的目标文件(包括link之前的目标文件即input object和link之后的目标文件output object)是由多个section 组成的,有:.text段、.data段、.bss段[24]。 text段是代码段,源程序的代码在编译链接后就放置在这一段之中。 data段是数据段,变量等各种数据都放在这一段中。 Bss 段放置零初始化数据目标文件中只有这一段的信息并没有分配空间,只有在运行时才会在内存中为他分配零初始化数据区域(将特定长度的内存置零) 除了这三个段外不同的程序还会有一些其它的段。 每一section 都有两个地址VMA和LMA。在通常情况下这两个地址值是相同的但将内核代码分解后,这两个地址就不相同了。它们的意思分别是: VMA(virtual address):表示在执行目标程序时,到哪里去寻找执行的程序和变量,表示程序的执行时实际地址。 LMA(load address):表示SECTION在flash上的地址,也就是load之前目标程序的SECTION存放地址。 如果想检查指定是否实现,可以使用’Objdump –h’来确定是否.data和.bss段有不同的VMA和LMA。[25] Idx Name Size VMA LMA File off Algn 0 .text … … … … … 1 .data 2 .bss 分解内核代码的具体的实现步骤是: 首先修改链接脚本vmlinux.*.in (在arch/arm/目录下),在链接脚本中包括一段内存段: MENORY { ram:ORAGIN =?,LENGTH = ? rom:ORAGIN =?,LENGTH = ? } 然后,在链接脚本中指定.data段时,指明.data段进入RAM时应该在那里(.data段和.bss段必须在正式执行内核之前拷贝到RAM之中.text段不需要进入RAM段) 所以应该指定.data在哪里起始 将.data: { … } 改为 .data:[ADDR] { … }( ADDR为ram中的一个地址) 程序 5.3 链接脚本的修改 把链接脚本修改完后,这样链接器linker已经把应该的设置都做完了。如果内核执行起来就会到ram里读.data数据,而此时,ram里什么也没有,所以应该将.data数据拷贝到RAM中。 需要编写代码在内核运行之前将.data拷贝到RAM中,把可读写的数据从.data的LMA拷贝到VMA。这段代码很简单,当WOLF Linux得到控制权后,先设置MMU(Memory Mangement Unit),初始化虚拟内存空间,然后,用一指针指到内核的数据段,根据数据段长度,把数据拷贝到上面脚本已经制定的内存处。接下来的内核就可以继续初始化设备,启动文件系统,最后各种环境建立,内核开始运行。 5.3.2. 低功耗和电源管理优化实现 嵌入式系统靠软件和硬件配合,最大限度地关掉系统不用的设备,降低频率,甚至降低电压,才能实现低功耗。功耗的控制最主要的是靠电源的管理,也与系统主频密切相关。 WOLF Linux是一款嵌入式操作系统,必须利用硬件平台的支持实现正常、低功耗工作模式的转换,这里主要以SA-1110开发板为例介绍这几种工作模式。 SA-1110中包含电源管理逻辑,控制EMPU在三种操作模式中切换,这三种模式是:运行态,空闲态,睡眠态[26],分别与上面所说的ACTIVE, STANDBY, SUSPEND相对应。后两种模式用来减少在不需要某些功能或电源供给不足时减少处理器的耗电量。下面简要介绍一下SA-1110的三种模式 a.运行态: 运行态是SA-1110的正常操作状态,所有的电源供给处于工作状态,所有的时钟处于运行中,每一种片上资源都是可用的。 b.空闲态: 空闲态允许软件应用在不使用CPU的时候将其停止,此时CPU进入halt。系统时钟可以切换到低频,以达到最小耗电。但仍然监视片上和外设的中断服务请求,如果中断发生,重新激活CPU。 在空闲态中,如果BATT_FAULT和/或VDD_FAULT引脚被驱动,SA-1110进入睡眠态。 c.睡眠态: 睡眠态提供了最大的电源节省和最少的可用功能,在从运行态或空闲态到睡眠态转换的过程中,SA-1110执行了一个有序的片上活动的shutdown指令序列,对处理器触发一个 内部RESET,将PWR_EN引脚变为负电平,向外部系统指示应将VDDI(1.5-V供电)变为0电压。此时内部的这个RESET关掉了处理器的大部分电源(VDDX I/O在睡眠态期间仍保持供电),睡眠状态机等待预编程的唤醒条件发生,如果发生,则将PWR_EN变为高电平(告知外部系统重新恢复VDDI供电),然后执行一个唤醒指令序列,在电源供给和时钟稳定后,电源管理逻辑使SA-1110 RESET,RESET控制器状态寄存器(RCSR)的状态位显示了软件当前的RESET是否源自于睡眠态[26]。 有两种进入睡眠的方式:通过软件或供电故障。 软件方式是通过设置电源管理控制寄存器(PMCR)中的睡眠强制位来完成的。这一位由软件设置并由硬件在睡眠态中清除。当SA-1110从睡眠中醒来时,这一位已经被清除了。 通过驱动供电故障引脚VDD_FAULT或BATT_FAULT之一进入睡眠态是另一种方式,VDD_FAULT引脚应该用来指示主供电失效,而BATT_FAULT应该用来指示电池缺失或电池电量不足。这些引脚都会导致进入睡眠态的操作,但是却意味着不同的唤醒指令执行序列。 首先,睡眠关机指令序列,这一指令序列由三步组成。 第一步执行下列动作: 1) 电源管理逻辑将GPIO的输出引脚切换到他们的睡眠状态。睡眠状态被提前编程从电源管理逻辑GPIO睡眠状态寄存器(PGSR)装入到GPIO输出数据寄存器中 2) DRAM进入自动刷新模式。内存控制器在完成处理中的操作之后,发送自动刷新命令到SDRAM。 如果睡眠是由于VDD_FAULT或BATT_FAULT的引脚之一的电平变化造成的。唤醒事件被设置成由软件编程的“故障状态”,“故障状态”允许由GP0或GP1之一作为唤醒事件。 第二步执行下列动作: 1) 所有可能的唤醒源被清除,主要包括清除所有的GPIO的电平跳变探测状态位和清除RTC警报中断位。这些被清除的状态位有可能造成SA-1110在任何一个时刻的苏醒。这一能力的提供是使SA-1110在进入由电源故障造成的睡眠前能有所准备。 2) SA-1110内部RESET,同时RESET_OUT引脚有效。 第三步执行下列动作: 1) 切换系统晶体振荡器,低频的振荡器一直在工作,当需要切换时,先把外部设备的时钟输入改成低频,待外部工作全部完成后,切换CPU的系统时钟,最后停止3.686-MHZ晶体振荡器及分频器件。 2) PWR_EN引脚输出低电平,外部系统必须将VDDI电源变为失效作为对此动作的响应。与SA_110不同,SA-1110系统并不是必须在睡眠态期间将VDDI变为零电平,然而如果这么做,会大大减少耗电量。上述的每一步大约耗时32.768-Khz时钟的一个周期,约30微秒。睡眠态期间,SA-1110监视预编程的唤醒事件。这个时间可能是在由CPU在设置睡眠强制位之前编程的,也可能电源管理逻辑在发现故障时设置的。如果SA-1110发现一个有效的唤醒事件并且没有BATT_FAULT信号,SA-1110便开始执行唤醒指令执行序列,如果BATT_FAULT信号有效,则唤醒事件被忽略,VDD_FAULT此时总是被忽略,因为VDDI供电此时是失效的。 唤醒指令序列同样分三步执行: 第一步: a. PWR_EN引脚输出有效信号,指示外部系统对VDDI引脚进行供电。 b. 内部定时器开始对系统内部的加电进行计时,加电历时大约10ms。 c. 启动3.686-MHZ晶振,并切换到高频
第二步 另一个内部定时器对3.686-MHZ晶振恢复工作至稳定状态进行定时,历时大约150ms。如果PCFR中的OPDE位为0,那么晶振一直正常工作,所以无需使用此定时器。在这种情况下,电源管理逻辑直接跳到第三步,而不必等晶振定时器计时完毕。 第三步 a. SA-1110内部RESET信号失效,CPU开始执行正常的启动指令序列。 b. RESET_OUT引脚失效,指示SA-1110将要从RESET向量的位置执行。 c. DRAM从自动刷新模式中的恢复因为DRAM在关机指令序列中被置于自动刷新状态,他们的内容睡眠期间受到保护。退出睡眠状态以后,软件必须重新配置DRAM控制寄存器,使DRAM跳出自动刷新状态,后者在睡眠期间由于掉电而丢失了信息。 低功耗设计就是利用上面嵌入式CPU提供的低功耗支持,再加上主板设计了对外围设备的时钟和电源的软件控制,达到软件降低功耗的效果。 在Linux中,有两种电源管理:APM (Advanced Power Management,高级电源管理)和ACPI (Advanced Configuration and Power Interface,高级配置和电源接口)。两个标准不能同时运行。缺省情况下Linux运行ACPI[27]。APM可以让你把机器处于Suspend(挂起)或Standby(备用)状态,以及检查电池容量。而ACPI还可以让你把外设(如:显示器、显卡、PCI总线)单独断电,在节省电能方面有更多的控制。为了让电源管理功能生效,需要在BIOS和Linux核心里要打开它,并且在Linux里加载必需的应用软件。 在ARM Linux中没有ACPI模块,只有APM和PM模块,PM模块包括了基本的电源管理,如Suspend/Sleep和Resume/Wake-Up。APM比PM多了电池状态的检测,APM应用包含:apmd(APM的后台服务程序)和其他的应用程序(如APM)。 WOLF Linux成功的改造了ARM Linux的PM模块,实现了目标系统的睡眠与唤醒(Suspend/Resume)。这两大部份是低功耗实现的关键。 系统进入低功耗的过程有两种:一种是从ACTIVE态进入STANDBY态,另一种是从其它状态进入SUSPEND态。进入STANDBY态与进入睡眠态有不同:1. 在STANDBY态显示屏自动刷新而睡眠态显示屏关闭,STANDBY态切到运行态CPU不须RESET但睡眠态切到运行态CPU需要RESET来恢复。由于进入STANDBY态时外围设备的管理与睡眠态相同,这里不再赘述。 进入SUSPEND态的过程是这样的:首先,WOLF Linux内核接收到了要求睡眠的请求,内核在当前允许的情况下,调用睡眠函数,寄存器内容保存到内存后,动态内存转自动刷新,设备切换到低频或低压,系统进入睡眠状态,各种寄存器内容丢失。当接收到外部中断,且该中断是唤醒源,比如GPIO或实时钟RTC中断。系统进入唤醒RESET,重新执行Boot Loader处的程序,恢复现场,转恢复程序,把寄存器等内容恢复,继续执行原来的程序。 系统的睡眠(Suspend)不仅仅是让CPU执行一条睡眠指令,它还包括切换频率(从高频切换到低频)切断系统许多不需要随时运行的设备的电源,停止部分设备的时钟输入等等一些用于外设管理的子模块。把这些子模块和CPU切换频率以及CPU的睡眠指令执行序列组合起来,形成睡眠部分,程序5.4给出该部分的伪代码(为了节省篇幅,假定指针和返回值都是正常的): WOLF_do_suspend(void) { /*申请内存,以保护SA-1110中重要寄存器的内容*/ sleep_save = kmalloc (SLEEP_SAVE_SIZE*sizeof(long), GFP_ATOMIC); /*把申请得到的虚拟内存转成物理地址,便于直接内存操作*/ sleep_save_p = virt_to_phys(sleep_save); /*向在PM模块中注册了的设备发送系统suspend的通知*/ retval = pm_send_all(PM_SUSPEND, (void ));/*向外设发睡眠指令*/ cli();/*关中断*/ RCNR = xtime.tv_sec; /* 保存当前时间 */ SAVE(OSCR); /* 保存重要寄存器 */ … /* 清除先前的Reset状态 */ RCSR = RCSR_HWR | RCSR_SWR | RCSR_WDR | RCSR_SMR; /* 设置Resume的返回地址 */ /*此寄存器的内容在睡眠期间不丢失*/ PSPR = virt_to_phys(sa1100_cpu_resume); WOLF_SwitchFreq(0);/*调用汇编程序,切换到低频,*/ sa1100_cpu_suspend();/*调用汇编子程序,进入睡眠,最后执行halt*/ } 程序 5.4 睡眠函数 睡眠函数用pm_send_all来通知外设睡眠。因为外设的电源和时钟不尽相同,在设备驱动程序中,每一种外设有它自己的睡眠程序,这些程序也在上面的系统睡眠函数中被执行:程序5.4中的pm_send_all函数遍历设备链表,对每一注册的设备用data指向的参数调用设备驱动程序中的回调函数。每一外设通过调用函数: struct pm_dev * pm_register(pm_dev_t type,unsigned long id,pm_callback callback);在循环链表中插入一个节点,然后驱动程序负责初始化该节点。这个链表节点类型定义如下: struct pm_dev /* 节点类型*/ { pm_dev_t type; /*设备类型*/ unsigned long id; /*设备ID*/ pm_callback callback; /*设备回调函数*/ void *data; /*回调函数的参数指针*/ unsigned long flags; /*保留位用*/ int state; /*当前状态*/ int prev_state; /*前一状态*/ struct list_head entry; /*循环链表表头*/ }; 睡眠函数也为系统的唤醒做好了准备:在关于唤醒源的代码中设置了GP0为SA-1110的唤醒源,GP0默认为SA-1110开发板上八个switch中的第一个,即可以通过按SW0键使SA-1110退出睡眠状态。主要是将系统被唤醒后执行的函数sa1100_cpu_resume的地址转换成物理地址并存入PSPR, 因为SA-1110睡眠后,寄存器的内容都丢失了,醒来后还没有恢复相关寄存器的内容,无法使用虚拟地址空间,所以准备工作要在睡眠前做好。 唤醒后的恢复指针是靠Boot Loader 来实现的,这是因为SA-1110在进入SUSPEND态之后,自动进入睡眠RESET,睡眠RESET不会影响电源管理逻辑,RTC,和GPIO都能唤醒CPU。当有按键或设置了RTC中断,比如闹钟程序,系统进入睡眠RESET过程,先软复位,执行位于0X0000H处的BootLoader,在RESET之后的启动过程中,WOLF Boot Loader可以检查RESET控制器RESET状态寄存器(RCSR)确定是哪一种原因造成的RESET。这一过程的实现见程序5.2(Boot Loader 的入口程序),从标号WAKEUP处开始,系统恢复刷新动态内存,恢复电源管理,恢复频率,然后把在PSPR中在睡眠前存的sa1100_cpu_resume地址赋给PC,CPU去执行sa1100_cpu_resume()。sa1100_cpu_Resume ()的代码(参见程序5.5)就是按照与睡眠相反的顺序进行恢复,最后通知所有的外设进行恢复后的准备工作。执行完恢复程序后,系统继续执行HALT指令的下一个指令。 sa1100_cpu_resume() { PSPR = 0; /*唤醒后,清零PSPR,这是系统从睡眠RESET继续后,第一条指令*/ … RESTORE(OSCR); /* 恢复寄存器 */ xtime.tv_sec = RCNR; /* 恢复当前时间 */ sti();/*开中断*/ kfree (sleep_save); retval = pm_send_all(PM_RESUME, (void *)0); } 程序5.5 唤醒后的恢复 ACTIVE态还有三种子状态:加速、正常、减速,这三个子状态也是对电源管理的优化设计。这三个子状态下,CPU都工作在运行态模式。它们是靠频率切换实现的(类似于当前PC主板的软跳频):当WOLF Linux进程调度决定需要加速运行时,调用底层频率控制模块,把系统主频切换到比标称频率高20%的上限频率上,CPU和各设备开始超频运行,这时系统功耗较大,但处理速度加快。当切换到正常态时,WOLF Linux再把主频切换回来。而当电量不足时,为节省电量,把主频切到标称的40%,经过实测,电流可以降到正常状况的1/5左右。 5.3.3. 本质安全的实现 本质安全(参见2.3.4.)是多媒体移动平台区别于其它手持设备和计算机系统的特征,本质安全的实现是靠硬件,驱动程序和内核的配合来完成的。单靠应用程序不可能做到从根基开始的安全性。所以,为了保证本质安全,采取的实现步骤是:硬件-BIOS和驱动程序-内核的核心部分-API,涉及到的具体实现有三方面: 1. SIM卡身份验证: 多媒体移动平台要求有SIM卡子系统作为本质安全的关键部分。身份验证就是通过SIM卡存储的程序和SIM卡不可改变的个性ID来实现。SIM卡子系统事先按照自己定义的格式烧录上验证程序,并把卡的序号作为个性ID;设备的主板也开辟了256位的地址空间,用类似于PROM的方式一次性烧上了设备的ID;在系统启动后,BIOS通过直接跳转到验证程序的入口,来调用验证程序,卡上的验证程序开始工作,读取设备ID,和自己的ID按某种算法验证,通过后才能继续执行启动。 WOLF Linux也对SIM卡进行验证。在CPU进入空闲态(Standby)之前,WOLF Linux调用驱动程序识别SIM卡,如果SIM卡不存在,则停止进入空闲态,直接转入睡眠。如果识别到卡,则驱动程序读取经过SIM卡加密的ID,按SIM卡规定的解密方式解开,然后判断该ID的格式是否合法,如果合法,才转入空闲态。 2. 加密算法: 加密解密算法是由SIM卡的ROM程序来实现的,主要有一些常用的经典算法,如:DES、MD5、RSA、BLOWFISH、ElGamal算法等。这些算法可以用API来调用,这种调用靠两级驱动程序实现。第一级驱动程序在WOLF Linux内部,此驱动程序把调用参数写入SIM卡子系统的寄存器中,然后向子系统发命令,通过检查状态寄存器,可以得到当前计算状态,当结果出现后,可以从寄存器中取出。SIM卡子系统在Linux系统上看起来都像一个文件,它存放在/dev/sim目录中并被称为"特殊文件"或是"设备节点"。第二级驱动程序在窗口系统,与窗口的触摸屏、键盘等驱动类似,该驱动进一步隐藏硬件细节,为用户程序提供标准的API。两个驱动程序通过虚拟文件交换来传递数据。下面分别是两个驱动程序的关键部分(这里对于驱动程序的编写不再赘述): #define SIMCOMR __REG(0xXXXXXXX) /* SIM卡子系统命令寄存器地址*/ #define SIMSTAR __REG(0xXXXXXXX) /*命令寄存器地址*/ #define SIMDATR __REG(0xXXXXXXX) /*数据区起始地址*/ int UseAlgorithm(struct file * file, DWORD Command) { BYTE pResult[MAX_OUT],pData[MAX_IN]; struct inode *inode = file->f_dentry->d_inode; struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >> SIM_DEVICE_ALGORITHM)]; … SIMCOMR = 0xXXXX;//请求 copy_to_user(pData,drv->input_buffers[drv->input_rear]+ drv->input_offset, BYTES_INPUT); while((SIMSTAR&ALGORITHM_MASK)&&!TimeOut());//等待设备就绪 SIMDATR= pData; SIMCOMR = Command;//按照算法种类发送不同的命令。 while((SIMSTAR&ALGORITHM_MASK)&&!TimeOut());//是不是成功? If(GetLastError()==SIM_TIME_OUT) return ERROR_SIM_TIME_OUT;//出错 memcpy(pResult,SIMDATR,LEGNTH);//将结果数据拷贝给用户 copy_from_user(drv->output_buffers[drv->output_rear]+ drv->output_offset, buf, BYTES_OUTPUT); return 0; } 程序5.6 第一级加密算法驱动 #define SIMDEVICE "/dev/sim" int WolfSim_CodeDecodeAlgorithm(void* pData,void* pResult,) { int fd; int bytes_read; fd = open(SIMDEVICE, O_NONBLOCK);//可读写方式打开设备。 if(fd < 0)//是否打开设备? { GdError(MWERROR_ERROR, "Open sim subsystem Error\n"); return -1; } write(pd_fd, pData, sizeof(pData)); bytes_read = read(pd_fd, pResult, sizeof(pResult)); if (bytes_read != sizeof(pResult)) return errorno; close(fd); return 0; } 程序5.7 第二级加密算法驱动 3. 应用程序注册和进程受限: WOLF SDK为每一个阅读软件增加了一个固定长度的标识数据,这个数据包含下列信息: typedef struct { DWORD dwIDApplication;//应用程序的ID DWORD dwApplicationAdr;//应用程序入口相对于程序头的位置 DWORD dwInitApplicationAdr;//应用程序初始化相对地址 DWORD dwReaderLength;//reader程序的长度。 DWORD dwReaderVersion;//版本号 DWORD dwGetBookInformationAdr;//取得书的信息的函数 BYTE Reserved[RECORD_IN_REGIST_LEN-6*sizeof(DWORD)]; }RECORDINREGISTER; 这个结构就是注册结构,当开发者被授权开发程序后,开发者注册的信息,以及编译链接后掌握的程序信息,会被集成开发环境写入到这个结构中,并按全局变量的形式编入到程序中。 阅读程序被安装在设备上后,注册信息就会被书架程序掌握,书架中相应就多支持一种或多种书籍,这个注册过程类似于windows的媒体播放软件的注册安装。这种支持一种或多种的书籍是在阅读程序注册时,WOLF Linux通过扫描注册结构的dwGetBookInformationAdr指针来得到的。这个指针指向了另外一个结构体,该结构体中存有支持书籍的种数和书籍的文件格式类型等信息。 当程序运行时,进程调度程序去验证程序的ID,以及其他身份证明的数据,如果它没有通过验证,则不允许它去读写受到保护的书籍卡。这就是进程受限。 5.3.4. 文件系统的配置和加载 Linux 的一个最重要特点就是它支持许多不同的文件系统。而多媒体移动阅读平台正好有多种不同的应用需求:经常读写的临时文件需要内存文件系统,书籍的存储需要windows的FAT或NTFS系列文件系统,内核常用的设备文件,系统目录窗口系统文件等需要长期存放在设备的Flash Memory上最好设计成jffs文件系统,具体选用的文件系统的种类与实际应用密切相关(参见2.3.2)。 文件系统的配置实现相对简单,只要在裁减和配置内核时,配置上所需的文件系统,然后编译即可。编译成功后,只是说明代码支持了某种文件系统,如果想要加载这种文件系统,还需要修改/etc/fstab文件。在内核引导过程时,它首先从LILO指定的设备上安装根文件系统,随后将加载/etc/fstab文件中列出的文件系统[28]。 /etc/fstab指定了该系统中的文件系统的类型、安装位置及可选参数[29]。 fstab是一个文本文件,在修改前要作好备份,因为破坏或删除其中的任何一行将导致下次系统引导时该文件系统不能被加载。以下是一个/etc/fstab的示例: #/etc/fstab /dev/hda1/ext2defaults11 /dev/hda2noneswapsw /dev/hda5/homeext2defaults12 /dev/cdrom/mnt/cdrom iso9660 noauto,ro0 0 ---- 从上面可以看出,每一个文件系统占一行,参数间由< TAB >隔开。其中,第一列表示将被加载的块设备或网络上的文件系统;第二列表示该文件系统的安装点(mount point),对于交换分区/dev/hda2不存在安装位置;第三列是该文件系统的类型,即上文提及的各种文件系统中的一种;第四列的内容是该文件系统加载参数,常用的有defaults(缺省)、sw(交换分区用)、rw(读/写)和ro(只读),而noauto(不自动加载)参数一般用于CD-ROM、软盘等可移动存储设备,防止系统引导时安装该文件系统;第五、六列分别是两个数字,左边的数字供备份程序确定该文件系统上次备份距现在的天数,以通知管理员进行备份,右边的数字代表fsck(文件系统检查命令)在系统引导时检测文件系统的顺序,根文件系统一般最先被检查,随后检查其他设备上的文件系统,而该列为0的文件系统根本不做检查(如CD-ROM或软驱)。 ---- 要把一个文件系统安装,即使其工作,可以使用mount命令。Mount命令的格式为: ---- #mount –t [type] –o[option] device_name mount_point ---- 其中type指文件系统的类型,option为选项,如remount选项可使该文件系统重新加载,以实现某些参数的改变。device_name处填写设备名称,如/dev/hda1, mount_point处为安装位置。将文件系统/home安装的命令为: ---- #mount –t ext2 /dev/hda5 /home ---- 如果文件系统已在/etc/fstab中出现,则加载时只需指出安装位置或设备名称,如: ---- #mount /home ---- 与此相对应,卸载一个文件系统的命令为umount, 将文件系统/home卸载: ---- #umount /home ---- 在Linux系统安装过程中,系统会自动建立并加载一些文件系统,如果需要为用户建立各自的文件系统,就要用到mkfs命令,其格式为: ---- #mkfs –t [type] device_name 5.4. 窗口系统 多媒体移动阅读平台的GUI是采用改造了的Micro Window,为了应用程序的兼容性和便于移植,其接口兼容了Micro Window,但是内部的驱动和一些API应该适应多媒体阅读的要求,因此它们的实现与通用的Micro Window是不同的。 5.4.1. 设备驱动 按实际意义讲窗口系统的设备驱动程序是虚拟设备驱动,因为它自己不是直接去操作硬件,还需要调用内核的硬件驱动程序来实现操作硬件。设立虚拟设备驱动可以使得窗口系统支持多种硬件,而让应用程序对硬件是透明的。 Micro Window连结系统中至少一个屏幕,鼠标和键盘驱动器。设备相关的图形引擎和新的中间层函数可以直接调用设备驱动程序执行特定的硬件操作。这种设置允许不同的硬件设备加入到Micro Window系统中而不影响整个系统的工作方式[30]。所以,虚拟驱动程序主要是针对显示器、鼠标(触摸屏)、键盘的。 1. 显示驱动 多媒体移动阅读平台的硬件配置要求要有VGA接口,同时还要有大分辨率的显示屏。对显示设备的要求较高,显示技术也是系统整体性能的重要衡量标准之一。在屏幕驱动程序层,实现中要考虑的重点是速度和颜色。如果速度慢,读者会产生烦躁心理,而颜色支持不好会使一些书籍失真。 窗口系统的显示驱动实现了下面的作用: a. 支持各种图形显示模式如VGA、Frame Buffer等。 b. 从硬件驱动得到屏幕大小、颜色位数来初始化缓冲区; c. 提供画点、垂直线、水平线、贴图、放大缩小贴图、拷贝粘贴函数;目前,所 有的鼠标移动,文本输出,位图输出都由这些低层函数实现。例如,鼠标的形状都是用画点描出的,贴图使用频率最高,显示图像、窗口移动等都要贴图。 d. 设置调色板和使用调色板,调整屏幕色彩。 e. 支持屏幕旋转90o角,多媒体阅读往往要求能够横竖排版,所以经常用到旋转屏幕。 显示驱动需要优化的是贴图和旋转。贴图有四种:屏幕 内存,内存 屏幕,内存 内存 屏幕,屏幕 但原理都是一样的。都是位图的传送。 因为显示点、线、图的程序已经实现了,旋转显示的实现要做速度优化。否则,正常显示的图像到了这一层,然后经过逐个象素的旋转处理,再到屏幕上可能需要几秒钟!这里,采用了到物理设备之前旋转的方法:旋转对应用程序是透明的,应用程序仍按正常的缓冲区显示,窗口系统的显示驱动负责把数据打到屏幕,这时的贴图的两种(从内存到屏幕或从屏幕到屏幕)就开始工作,往屏幕上送数据。判断是否要求图像旋转,如果有,则把源和目的x、y坐标变换成旋转时的坐标,这样,可以把源的数据按块拷贝到目的,显示驱动内部还可以对这种块的拷贝做优化,比如采用DMA方式传送,实践证明,这样的旋转贴图的速度与正常贴图速度相差无几。 2. 鼠标驱动 多媒体阅读平台的窗口系统的鼠标驱动要求支持触摸屏和GPM鼠标两种,触摸屏的驱动程序模仿鼠标的驱动程序,返回触摸笔在触摸屏上点击的位置。所以这两种对上层的表现都是返回点击以及抬起点的坐标。这个数据因不同的硬件而不同,触摸屏返回的数据是这样的: struct ts_format { unsigned short pressure; // 1: 笔按下, 0:笔抬起 unsigned short x; // 按下、抬起点的x数据 unsigned short y; // 按下、抬起点的y数据 unsigned pad; // 控制器相关的数据,默认为0。 }; 鼠标驱动程序的实现解码硬件设备驱动程序从AD转换器读得的鼠标数据,并返回鼠标位置的绝对数据和按键。因为坐标点从硬件返回的值不一定是按象素的,窗口系统的鼠标驱动就是解码这样的数据,并返回统一格式的鼠标数据。整个鼠标驱动也可以用这个数据结构来表示。一般情况下这个统一的数据具有这样的结构: MOUSEDEVICE mousedev = { PD_Open, /* open 函数指针,该函数打开硬件设备驱动*/ PD_Close, /*close函数指针,该函数关闭硬件设备驱动*/ PD_GetButtonInfo, /* GetButtonInfo函数指针,用于获得触摸屏信息*/ PD_Read, /* Read函数指针,该函数用于读取点击或抬起点的数据*/ NULL, /* Poll 保留*/ PD_SetCalibration, /*校正函数的指针 */ MWMOUSEMODE_COOKED, /* mode模式 */ 0, /* xmin 最小的x的初值*/ 0, /* xmax最大的x的初值*/ 0, /* ymin 最小的y的初值*/ 0, /* ymax最大的y的初值*/ 0, /* xswap x是否反向*/ 0, /* yswap y是否反向*/ 0, /* zthresh * 刷新的模式/ 1 /* showcursor 是否显示鼠标形状*/ }; 触摸屏也在这一层做了校正工作:校正程序可以让用户点触摸屏四个顶点,这四个顶点的数据作为触摸屏的范围,从这个范围可以得出实际点击的坐标(即触摸屏识别的坐标)和显示屏的坐标之间的差异。以后的点击数据按照这个范围做修正,就可以使点击的位置和显示位置相同。这个函数的指针就是PD_SetCalibration,在启动书架主窗口前可以调用这个函数,对触摸屏做进一步的修正。(基础的修正已经在BIOS中做过) 3. 键盘驱动 目标系统可以外接键盘也有按钮,所以用窗口系统来管理键盘是很必要的。驱动的实现方法也因这两种设备不同:这里对于标准键盘采用了Micro Window的tty键盘驱动, 通过文件描述符的方式打开和读取。对按钮则是做了硬件驱动程序,然后在虚拟驱动中,把不同按钮从硬件驱动中返回的数据解码按不同的标准键盘值返回给上层。这样,按钮就相当于键盘上的快捷键。 5.4.2. 设备无关的图形引擎 窗口系统的图形引擎用于把设备无关的图形、颜色等转换为设备相关的图形、颜色,转换后的数值才能被设备识别并被显示[31]。这些引擎对应用程序是API,而对驱动程序来说则是调用者。为了WOLF SDK开发的程序的通用性,和便于移植性,这些引擎采用了Micro Window 的图形引擎,所以在这里仅对它们的实现做简要介绍: 图形引擎的函数函数都以Gd开头,所传送的第一个参数是SCREENDEVICE结构指针(PSD)。PSD参数给出了底层显示设备的细节,如显示器的长和宽,颜色模式。按不同的功能,图形引擎可分为十类[32]: 1.区域 在Micro Window中,区域使用不互相重叠的矩形数组描述。Micro Window中区域有两种不同的实现方式,一种是使用有限内存的原始设计,另一种是动态生成矩形集合的新设计。原始设计使用一个MWCLIPRECT类型的静态数组描述复杂区域,任何矩形上的任意一点都被认为是区域上的一点。在新设计中则可以创建任意数组长度的区域,区域(MWCLIPREGION *)以动态生成的矩形数组的形式存储。在这种实现中,非重叠的矩形以“y-x”波段方式保存,每个波段中的矩形y高度相同,这种模型允许使用不同于clipping函数的实现。 2. 图形操作区(Clipping) 在Micro Window中,在任一时刻,图形引擎有一个clipping区域,即一个可以实施图形操作的矩形集合,一个点如果位于当前的clip矩形集合内部,就会被重画。 3. 画线 画线是最简单的图形操作,Microwindws中的GdPoint画点,GdLine画水平线,垂直线或斜线(使用Bresenham算法)。 4. 矩形,圆,椭圆 画矩形,圆,椭圆使用GdRect和GdEllipse函数,圆是x,y轴相同的椭圆,与直线一样,画矩形和椭圆使用当前的颜色和模式。 5. 多边形 Micro Window支持通过指定一个点数组画多边形。点之间使用GdLine函数联线。 6.内部填充 Micro Window使用GdFillRect函数填充矩形内部,矩形的边界和内部使用当前的前景色填充,使用GdFillEllipse填充圆和椭圆的内部,使用GdFillPoly填充多边形,内部填充的实现通过成果的调用屏幕驱动程序的DrawHorzline函数,如果不涉及到clipping,速度会很快。 7. 字体 字体为点阵字体。当前的系统编入了GB18030的16*16点阵字库。 8. 文本输出 输出文本首先用GdSetFont函数选择想要的字体,然后调用GdText函数。文本输出首先要进行完全的Clipping操作,然后使用函数Drawpixel画出该字符。 9. 颜色模式和调色板 上层应用把颜色使用COLORVAL值的方式传递给引擎函数。引擎将COLORVAL值转换成PIXELVAL值, PIXELVAL值是真正的传送给需要颜色参数的屏幕驱动程序入口点的值。 10. 位图传送(blitting) 引擎的位图传送是把设备无关的位图数据,经过转换,变成设备相关位图,然后调用上面讲的虚拟驱动中的贴图程序完成。 5.4.4. 应用程序主界面—书架的实现 书架虽然也属应用程序,但是它掌管着整个书籍资源和阅读软件的调用。每个书架页的数据结构和调用程序构成了书架程序。它的功能类似于windows的资源管理器。 书架有两个重要的数据结构: struct Book { char bookname[BOOK_NANE_LEN];// 书名 GR_WINDOW_ID wid; //窗口号 GR_IMAGE_ID icon; //书的图标 }; struct Page { struct Page *pPrev; //前页 struct Page *pNext; //下一页 struct Book BooksInPage[MAX_BOOKS_A_PAGE]; //本页的书 int PageNumber; //页号 }; 这两个结构记录了当前书架页的信息。书架页的结构是翻页有上翻和下翻两种可能,书架页的数据结构采用了双向链表。当读者用触摸笔或其他输入设备指定某本书时,从页结构中找到该本书的信息。因为每个页的书是存放在数组中的,所以,要找到这本书是非常快的。 对于阅读软件的管理是书架程序的又一个功能,书架启动前,先通过系统调用得到目前已经注册的书籍类型和阅读软件的类型以及名称。这样,所有已经注册的书籍都会被显示在书架上。当某本书被用户选择,书架根据该文件类型,从系统调用的信息中检索出应该调用的阅读软件,参数等等,开一个新的子进程,并调用阅读软件。
|