一. 开机的简要流程分析
Qualcomm 的平台软件支持两种启动方式:一种是 Nor Flash 启动方式,另外一种就
是 Nand Flash 启动方式。 Nor Flash 启动方式就相当于硬件直接找到一个入口点开始执行代码,相比较而言会比较简单,且 Amoi 没有采用此种方式,所以本文对于这种方式不做详细分析。另外一种就是 Nand Flash 启动方式,这种方式和 PC 的启动方式比较相像,也是 Amoi 采用的 Boot 方式,下面将详细分析在此方式下面的开机过程。
按下开机键之后,将产生一个时钟中断 ,从而通知 AMSS 主芯片的 Boot Load 硬件去将放置于 Nand Flash 上面的第一个 Block ( 8K )里面的 Boot 代码 Copy 到内核内存( RAM ,这个内存应该是 CPU 自带的内存,同后面提到的 SDRAM 有一定区别,可以把它当作 CPU 的 Cache )的 0xFFFF0000 地址,并开始执行Boot 代码。 Boot 的主要任务是完成整个系统的硬件初始化工作(类似于 PC 上面的 BIOS 所完成的硬件自检工作,至于 Boot 的详细工作机制,后文会有详细描述)。 Boot 所完成的工作里面,最重要的一件事就是会将整个手机软件代码( AMSS 软件包)拷贝到 SDRAM 中,并最后将控制权交给 AMSS 软件。 说白了,就是Boot 执行完成之后,代码的执行点将由 Boot 跳转到 AMSS 软件的的入口点函数 main(). (此函数在 mobile.c 里实现)。
代码运行到了 Main() 之后,在这个函数里面将完成操作系统( rex )的初始化工作,其实现方法是调用rex_init() 。 Rex_init() 完成的工作很简单:
1. 完成操作系统必要的一些数据结构( timer 链表、任务链表等))的初始化之外;
2. 接下来,它创建了三个任务,分别是: rex_idle_task 、 rex_dpc_task 和 tmc_task 。
Idle 任务没什么好解释的,目前这个任务为空,什么也没做, dpc_task 目前不知道是做什么的,暂时可以不用管。前面的这两个任务都属于操作系统层面的,由操作系统来维护,和手机软件关系不大。哪一个和手机软件关系大呢?答案是: tmc_task 。大家可以把这个当作操作系统的入口 ( 主 ) 任务,也可以把它当作整个手机软件的入口任务。即 AMSS 软件里的所有其它任务的创建和维护就是由这个 tmc_task 来完成的。
到此为止,整个 AMSS 软件还并没有跑起来,只是跑到了 tmc_task 里面了。在 tmc_task 里面,会调用tmc_init() 来完成整个 AMSS 软件包的初始化工作 ,其中最重要的一项工作就是调用 tmc_define_tasks() 将AMSS 软件包所有需要的任务都创建起来了。比如说 slee_task 、 dog_task 、 cm_task 、 wms_task 、 ui_task等。这些任务,一般不需要直接和 AL 层软件打交道,但请大家记住,手机上所有功能的实现最根本点就是由这些服务组件( Service Task )来完成的。将来大家跟踪一个具体的功能模块时,比如说通话模块,如果需要,可以再去深入研究它的具体实现。
好了,到现在为止,所有的 AMSS 核心软件就全部跑起来了(手机的功能模块,在软件方面就体现为 OS 层面的一个任务)。但现在大家还根本看不到 Brew 和 AEE 的影子。呵呵,各位不要急。到了这个层面之后,我想稍微多说几句。最早的 Qualcomm 平台,比如说 5xxx 系列,是根本没有 Brew 的,那个时候的 AL ( Application Layer )层软件开发,是直接调用底层 Service task 所提供的 API 来完成相应的工作的。从这种角度来看的话,显然那时的开发是比较郁闷和难度较高的。不过,到了 65xx 之后, Qualcomm 平台引入了 Brew ,手机开发商就没必要去从这么底层( Service API )的层面进行手机开发了,他们完全可以基于 Brew 来实现一台手机的所有功能( Qualcomm 给我们的参考代码,就是全 Brew 平台的)。
Brew 的运行环境 AEE 是如何跑起来的呢?关键在于 ui_task() ,由于 ui_task 和我们手机开发的关系非常密切,其地位也相当重要,所以,后文我将单独对它进行一个深入的研究与分析。到目前为止,大家只需要知道ui_task 将 AEE 加载起来了,并且,它起到了一个中间层的作用,即所有 AMSS 底层服务组件的消息,都将经由ui_task 而转到 AEE ,并最终转到具体的 App ( Applet )的执行代码里面( HandleEvent() )。
注意:
1. 上述的开机过程,在每一次按开机键都需要走一遍,即关机之后,整个系统的所有功能都将消失,而不像有些手机,看起来是关了机,但实际上底层还是有一些软件模块在跑。为什么可以肯定地说上述开机过程每次都必须走一遍,原因很简单,因为我们的平台软件是基于 Nand Flash 启动的,所有的代码都需要 Copy 到 SDRAM 才能运行,而关机断电之后, SDRAM 里的东东会全部丢失,所以,毫无疑问,上述的过程必须每次开机都执行;
2. 关机的过程相对比较简单,系统检测到关机中断之后,将调用 tmc_powerdown_handler ()来完成关机动作,它将把所有 AMSS 的任务都 Stop 掉,并最后调用 rex_exit() 退出 Rex ,从而完成整个关机动作。
3. 显然,关机动作前,如果有必要,每一个任务必须将它希望保存的信息保存到 Flash 上面,以便下次开机时可以得到这些信息;
开机流程简图
图 1 Qualcomm 平台开机框图
说明:
1. Tmc 是操作系统层面和 AMSS 软件关系最密切的一个任务,不过需要 OEM 商在此处修改的地方应该不多;
2. ui_task 是在操作系统层面, OEM 商需要重点研究清楚的一个任务,它是连接底层 Task 和上层AL 的一个中间层,有可能需要加入 OEM 商的操作流程;
3. CoreApp 是在 Brew 层面的一个 AL 层的入口 Applet ,它其着管理整个上层 AL 层软件的作用,根据产品需求,这个 App 需要定做;
4. AEE 是整个上层 App 的运行环境,目前 Qualcomm 没有公开它的源码,但它的运行机制, Amoi需要好好研究清楚,我将在另外一篇《 Qualcomm 平台 AEE 运行机制深入分析与研究》中探讨它的运行机理和调度机制,大家有兴趣可以参考此文;
二. Boot 代码深入分析
Boot 代码大部分是用汇编语言写的,也有小部分,可能需要由 OEM 商修改,所以用 C 语言来写。另外,Boot 代码属于 Driver 范围,所以大家可以在 drivers/boot 目录里面找到相应的代码。 Boot 的代码组织得非常模块化,整个 boot 的入口点是在 Boot_function_table.s 里面,这个汇编代码里面实际上是将 Boot 需要完成的任务封装成了不同的函数,由不同的函数来完成相应的工作,接下来,我将深入分析这些函数所完成的工作,如下所述。
a) mmu_enable_instruction_cache;
这个只有在 Nand 启动模式时才需要,打开 ARM 的指令 Cache.
b) boot_hw_ctrl_init
此函数主要是完成两条总线 (EBI1 、 EBI2) 控制器的初始化 , 这个函数执行完了 之后,系统就可以知道两条总线上连接了哪些设备,同时也可以找得到这些 设备,不过,至于单个设备自身的初始化,则不在这里 .
[ 注 ]
这个函数很重要, OEM 商如果需要加新的设备到系统中(挂在两条总线上),则需
要定做此模块,目前阶段主要是内存。另外,如前文所述,这个函数是由 C 语言
来写的,主要目的就是为了方便 OEM 商定做。内存设备的修改,可以在这个模块
里找到相应的数据结构,相对还是比较简单的。
c) boot_hw_tlmm_init
1. 晶振时钟的初始化;
2. 中断表的初始化;
3.GPIO 的初始化;
4.Msm 本身的驱动,除了 EBI2;
d) boot_rom_test
这个函数非常简单,只是做一个很简单的 Rom 检查 . (比对两个标志位来检查,并
没有一块一块地去检查)。
e) boot_ram_test
Ram 自检,具体算法其实也很简单,就是读、写内存以判断是否成功 .
f) boot_ram_init
1. 拷贝手机代码从 Nand Flash 到 SDRAM 。
a.Image__BB_RAM__Base:Core Code;
b.Image__APP_RAM__Base:App Code;
[ 注 ]
上述动作是分块进行的,原因是因为 Qualcomm 支持分块 Boot Load.
2. 将 Image__ZI_REGION__ZI 区域初始化为 0;
3. 初始化 OEM 要求的动态 Heap;
4. 至于代码段里的数据初始化,直接在 Image 里就完成了 ( 编译器完成 );
g) boot_stack_initialize
ARM 栈初始化 , 主要是为分块代码加载而预留的 .
h) boot_cache_mmu_init
ARM Mmu 初始化
注意:
到此为止,整个 Boot 的工作就告完结了,那么,它又是如何跳到 AMSS 的 main
入口点呢?原因很简单, ARM 编译器在链接的时候会自动做出一个 __rt_entry() ,
由此函数来完成 ARM 库函数的初始化,并最后将代码执行点跳转到 main ()。而
__rt_entry() 会在 boot_reset_handler.s 里调用,具体细节,大家可以不用太过关心,
只需要明白, Boot 跑完之后,手机软件就跑到了 main 里就 Ok 了。
三. Ui_task 的深入分析
从大的方向来讲, ui_task 只完成两件事,一件是必要的初始化工作(这个也是我们所关心的,即ui_task 到底完成了哪些工作);另外一件事就是各种信号量的事件处理,这也是我们比较关心的,即 ui_task 到底将哪些事件转发给了上层 App 。 搞清楚了上述两点,我们也就能大致把 ui_task 的承上启下的工作机理研究清楚。
1. ui_Init;
初始化过程中, ui_task 主要完成了如下几件事。
a) 创建一个用于 Kick Watchdog 的定时器,这样 WatchDog 能够及时得到 Kick ,假如今后发现手机在 ui_task 里面自动重启,很有可能就是这个定时器的 Timeout 设置得过短而造成的;
b) 注册通话相关的回调,主要是和紧急呼叫相关;
c) 电话本初始化,之所以要进行这个工作,主要是加快开机之后 AL 层软件操作电话本的速度,但这样将有可能导致开机速度过慢,如果开机速度过慢,可以考虑进入待机界面之后,在后台开一个task 去完成这项工作;
d) 初始化 Sound 设备;
e) 向底层服务任务 wms_task 注册 wms 回调,这个回调是在 IWms 组件里实现的。从这种角度来看, u 帮我们把 wms_task 和 IWMS 组件联系起来了,但并没有去将 AL 层软件和 IWMS 联系起来,这个工作将是由 AL 层软件自己去完成。当然,注册回调的这个工作也是可以在 AL 层完成,之所以在这里完成,而不是在 AL 层完成,其主要目的是这个工作可以做到与 AL 层无关,即 AL 层不需要关心这个事情,但这个事情是短消息功能得于实现的必须步骤;
f) 注册键盘消息回调;
通过这个回调,所有的按键消息都将经由底层的 hs_task 传到此回调函数里。然后回调函数将把所有的按键信息放到一个全局变量 ui_key_buffer 里面,接着发送一个 UI_KEY_SIG 信号给 ui_task通知它去处理按键信息,至于 ui_task 如何处理按键消息的,后面的 ui_handleSignals 里会有详细描述。
g) 初始化 Lcd ,这个工作不是 LCD 硬件设备的真正初始化,只是一些 UI 需要用到的 LCD 数据结构的初始化,和我们关系不大;
[ 注 ]
硬件的初始化,全部都在 hs_task 里面完成,从这种角度来看的话,系统能跑到 ui_task 里面,表明所有的硬件设备的驱动都已经成功加载。
h) 置开机标志 ui_powerup 为 True ;
i) 注册 IPC 信号量 UI_IPC_SIG ,这个可以暂时不管;
j) bridle_InitSWITable 的初始化,这个目标,暂时不知道,也可以先略过;
k) 初始化资源文件 ,其主要工作就是在 Rom 里面建立资源文件的符号链表,这样就可以让系统找到这些资源文件了(资源文件是被编译在代码段的,假如不这样做的话,系统将找不到这些资源文件);
l) Brew 运行环境 AEE 的初始化: AEE_Init ,这个函数看不到代码,大家只需要知道,到了这一步,整个 Brew 也就 Run 起来了,在 AEE 初始化完成之后,它将自动启动一个 Applet ,即CoreStartApp ,而 CoreStartApp 将把 CoreApp 启动起来;
m) 到此为止, ui_task 的初始化工作完成;
[ 注意 ]
1) 从上述的 ui_task 的初始化工作可以看出, ui_task 并没有完成手机 AL 层软件的
基本功能的初始化,比如说 Sim 卡检测、网络初始化等,这些工作,应该是在
CoreApp 里完成的
2) 真正和手机功能相关的初始化工作,是在 CoreApp 里完成的,这个 Applet 的工
作机理,后面也会有详细描述;
2. ui_HandleSignals;
ui_task 主要完成如下事件的处理。
a) 看门狗喂食;
b) TASK_STOP_SIG 信号,任务 Stop ,目前这个任务为空,没做任何事;
c) TASK_OFFLINE_SIG 的处理,这几个任务都属于操作系统层面的事件,目前我们可以暂时不管;
d) 处理关机信号 : CoreAppHandleStopSig (),这个只是处理 ui_task 在关机前需要完成的任务,比如说发送一个消息给 CoreApp 让它关掉自己,然后将 ui_task 关闭;
[A]
系统的真正关机信号是由 tmc 来处理,当它发现需要关机时,调用 tmc_powerdown_handler 来完成相应的工作,在这里就是给所有的任务发送 TASK_STOP_SIG 的信号。
[A]
深层次的关机处理,不需要我们了解,也没必要去知道,我们只需要知道在 ui_task 里面把该关的关掉就 Ok 了。
[A]
关机是一个层层深入的过程,每一个 App 或者任务只需要负责将它们自己创建的资源释放掉就 Ok了。而关机的导引线,显然是在 CoreApp 里截获到关机键之后发送出来的,事实上也是如此。
e) 网络掉线时,发送掉线信号给 CoreApp ;
[A]
其实这个信号完全可以在 CoreApp 里面,自己去注册,然后及时更新自己的网络状态,就不知有没有这种接口函数。
f) 处理按键消息 ,其主要完成如下的工作:
i. 打开背光;
ii. 特理按键到虚键值的转换 ;
iii. 按键声音的处理 ;
iv. 将按键消息传送到 AEE 执行环境,由它去负责按键的派发;
[ 注 ]
1. 背光的打开是由 ui 默认完成的,那这样的话,假如我不想按键时有背光,是否可行?看来就得修改此处的代码;
2.AEE 的按键派发机制如何?它能否保证处于显示最上层的 App 永远是可以得到 Key 的 App ,即假如一个 Applet 将自身 Hide ,它是否依然可以得到 Key ,而其它的 Applet 是否就不可以得到了?很怕也像 EMP 一样出现焦点丢失的情况;
g) 处理 AEE_APP_SIG 信号量,完成 AEE 的调度工作,这个任务是 ui 完成的最重要的一项工作,因为上层的 App 需要定时进行调度,目前看来,这个调度工作是由 AEE_APP_SIG 触发的,而AEE_APP_SIG 这个信号量,则由操作系统层面的一个定时器定时发送的。现在大家只要了解,AEE_Dispatch 会定时调用就 Ok 了,至于更详细的 AEE 调度机制,可以参考我的另外一篇《 AEE运行机制深入分析与研究》;
h) 处理 AEE_SIO_SIG 信号量,这个看不到代码,暂时略过不管;
3. 结论
通过上述对于 ui_task 的分析,可以看出, ui_task 真正和手机功能有关系的(即可能需要定制或者修改的地方),主要就是初始化资源文件 和 处理按键消息 这两部分。至于其它部分,目前都不需要Amoi 关心。手机真正功能的实现,比如说开机 Logo 的显示、 Sim 卡的检查、 Pin 码校验等,都是在CoreApp 里面完成的。
4. 其它
四. CoreApp 的深入分析
目前参考代码里面的 CoreApp 所完成的工作比较多且杂,主要说来有如下几件事。
a) 系统组件初始化;
b) 开机 Logo 的显示;
c) Sim 卡检测和 Pin 码校验;
d) 系统状态信息更新;
i. 电池状态;
ii. 网络信号;
iii. 网络模式;
e) IAnnunciator 的维护与更新;
f) 通话处理,打电话的输入框;
g) 主菜单处理;
h) 手机各种设置功能的处理;
i) 关机键的处理;
目前 CoreApp 里面的代码,完成了太多的事,其实完全可以剥离成不同的模块来完成,大致可以分成如下几个部分。
1. 总控模块; (CoreApp)
总控模块,主要完成手机按下开机键之后的各种初始化工作,同时此模块也是整个手机的控制中心,由它来完成手机的一些全局性工作,主要有如下几项。
1. 系统初始化、 Sim 卡检测和 Pin 码校验;
2. 开机 Logo 或者开机动画的显示;
3. 底层服务程序的启动;( WmsApp 、 DialApp 等);
4. 系统配置信息的统一管理;
由于写配置信息到 NV 上面是一件非常慢的工作,每次上层 App 改变配置之后都去操作 NV ,很影响速度。所以,可以在内存中开一个配置信息的 Buffer ,上层 App 操作的实际上是这个Buffer ,然后由 Core 在空闲的时候再统一写到 NV 上去。
5. 关机处理;
[ 注 ]
由于 CoreApp 是在 Idle Applet 的界面之下,所以,为了能够实现“一键回菜单”的功用,有可能需要修改 ui_task 里面的 Key 处理函数,将所有的 Key 消息转发给 Core ,这样 Core 就可以得到所有的 Key事件了。(现在的 ui_task 只把 Key 事件发送给了 AEE ,而 AEE 只会将 Key 事件发送给当前活动Applet )。
2. Idle 模块;
主要完成待机界面的画图工作,主要有两部分:
1. 系统信息指示栏;
2. 待机界面(位图、动画、时钟、日历等);
3. 软键
[ 注 ]
Idle 只负责界面工作,不负责具体的系统状态信息的获取工作,这个工作将由其它模块完成。
3. Polling
手机状态信息查询模块,主要是完成手机各种状态信息的更新与维护。主要有如下几种:
a. 电池强度;
b. 网络信号强度;
c. 网络模式 (C/G) ;
d. PLMN 网络名;
e. 短消息、通话状态、闹铃;(这个由专门的模块完成,不在 Polling 之列);
f. 各种外设信息;( USB 、耳机插入等);
g. 其它各种杂项信息;
4. Menu 模块
菜单模块主要分两部分,一部分是主菜单的实现,另一个子菜单的实现。一般来讲,手机上的菜单系统应该是由 Menu 模块去统一完成,而不是由每一个子程序去手动完成。菜单模块一般只需要负责到主菜单、二级菜单和三级菜单 就 Ok 了。三级菜单之后的界面,就由每一个 App 单独去维护了。
5. 其它功能 App 模块;
每一个功能模块,由一个专门的 App 来完成,这样的话,模块的独立性强,便于单独开发。模块间通过App 启动和消息传送的方式来发生关系和进行模块间通信。
6.
五. 后记
到此为止, Qualcomm 整个手机从按下开机键到跑到主菜机界面,整个流程一目了然。对于 Amoi 而言,目前需要关心和定做的部分其实不多,最头疼的 当属 CoreApp 的改造工作,当然这个就是后话了,笔者将在今后的文章中加以详述。
希望本文对于大家理解 Qualcomm 手机软件的运行流程有一定的帮助,如果有什么问题,请直接联系我,最后谢谢大家耐心把本文看完,谢谢。
六. 参考文档
a) 80-V1072-1_E_Boot_Block_Downloader.pdf
b) 80-V5316-1_K_QCT_Ext_API_RG.pdf
c) driver/boot 目录源码
d) service/tmc 目录源码
e) app/core 目录源码