内核 作者简介 Android 源码 GoldFish 下的驱动 驱动开发与移植 实战详解 MSM 内核和驱动 李骏 传感器、照相机 清华大学电子信息 驱 动 开 发 与 移 植Wi-Fi、GPS 工程专业学士,较 早进入 Android 开 发领域,有多年的 Android 开发经验, 熟 练 使 用 Java 和 /C++ 进行软件开 畅销书推荐: 发,熟悉 Android 层次结构和 Linux 驱动层的 结构及其上的开发,有着丰富的 Android 底层 和驱动层的优化、移植经验,擅长利用 JNI 技 术开发 Android 上的应用程序,曾带领团队利 用 NDK 技术成功开发过具有库仑计电池芯片 的电池管理软件,以及在 Android 上成功移植 人脸识别程序,目前在凹凸电子担任 Android 架构师。 实 战驱动开发与移植 详 解 实战详解 李骏 陈小玉 编著 陈小玉

硕士,南阳理工学 本书支持社区: 院计算机与信息工 程学院讲师,软 51CTO.com 件设计师。主要 面向实战的基于驱动和移植开发的技术书 从事 Android 应用 技术成就梦想 及游戏开发和教 学工作,熟练使用 Java 语言,具有多年的 Android 和 IOS 系统手 ●● 内容全面,涵盖了 Android 系统的所有驱动系统 机客户端软件设计经验,具备扎实的手机 / 桌

●● 每个实例都进行了详细讲解,力求读者易学、易用 面 /Web UI 设计开发基础,熟悉 Android 系统 的 UI design Guideline,熟悉人机交互、机器 ●● 凝聚一线工程师的智慧结晶,实用性强 学习和人工智能算法,擅长利用智能算法改进 Android 上的应用程序,使其更具有智能性。

封面设计:胡萍丽

分类建议:计算机 / 程序设计 / 移动开发 人民邮电出版社网址:www.ptpress.com.cn

28361 Android驱动开发与移植实战详解.indd 1-5 12-6-25 下午4:50 内核 Android 源码 GoldFish 下的驱动 MSM 内核和驱动 传感器、照相机 Wi-Fi、GPS

驱动开发与移植 实战详解 李骏 陈小玉 编著

人 民 邮 电 出 版 社 北 京

28361 Android驱动开发与移植实战详解.indd 6 12-6-25 下午4:50

内 容 提 要

Android 凭借其开源性、优异的用户体验和极为方便的开发方式,赢得了广大用户和开发者的青睐,目 前已经发展成为市场占有率很高的智能手机操作系统。 全书分为 18 章,依次讲解了 Android 系统的基本知识, Linux 内核的基本知识,分析了 Android 系统 的源码,深入分析 HAL 层的基本知识,GoldFish 下的驱动、MSM 内核和驱动、OMAP 内核和驱动、显示 系统驱动、输入系统驱动、振动器系统驱动、音频系统驱动和视频输出系统驱动,多媒体框架,传感器系 统、照相机系统、Wi-Fi 系统、蓝牙系统、GPS 系统和电话系统的知识。在每一章中,重点介绍了与 Android 驱动开发相关的底层知识,并对 Android 源码进行了详细的分析及驱动开发实现。 本书适合 Android 程序员、研发人员及 Android 爱好者学习,也可以作为相关培训学校和大专院校相 关专业的教学用书。

Android 驱动开发与移植实战详解 ♦ 编 著 李 骏 陈小玉 责任编辑 张 涛

♦ 人民邮电出版社出版发行 北京市崇文区夕照寺街 14 号 邮编 100061 电子邮件 [email protected] 网址 http://www.ptpress.com.cn 北京鑫正大印刷有限公司印刷

♦ 开本:800×1000 1/16 印张:35.25 字数:864 千字 2012 年 8 月第 1 版 印数:1 – 3 000 册 2012 年 8 月北京第 1 次印刷 ISBN 978-7-115-28361-0 定价:79.00 元 读者服务热线:(010)67132692 印装质量热线:(010)67129223 反盗版热线:(010)67171154 广告经营许可证:京崇工商广字第 0021 号

前 言

经过几年的发展,Android 已经成为了移动操作系统的翘楚。目前,已有众多的设备开始选择 使用 Android 系统,如智能手机、智能电视、平板电脑、上网本、MP3、MP4、智能相机、导航仪 等。相信在不久的未来,还将有更多采用 Android 系统的高科技产品进入到我们的生活中。这些设 备将产生各种各样的应用需求,尤其是与 Android 系统底层相关的应用,这将给开发者带来大量的 机会,尤其是系统级应用开发工程师。 随着 Android 版本的更新,从最初的触屏到现在的多点触摸,从普通的联系人到现在的数据同 步,从简单的 Map 到现在的导航系统,这都说明 Android 已经逐渐稳定,而且功能越来越 强大。此外,Android 平台不仅支持 Java 和 C++等主流的编程语言,还支持 Ruby、Python 等脚 本语言,甚至 Google 专为 Android 的应用开发推出了 Simple 语言,这使得 Android 有着非常广 泛的开发群体。

本书特色 本书内容丰富,实例覆盖全面。我们的目标是通过一本图书,提供多本图书的价值,读者可以 根据自己的需要有选择地阅读。在内容的编写上,本书具有以下特色。 (1)结构合理 从用户的实际需要出发,科学地安排知识结构,内容由浅入深,叙述清楚。全书精心筛选了具 有代表性的、读者关心的典型知识点,几乎包括 Android 底层和驱动技术的各个方面。 (2)易学易懂 本书条理清晰、语言简洁,可帮助读者快速掌握每个知识点。使读者既可以按照本书编排的章 节顺序进行学习,也可以根据自己的需求对某一章节进行有针对性的学习。 (3)实用性强 本书注重实用性和可操作性,详细讲解了各个部分的源码知识,使用户在掌握相关的操作技能 的同时,还能学习到相应的开发知识。

读者对象 Android 程序员 Android 底层和框架开发者 大中专院校相关专业的老师和学生 相关专业毕业设计的学生 Android 编程爱好者 相关培训机构的老师和学员

本团队在编写过程中,得到了出版社的大力支持,正是编辑的求实、耐心和效率才能使本书快 速出版。南阳理工学院陈小玉编写了第 7~11 章和第 16 章。另外也十分感谢我们的家人,在我们 写作的时候给予了巨大的支持。由于本团队水平有限,有纰漏和不尽如人意之处在所难免,诚请读 者提出意见或建议,以便修订并使之更臻完善。编辑联系邮箱为 [email protected]。源程序 下载地址为:www.toppr.net。

编 者

2

目 录

第 1 章 迅猛发展的 Android 系统...... 1 2.3 Linux 内核简介...... 14 2.3.1 内核的体系结构 ...... 14 1.1 智能手机世界...... 1 2.3.2 和 Android 相关的 Linux 1.1.1 何谓智能手机 ...... 1 内核知识 ...... 17 1.1.2 当前主流智能手机系统 ...... 1 2.4 分析 Linux 内核源码...... 20 1.2 Android 的自身优势...... 3 2.4.1 源码目录结构 ...... 21 1.2.1 开源...... 3 2.4.2 浏览源码的工具 ...... 23 1.2.2 强大的开发团队支持 ...... 3 2.4.3 用汇编语言编写内核 1.2.3 实行奖励机制 ...... 4 代码 ...... 24 1.3 认识驱动...... 4 2.4.4 Linux 内核的显著特性 ...... 24 1.4 分析 Android 架构...... 5 2.4.5 学习 Linux 内核的过程...... 32 1.4.1 操作系统层( OS) ...... 5 1.4.2 各种库和 Android 运行 第 3 章 开始分析 Android 源码 ...... 37 环境...... 6 3.1 搭建 Linux 开发环境和工具...... 37 1.4.3 应用程序 ...... 7 3.1.1 搭建 Linux 开发环境 ...... 37 1.4.4 应用程序框架 ...... 7 3.1.2 设置环境变量 ...... 38 1.5 开源的问题...... 8 3.1.3 安装编译工具 ...... 38 1.5.1 雾里看花的开源 ...... 8 3.2 获取 Android 源码...... 39 1.5.2 从选择 Java 开始谈为什么 3.3 分析 Android 源码结构 ...... 41 不开源驱动程序 ...... 8 3.4 编译 Android 源码...... 46 1.5.3 对驱动开发者的影响 ...... 9 3.5 运行 Android 源码...... 48 第 2 章 简要分析 Linux 内核 ...... 10 3.6 实践演练——演示两种编译 Android 程序的方法 ...... 49 2.1 Linux 基础 ...... 10 3.6.1 编译 Native C 的 2.1.1 Linux 历史简介...... 10 helloworld 模块 ...... 49 2.1.2 主要版本 ...... 10 3.6.2 手工编译 C 模块...... 50 2.1.3 Linux 的发展机遇 ...... 11 3.7 编译 Android Kernel ...... 53 2.2 Android 和 Linux 的关系 ...... 11 3.7.1 获取 Goldfish 内核代码 .....53 2.2.1 Android 继承于 Linux ...... 12 3.7.2 获取 MSM 内核代码 ...... 56 2.2.2 Android 和 Linux 内核的 3.7.3 获取 OMAP 内核代码 ...... 56 区别...... 12

3.7.4 编译 Android 的 Linux 5.4 移植总结 ...... 104 内核...... 56 5.4.1 移植各个 Android 部件的 3.8 运行模拟器...... 58 方式 ...... 104 3.8.1 Linux 环境下运行模拟器 5.4.2 辅助工作 ...... 105 的方法...... 58 第 6 章 常见的驱动平台...... 112 3.8.2 模拟器辅助工具——ADB...59 6.1 专用驱动 ...... 112 第 4 章 驱动移植...... 62 6.1.1 Binder 驱动程序...... 112 4.1 Android 移植...... 62 6.1.2 Logger 驱动...... 122 4.1.1 移植的任务 ...... 62 6.1.3 组件 Lowmemorykiller .....123 4.1.2 移植的内容 ...... 62 6.1.4 Timed Output 驱动程序 ....123 4.1.3 驱动开发需要做的工作 .....63 6.1.5 Timed Gpio 驱动程序 ...... 125 4.2 Android 对 Linux 的改造 ...... 64 6.1.6 唤醒和休眠 ...... 126 4.2.1 Android 的核心驱动...... 64 6.1.7 Ashmem 驱动程序...... 130 4.2.2 为 Android 构建 Linux 6.1.8 Pmem 驱动程序 ...... 133 操作系统...... 67 6.1.9 Alarm 驱动程序 ...... 134 4.3 内核空间和用户空间接口 ...... 68 6.1.10 USB Gadget 驱动程序 ....136 4.3.1 实现系统和硬件之间的 6.1.11 Android Paranoid 驱动 交互...... 68 程序 ...... 137 4.3.2 实现内核到用户空间的 6.2 Goldfish 设备驱动 ...... 138 数据传输...... 70 6.3 MSM 内核和驱动...... 146 4.4 三类驱动程序...... 74 6.3.1 MSM 基础...... 146 4.4.1 字符设备驱动 ...... 74 6.3.2 移植 MSM 内核 ...... 148 4.4.2 块设备驱动 ...... 82 6.3.3 移植 MSM...... 150 4.4.3 网络设备驱动 ...... 86 6.3.4 高通特有的组件 ...... 156 6.4 OMAP 内核和驱动...... 157 第 5 章 深入详解 HAL 层...... 87 6.4.1 OMAP 基础...... 157 ...... 5.1 初识 HAL 层 87 6.4.2 OMAP 内核...... 159 ...... 5.1.1 HAL 层简介 87 6.4.3 移植 OMAP 体系结构...... 161 5.1.2 比较 HAL_legacy 和 6.4.4 移植 Android 专用驱动 ...... HAL 89 和组件 ...... 168 ...... 5.2 分析 HAL 层源码 89 6.4.5 OMAP 的设备驱动...... 169 5.2.1 分析 HAL module ...... 89 第 7 章 输入系统驱动...... 175 5.2.2 分析 mokoid 工程...... 92 5.3 Sensor 在 HAL 层的表现...... 101 7.1 输入系统介绍 ...... 175 5.3.1 HAL 层的 Sensor 代码 .....101 7.1.1 Android 输入系统结构元素 5.3.2 总结 Sensor 编程的流程 ...103 介绍 ...... 175

2

7.1.2 Android 输入系统驱动.....176 8.4 实现电话系统驱动 ...... 219 7.1.3 Input 系统的层次结构...... 177 8.4.1 RIL 中消息队列的建立 ....219 7.1.4 移植工作...... 177 8.4.2 与底层 Modem 通信...... 224 7.2 Input 驱动源码分析...... 178 8.5 深入分析实现 Android 电话系统 7.2.1 文件 input.h...... 178 的流程 ...... 229 7.2.2 文件 KeycodeLabels.h ...... 182 8.5.1 初始启动流程 ...... 229 7.2.3 文件 KeyCharacterMap.h...186 8.5.2 接收信息流程 ...... 231 7.2.4 Kl 格式文件 ...... 187 8.5.3 等待硬件响应 ...... 233 7.2.5 kcm 格式文件 ...... 188 第 9 章 显示系统驱动...... 236 7.2.6 文件 EventHub.cpp ...... 188 ...... 7.3 Input 设备的运作过程...... 191 9.1 显示系统基础 236 ...... 7.3.1 Input 设备的注册 9.1.1 Android 的版本 236 ...... (硬件驱动层) ...... 192 9.1.2 不同的显示系统 237 7.3.2 Input 子系统的加载过程 9.1.3 FrameBuffer 驱动的 ...... (子系统核心层) ...... 192 使用基础 237 ...... 7.3.3 Input 子系统的事件处理 9.2 移植 Android 显示系统 238 ...... (事件处理层) ...... 194 9.2.1 FrameBuffer 驱动程序 238 ...... 7.4 模拟器的输入驱动...... 198 9.2.2 硬件抽象层 242 ...... 7.5 高通平台的输入驱动实现 ...... 199 9.3 实现显示系统的驱动程序 254 7.5.1 触摸屏驱动 ...... 199 9.3.1 Goldfish 中的 FrameBuffer ...... 7.5.2 按键和轨迹球驱动 ...... 205 驱动程序 254 7.6 OMAP 处理器中的输入驱动 9.3.2 使用 Gralloc 模块的驱动 ...... 实现 ...... 208 程序 257 ...... 7.6.1 触摸屏驱动程序 ...... 208 9.4 MSM 中显示驱动的实现 266 7.6.2 键盘驱动程序 ...... 208 9.4.1 MSM 中的 FrameBuffer 驱动程序 ...... 267 第 8 章 电话系统驱动...... 210 9.4.2 MSM 中的 Gralloc 8.1 电话系统基础...... 210 驱动程序 ...... 269 8.1.1 Android 电话系统简介.....210 9.5 OMAP 中显示驱动的实现...... 276 8.1.2 深入分析电话系统的 9.5.1 文件 omapfb-main.c ...... 276 实现文件...... 211 9.5.2 文件 omapfb.h ...... 278 8.1.3 电话系统结构 ...... 212 9.6 6416 中 FrameBuffer 的工作 8.2 移植 Modem 驱动和 RIL 硬件 原理 ...... 279 抽象层 ...... 213 第 10 章 音频系统驱动...... 286 8.3 移植和调试...... 214 ...... 8.3.1 驱动程序...... 214 10.1 音频系统结构 286 ...... 8.3.2 RIL 接口...... 216 10.2 音频系统的层次 287

3

10.2.1 层次说明...... 287 11.2 移植的内容 ...... 346 10.2.2 Media 库中的 Audio 11.3 分析硬件抽象层 ...... 346 框架...... 288 11.3.1 Overlay 系统硬件抽象层的 10.2.3 本地代码...... 291 接口 ...... 346 10.2.4 JNI 代码 ...... 294 11.3.2 实现硬件抽象层...... 349 10.2.5 Java 代码...... 295 11.3.3 实现接口 ...... 350 10.3 移植工作...... 296 11.4 实现 Overlay 硬件抽象层...... 351 10.3.1 两个任务...... 296 11.5 在 OMAP 平台实现 Overlay 10.3.2 Audio 的硬件抽象层 ...... 296 系统 ...... 353 10.3.3 实现 AudioFlinger 中的 11.5.1 实现输出视频驱动 Audio 硬件抽象层 ...... 298 程序 ...... 353 10.3.4 真正实现 Audio 硬件 11.5.2 实现 Overlay 硬件 抽象层...... 304 抽象层 ...... 354 10.4 MSM 平台实现 Audio 11.6 系统层调用 Overlay 模块...... 360 驱动系统...... 304 11.6.1 测试文件 ...... 360 10.4.1 实现 Audio 驱动程序 .....304 11.6.2 在 Android 系统中创建 10.4.2 实现硬件抽象层 ...... 305 Overlay ...... 361 10.5 OSS 平台实现 Audio 驱动系统 ...309 11.6.3 管理 Overlay HAL 模块 ...363 10.5.1 OSS 驱动程序介绍...... 309 11.7 抽象层实现 10.5.2 mixer...... 310 (V4l2 驱动实现方式) ...... 364 10.6 ALSA 平台实现 Audio 系统...... 317 第 12 章 振动器系统驱动 ...... 369 10.6.1 ALSA 基础...... 317 ...... 10.6.2 注册音频设备和音频 12.1 振动器系统结构 369 ...... 驱动...... 318 12.1.1 硬件抽象层 371 ...... 10.6.3 ALSA 的底层接口...... 318 12.1.2 JNI 框架部分 372 ...... 10.6.4 放音流程...... 320 12.2 开始移植 373 ..... 10.6.5 录音流程...... 325 12.2.1 移植振动器驱动程序 373 ...... 10.6.6 在 Android 中使用 ALSA 12.2.2 实现硬件抽象层 374 ... 声卡...... 328 12.3 在 MSM 平台实现振动器驱动 375 10.6.7 在 OMAP 平台移植 Android 第 13 章 Android 多媒体插件框架...... 379 的 ALSA 声卡驱动...... 337 13.1 Android 多媒体插件...... 379 10.7 6410 中的 ALSA 驱动...... 340 13.2 需要移植的内容 ...... 380 10.7.1 ALSA 的设备文件...... 340 13.3 OpenCore 引擎详解...... 381 10.7.2 创建声卡和 PCM 设备 ...341 13.3.1 OpenCore 的层次结构 ....381 ...... 第 11 章 视频输出系统驱动 344 13.3.2 OpenCore 的代码结构 ....382 .... 11.1 视频输出系统结构 ...... 344 13.3.3 OpenCore 的编译结构 383

4

13.3.4 OpenCore OSCL ...... 387 15.2.3 实现上层部分 ...... 456 13.3.5 实现 OpenCore 中的 15.3 实现传感器 ...... 460 OpenMax 部分 ...... 389 第 16 章 Wi-Fi系统、蓝牙系统 13.3.6 OpenCore 的扩展...... 401 和 GPS 系统...... 466 13.4 引擎 ...... 408 13.4.1 代码结构...... 408 16.1 Wi-Fi 系统的应用和移植...... 466 13.4.2 实现 OpenMax 接口 ...... 408 16.1.1 Wi-Fi 系统的结构 ...... 466 13.4.3 Video Buffer 传输流程 ...412 16.1.2 移植的内容 ...... 468 16.1.3 移植和调试 ...... 468 第 14 章 Camera照相机驱动...... 418 16.1.4 OMAP 平台实现 Wi-Fi ...476 系统的结构...... 14.1 Camera 418 16.1.5 配置 Wi-Fi ...... 478 14.2 移植的内容...... 421 16.1.6 SDIO 设备的移植 ...... 481 14.2.1 fimc 驱动模块的加载.....421 16.1.7 移植 Wi-Fi 驱动的注意 14.2.2 V4l2 驱动的用法 ...... 425 事项 ...... 485 14.3 移植和调试...... 426 16.2 蓝牙系统的应用和移植 ...... 486 14.3.1 V4L2 驱动程序...... 426 16.2.1 蓝牙结构 ...... 487 14.3.2 硬件抽象层 ...... 434 16.2.2 移植的内容 ...... 489 14.4 实现 Camera 系统的硬件 16.2.3 具体移植 ...... 490 抽象层 ...... 438 16.2.4 MSM 平台的蓝牙驱动....492 14.4.1 Java 程序部分...... 438 16.2.5 本地适配器连接过程 .....494 14.4.2 Java 本地调用部分 ...... 439 远程适配器连接过程 ..... 14.4.3 本地库 libui.so ...... 440 16.2.6 498 .... 14.4.4 Camera 服务 16.2.7 分析 6410 的蓝牙驱动 500 ...... libcameraservice.so ...... 441 16.3 定位系统 510 ...... 14.5 实现 Camera 系统...... 445 16.3.1 系统结构 510 ...... 14.5.1 在 MSM 平台实现 Camera 16.3.2 移植的内容 512 系统...... 445 16.3.3 移植和调试 ...... 512 14.5.2 OMAP 平台实现 Camera 16.3.4 GPS 的串口驱动和数据 系统...... 448 读取 ...... 525 借助 驱动使用照相机 14.6 Sensor 第 17 章 振动器驱动和警报器驱动...... 529 系统 ...... 449 17.1 Alarm 系统基础 ...... 529 第 章 传感器系统驱动 ...... 15 452 17.1.1 Alarm 系统的结构 ...... 529 15.1 传感器系统的结构 ...... 452 17.1.2 移植的内容 ...... 532 15.2 移植 Sensor 驱动 ...... 454 17.2 移植和调试 ...... 532 15.2.1 移植驱动程序 ...... 454 17.3 实现 Alarm 驱动 ...... 535 15.2.2 移植硬件抽象层 ...... 455 17.4 MSM 平台实现 Alarm...... 542

5

第 18 章 光系统驱动和电池系统驱动 ....544 18.1.5 深入分析 Android 的 光系统 ...... 548 18.1 Lights 光系统的应用和移植...... 544 18.2 Battery 电池系统的应用和移植 ...549 18.1.1 Lights 系统的结构...... 544 18.2.1 Battery 系统的结构...... 549 18.1.2 移植的内容 ...... 545 18.2.2 移植的内容 ...... 551 18.1.3 移植和调试 ...... 546 18.2.3 移植和调试 ...... 551 18.1.4 MSM 平台实现光 18.2.4 模拟器中实现电池系统 ...554 系统...... 547

6

第 1 章 迅猛发展的 Android 系统

Android(安卓)是 IT 巨头谷歌公司推出的一款手机系统,是建立在 Linux 内核基础之上的, 能够迅速建立手机软件的解决方案。Android 的功能十分强大,作为一个新兴的热点,已经成为了 软件行业中的一股新兴力量。据市场数据统计,Android 系统已经成为当今市场占有率第一位的智 能手机系统。本章将简单介绍 Android 的发展历程和背景,让读者了解 Android 的辉煌之路。

1.1 智能手机世界

智能手机是指具有像个人电脑那样独立的操作系统,用户可以在上面安装自己需要的第三方软 件或游戏程序,并且可以通过移动通信网络接入无线网络。在 Android 系统诞生之前已经有很多优 秀的智能手机产品,例如家喻户晓的 Symbian(塞班)系列和微软的 Windows Mobile 系列等。

1.1.1 何谓智能手机 当今公认的智能手机必须具备下面的功能标准: (1)操作系统必须支持新应用的安装; (2)高速度处理芯片; (3)支持播放式的手机电视; (4)大存储芯片和存储扩展能力; (5)支持 GPS 导航。 后来手机联盟制定了一个标准,在该标准中列出了智能手机的如下主要特点: (1)具备普通手机的全部功能,例如可以进行正常的通话和发短信等手机应用; (2)是一个开放性的操作系统,在系统平台上可以安装更多的应用程序,从而实现功能的无限扩充; (3)具备上网功能; (4)具备 PDA 的功能,实现个人信息管理、日程记事、任务安排、多媒体应用、浏览网页; (5)可以根据个人需要扩展机器的功能; (6)扩展性能强,并且可以支持很多第三方软件。

1.1.2 当前主流智能手机系统 当今最主流的智能手机系统有微软的 Windows Phone 系列、塞班系列、PDA、黑莓、苹果的 iOS

Android 驱动开发与移植实战详解

和本书的主角 Android。

1.微软的 Windows Mobile

Windows Mobile 是微软公司的一款杰出产品,Windows Mobile 将熟悉的 Windows 桌面扩展到 了个人设备中。使用 Windows Mobile 操作系统的设备主要有 PPC 手机、PDA、随身音乐播放器等。 Windows Mobile 操作系统有三种,分别是 Windows Mobile Standard、Windows Mobile Professional、 Windows Mobile Classic。当前的最新版本是 Windows Phone 7,Windows Phone 8 也即将发布。

2.塞班系统

塞班系统 Symbian 是一款久负盛名的手机系统,最初是由诺基亚、索尼爱立信、摩托罗拉、西 门子等几家大型移动通信设备商共同出资组建的一个合资公司,职责是专门研发手机操作系统。 Symbian 有着良好的界面,采用内核与界面分离技术,对硬件的要求比较低,支持 C++、Visual Basic 和 J2ME。目前根据人机界面的不同,Symbian 体系的 UI(,用户界面)平台分为 Series60、Series80、Series90、UIQ 等。其中 Series60 主要是给数字键盘手机用,Series80 是为完整 键盘所设计,Series90 则是为触控笔方式而设计。 2010 年 9 月,诺基亚宣布将从 2011 年 4 月起从 Symbian 基金会(Symbian Foundation)手中 收回 Symbian 操作系统控制权。

3.Palm

Palm 是流行的个人数字助理(PDA,又称掌上电脑)的传统名字。从广义上讲,Palm 是 PDA 的一种,是 Palm 公司发明的。而从狭义上讲,Palm 是 Palm 公司生产的 PDA 产品,区别于 SONY 公司的 Clie 和 Handspring 公司的 Visor/Treo 等其他运行 Palm 操作系统的 PDA 产品。其显著特点 之一是写入装置输入数据的方法,能够点击显示器上的图标选择输入的项目。2009 年 2 月 11 日, Palm 公司 CEO Ed Colligan 宣布以后将专注于 WebOS 和 Windows Mobile 的智能设备,而将不会再 有基于“Palm OS”的智能设备推出,除了 Palm Centro 会在以后和其他运营商合作时继续推出。

4.黑莓 BlackBerry

BlackBerry 是加拿大 RIM 公司推出的一种移动电子邮件系统终端,其特色是支持推动式电子邮 件、手提电话、文字短信、互联网传真、网页浏览及其他无线资讯服务,其最大优势在于收发邮件。

5.iOS

iOS 是苹果手机产品 iPhone 的操作系统,iPhone 是一款手机产品,由苹果公司在 2007 年 1 月 9 日举行的 Macworld 宣布推出,并在 2007 年 6 月 29 日在美国上市。IPhone 实现了将创新的移动 电话、可触摸宽屏 iPod 以及具有桌面级电子邮件、网页浏览、搜索和地图功能的突破性因特网通 信设备这三种产品完美地融为一体。每一件 iPhone 产品都是一件艺术品,无论是外观还是品质和 性能,都拥有超高的人气。推出的每一款新 iPhone,都马上成为市面智能手机的标杆。在 2011 年 10 月 4 日推出了当前最新的 iPhone 4S。

2 第 1 章 迅猛发展的 Android 系统

6.Android

Android 是本书的主角,是一款基于 Linux 内核的开源手机操作系统的名称,该平台由操作系 统、中间件、用户界面和应用软件组成,号称是首个为移动终端打造的真正开放和完整的移动软件。

1.2 Android 的自身优势

Android 采用了 WebKit 浏览器引擎,具备触摸屏、高级图形显示和上网功能,用户能够在手 机上查看电子邮件、搜索网址和观看视频节目等,同时 Android 还具有比 iPhone 等其他手机更强 的搜索功能,可以说是一种融入全部 Web 应用的平台。正是因为其特有的巨大优势,在 2010 年下 半年,Android 规模便超越了苹果 iPhone,大有一统手机系统之势!并且终于在 2011 年超越了塞班, 成为市场占有率最高的智能手机系统。

1.2.1 开源 Android 出身于 Linux 世家,是一款开源的手机操作系统。Android 功成名就之后,各大手机联 盟纷纷加入,这个联盟由包括中国移动、摩托罗拉、高通、宏达电子和 T-Mobile 在内的 30 多家 技术和无线应用的领军企业组成。通过与运营商、设备制造商、开发商和其他有关各方结成深层次 的合作伙伴关系,希望借助建立标准化、开放式的移动电话软件平台,在移动产业内形成一个开放 式的生态系统。

1.2.2 强大的开发团队支持 Android 的研发队伍阵容强大,包括摩托罗拉、Google、HTC(宏达电子)、PHILIPS、T-Mobile、 高通、魅族、三星、LG 以及中国移动在内的 34 家企业。它们都将基于该平台开发手机的新型业务, 应用之间的通用性和互联性将在最大程度上得到保持。并且还成立了手机开放联盟,联盟中的成员 由手机制造商、半导体公司和软件公司组成,具体名单如下。

1.手机制造商

台湾宏达国际电子(HTC)(Palm 等多款智能手机的代工厂),摩托罗拉(美国最大的手机制 造商),韩国三星电子(仅次于诺基亚的全球第二大手机制造商),韩国 LG 电子,中国移动(全球 最大的移动运营商),日本 KDDI(2900 万用户),日本 NTT DoCoMo(5200 万用户),美国 Sprint Nextel(美国第三大移动运营商,5400 万用户),意大利电信(Telecom Italia,意大利主要的移动 运营商,3400 万用户),西班牙 Telefónica (在欧洲和拉美有 1.5 亿用户),T-Mobile(德意志电信 旗下公司,在美国和欧洲有 1.1 亿用户)。

2.半导体公司

Audience Corp(声音处理器公司),Broadcom Corp(无线半导体主要提供商),英特尔(Intel), Marvell Technology Group,Nvidia (图形处理器公司),SiRF(GPS 技术提供商),Synaptics(手

3 Android 驱动开发与移植实战详解

机用户界面技术),德州仪器(Texas Instruments),高通(Qualcomm),惠普 HP(Hewlett-Packard Development Company,L.P)。

3.软件公司

Aplix,Ascender,eBay 的 Skype,Esmertec,Living Image,NMS Communications,Noser Engineering AG,Nuance Communications,PacketVideo,SkyPop,Sonix Network,TAT-The Astonishing Tribe,Wind River Systems。

1.2.3 实行奖励机制 安卓(Android)为了提高程序员的开发积极性,不但为其提供了一流硬件的设置,还提供了一 流的软件服务。并且采取了振奋人心的奖励机制,定期召开比赛,创意和应用夺魁者将会得到重奖。

1.开发 Android 平台的应用

在 Android 平台上,程序员可以开发出各式各样的应用。其中 Android 应用项目是通过 Java 语言开发的,只要具备 Java 开发基础,就能很快地上手并掌握。作为单独的 Android 开发,Java 编程门槛并不高,即使没有编程经验的门外汉,也可以在突击学习 Java 之后而学会 Android。另外, Android 完全支持 2D、3D 和数据库,并且和浏览器实现了集成。所以通过 Android 平台,程序员 可以迅速、高效地开发出绚丽多彩的应用,例如常见的工具、管理、互联网和游戏等。

2.奖金丰厚的 Android 大赛

为了吸引更多的用户使用 Android 开发,已经成功举办了奖金为 1000 万美元的开发者竞赛。 鼓励开发人员创建出有创意的实用软件。这种大赛对于开发人员来说,不但能练习自己的开发水平, 并且有机会获得高额的奖金。

3.在 Android Market 上获取收益

Android Market 地址是 http://www.Android.com/market/。为了能让 Android 平台吸引更多的关 注,谷歌开发了自己的 Android 软件下载店 Android Market,允许开发人员将应用程序在上面发布, 也允许 Android 用户随意下载获取自己喜欢的程序。开发者需要申请开发者账号,有账号后才能将 自己开发的作品上传到 Android Market,并且可以对自己的软件进行定价。只要开发的软件作品足 够吸引人,就可以获得好的金钱回报。

1.3 认识驱动

驱动是硬件和软件之间的媒介和载体,是计算机等电子产品运行的根本。生活中总会遇到这样 的场景:买了一个新 USB 鼠标,插在电脑上后会提示安装新的驱动;买了一台新的打印机,也需 要安装驱动后才能使用。驱动含有推动和发动之意,计算机领域中的驱动也含有推动之意。 当我们在电脑中安装新硬件时,总会被要求放入“这种硬件的驱动程序”。其实在 Windows 系

4 第 1 章 迅猛发展的 Android 系统

统中,在安装主板、光驱、显卡、声卡这些硬件产品时都对应着一套完整的驱动程序。如果需要外 接别的硬件设备,也还需要安装相应的驱动程序,例如外接游戏硬件要安装手柄、方向盘、摇杆、 跳舞毯等的驱动程序,外接打印机要安装打印机驱动程序,上网或接入局域网要安装网卡、Modem 甚至 ISDN、ADSL 的驱动程序。 和 Windows 系统一样,在 Android 手机中也经常需要使用一些外部硬件设备,例如蓝牙耳机、 存储卡和摄像头。要想使用这些外部辅助设备,也需要安装对应的驱动程序。驱动程序是添加到操 作系统中的一段代码,通常这段代码比较简短,但是在里面包含了和硬件相关的设备信息。有了这 些信息,计算机就可以与设备进行通信,从而使用这些硬件。驱动程序是硬件厂商根据操作系统编 写的配置文件,可以说没有驱动程序,计算机中的硬件就无法工作。操作系统不同,对应的硬件驱 动程序也不同。硬件厂商为了保证硬件的兼容性及增强硬件的功能,会不断更新、升级驱动程序, 例如显卡芯片公司 Nvidia 平均每个月会升级驱动程序 2 到 3 次。 驱动程序是硬件的一个构成部分,当我们安装新的硬件时,也必须安装对应的驱动程序。凡是 安装一个原本不属于我们电脑中或手机中的硬件设备时,系统就会要求你安装驱动程序,将新的硬 件与电脑或手机系统连接起来。驱动程序在此扮演了一个沟通的角色,负责把硬件的功能告诉电脑 系统,并且也将系统的指令传达给硬件,让它开始工作。 手机中的驱动和电脑中的驱动一样,当在手机中使用数据线、蓝牙、红外等连接方式连接电脑 时,在一般情况下需要驱动程序。而且在一部分手机中,通过数据线、蓝牙、红外方式连接电脑后 还需要软件才能传输数据到电脑,或者传输数据到手机。此时可以使用购买手机时的随机光盘中的 驱动程序解决问题,驱动程序也可以在手机网站或论坛上下载。

1.4 分析 Android 架构

Android 作为一个移动设备的平台,其软件层次结构包括操作系统(OS)、中间件(MiddleWare) 和应用程序(Application)。根据 Android 的软件框图,其软件层次结构自下而上分为以下 4 层。 (1)操作系统层(OS)。 (2)各种库(Library)和 Android 运行环境(RunTime)。 (3)应用程序框架(Application Framework)。 (4)应用程序(Application)。 上述各个层的具体结构如图 1-1 所示。

1.4.1 操作系统层(OS) Android 使用 Linux2.6 作为操作系统,Linux2.6 是一种标准的技术,Linux 也是一个开放的操 作系统。Android 对操作系统的使用包括核心和驱动程序两部分,Android 的 Linux 核心为标准的 Linux2.6 内核,Android 更多的是需要一些与移动设备相关的驱动程序。主要的驱动如下所示。 显示驱动(Display Driver):常用基于 Linux 的帧缓冲(Frame Buffer)驱动。 Flash 内存驱动(Flash Memory Driver):是基于 MTD 的 Flash 驱动程序。 照相机驱动(Camera Driver):常用基于 Linux 的 v4l(Video for Linux )驱动。

5 Android 驱动开发与移植实战详解

音频驱动(Audio Driver):常用基于 ALSA(Advanced Linux Sound Architecture,高 级 Linux 声音体系)驱动。

主屏幕 联系人 电话 浏览器 …… 应用程序

活动管理器 窗口管理器 内容提供器 视图系统 通知管理器 应用程序 框架 包管理器 电话管理器 资源管理器 本地管理器 XMPP服务

接口管理器 包管理器 包管理器 持久层库 Android 包管理器 位图及矢量 浏览器引擎 库 运行 环境 Dalvik虚拟 2D图形引 机器 中间协议 libc函数库 擎

Flash内存驱 Binder IPC 显示驱动 相机驱动 蓝牙驱动 动 驱动 Linux 内核层 USB驱动 键盘驱动 WiFi驱动 音频驱动 能源管理

▲图 1-1 Android 操作系统的组件结构图

WiFi 驱动(Camera Driver):基于 IEEE 801.31 标准的驱动程序。 键盘驱动(KeyBoard Driver):作为输入设备的键盘驱动。 蓝牙驱动(Bluetooth Driver):基于 IEEE 801.35.1 标准的无线传输技术。 Binder IPC 驱动:Android 一个特殊的驱动程序,具有单独的设备节点,提供进程间通信的 功能。 Power Management(能源管理):管理电池电量等信息。

1.4.2 各种库和 Android 运行环境 本层次对应一般嵌入式系统,相当于中间件层次。Android 的本层次分成两个部分,一个是各 种库(Library),另一个是 Android 运行环境(RunTime)。本层的内容大多是使用 C++实现的。其 中包含的各种库如下。 C 库:C 语言的标准库,也是系统中一个最为底层的库,C 库是通过 Linux 的系统调用来实 现的。

6 第 1 章 迅猛发展的 Android 系统

多媒体框架(Media Framework):这部分内容是 Android 多媒体的核心部分,基于 PacketVideo(即 PV)的 OpenCORE,从功能上本库一共分为两大部分,一部分是音频、视频的回 放(PlayBack),另一部分则是音视频的纪录(Recorder)。 SGL:2D 图像引擎。 SSL:即 Secure Socket Layer,位于 TCP/IP 协议与各种应用层协议之间,为数据通信提供 安全支持。 OpenGL ES 1.0:提供了对 3D 的支持。 界面管理工具(Surface Management):提供了对管理显示子系统等功能。 SQLite:一个通用的嵌入式数据库。 WebKit:网络浏览器的核心。 FreeType:位图和矢量字体的功能。 Android 的各种库一般是以系统中间件的形式提供的,它们均有的一个显著特点就是与移动设 备的平台的应用密切相关。 Android 运行环境主要是指虚拟机技术——。Dalvik 虚拟机和一般 JAVA 虚拟机(Java VM)不同,它执行的不是 JAVA 标准的字节码(Bytecode),而是 Dalvik 可执行格式(.dex)中执 行文件。在执行的过程中,每一个应用程序即一个进程(Linux 的一个 Process)。二者最大的区别 在于 Java VM 是基于栈的虚拟机(Stack-based),而 Dalvik 是基于寄存器的虚拟机(Register-based)。 显然,后者最大的好处在于可以根据硬件实现更大的优化,这更适合移动设备的特点。

1.4.3 应用程序 Android 的应用程序(Application)主要是用户界面(User Interface)方面的,通常以 Java 程 序编写,其中还可以包含各种资源文件(放置在 res 目录中)。Java 程序及相关资源经过编译后, 将生成一个 APK 包。Android 本身提供了主屏幕(Home)、联系人(Contact)、电话(Phone)、浏 览器(Brower)等众多的核心应用。同时应用程序的开发者还可以使用应用程序框架层的 API 实 现自己的程序。这也是 Android 开源的巨大潜力的体现。

1.4.4 应用程序框架 Android 的应用程序框架(Application Framework)为应用程序层的开发者提供 API,它 实 际 上 是一个应用程序的框架。由于上层的应用程序是以 Java 构建的,因此本层次提供的首先包含了 UI 程序中所需要的各种控件,例如 View(视图组件),其中又包括了 List(列表)、Grid(栅格)、Text Box(文本框)、Button(按钮)等,甚至一个嵌入式的 Web 浏览器。 一个基本的 Andoid 应用程序,可以使用应用程序框架中的如下部分。 Activity(活动)。 Broadcast Intent Receiver(广播意图接收者)。 Service(服务)。 Content Provider(内容提供者)。 Intent and Intent Filter(意图和意图过滤器)。

7 Android 驱动开发与移植实战详解

1.5 开源的问题

我们都知道 Android 是基于 Linux 内核的,因为一直以来 Linux 是开源的,所以基于 Linux 内 核的 Android 也号称开源,所以一经推出后就受到了广大程序员和手机厂商的青睐。但是在开源方 面 Android“半遮半掩”,具体原因得从 Android 的发展历史谈起。

1.5.1 雾里看花的开源 在 Android 刚被推出的时候,只能用 Java 语言开发应用程序,这就需要所有的应用程序都运 行在一个巨大的虚拟机上。在 2009 年 6 月,Android 发布了 NDK 工具包,这样就可以支持 C/C++ 语言编程,但是性能不如 SKD 工具包中的 Java 语言。 2010 年 2 月, 的维护者 Greg Kroah-Hartman 宣布,将 Android 代码从 Linux Kernel 代码库中删除,此事对于普通用户可能并没有什么影响,但对于开发者,尤其是开源社区的开发者 来说,算是一颗重磅炸弹。消息公布以后,外界普遍觉得惊讶和可惜。好不容易,才有了一个这么 受欢迎的开源手机系统,应该齐心协力共同开发才对,为什么要“窝里斗”呢?到底是什么矛盾, 使得 Linux Kernel 小组剔除 Android 代码呢? 从 Linux 2.6.33 版本开始,Google 智能手机操作系统 Android 核心代码全部被删除。这是因为 提倡开源的 Android 在 Linux 面前使用了雾里看花的把戏,它修改了 Kernel 内核,但是又不提供修 改的细节,这相当于自己搞了一个封闭的系统。尽管 Android 取得了空前的成功,但是 Google 也 放弃了构建一个真正开源的手机系统的机会,从而也就不能获得由全世界程序员提供智慧、分享代 码和推动创新的好处。由此可见,是因为 Android 的不真正开源,所以才被从 Linux 体系中删除。 Android 与 Ubuntu、Debian、Redhat 等传统的 Linux 发行版相比,只有系统的底层结构是一样 的,而其他东西在 Android 中都不一样,尤其是程序员的编程接口是完全不同的。所以必须重新写 Android 应用程序后才能使用,现存的 Linux 程序无法移植上去。由此可见,Android 是一种全新 的系统,它与 Linux 的距离有很远。

1.5.2 从选择 Java 开始谈为什么不开源驱动程序 (1)使用 Java 的好处。 Android 很好地解决了长期令手机制造商头痛不已的问题:在业界缺乏一个开源的 Java 虚拟机 和统一的应用程序接口。使用 Android 后,程序员只要编写一次程序就可以用在各种手机硬件平台 之上。这就是 Android 应用程序使用 Java 语言开发的原因,因为如果不这样做的话,无法让程序 实现和硬件无关。 可能很多熟知 Linux 的读者会反问:传统的 Linux 系统也不依赖特定的硬件,只要把源代码根 据不同的平台分别编译,同一个程序就可以在不同的硬件架构、不同的 Linux 发行版中使用。那么 Android 只采用 Kernel、只允许用 Java 编程的真正原因到底是什么呢? (2)为什么驱动不开源。 Linux Kernel 的版权是 GPL。在此版本下,硬件厂商都希望自己的硬件能在 Linux Kernel 下

8 第 1 章 迅猛发展的 Android 系统

运行,此时就必须使用驱动程序。但是如果把驱动程序的源码公开,就等于公开硬件规格,这是广 大硬件厂商所不能接受的。所以硬件厂商只提供编好的驱动程序,而不提供原始码。 Android 的重点是商业应用,为了解决上述驱动开源的问题,Google 采用了自己的方法来绕过 这个问题。Google 把驱动程序移到 “userspace”中,即让驱动程序在 Linux Kernel 上面运行,而 不是一起运行,这样就可以避过 GPL 规则。然后在 Kernel 开一个小门,让本来不能直接控制到硬 件的 “userspace” 程序也可以碰得到,此时只需公布这个开的“小门”程序源码即可。由此可见, Google 在 Kernel 和应用程序之间设计了一个中间层,这样既不违反 GPL 许可,又能不让外界看到 厂商的硬件驱动和应用程序的源码。 (3)带来的问题。 但是 Google 的上述做法随之带来了一个问题,Kernel 和 Android 采取不同的许可证,Kernel 采用 GPL 许可证,而 Android 采用 Apache Software License(简称 ASL)许可证。在 GPL 许可证 中规定,对源码的任何修改都必须开源,所以 Android 需要开源,因为它修改了 Kernel。而在 ASL 许可证中规定,用户可以随意使用源码而不必开源,所以建立在 Android 之上的硬件驱动和应用程 序都可以保持封闭。这种封闭得到了更多硬件厂商的支持,Google 特意修改了 Kernel,使得原本 应该包括在 Kernel 中的某些功能都被转移到了“userspace”中,所以就避开了开源。 (4)影响。 Google 的上述行为有利于推广 Android,并且可以吸引更多厂商和软件开发商的加入,但是同 时也宣布放弃了构建一个真正开源的手机系统的机会。所有为 Android 写的硬件驱动都不能合并到 Kernel 中,因为它们只在 Google 的代码里才有效,而在 Kernel 里根本没法用。

1.5.3 对驱动开发者的影响 所有为 Android 写的硬件驱动都不能合并到 Kernel 中。这些驱动程序只能在 Google 代码中有 效,而在 Kernel 中根本没法用。正是因为这个原因,Google 从不把大量的硬件驱动程序和平台源 码提交给 Kernel。 既然硬件厂商都不开源驱动代码,对于我们生存在 Android 底层的开发人员,特别是从事驱动 开发的成员来说,就带来了巨大的就业机会。我们可以为硬件厂商开发不开源的驱动程序而获得报 酬,为此随着 Android 的异常火爆,市面上有很多企业在招聘 Android 驱动开发人员。由此可见, 驱动的不开源给我们的学习带来了难题,但同样也为以后的就业机会增加了砝码。

9

第 2 章 简要分析 Linux 内核

Android 系统是基于 Linux 内核的,所以在学习 Android 底层开发之前,很有必要了解一些 Linux 内核的基本知识。在本章将简要讲解 Linux 内核的基础知识,特别是和 Android 系统密切相关的知 识,为读者步入本书后面高级知识的学习打下基础。

2.1 Linux 基础

Linux 是一类 Unix 计算机操作系统的统称,Linux 操作系统也是自由软件和开放源代码发展中 最著名的例子。严格来讲,Linux 一词本身只表示 Linux 内核,但实际上人们已经习惯了用 Linux 来形容整个基于 Linux 内核,并且使用 GNU 工程各种工具和数据库的操作系统。

2.1.1 Linux 历史简介 Linux 最早是由一位名叫 Linus Torvalds 的计算机业余爱好者设计的,当时他是芬兰赫尔辛基 大学的学生。他的目标是想设计一个代替 Minix(是由一位名叫 Andrew Tannebaum 的计算机教授 编写的一个操作系统示教程序)的操作系统,这个操作系统可用于 386、486 或奔腾处理器的个人 计算机上,并且具有 Unix 操作系统的全部功能,由此开始了 Linux 雏形的设计。 1983 年,理查德·马修·斯托曼(Richard Stallman)创立了 GNU 计划(GNU Project)。这个 计划的其中一个目标是为了发展一个完全免费自由的 Unix-like 操作系统。自 20 世纪 90 年代发起 这个计划以来,GNU 开始大量地产生或收集各种系统所必备的元件,像函式库(library)、编译器 (compiler)、侦错工具(debugger)、文字编辑器(text editor)、网页服务器(web server),以及一 个 Unix 的使用者接口(Unix shell)。1990 年,GNU 计划开始在马赫微核(Mach microkernel)的 架构之上开发系统核心,也就是所谓的 GNU Hurd,但是这个基于 Mach 的设计异常复杂,发展进 度相对缓慢。

2.1.2 主要版本 在当前市面中的主流 Linux 发行版本有 Asianux,ArchLinux,B2D Linux,Conectiva Linux, Debian GNU/Linux,Fedora Core,Gentoo Linux,Knoppix Linux,Linux From Scratch,Magic Linux, Mandriva Linux,Red Hat Linux,Slackware Linux,Stanix Live CD,SUSE Linux,Turbo Linux,Ubuntu Linux.IPCop,NUtyx,DEFT,NETbsd,turbolinux。

第 2 章 简要分析 Linux 内核

在国内主流的 Linux 发行版本有红旗 Linux(Redflag Linux),Qomo Linux,冲 浪 Linux(Xteam Linux),蓝点 Linux,新华 Linux,共创 Linux,百资 Linux,veket,lucky8k-veket.Open Desktop, Hiweed GNU/Linux,Magic Linux ,Engineering Computing GNU/Linux ,kylin,中标普华 Linux, 中软 Linux,新华华镭 Linux(RaysLX),CD Linux ,MC Linux ,即时 Linux(Thizlinux),b2d linux, IBOX,MCLOS,FANX,Everest,酷博 linux,新氧 Linux,Hiweed,雨林木风,Deepin Linux, 宏碁 Linux,PUD GNU/Linux。

2.1.3 Linux 的发展机遇 长久以来,在计算机操作系统领域,Windows、Netware 和 UNIX 一直占据主导地位。但是最近 几年发生了一些变化,以自由标榜自己的 Linux 越来越显示出其咄咄逼人的气势,正日益成为一个令 人生畏的对手。据估计现在全球已有 1000 多万 Linux 用户,有 31%的互联网服务器采用了 Linux 系统。 Linux 正在发起一项产业革命,逐渐吞噬着传统操作系统的市场份额。请回头看一看,看看我 们平常使用的技术:嵌入式系统、移动计算、移动互联网工具、服务器、超级计算。在几乎每个技 术领域,Linux 正在展现出作为未来主导平台的势头。随着 SaaS、云计算、虚拟化、移动平台、企 业 2.0 等新兴技术的发展,Linux 事业正面临巨大发展机遇。主要体现在如下 3 个方面。 (1)向企业级核心应用迈进。 Linux 的采用已由网络领域逐步转向了关键业务应用,企业关键任务成为 IBM 的增长领域,比 如 ERP 软件。同时,随着 IT 决策逐步从 IT 主管下放给 IT 管理人员,这些管理者对 Linux 显示了 强烈的支持,但同时对安全、可用性与服务提出了更高的需求。尽管很多用户仍将 Unix 视为关键 任务的平台,但随着 Linux 开发者逐步缩小两者的功能性差距,有越来越多的用户开始将关键业务 部署在 Linux 之上。 (2)Linux 将主导移动平台。 Linux 进入到移动终端操作系统后,很快就以其开放源代码的优势吸引了越来越多的终端厂商 和运营商对它的关注,包括摩托罗拉和 NTT DoCoMo 等知名的厂商。已经开发出的基于 Linux 的 手机有摩托罗拉的 zn5、V8、A1210、A810、A760、A768、A780、e680i、e680、e2、e680g、E6、 E8、em30、CEC 的 e2800、三星的 i519 等。 Linux 与其他操作系统相比是个后来者,但 Linux 具有两个其他操作系统无法比拟的优势。其 一,Linux 具有开放的源代码,能够大大降低成本。其二,既满足了手机制造商根据实际情况有针 对性地开发自己的 Linux 手机操作系统的要求,又吸引了众多软件开发商对内容应用软件的开发, 丰富了第三方应用。 (3)新技术为 Linux 加速。 在目前的企业级计算领域,云计算、SaaS、虚拟化是热门技术话题。在这些领域,Linux 同样 大有可为。云计算将全部使用 Linux,Linux 也是一款未来运行数据中心虚拟机的理想操作系统。

2.2 Android 和 Linux 的关系

Linux 和 Android 之间的关系密切,具体说来主要有如下 3 点。

11 Android 驱动开发与移植实战详解

(1)Android 采用 Linux 作为内核。 (2)Android 对 Linux 内核做了修改,目的是适应其在移动设备上的应用。 (3)Andorid 开始是作为 Linux 的一个分支,后来由于无法并入 Linux 的主开发树,已被 Linux 内核组从开发树中删除。

2.2.1 Android 继承于 Linux Android 是在 Linux 2.6 的内核基础之上运行的,提供的核心系统服务包括安全、内存管理、进 程管理、网络组和驱动模型等内容。内核部分还相当于介于硬件层和系统中其他软件组之间的一个 抽象层次。但是严格来说它不算是 Linux 操作系统。 因为 Android 内核是由标准的 Linux 内核修改而来的,所以很自然地继承了 Linux 内核的很多 优点,并且保留了 Linux 内核的主题架构。Android 本身有了很大的创新,按照移动设备的需求在 文件系统、内存管理、进程间通信机制和电源管理方面进行了修改,根据需要添加了很多相关的驱 动程序和必要的新功能。 总而言之,Android 很大程度地保留了 Linux 的基本架构,也正因为如此,Android 的应用性和 扩展性更强。当前 Android 版本对应的 Linux 内核版本如下所示。 Android1.5:Linux-2.6.27。 Android1.6:Linux-2.6.29。 Android2.0,2.1:Linux-2.6.29。 Android2.2:Linux-2.6.32.9。 Android2.2:Linux-2.6.36。

2.2.2 Android 和 Linux 内核的区别 Android 系统的系统层面的底层是基于 Linux 内核,中间加上了一个叫做 Dalvik 的 Java 虚拟机, 表面层上面是 Android 运行库。每个 Android 应用都运行在自己的进程上,享有 Dalvik 虚拟机为它 分配的专有实例。为了支持多个虚拟机在同一个设备上高效运行,Dalvik 被谷歌工程师们改写了。 Dalvik 虚拟机执行的是 Dalvik 格式的可执行文件.dex。该格式经过了专门的优化处理,目的是 将内存耗用降到最低。Java 编译器将 Java 源文件转为 class 文件,class 文件又被内置的 dx 工具转 化为 dex 格式文件,这种文件在 Dalvik 虚拟机上注册并运行。由此可见,Android 系统的应用软件 都是运行在 Dalvik 之上的 Java 软件,而 Dalvik 是运行在 Linux 中的,在一些底层功能——比如线 程和低内存管理方面,Dalvik 虚拟机是依赖 Linux 内核的。 综上所述可以得出一个结论:Android 是运行在 Linux 之上的操作系统,但是它本身不能算是 Linux 的某个版本。 Android 内核和 Linux 内核的差别主要体现在下面的 11 个方面。 (1)Android Binder。 Android Binder 是基于 OpenBinder 框架的一个驱动,用于提供 Android 平台的进程间通信(IPC, inter-process communication)。原来的 Linux 系统上层应用的进程间通信主要是 D-bus(desktop bus), 采用消息总线的方式来进行 IPC。

12 第 2 章 简要分析 Linux 内核

其源代码位于 drivers/staging/android/binder.c。 (2)Android 电源管理(PM)。 Android 电源管理是一个基于标准 Linux 电源管理系统的轻量级的 Android 电源管理驱动,针 对嵌入式设备做了很多优化。利用锁和定时器来切换系统状态,控制设备在不同状态下的功耗,以 达到节能的目的。 其源代码分别位于如下文件。 kernel/power/earlysuspend.c。 kernel/power/consoleearlysuspend.c。 kernel/power/fbearlysuspend.c。 kernel/power/wakelock.c。 kernel/power/userwakelock.c。 (3)低内存管理器(Low Memory Killer)。 Android 中的低内存管理器和 Linux 标准的 OOM(Out Of Memory)相比,其机制更加灵活, 它可以根据需要杀死进程来释放需要的内存。低内存管理器的代码很简单,关键的一个函数是 Lowmem_shrinker。作为一个模块在初始化时调用 register_shrinke 注册了一个 lowmem_shrinker,它 会被 vm 在内存紧张的情况下调用。Lowmem_shrinker 完成具体操作。简单说就是寻找一个最合适 的进程杀死,从而释放它占用的内存。 其源代码位于 drivers/staging/android/lowmemorykiller.c。 (4)匿名共享内存(Ashmem)。 匿名共享内存为进程间提供大块共享内存,同时为内核提供回收和管理这个内存的机制。如果 一个程序尝试访问 Kernel 释放的一个共享内存块,它将会收到一个错误提示,然后重新分配内存 并重载数据。 其源代码位于 mm/ashmem.c。 (5)Android PMEM(Physical)。 PMEM 用于向用户空间提供连续的物理内存区域,DSP 和某些设备只能工作在连续的物理内 存上。驱动中提供了 mmap,open,release 和 ioctl 等接口。 源代码位于 drivers/misc/pmem.c。 (6)Android Logger。 Android Logger 是一个轻量级的日志设备,用于抓取 Android 系统的各种日志,是 Linux 所没有的。 其源代码位于 drivers/staging/android/logger.c。 (7)Android Alarm。 Android Alarm 提供了一个定时器用于把设备从睡眠状态唤醒,同时它也提供了一个即使在设 备睡眠时也会运行的时钟基准。其源代码位于下面的两个文件。 drivers/rtc/alarm.c。 drivers/rtc/alarm-dev.c。 (8)USB Gadget 驱动。 此驱动是一个基于标准 Linux USB gadget 驱动框架的设备驱动,Android 的 USB 驱动是基于

13 Android 驱动开发与移植实战详解

gadget 框架的。 其源代码位于如下文件。 drivers/usb/gadget/android.c。 drivers/usb/gadget/f_adb.c。 drivers/usb/gadget/f_mass_storage.c。 (9)Android Ram Console。 为了提供调试功能,Android 允许将调试日志信息写入一个被称为 RAM Console 的设备里,它 是一个基于 RAM 的 Buffer。 其源代码位于 drivers/staging/android/ram_console.c。 (10)Android timed device。 Android timed device 提供了对设备进行定时控制功能,目前仅仅支持 vibrator 和 LED 设备。 其源代码位于 drivers/staging/android/timed_output.c(timed_gpio.c)。 (11)Yaffs2 文件系统。 在 Android 系统中,采用 Yaffs2 作为 MTD nand flash 文件系统。Yaffs2 是一个快速稳定的应用于 NAND 和 NOR Flash 的跨平台的嵌入式设备文件系统。同其他 Flash 文件系统相比,Yaffs2 使用更小 的内存来保存其运行状态,因此它占用内存小;Yaffs2 的垃圾回收非常简单而且快速,因此能达到更 好的性能;Yaffs2 在大容量的 NAND Flash 上性能表现尤为明显,非常适合大容量的 Flash 存储。 其源代码位于 fs/yaffs2/目录下。 Android 是在 Linux 2.6 的内核基础之上运行的,提供的核心系统服务包括安全、内存管理、进 程管理、网络组和驱动模型等内容。内核部分还相当于介于硬件层和系统中其他软件组之间的一个 抽象层次。但是严格来说它不算是 Linux 操作系统。

2.3 Linux 内核简介

既然 Android 内核与 Linux 内核有密切的联系,所以在学习 Android 底层驱动开发之前,很有 必要了解 Linux 内核的基本知识。本节将简要介绍 Linux 内核的基本应用知识,为读者步入本书后 面高级知识的学习打下基础。

2.3.1 内核的体系结构 图 2-1 显示了一个完整 Linux 系统的最基本的视图,由此可见内核的作用就是将应用程序和硬 件分离开来。 内核的主要任务是负责与计算机硬件进行交互,实现对硬件的编程控制和接口操作,调度对硬 件资源的访问。内核为用户应用程序提供了一个高级的执行环境和访问硬件的虚拟接口。提供硬件 的兼容性是内核的设计目标,几乎所有的硬件都可以得到 Linux 的支持,只要不是为其他操作系统 所定制的就可以。 与硬件兼容性相关的是可移植性,即在不同的硬件平台上运行 Linux 的能力。从最初只支持标 准 IBM 兼容机上的 Intel X86 架构到现在可以支持 Alpha、ARM、MIPS、PowerPC 等硬件平台。

14 第 2 章 简要分析 Linux 内核

再看图 2-2 所示的 Linux 操作系统的基本视图。

应用

标准C库

应用

体系无关部分

内核 体系相关部分

硬件设备 硬件设备

▲图 2-1 Linux 操作系统的基本视图 ▲图 2-2 Linux 操作系统的基本视图

由图 2-2 可以得出 Linux 内核分为如下两部分。 (1)体系相关部分:这部分内核为体系结构和硬件所特有。 (2)体系无关部分:这部分内核是可移植的。体系无关部分通常会定义与体系相关部分的接口, 这样,内核向新的体系结构移植的过程就变成确认这些接口的特性并将它们加以实现的过程。 用户应用程序和内核之间的联系是通过标准 C 库来实现,标准 C 库是应用程序和内核之间的 中间层。标准 C 库函数是建立在内核提供的系统调用基础之上的,通过此 C 库以及内核体系无关 部分与体系相关部分的接口,使用户应用程序和部分内核都成为可移植的。 根据上述描述,可以得出 Linux 操作系统的标准视图。具体如图 2-3 所示。

应用

标准C库

体系无关部分

进程 内存 文件 网络 管理 管理 系统 管理

体系相关部分

硬件设备

▲图 2-3 Linux 系统的标准视图

15 Android 驱动开发与移植实战详解

上述视图中的主要构成模块的具体说明如下所示。 (1)系统调用接口。 为了与用户应用程序进行交互,内核提供了一组系统调用接口,通过这组接口,应用程序可以 访问系统硬件和各种操作系统资源。 系统调用接口层在用户应用程序和内核之间添加了一个中间层,形象地说,它扮演了一个函数 调用多路复用和多路分解器的角色。 (2)进程管理。 进程管理负责创建和销毁进程,并处理它们之间的互相联系(进程间通信),同时负责安排调 度它们去分享 CPU。 进程管理部分实现了一个进程世界的抽象,这个进程世界类似于我们的人类世界,只不过我们 人类世界里的个体是人,而在进程世界里则是一个一个的进程,人与人之间通过书信、手机、网络 等进行交互,而各个进程之间则是通过不同方式的进程间通信,我们所有人都在分享同一个地球, 而所有进程都在分享一个或多个 CPU。 (3)内存管理。 在进程世界里,内存是重要的资源之一,就好比我们的土地。因此,管理内存的策略与方式是 决定系统性能的一个关键因素。 内核的内存管理部分根据不同的需要,提供了包括 malloc/free 在内的许多简单或者复杂的接 口,并为每个进程都提供了一个虚拟的地址空间,基本上实现虚拟内存对进程的按需分配。 (4)虚拟文件系统。 虚拟文件系统为用户空间提供了文件系统接口,同时又为各个具体的文件系统提供了通用的接 口抽象。在 VFS 上面,是对诸如 open、close、 read 和 write 之类函数的一个通用 API 抽象,在 VFS 下面则是具体的文件系统,它们定义了上层函数的实现方式。 通过虚拟文件系统,我们可以利用标准的 Linux 文件系统调用对不同介质上的不同文件系统进 行操作。应该说,VFS 是内核在各种具体的文件系统上建立的一个抽象层,它提供了一个通用的文 件系统模型,而该模型囊括了我们所能想到的所有文件系统的行为。 (5)网络功能。 网络子系统处理数据包的收集、标识、分发,路由和地址的解析等所有网络有关的操作。socket 层是网络子系统的标准 API,它为各种网络协议提供了一个用户接口。 (6)设备驱动程序。 操作系统的目的是为用户提供一种方便访问硬件的途径,因此,几乎每一个系统操作最终都会 映射到物理的硬件设备上。除了 CPU、内存等有限的几个对象,所有设备的访问控制操作都要由 相关的代码来完成,这些代码就是所谓的设备驱动程序。 (7)代码。 这里的代码需要依赖体系结构,因为部分内核代码是体系相关的,在“./linux/arch”子目录中 定义了内核源代码中依赖于体系结构的部分,其中包含了对应各种特定体系结构的子目录。比如, 对于一个典型的桌面系统来说,使用的是 i386 目录。 每个特定体系结构对应的子目录又包含了很多下级子目录,分别关注内核中的一个特定方面,

16 第 2 章 简要分析 Linux 内核

比如引导、内核、内存管理等。

2.3.2 和 Android 相关的 Linux 内核知识 我们知道,Android 是建立在 Linux2.6 的内核基础之上运行的,提供的核心系统服务包括安全、 内存管理、进程管理、网络组和驱动模型等内容。接下来将简要讲解上述核心系统服务的基本知识。

1.安全

关于 Linux 安全的知识主要涉及用户权限问题和目录权限问题。 (1)Linux 系统中用户和权限。 Linux 系统中的每个文件和目录都有访问权限,用它来确定谁可以通过何种方式对文件和目录 进行访问和操作。Linux 系统中规定了文件 3 种不同类型的用户:文件拥有者用户(user)、同组用 户(group)、可以访问系统的其他用户(others)。并规定 3种访问文件或目录的方式:读(r)、写 (w)、可执行或查找(x)。 (2)文件及目录权限的功能。 读权限(r)表示只允许指定用户读取相应文件的内容,禁止对它做任何的更改操作,如目录 读权限表示可以列出存储在该目录下的文件,即读目录内容。写权限(w)表示允许指定用户打开 并修改文件,如目录写表示允许你从目录中删除或创建文件或目录。执行权限(x)表示允许指定 用户将该文件作为一个程序执行,如对目录表示允许你在目录中查找,并能用 cd 命令将工作目录 切换到该目录。 Linux 系统在创建文件的时候会自动把该文件的读写权限分配给其属主,使用户能够显示和修 改该文件。也可以将这些权限改变为其他的组合形式。一个文件若有执行权限,则允许它作为一个 程序被执行。

2.内存管理

内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,我们不必担心内存是如何管 理的,这并不能使内存管理的重要性有一点点降低。对实际编程来说,理解内存管理器的能力与局 限性至关重要。在大部分系统语言中必须进行内存管理,例如 C 和 C++。 追溯到在 Apple II 上进行汇编语言编程的时代,那时内存管理还不是个大问题。实际上在运行 整个系统,系统有多少内存我们就有多少内存。我们甚至不必费心思去弄明白它有多少内存,因为 每一台机器的内存数量都相同。所以,如果内存需要非常固定,那么您只需要选择一个内存范围并 使用它即可。 但是即使是在这样一个简单的计算机中也会有问题,尤其是当我们不知道程序的每个部分将需要 多少内存时。如果我们的空间有限,而内存需求是变化的,那么需要用一些方法来满足下面的需求。 (1)确定您是否有足够的内存来处理数据。 (2)从可用的内存中获取一部分内存。 (3)向可用内存池(pool)中返回部分内存,以使其可以由程序的其他部分或者其他程序使用。 实现上述需求的程序库称为分配程序(allocator),因为它们负责分配和回收内存。程序的动态

17 Android 驱动开发与移植实战详解

性越强,内存管理就越重要,您的内存分配程序的选择也就更重要。让我们来了解可用于内存管理 的不同方法,它们的好处与不足,以及它们最适用的情形。

3.进程管理

在 Linux 操作系统中包括了三种不同类型的进程,分别是交互进程、批处理进程和守护进程。 每种进程都有自己的特点和属性。交互进程是由一个 Shell 启动的进程。交互进程既可以在前台运 行,也可以在后台运行。批处理进程和终端没有联系,是一个进程序列。系统守护进程是 Linux 系 统启动时启动的进程,并在后台运行。 Linux 管理进程的最好方法就是使用命令行下的系统命令。Linux 下面的进程涉及的命令有如 ps、kill、pgrep 等工具。 (1)父进程和子进程。 父进程和子进程的关系是管理和被管理的关系,当父进程终止时,子进程也随之而终止。但子 进程终止,父进程并不一定终止。比如 httpd 服务器运行时,我们可以杀掉其子进程,父进程并不 会因为子进程的终止而终止。在进程管理中,当我们发现占用资源过多,或无法控制的进程时,应 该杀死它,以保护系统的稳定安全运行。 (2)进程命令。 在 Linux 中,通过命令来管理和操作进程,其中常用的命令可以分为如下几类。 ‰ 监视进程命令。 ps(process status 命令)用于显示瞬间进程(process)的动态,其使用方式如下所示。

ps [options] [--help]

ps 的参数非常多,常用参数的具体说明如下所示。

¾ -A:列出所有的进程。

¾ -w:显示加宽,可以显示较多的资讯。

¾ -au:显示较详细的资讯。

¾ -aux:显示所有包含其他使用者的进程。 pstree 命令,其功能是将所有进程以树状图显示,树状图将会以 pid(如果有指定)或是以 init 这个基本进程为根(root),如果有指定使用者 id,则树状图会只显示该使用者所拥有的进程。 其使用方式如下所示。

pstree [-a] [-c] [-h|-Hpid] [-l] [-n] [-p] [-u] [-G|-U] [pid|user] pstree -V

常用参数的具体说明如下所示。

¾ -a:显示该进程的完整指令及参数,如果是被记忆体置换出去的进程则会加上括号。

¾ -c:如果有重覆的进程名,则分开列出(预设值时会在前面加上*)。 rop 命令用于实时显示 process 的动态,其使用方式如下所示。

top [-] [d delay] [q] [c] [S] [s] [i] [n] [b]

常用参数的具体说明如下所示。

18 第 2 章 简要分析 Linux 内核

¾ d:改变显示的更新速度,或是在交谈式指令列(interactive command)按 s。

¾ q:没有任何延迟的显示速度,如果使用者是有 superuser 的权限,则 top 将会以最高 的优先序执行。

¾ c:切换显示模式,共有两种模式,一种是只显示执行档的名称,另一种是显示完整的 路径与名称。

¾ S:累积模式,会将已完成或消失的子进程(dead child process)的 CPU time 累积起来。

¾ s:安全模式,将交谈式指令取消,避免潜在的危机。

¾ i:不显示任何闲置(idle)或无用(zombie)的进程。

¾ n:更新的次数,完成后将会退出 top。

¾ b:批次档模式,搭配“n”参数一起使用,可以用来将 top 的结果输出到档案内。 ‰ 控制进程命令。 向 Linux 系统的内核发送一个系统操作信号和某个程序的进程标识号,系统内核就可以对进程 标识号指定的进程进行操作。例如在 top 命令中会看到系统运行的许多进程,有时就需要使用 kill 中止某些进程来提高系统资源。在安装和登录命令中使用多个虚拟控制台的作用是,当一个程序出 错造成系统死锁时可以切换到其他虚拟控制台工作关闭这个程序。此时使用的命令就是 kill,因为 kill 是大多数 Shell 内部命令可以直接调用的。 在 Linux 系统中,使用 kill 命令来控制进程。kill 可以删除执行中的程序或工作,可以将指定 的信息送至程序,预设的信息为 SIGTERM(15),可将指定程序终止。若仍无法终止该程序,可 使用 SIGKILL(9)信息尝试强制删除程序。程序或工作的编号可利用 ps 指令或 jobs 指令查看。 有如下两种使用 kill 命令的方式。

kill [-s <信息名称或编号>][程序] kill [-l <信息编号>]

各个参数的具体说明如下所示。

¾ -l <信息编号>:如果不加<信息编号>选项,则-l 参数会列出全部的信息名称。

¾ -s <信息名称或编号>:指定要送出的信息。

进程是 Linux 系统中一个非常重要的概念。Linux 是一个多任务的操作系统,系 统上经常同时运行着多个进程。我们不关心这些进程究竟是如何分配的,或者是内 注意 核如何管理分配时间片的,我们关心的是如何去控制这些进程,让它们能够很好地 为用户服务。

‰ 进程优先级设定(nice 命令)。 使用 nice 命令可以使用更改过的优先顺序来执行程序,如果未指定程序则会显示出目前的排 程优先顺序,默认的 adjustment 为 10,范围为-20(最高优先序)到 19(最低优先序)。使用 nice 命令的语法格式如下所示。

nice [-n adjustment] [-adjustment] [--adjustment=adjustment] [--help] [--version] [command [arg...]

19 Android 驱动开发与移植实战详解

各个参数的具体说明如下所示。

¾ -n adjustment,-adjustment,--adjustment=adjustment:在原有的优先顺序前面增加 adjustment 标识。

¾ --help:显示求助讯息。

¾ --version:显示版本资讯。

4.设备驱动程序

既然是一本讲解内核和驱动开发的书籍,就不可避免地讲解设备驱动程序的知识。设备驱动程 序用于与系统连接的输入/输出装置通信,如硬盘、软驱、各种接口、声卡等。按照经典的 UNIX 箴言“万物皆文件”(everything is a file),对外设的访问可利用/dev 目录下的设备文件来完成,程 序对设备的处理完全类似于常规的文件。设备驱动程序的任务在于支持应用程序经由设备文件与设 备通信。通常可以将外设分为以下两类。 (1)字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此 类设备支持按字节/字符来读写数据。举例来说,调制解调器是典型的字符设备。 (2)块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘是典型 的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通 常是 512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。 编写块设备的驱动程序比字符设备要复杂得多,因为内核为提高系统性能广泛地使用了缓存机制。

5.网络

网卡也可以通过设备驱动程序控制,但在内核中属于特殊状况,因为网卡不能利用设备文件访 问。原因在于在网络通信期间,数据打包到了各种协议层中。在接收到数据时,内核必须针对各协 议层的处理,对数据进行拆包与分析,然后才能将有效数据传递给应用程序。在发送数据时,内核 必须首先根据各个协议层的要求打包数据,然后才能发送。 为支持通过文件接口处理网络连接(按照应用程序的观点),Linux 使用了源于 BSD 的套接字 抽象。套接字可以看做应用程序、文件接口、内核的网络实现之间的代理。

2.4 分析 Linux 内核源码

长期以来,公认的学习内核的最好方法是分析内核代码。内核代码本身是最好的参考资料,学 习内核是一项浩大的工程,在学习之前需要首先做到以下三个方面。 (1)熟练使用 Linux 操作系统。 Linux 操作系统是 Linux 内核在用户层面的具体体现,只有熟练掌握 Linux 的基本操作,才能 在内核学习的过程中达到事半功倍的效果。 (2)掌握操作系统理论基础。 要掌握操作系统中比较基础的理论,比如分时(time-shared)和实时(real-time)的区别,进 程的概念、CPU 和系统总线、内存的关系等。

20 第 2 章 简要分析 Linux 内核

(3)掌握 C 语言基础。 不需要很精通 C 语言,但必须能够理解链表、散列表等数据结构的 C 实现,并熟练运用 GCC 编译器。总之对 C 语言越熟悉就会对我们的内核学习越有帮助。

2.4.1 源码目录结构 Linux 内核源码的官方下载地址为 http://www.kernel.org/,如图 2-4 所示。

▲图 2-4 Linux 内核官方下载界面

当下载内核代码后,很有必要知道内核源码的整体分布情况。通常其内核代码保存在 “/usr/src/linux”目录下,该目录下的每一个子目录都代表了一个特定的内核功能性子集,接下来将 针对 2.6.23 版本进行简单描述。 (1)目录“Documentation”。 此目录下面没有内核代码,只有很多质量参差不齐的文档,但往往能够给我们提供很多的帮助。 (2)目录“arch”。 所有与体系结构相关的代码都在此目录以及“include/asm-*/”目录中,Linux 支持的每种体系 结构在 arch 目录下都有对应的子目录,而在每个体系结构特有的子目录下又至少包含如下 3 个子 目录。 kernel:存放支持体系结构特有的诸如信号量处理和 SMP 之类特征的实现。 lib:存放体系结构特有的对诸如 strlen 和 memcpy 之类的通用函数的实现。 mm:存放体系结构特有的内存管理程序的实现。 除了上述这 3 个子目录之外,大多数体系结构在必要的情况下还有一个 boot 子目录,包含了 在这种硬件平台上启动内核所使用的部分或全部平台特有代码。另外在大部分体系结构所特有的子 目录中,还应该根据需要包含供附加特性使用的其他子目录。比如,i386 目录包含一个 math-emu 子目录,其中包括了在缺少数学协处理器(FPU)的 CPU 上运行模拟 FPU 的代码。 (3)目录“drivers”。 此目录是内核中最庞大的一个目录,显卡、网卡、SCSI 适配器、PCI 总线、USB 总线和其他 任何 Linux 支持的外围设备或总线的驱动程序都可以在这里找到。 (4)目录“fs”。 在此目录中保存了虚拟文件系统(VFS,Virtual File System)的代码,还有各个不同文件系统

21 Android 驱动开发与移植实战详解

的代码。Linux 支持的所有文件系统在 fs 目录下面都有一个对应的子目录。例如 ext2 文件系统对应 的是 fs/ext2 目录。 一个文件系统是存储设备和需要访问存储设备的进程之间的媒介。存储设备可能是本地的物理 上可访问的,比如硬盘或 CD-ROM 驱动器,它们分别使用 ext2/ext3 和 isofs 文件系统。也可能是 通过网络访问的,使用 NFS 文件系统。 还有一些虚拟文件系统,例如 proc 是以一个标准文件系统出现的,然而它其中的文件只存在 于内存中,并不占用磁盘空间。 (5)目录“include”。 在此目录中包含了内核中大部分的头文件,它们按照“include/asm-*/”的子目录格式进行分组。 这种格式的子目录有多个,每一个都对应着一个 arch 的子目录,例如“include/asm-alpha”、 “include/asm-arm”和“include/asm-i386”等。在每个子目录中的文件中,都定义了支持给定体系 结构所必需的预处理器宏和内联函数,这些内联函数多数都是全部或部分使用汇编语言实现的。 在编译内核时,系统会建立一个从 include/asm 目录到目标体系结构特有的目录的符号链接。 比如对于 arm 平台,就是 include /asm-arm 到 include/asm 的符号链接。因此,体系结构无关部分的 内核代码可以使用如下形式包含体系相关部分的头文件。 (6)目录“init”。 此目录保存了内核的初始化代码,包括 main.c、创建早期用户空间的代码以及其他初始化代码。 (7)目录“ipc”。 IPC 即进程间通信(interprocess communication),在此目录中包含了共享内存、信号量以及其 他形式 IPC 的代码。 (8)目录“kernel”。 此目录是内核中最核心的部分,包括进程的调度(kernel/sched.c),以及进程的创建和撤销 (kernel/fork.c 和 kernel /exit.c)等,和平台相关的另外一部分核心的代码在 arch/*/kernel 目录。 (9)目录“lib”。 此目录中保存了库代码,这些代码实现了一个标准 C 库的通用子集,包括字符串和内存操作 的函数(strlen、mmcpy 和其他类似的函数)以及有关 sprintf 和 atoi 的系列函数。与 arch/lib 下的 代码不同,这里的库代码都是使用 C 编写的,在内核新的移植版本中可以直接使用。 (10)目录“mm”。 在此目录中包含了体系结构无关部分的内存管理代码,体系相关的部分位于arch/*/mm目录下。 (11)目录“net”。 在此目录中保存了和网络相关代码,实现了各种常见的网络协议,如 TCP/IP、IPX 等。 (12)目录“scripts”。 在该目录下没有内核代码,只包含了用来配置内核的脚本文件。当运行 make menuconfig 或者 make xconfig 之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。 (13)目录“block”。 在此目录中保存了 block 层的实现代码。最初 block 层的代码一部分位于 drivers 目录,一部分 位于 fs 目录,从 Linux 2.6.15 版本开始,block 层的核心代码被提取出来放在了顶层的 block 目录中。

22 第 2 章 简要分析 Linux 内核

(14)目录“crypto”。 在此目录中保存了内核本身所用的加密 API 信息,实现了常用的加密和散列算法,还有一些 压缩和 CRC 校验算法。 (15)目录“security”。 在此目录下包括了不同的 Linux 安全模型的代码,比如 NSA Security-Enhanced Linux。 (16)目录“sound”。 在此目录下保存了声卡驱动以及其他声音相关的代码。 (17)目录“usr”。 此目录实现了用于打包和压缩的 cpio 等。

2.4.2 浏览源码的工具 使用一个功能强大并方便的代码浏览工具有助于我们提高学习内核代码的效率。接下来将简单 介绍当前浏览 Linux 内核源码的常用工具。

1.Source Insight

这是在 Windows 下最为方便快捷的代码浏览工具,是一款商业软件。Source Insight 是一个面 向项目开发的程序编辑器和代码浏览器,拥有内置的对 C/C++、C#和 Java 等程序的分析。Source Insight 可以分析你的源代码并在你工作的同时动态维护它自己的符号数据库,并自动显示有用的上 下文信息。Source Insight 不仅仅是一个强大的程序编辑器,它还能显示 reference trees,class inheritance diagrams 和 call trees。Source Insight 提供了最快速的对源代码的导航和任何程序编辑器 的源信息。Source Insight 为我们提供了快速访问源代码和源信息的能力。与众多其他编辑器产品不 同,Source Insight 能在你编辑的同时分析你的源代码,为你提供实用的信息并立即进行分析。 当安装 Source Insight 后,需要先打开 Source Insight 并创建一个工程,然后将内核代码加入到 该工程中,并进行文件同步,这样就可以在代码之间进行关联阅读了。 Source Insight 的缺点是并没有对应 Linux 的版本。因此对于很多 Linux 初学者来说,在一个完 全的 Linux 环境下进行学习,需要寻找一个可以取代 Source Insight 的代码浏览工具。

2.Vim+Cscope

Vim 是 Linux 环境下的最佳浏览工具,各种 Linux 发行版都会默认安装此工具。虽然 Vim 默认 的编辑界面很普通,但是可以通过配置文件.vimrc 添加不同的界面效果来优化界面。并且还可以配 合 TagList、WinManager 等插件或工具,将 Vim 打造成一个功能强大的代码浏览编辑工具。

3.LXR

LXR(Linux Cross Reference)也是一种比较流行的 Linux 内核源代码浏览工具,下载地址为 http://lxr.linux.no/。如果我们的目的只是浏览 Linux 内核代码,则没有必要安装 LXR。因为在网站 http://lxr.linux.no/上在线提供了几乎所有版本的 Linux 内核代码,我们只需登录该网站,选择某一 特定的内核版本后就可以在线阅读内核代码。

23 Android 驱动开发与移植实战详解

2.4.3 用汇编语言编写内核代码 很多读者可能禁不住要问,Java、C++和 C#功能强大,Visual Basic 易于使用,但是为什么还 要使用古老的汇编语言来编写内核代码呢?这是因为出于以下三个方面的考虑。 (1)Linux 内核中的底层代码直接和硬件打交道,需要一些专用的指令,而这些指令在 C 语言 中并无对应的语言成分。 (2)内核中实现某些操作的过程、代码段或函数,在运行时会很频繁地被调用,这时用汇编语 言编写,会大幅度提高时间效率。 (3)在某些特别的场合,一段代码的空间效率也很重要,比如操作系统的引导程序一定要容纳 在磁盘的第一个扇区中,多一个字节都不行。这时只能用汇编语言编写。 在 Linux 内核代码中,以汇编语言编写的代码有如下两种不同的形式。 完全的汇编代码,这样的代码采用“.s”作为文档名的后缀; 嵌入在 C 代码中的汇编语言片段。 对于新接触 Linux 内核源码的读者,即使比较熟悉 i386 汇编语言,在理解内核中的汇编代码 时都会感到困难。原因是在内核的汇编代码中,采用的是不同于常用 Intel i386 汇编语言的 AT&T 格式的汇编语言,而在嵌入 C 代码的汇编语言片段中,更是增加了一些指导汇编工具如何分配使 用寄存器、如何与 C 代码中定义的变量相结合的语言成分。这些成分使得嵌入 C 代码的汇编语言 片断实际上变成了介于汇编和 C 之间的一种中间语言。

2.4.4 Linux 内核的显著特性 下载 Linux 内核代码并安装浏览工具后,就可以浏览并分析 Linux 内核的源码了。在接下来的 内容中,将简要讲解 Linux 内核的显著特性,为读者步入本书后面知识的学习打下基础。

1.GCC 特性

Linux 内核使用 GNU Compiler Collection(GCC)套件的几个特殊功能,这些功能包括提供快捷 方式和简化以及向编译器提供优化提示等。GCC 和 Linux 是一对出色的组合,Linux 完全依靠 GCC 在新的体系结构上运行,并且 Linux 可以利用 GCC 中的特性(称为扩展)实现更多功能和优化。 (1)基本功能。 GCC 具有如下两大功能。 功能性:扩展提供新功能。 优化:扩展帮助生成更高效的代码。 GCC 允许通过变量的引用识别类型,这种操作支持泛型编程。在 C++、Ada 和 Java 语言等 许多现代编程语言中都可以找到相似的功能。例如 Linux 使用 typeof 构建 min 和 max 等依赖于类 型的操作。使用 typeof 构建一个泛型宏的代码如下所示。

#define min(x, y) ({ typeof(x) _min1 = (x); typeof(y) _min2 = (y);

24 第 2 章 简要分析 Linux 内核

(void) (&_min1 == &_min2); _min1 < _min2 ? _min1 : _min2; })

GCC 还支持范围,在 C 语言的许多方面都可以使用范围。最常见的是在 switch/case 块中的 case 语句中使用。使用 switch/case 也可以通过使用跳转表实现进行编译器优化。在复杂的条件结构 中,通常依靠嵌套的 if 语句实现与下面代码相同的结果,但是下面的代码更简洁。具体代码如下 所示。

static int sd_major(int major_idx){ switch (major_idx) { case 0: return SCSI_DISK0_MAJOR; case 1 ... 7: return SCSI_DISK1_MAJOR + major_idx - 1; case 8 ... 15: return SCSI_DISK8_MAJOR + major_idx - 8; default: BUG(); return 0; /* shut up gcc */ } }

(2)属性。 GCC 允许声明函数、变量和类型的特殊属性,以便指示编译器进行特定方面的优化和更仔细 的代码检查。使用属性的方式非常简单,只需在声明后面加上下面的代码即可。

attribute__ (( ATTRIBUTE ))

其中 Attribute 是属性的说明,多个说明之间以逗号分隔。GCC 可以支持十几个属性,接下来 将介绍一些比较常用的属性。 ‰ 属性 noreturn。 属性 noreturn 用在函数中,表示该函数从不返回。它能够让编译器生成较为优化的代码,消除 不必要的警告信息。 ‰ 属性 format(archetype,string-index,first-to-check)。 属性 format 用在函数中,表示该函数使用 printf、scanf、strftime 或 strfmon 风格的参数,并 可以让编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。 archetype:指定是哪种风格。 string-index:指定传入函数的第几个参数是格式化字符串。 first-to-check:指定从函数的第几个参数开始按照上述规则进行检查。 例如下面的内核代码,表示 printk 的第一个参数是格式化字符串,从第二个参数开始根据格式 化字符串检查参数。

++++ include/linux/kernel.h static inline int printk(const char *s, ...) __attribute__ ((format (printf, 1, 2)));

25 Android 驱动开发与移植实战详解

‰ 属性 unused。 属性 unused 用于函数和变量,表示该函数或变量可能并不使用,这个属性能够避免编译器产 生警告信息。 ‰ 属性 aligned(ALIGNMENT)。 属性 aligned 常用在变量、结构或联合中,用于设定一个指定大小的对齐格式,以字节为单位。 例如下面的内核代码。

++++ drivers/usb/host/ohci.h struct ohci_hcca { #define NUM_INTS 32 __hc32 int_table [NUM_INTS]; /* periodic schedule */ /* * OHCI defines u16 frame_no, followed by u16 zero pad. * Since some processors can't do 16 bit bus accesses, * portable access must be a 32 bits wide. */ __hc32 frame_no; /* current frame number */ __hc32 done_head; /* info returned for an interrupt */ u8 reserved_for_hc [116]; u8 what [4]; /* spec only identifies 252 bytes :) */ } __attribute__ ((aligned(256)));

上述代码表示结构体 ohci_hcca 的成员以 256 字节对齐。如果 aligned 后面不紧跟一个指定的数 字值,那么编译器将依据目标机器情况使用最大、最有益的对齐方式。 需要注意的是,属性 attribute 的效果与我们的连接器有关,如果连接器最大只支持 16 字节对 齐,那么此时定义 32 字节对齐是没有任何意义的。 ‰ 属性 packed。 属性 packed 用在变量和类型中,当用在变量或结构体成员时表示使用最小可能的对齐,当用 在枚举、结构体或联合类型时表示该类型使用最小的内存。属性 packed 多用于定义硬件相关的结 构时,使元素之间不会因对齐产生问题。例如下面的内核代码。

++++ include/asm-i386/desc.h struct usb_interface_descriptor { __u8 bLength; __u8 bDescriptorType;

__u8 bInterfaceNumber; __u8 bAlternateSetting; __u8 bNumEndpoints; __u8 bInterfaceClass; __u8 bInterfaceSubClass; __u8 bInterfaceProtocol; __u8 iInterface; } __attribute__ ((packed));

在上述代码中,__attribute__ ((packed))告诉编译器 usb_interface_descriptor 的元素为 1 字节对

26 第 2 章 简要分析 Linux 内核

齐,不要再添加填充位。因为定义此结构的代码和 usb spec 中的是完全一样的。如果不给编译器这 个暗示,则编译器会依据平台的类型在结构的每个元素之间添加一定的填充位,使用这个结构时就 不能达到预期的结果。 (3)内建函数。 在 GCC 中提供了大量的内建函数,其中有很多是标准 C 库函数的内建版本,例如 memcpy(), 它们的功能与对应的 C 库函数的功能相同,在此将不再进行讲解。 在内建函数中,还有很多函数的名字是以__builtin 开始的,接下来将对__builtin_expect()进行 详细分析,其他__builtin_xxx()函数的原理和此函数类似,本书中将不再一一介绍。 使用函数__builtin_expect()的格式如下所示。

__builtin_expect (long exp, long c)

为什么 Linux 会推出__builtin_xxx()函数呢?这是因为大部分代码在分支预测方面做得比较糟 糕,所以 GCC 提供了此内建函数来帮助处理分支预测,并优化程序。 第一个参数 exp:是一个整型的表达式,返回值也是此 exp。 第二个参数 c:其值必须是一个编译期的常量。 由此可见,此内建函数的意思就是 exp 的预期值为 c,编译器可以根据这个信息适当地重排条 件语句块的顺序,将符合这个条件的分支放在合适的地方。 我们看此函数在 Linux 内核中的应用,具体代码如下所示。

++++ include/linux/compiler.h #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0)

在上述代码中,unlikely(x)的功能是告诉编译器条件 x 发生的可能性不大,likely 的功能是告诉 编译器条件 x 发生的可能性很大。它们一般用在条件语句里,if 语句不变,当 if 条件为 1 可能性非 常小时,可以在条件表达式外面包装一个 unlikely(),那么这个条件块里语句的目标码可能就会被 放在一个比较远的位置,以保证经常执行的目标码更紧凑。如果可能性非常大,则使用 likely()实 现包装。

2.链表的重要性

链表和本书讲解的驱动密切相关,例如 USB 驱动。内核中链表的实现位于文件 “include/linux/list.h”中,下面是定义链表数据结构的代码。

struct list_head { struct list_head *next, *prev; };

在结构 list_head 中包含了两个指向 list_head 结构的指针 prev 和 next,内核中的链表实际上都 是双链表。在 C 语言中定义链表结构的格式如下所示。

struct list_node { struct list_node *next;

27 Android 驱动开发与移植实战详解

ElemType data; };

当通过上述格式使用链表时,对于每一种数据类型都要定义它们各自的链表结构。而内核中的 链表却与此不同,它并没有数据域,不是在链表结构中包含数据,而是在描述数据类型的结构中包 含链表。 如果在 hub 驱动中使用 struct usb_hub 来描述 hub 设备,hub 需要处理一系列的事件,比如当 探测到一个设备连进来时,就会执行一些代码去初始化该设备,所以 hub 就创建了一个链表来处理 各种事件,这个链表的结构如图 2-5 所示。

▲图 2-5 hub 链表的结构

在 Linux 代码中,完整展示了链表的操作过程,接下来我们将简单剖析对应的 Linux 代码。 (1)声明与初始化。 可以使用如下两种方式来声明链表。 使用 LIST_HEAD 宏在编译时静态初始化; 使用 INIT_LIST_HEAD()在运行时进行初始化。 对应的 Linux 代码如下所示。

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name)

static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; }

无论采用哪种方式,新生成的链表头的两个指针 next、prev 都初始化为指向自己。

28 第 2 章 简要分析 Linux 内核

(2)判断链表是否为空。 对应的 Linux 代码如下所示。

static inline int list_empty(const struct list_head *head) { return head->next == head; }

(3)插入操作。 建立链表后,就不可避免地对其进行操作,例如向里面添加数据。使用函数 list_add()和 list_add_tail()可以完成添加数据的工作。对应的 Linux 代码如下所示。

static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); }

其中,函数 list_add()将数据插入在 head 之后,函数 list_add_tail()将数据插入在 head->prev 之 后。对于循环链表来说,因为表头的 next、prev 分别指向链表中的第一个和最后一个节点,所以函 数 list_add()和 list_add_tail()的区别并不大。 (4)删除操作。 可以使用函数 list_replace_init()从链表里删除一个元素,并且将其初始化。对应的 Linux 代码 如下所示。

static inline void list_replace_init(struct list_head *old, struct list_head *new) { list_replace(old, new); INIT_LIST_HEAD(old); }

(5)遍历操作。 在内核中的链表仅仅保存了 list_head 结构的地址,我们应该如何通过它获取一个链表节点真 正的数据项呢?此时就需要使用 list_entry 宏,通过它可以很容易地获得一个链表节点的数据。对 应的 Linux 代码如下所示。

#define list_entry(ptr, type, member) \ container_of(ptr, type, member)

假如以 hub 驱动为例,当要处理 hub 的事件的时候,需要知道具体是哪个 hub 触发了这起事件。 而 list_entry 的作用是从 struct list_head event_list 中得到它所对应的 struct usb_hub 结构体变量。例

29 Android 驱动开发与移植实战详解

如下面的代码。

struct list_head *tmp; struct usb_hub *hub; tmp = hub_event_list.next; hub = list_entry(tmp, struct usb_hub, event_list);

通过上述代码,从全局链表 hub_event_list 中取出一个叫做 tmp 的结构体变量,然后通过 tmp 获得它所对应的 struct usb_hub。

3.Kconfig 和 Makefile

Kconfig 和 Makefile 是浏览内核代码时最为依仗的两个文件。几乎 Linux 内核中的每一个目录 下边都有一个 Kconfig 文件和一个 Makefile 文件。通过 Kconfig 和 Makefile,可以让我们了解一个 内核目录下面的结构。在研究内核的某个子系统、某个驱动或其他某个部分之前,需要仔细阅读目 录下对应的 Kconfig 和 Makefile 文件。 (1)Kconfig 结构。 每种平台对应的目录下面都有一个 Kconfig 文件,例如 arch/i386/Kconfig。Kconfig 文件通过 source 语句可以构建一个 Kconfig 树。文件“arch/i386/Kconfig”的代码片段如下所示。

mainmenu "Linux Kernel Configuration"

config X86_32 bool default y help This is Linux's home port. Linux was originally native to the Intel 386, and runs on all the later x86 processors including the Intel 486, 586, Pentiums, and various instruction-set-compatible chips by AMD, Cyrix, and others. …… source "init/Kconfig"

menu "Processor type and features"

source "kernel/time/Kconfig" …… config KTIME_SCALAR bool default y

Kconfig 的详细语法规则可以参看内核文档 Documentation/kbuild /kconfig-language.txt,下面对 其简单介绍。

30 第 2 章 简要分析 Linux 内核

‰ 菜单项。 使用关键字 config 可以定义一个新的菜单项,例如下面的代码。

config MODVERSIONS bool "Set version information on all module symbols" depends on MODULES help Usually, modules have to be recompiled whenever you switch to a new kernel. ...

后面的代码定义了该菜单项的属性,包括类型、依赖关系、选择提示、帮助信息和缺省值等。 常用的类型有 bool、tristate、string、hex 和 int。类型 bool 只能被选中或不选中,类型 tristate 的菜单项多了编译成内核模块的选项。 依赖关系是通过 depends on 或 requires 定义的,指出此菜单项是否依赖于另外一个菜单项。 帮助信息需要使用 help 或---help---来指出。 ‰ 菜单组织结构。 菜单选项可以通过如下两种方式组成树状结构。 第一种方式:使用关键字 menu 显式声明为菜单,例如下面的代码。

menu "Bus options (PCI, PCMCIA, EISA, MCA, ISA)" config PCI …… endmenu

第二种方式:也可以使用依赖关系确定菜单结构,例如下面的代码。

config MODULES bool "Enable loadable module support" config MODVERSIONS bool "Set version information on all module symbols" depends on MODULES comment "module support disabled" depends on !MODULES

其中菜单项 MODVERSIONS 依赖于 MODULES,所以它就是一个子菜单项。这要求菜单项和 它的子菜单项同步显示或不显示。 ‰ 关键字 Kconfig。 Kconfig 文件描述了一系列的菜单选项,除了帮助信息外,在文件中的每一行都以一个关键字 开始,主要有 config、menuconfig、choice/endchoice、comments、menu/endmenu、if/endif、source 等,只有前 5 个可以用在菜单项定义的开始,它们都可以结束一个菜单项。 (2)Makefile。 Linux 内核的 Makefile 分为如下 5 个组成部分。 Makefile:最顶层的 Makefile。 .config:内核的当前配置文档,编译时成为顶层 Makefile 的一部分。

31 Android 驱动开发与移植实战详解

arch/$(ARCH)/Makefile:和体系结构相关的 Makefile。 Makefile.*:一些特定 Makefile 的规则。 kbuild 级别 Makefile:各级目录下的大概约 500 个文档,编译时根据上层 Makefile 传下来 的宏定义和其他编译规则,将源代码编译成模块或编入内核。顶层的 Makefile 文档读取.config 文 档的内容,并总体上负责 build 内核和模块。Arch Makefile 则提供补充体系结构相关的信息。其 中.config 的内容是在 MAKE MENUCONFIG 的时候,通过 Kconfig 文档配置的结果。 假如想把自己写的一个 Flash 的驱动程式加载到工程中,并且通过 menuconfig 配置内核时会选 择该驱动,此时该怎么办呢?其实借助 Makefile 就可以实现,解决流程如下所示。 将编写的 flashtest.c 文档添加到/driver/mtd/maps/ 目录下。 修改/driver/mtd/maps 目录下的 kconfig 文档,修改代码如下所示。

config MTD_flashtest tristate “ap71 flash"

这样当 MAKE MENUCONFIG 时会出现 ap71 flash 选项。 修改该目录下 makefile 文档,添加下面的代码内容。

obj-$(CONFIG_MTD_flashtest) += flashtest.o

如果此时运行 make menucofnig 会发现 ap71 flash 选项,如果选择此选项,该选择就会保存 在.config 文档中。当编译内核时会读取.config 文档,当发现 ap71 flash 选项为 yes 时,系统在调用 /driver/mtd/maps/中的 makefile 时,会把 flashtest.o 加入到内核中。

2.4.5 学习 Linux 内核的过程 在接下来的内容中,将通过两个具体的应用来演示学习 Linux 内核的过程。

1.分析 USB 子系统的代码

Linux 内核中 USB 子系统的代码位于“drivers/usb”目录下,进入该目录,执行命令 ls 后将会 显示如下结果。

atm class core gadget host image misc mon serial storage Kconfig Makefile README usb-skeleton.c

目录“drivers/usb”一共包含 10 个子目录和 4 个文件,为了理解每个子目录的作用,有必要首 先阅读 README 文件。根据 README 文件的描述,得知了“drivers/usb”目录下各个子目录的作 用,具体说明如下所示。 (1)core。 这是内核开发者针对部分核心的功能特意编写的代码,用于为其他的设备驱动程序提供服务, 比如申请内存,实现一些所有的设备都会需要的公共函数,并命名为 USB core。 (2)host。 早期的内核结构并不像现在这般富有层次感,几乎所有的文件都直接堆砌在“drivers/usb/”目

32 第 2 章 简要分析 Linux 内核

录下,这其中包括“usb core”和其他各种设备驱动程序的代码。 后来在“drivers/usb/”目录下面单独列出了“core”子目录,用于存放一些比较核心的代码, 比如整个 USB 子系统的初始化、root hub 的初始化、host controller 的初始化代码。 后来随着技术的发展,出现了多种 USB host controller,于是内核开发者把 host controller 有关 的公共代码保留在“core”目录下,而其他各种 host controller 对应的特定代码则移到“host”目录 下,让相应的负责人去维护。为此针对 host controller 单独创建了子目录“host”,它用于存放与其 相关的代码。 (3)gadget。 用于存放 USB gadget 的驱动程序,控制外围设备如何作为一个 USB 设备和主机通信。比如, 嵌入式开发板通常会支持 SD 卡,使用 USB 连接线将开发板连接到 PC 时,通过 USB gadget 架构 的驱动,可以将该 SD 卡模拟成 U 盘。 除 core、host 和 gadget 之外,其他几个目录分门别类存放了各种 USB 设备的驱动,例如 U 盘 的驱动位于“storage”子目录,触摸屏和 USB 键盘鼠标的驱动位于“input”子目录。 因为我们的目的是研究内核对 USB 子系统的实现,而不是特定设备或 host controller 的驱动, 所以通过对 README 文件的分析,应该进一步关注“core”子目录。

2.分析 USB 系统的初始化代码

通过分析 Kconfig 和 Makefile 文件,可以帮助我们在庞大复杂的内核代码中定位以及缩小目标代 码的范围。为了研究内核对 USB 子系统的实现,需要在目标代码中找到 USB 子系统的初始化代码。 Linux 内核针对某个子系统或某个驱动,使用 subsys_initcall 或 module_init 宏来指定初始化函 数。在内核文件“drivers/usb /core/usb.c”中,可以发现下面的代码。

subsys_initcall(usb_init); module_exit(usb_exit);

在上述代码中,可以将 subsys_initcall 理解为 module_init,只不过因为该部分代码比较核心, 开发者们把它看做一个子系统,而不仅仅是一个模块。在 Linux 中,类似此类别的设备驱动被归结 为一个子系统,例如 PCI 子系统和 SCSI 子系统。通常“drivers/”目录下第一层的每个目录代表一 个子系统,因为它们分别代表了一类设备。 subsys_initcall(usb_init)表示,函数 usb_init()是 USB 子系统的初始化函数,而 module_exit 则表 示,usb_exit 函数是 USB 子系统结束时的清理函数。为了研究 USB 子系统在内核中的实现,需要 从函数 usb_init()开始分析,对应的内核代码如下所示。

static int __init usb_init(void) { int retval; if (nousb) { pr_info("%s: USB support disabled\n", usbcore_name); return 0; }

33 Android 驱动开发与移植实战详解

retval = ksuspend_usb_init(); if (retval) goto out; retval = bus_register(&usb_bus_type); if (retval) goto bus_register_failed; retval = usb_host_init(); if (retval) goto host_init_failed; retval = usb_major_init(); if (retval) goto major_init_failed; retval = usb_register(&usbfs_driver); if (retval) goto driver_register_failed; retval = usb_devio_init(); if (retval) goto usb_devio_init_failed; retval = usbfs_init(); if (retval) goto fs_init_failed; retval = usb_hub_init(); if (retval) goto hub_init_failed; retval = usb_register_device_driver (&usb_generic_driver, THIS_MODULE); if (!retval) goto out;

usb_hub_cleanup(); hub_init_failed: usbfs_cleanup(); fs_init_failed: usb_devio_cleanup(); usb_devio_init_failed: usb_deregister(&usbfs_driver); driver_register_failed: usb_major_cleanup(); major_init_failed: usb_host_cleanup(); host_init_failed: bus_unregister(&usb_bus_type); bus_register_failed: ksuspend_usb_cleanup(); out: return retval; }

接下来开始分析上述代码。

34 第 2 章 简要分析 Linux 内核

(1)标记__init。 关于 usb_init,第一个问题是上述第一行代码中的__init 标记有什么意义?在前面讲解 GCC 扩 展的特殊属性 section 时曾经提到,__init 修饰的所有代码都会被放在.init.text 节,当初始化结束后 就可以释放这部分内存。但是内核是如何调用到__init 所修饰的这些初始化函数的呢?为了回答这 个问题,需要用到 subsys_initcall 宏的知识,它在文件“include/linux/init.h”中的定义格式如下所示。

#define subsys_initcall(fn) __define_initcall("4",fn,4)

此时出现了一个新的宏__define_initcall,它用来将指定的函数指针 fn 存放到“.initcall.init”节 。 对于 subsys_initcall 宏,则表示把 fn 存放到“.initcall.init”的子节“.initcall4.init”。 为了理解“.initcall.init”、“.init.text”和“.initcall4.init”之类的符号,还需要了解和内核可执行 文件相关的概念。内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、 数据、init 数据、bass 等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链 接器脚本的功能是将输入对象文件的各节映射到输出文件中。换句话说,它将所有输入对象文件都 链接到单一的可执行文件中,将该可执行文件的各节装入指定地址处。vmlinux.lds 是保存在 “arch//”目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏 移量处。 打开文件“arch/i386/kernel/vmlinux.lds”,搜索关键字“initcall.init”后便会看到如下结果。

__inicall_start = .; .initcall.init : AT(ADDR(.initcall.init) - 0xC0000000) { *(.initcall1.init) *(.initcall2.init) *(.initcall2.init) *(.initcall4.init) *(.initcall5.init) *(.initcall6.init) *(.initcall7.init) } __initcall_end = .;

其中“__initcall_start”指向“.initcall.init”节的开始,“__initcall_end”指向“.initcall.init”节 的结尾。而“.initcall.init”节又被分为了如下 7 个子节。

.initcall1.init .initcall2.init .initcall2.init .initcall4.init .initcall5.init .initcall6.init .initcall7.init

宏 subsys_initcall 将指定的函数指针放在了“.initcall4.init”子节,至于其他宏的功能也类似, 例如 core_initcall 将函数指针放在了“.initcall1.init”子节,device_initcall 将函数指针放在了 “.initcall6.init”子节等。

35 Android 驱动开发与移植实战详解

各个子节的顺序是确定的,即先调用“.initcall1.init”中的函数指针,然后再调用“.initcall2.init” 中的函数指针。__init 修饰的初始化函数在内核初始化过程中调用的顺序和.initcall.init 节里函数指 针 的顺序有关,不同的初始化函数被放在不同的子节中,因此也就决定了它们的调用顺序。 (2)模块参数。 在前面 usb_init 函数代码中,代码 nousb 在 drivers/usb/core/usb.c 文件中定义为如下格式。

static int nousb; /* Disable USB when built into kernel image */ module_param_named(autosuspend, usb_autosuspend_delay, int, 0644); MODULE_PARM_DESC(autosuspend, "default autosuspend delay");

从中可知 nousb 是个模块参数,用于在内核启动的时候禁止 USB 子系统。关于模块参数,在 加载模块的时候可以指定,但是如何在内核启动的时候指定?打开系统的 grub 文件,然后找到 kernel 行,例如下面的代码。

kernel /boot/vmlinuz-2.6.18-kdb root=/dev/sda1 ro splash=silent vga=0x314

其中的 root、splash、vga 等都表示内核参数。当某一模块被编译进内核的时候,它的模块参数 便需要在 kernel 行来指定,其格式为

模块 名.参数=值

例如下面的代码:

modprobe usbcore autosuspend=2

对应到 kernel 行的代码如下所示:

usbcore.autosuspend=2

通过命令“modinfo -p ${modulename}”可以得知一个模块有哪些参数可以使用。而对于已经 加载到内核里的模块,其模块参数会列举在“/sys/module /$ {modulename}/parameters/”目录下面, 可以使用如下命令去修改。

echo -n ${value} > /sys/module/$ {modulename}/parameters/${parm}

关于函数 usb_init(),除了上面介绍的代码外,余下的代码分别完成 usb 各部分的初始化,其他 代码的具体分析工作可以参阅下载 Linux 内核代码,具体含义可以参阅相关的书籍和资料。本章内 容目的是教会读者参考 Linux 内核,为步入本书后面知识的学习做好知识积累。

36

第 3 章 开始分析 Android 源码

经过前面的介绍,简单了解了 Linux 内核的基础知识,并掌握了学习 Linux 内核的基本方法。 在本章将简要分析 Android 源码的基本知识,详细介绍各个目录中主要文件的具体功能。希望读者 仔细品味,为步入本书后面知识的学习打下基础。

3.1 搭建 Linux 开发环境和工具

因为 Android 是基于 Linux 的,所以作为底层应用的 Android 驱动开发需要在 Linux 环境下进 行。在进行开发之前很有必要讲解搭建开发环境的知识,接下来将简单介绍搭建 Linux 开发环境的 方法。

3.1.1 搭建 Linux 开发环境 在 Linux 环境下开发 Android 时,需要安装相关的包并配置 Java 环境,具体流程如下所示。 (1)安装包。 如果是在 Ubuntu 主机上,需要安装下面的包。

sudo apt-get install git-core sudo apt-get install gnupg sudo apt-get install flex sudo apt-get install bison sudo apt-get install gperf sudo apt-get install libsdl-dev sudo apt-get install libesd0-dev sudo apt-get install build-essential sudo apt-get install zip sudo apt-get install curl sudo apt-get install libncurses5-dev sudo apt-get install zlib1g-dev

(2)安装 Java 环境 JDK。 在 Linux 中安装 Java 环境 JDK 的命令如下所示。

sudo apt-get install sun-java6-jre sun-java6-plugin sun-java6-fonts sun-java6-jdk Android 的官方文档说如果使用 sun-java6-jdk 可能会出问题,需要用 sun-java5-jdk。经过笔者

Android 驱动开发与移植实战详解

的测试发现,如果仅仅 make(make 不包括 make sdk),使用 sun-java6-jdk 是没有问题的。但是如 果 make sdk 就会发生问题,也就是说在 make doc 出问题,它需要的 javadoc 版本为 1.5。 所以在此建议读者,在安装 sun-java6-jdk 后最好再安装 sun-java5-jdk,或者只安装 sun-java5-jdk。 笔者是 sun-java6-jdk 和 sun-java5-jdk 都安装了,并只修改 javadoc.1.gz 和 javadoc。因为只有这两个是 make sdk 用到的。这样除了 javadoc 工具使用 1.5 版本外,其他的都使用 1.6 版本。具体命令如下。

sudo apt-get install sun-java5-jdk

然后还需要修改 javadoc 的 link。

cd /etc/alternatives sudo rm javadoc.1.gz sudo ln -s /usr/lib/jvm/java-1.5.0-sun/man/man1/javadoc.1.gz javadoc.1.gz sudo rm javadoc sudo ln -s /usr/lib/jvm/java-1.5.0-sun/bin/javadoc javadoc

3.1.2 设置环境变量 在 Linux 环境下开发 Android 时需要设置环境变量。

vim ~/.bashrc

然后需要在.bashrc 中新增或整合 PATH 变量,例如和 Java 程序开发/运行相关的一些环境变量。

JAVA_HOME=/usr/lib/jvm/java-6-sun JRE_HOME=${JAVA_HOME}/jre export ANDROID_JAVA_HOME=$JAVA_HOME export CLASSPATH=.:${JAVA_HOME}/lib:$JRE_HOME/lib:$CLASSPATH export JAVA_PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin export JAVA_HOME; export JRE_HOME; export CLASSPATH; HOME_BIN=~/bin/ export PATH=${PATH}:${JAVA_PATH}:${JRE_PATH}:${HOME_BIN}; #echo $PATH;

最后不要忘记同步上述变化。

source ~/.bashrc

3.1.3 安装编译工具 在 Linux 环境下开发 Android 时必须安装编译工具,例如 git 和 repo。其中 repo 的作用是更新 Android 源码,是对调用 git 的封装工具。安装 repo 的流程如下所示。 (1)创建~/bin 目录,用来存放 repo 程序。

$ cd ~ $ mkdir bin (2)将目录添加到环境变量 PATH 中,然后下载 repo 脚本并使其可执行。

38 第 3 章 开始分析 Android 源码

$ curl http://android.git.kernel.org/repo >~/bin/repo $ chmod a+x ~/bin/repo

另外,在编译 Android 系统时,需要使用编译主机的工具 GCC。对于编译目标主机文件,Android 在 prebuilt 目录中集成了 GCC 交叉编译工具链。

3.2 获取 Android 源码

Android 的源码可以从网址 http://source.android.com/获取。在网址 http://source.android.com/source/ downloading.html 上详细介绍了获取 Android 源码的方法,如图 3-1 所示。

▲图 3-1 Linux 下获取 Android 源码的方法

需要使用 repo 或 git 工具来下载 Android 源码,具体流程如下所示。 (1)创建源代码下载目录,命令如下所示。

mkdir /work/android-froyo-r2

(2)用 repo 工具初始化一个版本,假如是 Android2.2r2,则命令如下所示。

cd /work/android-froyo-r2 repo init -u git://android.git.kernel.org/platform/manifest.git -b froyo

在初始化过程中会显示相关的版本的 TAG 信息,同时会提示我们输入用户名和邮箱地址,上 面的命令初始化的是 android2.2 froyo 的最新版本。 (3)因为 Android2.2 有很多个版本,这些版本信息可以从 TAG 信息中看出来。当前 froyo 的 所有版本信息如下所示。

* [new tag] android-2.2.1_r1 -> android-2.2.1_r1 * [new tag] android-2.2_r1 -> android-2.2_r1

39 Android 驱动开发与移植实战详解

* [new tag] android-2.2_r1.1 -> android-2.2_r1.1 * [new tag] android-2.2_r1.2 -> android-2.2_r1.2 * [new tag] android-2.2_r1.3 -> android-2.2_r1.3 * [new tag] android-cts-2.2_r1 -> android-cts-2.2_r1 * [new tag] android-cts-2.2_r2 -> android-cts-2.2_r2 * [new tag] android-cts-2.2_r3 -> android-cts-2.2_r3

每次下载的都是最新的版本,当然也可以根据 TAG 信息下载某一特定的版本。例如:

repo init -u git://android.git.kernel.org/platform/manifest.git -b android-cts-2.3_r1

(4)开始下载源码,命令是。

repo sync

froyo 版本的代码很大,超过 2G,下载过程非常漫长,需要读者耐心等待。 (5)最后编译一步是代码,命令是。

cd /work/android-froyo-r2 make

上述 repo 获取的方式获取源码的速度非常慢,要想快速、省心地查看 Android 源码,可以通 过网页浏览的方式来访问 Android 代码库,浏览路径是 http://android.git.kernel.org/,界面如图 3-2 所示。

▲图 3-2 页面浏览方式访问 Android 代码库

正是因为上述过程非常缓慢,所以建议不要使用 repo 来下载 Android 源码,可以直接登录 http://www.androidin.com/bbs/pub/cupcake.tar.gz 来下载,解压出来的“cupcake”下也有“.repo”文 件夹,此时可以通过 repo sync 来更新 cupcake 代码。命令如下所示。

tar -xvf cupcake.tar.gz

40 第 3 章 开始分析 Android 源码

3.3 分析 Android 源码结构

可以将 Android 源码的全部工程分为如下 3 个部分。 Core Project:核心工程部分,这是建立 Android 系统的基础,被保存在根目录的各个文件 夹中。 External Project:扩展工程部分,可以使其他开源项目具有扩展功能,被保存在“external” 文件夹中。 Package:包部分,提供了 Android 的应用程序、内容提供者、输入法和服务,被保存在 “package”文件夹中。 无论是 Android 1.5 还是 Android 2.2 和 Android 2.3,各个版本的源码目录基本类似。在里面包 含了原始 Android 的目标机代码、主机编译工具和仿真环境。在接下来的内容中,将简单介绍 Android 源码的目录结构,并注释常用目录的含义。 (1)第一级目录。 解压缩代码包后,第一级别的目录和文件的结构如下所示。 |– Makefile (全局的 Makefile) |– (Bionic 含义为仿生,这里面是一些基础的库的源代码) |– bootloader (引导加载器) |– build (build 目录中的内容不是目标所用的代码,而是编译和配置所需要的脚 本和工具) |– dalvik (Java 虚拟机) |– development (程序开发所需要的模板和工具) |– external (目标机器使用的一些库) |– frameworks (应用程序的框架层) |– hardware (与硬件相关的库) |– kernel (Linux2.6 的源代码) |– packages (Android 的各种应用程序) |– prebuilt (Android 在各种平台下编译的预置脚本) |– recovery (与目标的恢复功能相关) `– system (Android 的底层的一些库) Makefile:是整个 Android 编译所需要的真正的 Makefile,它被顶层目录的 Makefile 引用。 Makefile 目录下的 envsetup.sh:是一个在使用仿真器运行的时候,用于设置环境的脚本。 dalvik 目录:提供 Android Java 应用程序运行的基础——JAVA 虚拟机。 (2)development 目录。 展开 development 目录后,里面的同一个级别的目录结构如下所示。 development |– apps (Android 应用程序的模板)

41 Android 驱动开发与移植实战详解

|– build (编译脚本模板) |– cmds |– data |– docs |– emulator (仿真相关) |– host (包含 Windows 平台的一些工具) |– ide |– pdk |– samples (一些示例程序) |– simulator (大多是目标机器的一些工具) `– tools 目录 emulator:里面的“qemud”是使用 QEMU 仿真时目标机器运行的后台程序,“skins” 是仿真时手机的界面。 目录 samples:在里面包含了很多 Android 简单工程,这些工程为开发者学习开发 Android 程序提供了很大便利,可以作为模板使用。 (3)external 目录。 展开 external 目录后,里面的同一个级别的目录结构如下所示。 external/ |– aes |– apache-http |– bluez |– clearsilver |– dbus |– dhcpcd |– dropbear |– elfcopy |– elfutils |– emma |– esd |– expat |– fdlibm |– freetype |– |– giflib |– googleclient |– icu4c |– iptables

42 第 3 章 开始分析 Android 源码

|– jdiff |– jhead |– jpeg |– libffi |– libpcap |– libpng |– libxml2 |– netcat |– netperf |– neven |– opencore |– openssl |– oprofile |– ping |– ppp |– protobuf |– qemu |– safe-iop |– skia |– sonivox |– |– srec |– strace |– tagsoup |– tcpdump |– tinyxml |– tremor |– webkit |– wpa_supplicant |– yaffs2 `– zlib 在上述 external 目录中,每个目录表示 Android 目标系统中的一个模块,这些模块可能由一个 或者多个库构成。其中常用目录的具体说明如下所示。 opencore:是 Android 多媒体框架的核心。 webkit:是 Android 网络浏览器的核心。 sqlite:是 Android 数据库系统的核心。 openssl:是 Secure Socket Layer,表示一个网络协议层,用于为数据通信提供安全支持。

43 Android 驱动开发与移植实战详解

(4)frameworks 目录。 展开 frameworks 目录后,里面的同一个级别的目录如下所示。 frameworks/ |– base |– opt `– policies frameworks:是 Android 应用程序的框架。 hardware:是一些与硬件相关的库。 kernel:是 Linux2.6 的源代码。 (5)packages 目录。 展开 packages 目录后,发现里面包含了两个目录,其中目录“apps”中保存了 Android 中的各 种应用程序,目录“providers”中保存的是一些内容提供者信息,即在 Android 中的一个数据源。 packages 目录展开后的具体结构如下所示。 packages/ |– apps | |– AlarmClock | |– Browser | |– Calculator | |– Calendar | |– Camera | |– Contacts | |– Email | |– GoogleSearch | |– HTMLViewer | |– IM | |– Launcher | |– Mms | |– Music | |– PackageInstaller | |– Phone | |– Settings | |– SoundRecorder | |– Stk | |– Sync | |– Updater | `– VoiceDialer `– providers

44 第 3 章 开始分析 Android 源码

|– CalendarProvider |– ContactsProvider |– DownloadProvider |– DrmProvider |– GoogleContactsProvider |– GoogleSubscribedFeedsProvider |– ImProvider |– MediaProvider `– TelephonyProvider 目录“packages”中两个目录的内容大多数都是使用 Java 编写的程序,各个文件夹的层次结构 是类似的。 (6)prebuilt 目录。 展开 prebuilt 目录后的同级别目录结构如下所示。 prebuilt/ |– Android.mk |– android-arm |– common |– darwin-x86 |– linux-x86 `– windows (7)system 目录。 展开 system 目录后,两个级别的目录结构如下所示。 system/ |– bluetooth | |– bluedroid | `– brfpatch |– core | |– Android.mk | |– README | |– adb | |– cpio | |– debuggerd | |– fastboot | |– include (各个库接口的头文件) | |– init | |– libctest | |– libcutils

45 Android 驱动开发与移植实战详解

| |– liblog | |– libmincrypt | |– libnetutils | |– libpixelflinger | |– libzipfile | |– logcat | |– logwrapper | |– mkbootimg | |– mountd | |– netcfg | |– rootdir | |– sh | `– toolbox |– extras | |– Android.mk | |– latencytop | |– libpagemap | |– librank | |– procmem | |– procrank | |– showmap | |– showslab | |– sound | |– su | |– tests | `– timeinfo `– wlan `– ti

3.4 编译 Android 源码

编译 Android 源码的方法非常简单,只需要使用 Android 源码主目录下的 Makefile 文件并执行 make 命令即可实现。在编译 Android 源码之前,需要先确定已经完成同步工作。进入 Android 源码 目录后使用 make 命令进行编译,下面是使用此命令的格式。

make

编译 Android 源码可以得到“~/project/android /cupcake/out”目录,编译过程会有点慢,需要

46 第 3 章 开始分析 Android 源码

读者耐心等待。 虽然编译方法非常简单,但是作为初学者来说很容易出错,其中常见的编译错误有如下几条。 (1)缺少必要的软件。 进入到 Android 目录下,使用 make 命令编译,可能会发现出现如下错误提示。

host C: libneo_cgi <= external/clearsilver/cgi/cgi.c external/clearsilver/cgi/cgi.c:22:18: error: zlib.h: No such file or directory

上述错误是因为缺少 zlib1g-dev,需要使用 apt-get 命令从软件仓库中安装 zlib1g-dev,具体命 令如下所示。

sudo apt-get install zlib1g-dev

同理,我们必须安装下面的软件,否则也会出现上述类似的错误。

sudo apt-get install flex sudo apt-get install bison sudo apt-get install gperf sudo apt-get install libsdl-dev sudo apt-get install libesd0-dev sudo apt-get install libncurses5-dev sudo apt-get install libx11-dev

(2)没有安装 Java 环境 JDK。 当安装所有上述软件后,运行 make 命令再次编译 Android 源码。如果在之前忘记安装 Java 环 境 JDK,则此时会出现很多 Java 文件无法编译的错误,如果打开 Android 的源码,可以看到在如 下目录中有很多 Java 源文件。

android/dalvik/libcore/dom/src/test/java/org/w3c/domts

这充分说明在编译 Android 之前必须先安装 Java 环境 JDK,安装流程如下所示。 从 SUN 官方网站下载 jdk-6u16-linux-i586.bin 文件,然后安装。 在 Ubuntu 8.04 中,/etc/profile 文件是全局的环境变量配置文件,它适用于所有的 shell。在登 录 Linux 系统时应该先启动/etc/profile 文件,然后再启动用户目录下的~/.bash_profile、~/.bash_login 或~/.profile 文件中的其中一个,执行的顺序和上面的排序一样。如果~/.bash_profile 文件存在的话, 则还会执行~/.bashrc 文件。在此只需要把 JDK 的目录放到/etc/profile 目录下即可。

JAVA_HOME=/usr/local/src/jdk1.6.0_16 PATH=$PATH:$JAVA_HOME/bin:/usr/local/src/android-sdk-linux_x86-1.1_r1/tools:~/bin

重新启动机器,输入 java –version 命令,输出下面的信息则表示配置成功。

ava version "1.6.0_16" Java(TM) SE Runtime Environment (build 1.6.0_16-b01) Java HotSpot(TM) Client VM (build 13.2-b01, mixed mode, sharing)

当成功编译 Android 源码后,在终端会输出如下提示。

47 Android 驱动开发与移植实战详解

Target system fs image: out/target/product/generic/obj/PACKAGING/systemimage_unopt_ intermediates/system.img Install system fs image: out/target/product/generic/system.img Target ram disk: out/target/product/generic/ramdisk.img Target userdata fs image: out/target/product/generic/userdata.img Installed file list: out/target/product/generic/installed-.txt root@dfsun2009-desktop:/bin/android#

3.5 运行 Android 源码

当编译完整个项目后,需要在系统中安装模拟器后才能观看编译后的运行效果。当前最新模拟 器的下载地址为 http://developer.android.com/sdk/index.html,如图 3-3 所示。

▲图 3-3 最新 SDK 的下载页面

解压后需要把目录/usr/local/src/android-sdk-linux_x86-1.1_r12/tools 加入到系统环境变量 /etc/profile 中。然后找到编译后 Android 的目录文件 out,此时会发现在 android/out/host/linux-x86/bin 目录下多了很多应用程序,这些应用程序就是 Android 得以运行的基础,所以我们需要把这个目录 也添加到系统 PATH 下,并在$HOME/.profile 文件中加入如下内容。

PATH="$PATH:$HOME/android/out/host/linux-x86/bin"

接下来需要把 Android 的镜像文件加载到 emulator 中,使 emulator 可以看到 Android 运行的实 际效果,然后在$HOME/.profile 文件中加入如下内容。

ANDROID_PRODUCT_OUT=$HOME/android/out/target/product/generic export ANDROID_PRODUCT_OUT

然后重新启动机器,此时就可以进入到模拟器目录中并启动模拟器。

cd $HOME/android/out/target/product/generic

48 第 3 章 开始分析 Android 源码

emulator -image system.img -data userdata.img -ramdisk ramdisk.img

上述流程是在 Linux 下运行 Android 程序的方法,和本书第 1 章介绍的在 Windows 下搭建 Android 开发环境,并运行 Android 程序的方法完全不同。

3.6 实践演练——演示两种编译 Android 程序的方法

Android 编译环境本身比较复杂,并且不像普通的编译环境那样只有顶层目录下才有 Makefile 文件,而其他的每个 component 都使用统一标准的 Android.mk. Android.mk 文件。不过这并不是我 们熟悉的 Makefile,而是经过 Android 自身编译系统的很多处理。所以说要真正理清楚其中的联系 还比较复杂,不过这种方式的好处在于,编写一个新的 Android.mk 给 Android 增加一个新的 Component 会变得比较简单。为了使读者更加深入地理解在 Linux 环境下编译 Android 程序的方法, 在接下来的内容中,将分别演示两种编译 Android 程序的方法。

3.6.1 编译 Native C 的 helloworld 模块 编译 Java 程序可以直接采用 的集成环境来完成,实现方法非常简单,在此就不再重复 了。接下来主要针对 C/C++来说明,通过一个例子来说明如何在 Android 中增加一个 C 程序的 Hello World。 ( 1 )在$(YOUR_ANDROID)/development 目录下创建一个名为 hello 的目录,并用 $(YOUR_ANDROID)指向 Android 源代码所在的目录。

- # mkdir $(YOUR_ANDROID)/development/hello

(2)在目录$(YOUR_ANDROID)/development/hello/下编写一个名为 hello.c 的 C 语言文件,文 件 hello.c 的代码如下所示。

#include int main() { printf("Hello World!\n");//输出 Hello World return 0; } (3)在目录$(YOUR_ANDROID)/development/hello/下编写 Android.mk 文件。这是 Android Makefile 的标准命名,不能更改。文件 Android.mk 的格式和内容可以参考其他已有的 Android.mk 文件的写法,针对 helloworld 程序的 Android.mk 文件内容如下所示。

LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ hello.c LOCAL_MODULE := helloworld include $(BUILD_EXECUTABLE) LOCAL_SRC_FILES:用来指定源文件用。

49 Android 驱动开发与移植实战详解

LOCAL_MODULE:指定要编译的模块的名字,在下一步骤编译时将会用到。 include $(BUILD_EXECUTABLE):表示要编译成一个可执行文件,如果想编译成动态库则 可用 BUILD_SHARED_LIBRARY,这些具体用法可以在$(YOUR_ANDROID)/build/core/config.mk 查到。 (4)回到 Android 源代码顶层目录进行编译。

# cd $(YOUR_ANDROID) && make helloworld

在此需要注意,make helloworld 中的目标名 helloworld 就是上面 Android.mk 文件中由 LOCAL_MODULE 指定的模块名。最终的编译结果如下所示。

target thumb C: helloworld <= development/hello/hello.c target Executable: helloworld (out/target/product/generic/obj/EXECUTABLES/helloworld_ intermediates/LINKED/helloworld) target Non-prelinked: helloworld (out/target/product/generic/symbols/system/bin/ helloworld) target Strip: helloworld (out/target/product/generic/obj/EXECUTABLES/helloworld_ intermediates/helloworld) Install: out/target/product/generic/system/bin/helloworld

(5)如果和上述编译结果相同,则编译后的可执行文件存放在如下目录:

out/target/product/generic/system/bin/helloworld

这样通过 adb push 将它传送到模拟器上,再通过 adb shell 登录到模拟器终端后就可以执行了。

3.6.2 手工编译 C 模块 在前面讲解了通过标准的 Android.mk 文件来编译 C 模块的具体流程,其实我们可以直接运用 gcc 命令行来编译 C 程序,这样可以更好地了解 Android 编译环境的细节。具体流程如下所示。 (1)在 Android 编译环境中,提供了 showcommands 选项来显示编译命令行,我们可以通过打 开这个选项来查看一些编译时的细节。 (2)在具体操作之前需要使用如下命令把前面的 helloworld 模块清除。

# make clean-helloworld

上面的 make clean-$(LOCAL_MODULE)命令是 Android 编译环境提供的 make clean 的方式。 (3)使用 showcommands 选项重新编译 helloworld,具体命令如下所示。

# make helloworld showcommands build/core/product_config.mk:229: WARNING: adding test OTA key target thumb C: helloworld <= development/hello/hello.c prebuilt/linux-x86/toolchain/arm-eabi-3.2.1/bin/arm-eabi-gcc -I system/core/include -I hardware/libhardware/include -I hardware/ril/include -I dalvik/libnativehelper/ include -I frameworks/base/include -I external/skia/include -I out/target/product/ generic/obj/include -I bionic/libc/arch-arm/include -I bionic/libc/include -I bionic/libstdc++/include -I bionic/libc/kernel/common -I bionic/libc/kernel/arch- arm -I bionic/libm/include -I bionic/libm/include/arch/arm -I bionic/libthread_db/

50 第 3 章 开始分析 Android 源码

include -I development/hello -I out/target/product/generic/obj/EXECUTABLES/ helloworld_intermediates -c -fno-exceptions -Wno-multichar -march=armv5te -mtune= xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind- -fstack-protector -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_ 5TE__ -include system/core/include/arch/linux-arm/AndroidConfig.h -DANDROID -fmessage- length=0 -W -Wall -Wno-unused -DSK_RELEASE -DNDEBUG -O2 -g -Wstrict-aliasing=2 -finline-functions -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse- after-loop -frename-registers -DNDEBUG -UDEBUG -mthumb -Os -fomit-frame-pointer -fno- strict-aliasing -finline-limit=64 -MD -o out/target/product/generic/obj/ EXECUTABLES/helloworld_intermediates/hello.o development/hello/hello.c

target Executable: helloworld (out/target/product/generic/obj/EXECUTABLES/helloworld_ intermediates/LINKED/helloworld)

prebuilt/linux-x86/toolchain/arm-eabi-3.2.1/bin/arm-eabi-g++ -nostdlib -Bdynamic -Wl,-T,build/core/armelf.x -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc -o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld -Lout/target/product/generic/obj/lib -Wl,-rpath-link=out/target/product/generic/obj/lib -lc -lstdc++ -lm out/target/ product/generic/obj/lib/crtbegin_dynamic.o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/hello.o -Wl,--no-undefined prebuilt/linux-x86/toolchain/arm-eabi-3.2.1/bin/../lib/gcc/arm-eabi/3.2.1/interwork/l ibgcc.a out/target/product/generic/obj/lib/crtend_android.o target Non-prelinked: helloworld (out/target/product/generic/symbols/system/bin/helloworld) out/host/linux-x86/bin/acp -fpt out/target/product/generic/obj/EXECUTABLES/helloworld_ intermediates/LINKED/helloworld out/target/product/generic/symbols/system/bin/helloworld target Strip: helloworld (out/target/product/generic/obj/EXECUTABLES/helloworld_ intermediates/helloworld) out/host/linux-x86/bin/soslim --strip --shady --quiet out/target/product/generic/ symbols/system/bin/helloworld --outfile out/target/product/generic/obj/EXECUTABLES/ helloworld_intermediates/helloworld

Install: out/target/product/generic/system/bin/helloworld

out/host/linux-x86/bin/acp -fpt out/target/product/generic/obj/EXECUTABLES/helloworld_ intermediates/helloworld out/target/product/generic/system/bin/helloworld

从上述命令行可以看到,Android 编译环境所用的交叉编译工具链如下: prebuilt/linux-x86/toolchain/arm-eabi-3.2.1/bin/arm-eabi-gcc 其中参数“-I ”和“-L ”分别指定了所用的 C 库头文件和动态库文件路径分别是 “bionic/libc/include ”和“out/target/product/generic/obj/lib”,其他还包括很多编译选项以及-D 所定 义的预编译宏。 (4)此时就可以利用上面的编译命令来手工编译 helloworld 程序,首先手工删除上次编译得到 的 helloworld 程序。

51 Android 驱动开发与移植实战详解

# rm out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/hello.o # rm out/target/product/generic/system/bin/helloworld

然后再用 gcc 编译以生成目标文件。

# prebuilt/linux-x86/toolchain/arm-eabi-3.2.1/bin/arm-eabi-gcc -I bionic/libc/arch-arm/ include -I bionic/libc/include -I bionic/libc/kernel/common -I bionic/libc/kernel/arch -arm -c -fno-exceptions -Wno-multichar -march=armv5te -mtune=xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -D__ARM_ARCH_ 5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -include system/core/include/ arch/linux-arm/AndroidConfig.h -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -DSK_RELEASE -DNDEBUG -O2 -g -Wstrict-aliasing=2 -finline-functions -fno-inline- functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers -DNDEBUG -UDEBUG -mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -MD -o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/hello.o development/hello/hello.c

如果此时与 Android.mk 编译参数进行比较,会发现上面主要减少了不必要的参数“-I”。 (5)接下来开始生成可执行文件。

# prebuilt/linux-x86/toolchain/arm-eabi-3.2.1/bin/arm-eabi-gcc -nostdlib -Bdynamic -Wl,-T,build/core/armelf.x -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc -o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/ LINKED/helloworld -Lout/target/product/generic/obj/lib -Wl,-rpath-link=out/target/ product/generic/obj/lib -lc -lm out/target/product/generic/obj/EXECUTABLES/helloworld_ intermediates/hello.o out/target/product/generic/obj/lib/crtbegin_dynamic.o -Wl,--no- undefined ./prebuilt/linux-x86/toolchain/arm-eabi-3.2.1/bin/../lib/gcc/arm-eabi/3.2.1 /interwork/libgcc.a out/target/product/generic/obj/lib/crtend_android.o

在此需要特别注意的是参数-Wl,-dynamic-linker,/system/bin/linker,它指定了 Android 专用的动 态链接器是/system/bin/linker,而不是平常使用的 ld.so。 (6)最后可以使用命令 file 和 readelf 来查看生成的可执行程序。

# file out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/ helloworld out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped # readelf -d out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/ LINKED/helloworld |grep NEEDED 0x00000001 (NEEDED) Shared library: [libc.so] 0x00000001 (NEEDED) Shared library: [libm.so]

这就是 ARM 格式的动态链接可执行文件,在运行时需要 libc.so 和 libm.so。当提示“not stripped”时表示它还没被 STRIP(剥离)。嵌入式系统中为节省空间通常将编译完成的可执行文 件或动态库进行剥离,即去掉其中多余的符号表信息。在前面“make helloworld showcommands” 命令的最后我们也可以看到,Android 编译环境中使用了“out/host/linux-x86/bin/soslim”工具进 行 STRIP。

52 第 3 章 开始分析 Android 源码

3.7 编译 Android Kernel

编译 Android Kernel 代码就是编译 Android 内核代码,在编译之前需要先了解在 Android 开源 系统中主要包括如下三部分代码。 仿真器公共代码:对应的工程名是 kernel/common.get。 MSM 平台的内核代码:对应的工程名是 kernel/msm.get。 OMAP 平台的内核代码:对应的工程名是 kernel/omap.get。

3.7.1 获取 Goldfish 内核代码 Goldfish 是一种虚拟的 ARM 处理器,通常在 Android 的仿真环境中使用。在 Linux 的内核中, Goldfish 作为 ARM 体系结构的一种“机器”。在 Android 的发展过程中,Goldfish 内核的版本也从 Linux2.6.25 升级到了 Linux2.6.29。此处理器的 Linux 内核和标准的 Linux 内核的差别有以下三个方面。 Goldfish 机器的移植。 Goldfish 一些虚拟设备的驱动程序。 Android 中特有的驱动程序和组件。 Goldfish 处理器有两个版本,分别是 ARMv5 和 ARMv7,在一般情况下,只需使用 ARMv5 版 本即可。在 Android 开源工程的代码仓库中,使用 git 工具得到 Goldfish 内核代码的命令如下所示。

$ git clone git://android.git.kernel.org/kernel/common.git

在其 Linux 源代码的根目录中,配置和编译 Goldfish 内核的过程如下所示。

$make ARCH=arm goldfish_defconfig .config $make ARCH=arm CROSS_COMPILE={path}/arm-none-linux-gnueabi-

其中,在 CROSS_COMPILE=指定交叉编译工具的路径。 编译结果如下所示。

LD vmlinux SYSMAP system.map SYSMAP .tmp_system.map OBJCOPY arch/arm/boot/Image Kernel: arch/arm/boot/Image is ready AS arch/arm/boot/compressed/head.o GZIP arch/arm/boot/compressed/piggy.gz AS arch/arm/boot/compressed/piggy.o CC arch/arm/boot/compressed/misc.o LD arch/arm/boot/compressed/vmlinux OBJCONPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready

vmlinux:是 Linux 进行编译和连接之后生成的 Elf 格式的文件。 Image:是未经过压缩的二进制文件。

53 Android 驱动开发与移植实战详解

piggy:是一个解压缩程序。 zImage:是解压缩程序和压缩内核的组合。 在 Android 源代码的根目录中,vmlinux 和 zImage 分别对应 Android 代码 prebuilt 中的预编译 的 arm 内核。使用 zImage 可以替换 prebuilt 中的“prebuilt/android-arm/”目录下的 goldfish_defconfig, 此文件的主要片断如下所示。

CONFIG_ARM=y # # System Type # CONFIG_ARCH_GOLDFISH=y # # Goldfish options # CONFIG_MACH_GOLDFISH=y # CONFIG_MACH_GOLDFISH_ARMV7 is not set

因为 GoldFish 是 arm 处理器,所以 CONFIG_ARM 宏需要被使能,CONFIG_ARCH_GOLDFISH 和 CONFIG_MACH_GOLDFISH 宏是 GoldFish 处理器这类机器使用的配置宏。 在 gildfish_defconfig 中,与 Android 系统相关的宏如下所示。

# # android # CONFIG_ANDROID=y CONFIG_ANDROID_BUNDER_IPC=y #binder ipc 驱动程序 CONFIG_ANDROID_LOGGER=y #log 记录器驱动程序 # CONFIG_ANDROID_RAM_CONSOLE is not set CONFIG_ANDROID_TIMED_OUTPUT=y #定时输出驱动程序框架 CONFIG_ANDROID_LOW_MEMORY_KILLER=y CONFIG_ANDROID_PMEM=y #物理内存驱动程序 CONFIG_ASHMEM=y #匿名共享内存驱动程序 CONFIG_RTC_INTF_ALARM=y CONFIG_HAS_WAKELOCK=y 电源管理相关的部分 wakelock 和 earlysuspend CONFIG_HAS_EARLYSUSPEND=y CONFIG_WAKELOCK=y CONFIG_WAKELOCK_STAT=y CONFIG_USER_WAKELOCK=y CONFIG_EARLYSUSPEND=y goldfish_defconfig 配置文件中,另外有一个宏是处理器虚拟设备的“驱动程序”,其内容如下: CONFIG_MTD_GOLDFISH_NAND=y CONFIG_KEYBOARD_GOLDFISH_EVENTS=y CONFIG_GOLDFISH_TTY=y CONFIG_BATTERY_GOLDFISH=y CONFIG_FB_GOLDFISH=y CONFIG_MMC_GOLDFISH=y CONFIG_RTC_DRV_GOLDFISH=y 在 Goldfish 处理器的各个配置选项中,体系结构和 Goldfish 的虚拟驱动程序基于标准 Linux 的

54 第 3 章 开始分析 Android 源码

内容的驱动程序框架,但是这些设备在不同的硬件平台的移植方式不同;Android 专用的驱动程序 是 Android 中特有的内容,非 Linux 标准,但是和硬件平台无关。 和原 Linux 内核相比,Android 内核增加了 Android 的相关 Driver,对应的目录如下所示。

kernel/drivers/android

主要分为以下几类 Driver。 Android IPC 系统:Binder (binder.c)。 Android 日志系统:Logger (logger.c)。 Android 电源管理:Power (power.c)。 Android 闹钟管理:Alarm (alarm.c)。 Android 内存控制台:Ram_console (ram_console.c)。 Android 时钟控制的 gpio:Timed_gpio (timed_gpio.c)。 对于本书讲解的驱动程序开发来说,我们比较关心的是 GoldFish 平台下相关的驱动文件,具 体说明如下所示。 (1)字符输出设备。 kernel/drivers/char/goldfish_tty.c (2)图像显示设备(Frame Buffer)。 kernel/drivers/video/goldfishfb.c (3)键盘输入设备文件。 kernel/drivers/input/keyboard/goldfish_events.c (4)RTC 设备(Real Time Clock) 文件。 kernel/drivers/rtc/rtc-goldfish.c (5)USB Device 设备文件。 kernel/drivers/usb/gadget/android_adb.c (6)SD 卡设备文件。 kernel/drivers/mmc/host/goldfish.c (7)FLASH 设备文件。 kernel/drivers/mtd/devices/goldfish_nand.c kernel/drivers/mtd/devices/goldfish_nand_reg.h (8)LED 设备文件。 kernel/drivers/leds/ledtrig-sleep.c (9)电源设备。 kernel/drivers/power/goldfish_battery.c (10)音频设备。 kernel/arch/arm/mach-goldfish/audio.c (11)电源管理。 kernel/arch/arm/mach-goldfish/pm.c

55 Android 驱动开发与移植实战详解

(12)时钟管理。 kernel/arch/arm/mach-goldfish/timer.c

3.7.2 获取 MSM 内核代码 在当前市面中谷歌的手机产品 G1 是基于 MSM 内核的,MSM 是高通公司的应用处理器,在 Android 代码库中公开了对应的 MSM 的源代码。在 Android 开源工程的代码仓库中,使用 git 工具 得到 MSM 内核代码的命令如下所示。

$ git clone git://android.git.kernel.org/kernel/msm.git

3.7.3 获取 OMAP 内核代码 OMAP 是德州仪器公司的应用处理器,Android 使用的是 OMAP3 系列的处理器。在 Android 代码库中公开了对应的 MSM 的源代码,使用 git 工具得到 MSM 内核代码的命令如下所示。

$ git clone git://android.git.kernel.org/kernel/omap.git

3.7.4 编译 Android 的 Linux 内核 了解了上述三类 Android 内核后,下面开始讲解编译 Android 的 Linux 内核的方法,假设以 Ubuntu8.10 为例,完整编译 Android 的 Linux 内核的流程如下所示。 (1)构建交叉编译环境。 Android 的默认硬件处理器是 ARM,因此我们需要在自己的机器上构建交叉编译环境。交叉 编译器 GNU Toolchain for ARM Processors 下载地址如下: http://www.codesourcery.com/gnu_toolchains/arm/download.html 点击 GNU/Linux 对应的链接,再点击 IA32 GNU/Linux Installer 链接后直接下载,如图 3-4 所示。

▲图 3-4 下载交叉编译器

把 arm-2008q3-72-arm-none-linux-gnueabi-i686-pc-linux- gnu.tar.bz2 解压到一个目录下,例如 “~/programes/”,并加入 PATH 环境变量:

vim ~/.bashrc

56 第 3 章 开始分析 Android 源码

然后添加:

ARM_TOOLCHIAN=~/programes/arm-2008q3/bin/ export PATH=${PATH}:${ARM_TOOLCHIAN};

保存后并执行“source ~/.bashrc“命令。 (2)获取内核源码。 源码地址如下: http://code.google.com/p/android/downloads/list 选择的内核版本要与选用的模拟器版本尽量一致。下载并解压后得到 kernel.git 文件夹:

tar -xvf ~/download/linux-2.6.25-android-1.0_r1.tar.gz

(3)获取内核编译配置信息文件。 编译内核时需要使用 configure,通常 configure 有很多选项,我们往往不知道需要哪些选项。 在运行 Android 模拟器时,有一个文件“/proc/config.gz”,这是当前内核的配置信息文件,获取并 解压 config.gz,将其放到“kernel.git/”下,然后改名为.config。命令如下所示。

cd kernel.git/ emulator & adb pull /proc/config.gz gunzip config.gz mv config .config

(4)修改 Makefile。 修改 195 行的代码:

CROSS_COMPILE = arm-none-linux-gnueabi- 将 CROSS_COMPILE 值改为 arm-none-linux-gnueabi-,这是我们安装的交叉编译工具链的前缀, 修改此处意在告诉 make 在编译的时候要使用该工具链。然后注释 562 和 563 行的如下代码:

#LDFLAGS_BUILD_ID = $(patsubst -Wl$(comma)%,%,/ # $(call ld-option, -Wl$(comma)--build-id,))

必须将上述代码中的 build id 值注释掉,因为目前版本的 Android 内核不支持该选项。 (5)编译。 使用 make 进行编译,并同时生成 zImage。

LD arch/arm/boot/compressed/vmlinux OBJCOPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready

这样生成的 zImage 大小为 1.23M,android- sdk-linux_x86-1.0_r2/tools/lib/images/kernel-qemu 是 1.24M。 (6)使用模拟器加载内核测试。 命令如下所示。

57 Android 驱动开发与移植实战详解

cd android/out/cupcake/out/target/product/generic emulator -image system.img -data userdata.img -ramdisk ramdisk.img -kernel ~/project/android/kernel.git/arch/arm/boot/zImage &

到此为止,模拟器就加载成功了。

3.8 运行模拟器

驱动开发是基于 Linux 环境进行的,所以很有必要讲解在 Linux 下运行模拟器的知识。在本节 的内容中,将为读者介绍在 Linux 环境下运行模拟器的知识。

3.8.1 Linux 环境下运行模拟器的方法 下面以 Linux ubuntu 8.10 为平台,介绍搭建 Android 开发环境的具体方法。具体流程如下所示。 (1)安装虚拟光驱 daemon400.exe。 (2)在 Windows XP 下用虚拟光驱安装 ubuntu 8.10,iso 文件为 ubuntu-8.10-beta-desktop-i386.iso。 (3)用 dpkg 命令打开 patch。首先进入 ubuntu 系统,将 ubuntu_package_0430.tar.gz 解压。

tar –zvxf ubuntu_package_0430.tar.gz

然后打开 patch:

sudo dpkg -i *.deb

如果存在没有成功的,则再一次执行下面的命令。

sudo dpkg i filename.deb

可能有些需要一起运行 dpkg,形式如下所示。

sudo dpkg i filename1.deb filename1.deb

另外,还需要重新将 java5 执行 dpkg 命令(因为用 java6 会有问题)。 (4)编译源码和 Android sdk。 编译原码:解压原码到本地,进入原码目录,执行下面的命令。

make

编译 sdk:在 make 完成后,直接 make sdk,会在 out/host/linux-x86/sdk 下面生成 mdk 文件及 文件夹,形如 android-sdk_eng.xxx_linux-x86。 (5)安装 Eclipse。 直接用如下命令解压 eclipse-jee-ganymede-SR2-linux-gtk.tar.gz 即可安装。

tar -zvxf eclipse-jee-ganymede-SR2-linux-gtk.tar.gz

(6)在 Eclipse 下配置 Android,在此需要注意“eclipse-jee-ganymede-SR2-linux-gtk.tar.gz”对 应的 ADT 要用 ADT-0.9.1.zip,而不是 0.8.1 版本。

58 第 3 章 开始分析 Android 源码

(7)测试刚才编译好的 SDK,分为如下三个步骤。 在 Eclipse 中将 android SDK 目录设置成自己编译生成的 SDK 目录,例如 out/host/linux-x86/sdk/android-sdk_eng.xxx_linux-x86。选 择【 Window】︱【 preferences】︱【 Android】 中的 SDK Location,进行设置。 创建 AV D :在 Eclipse 中,选择【Window】︱【Android AVD Manager】,将 name、target、 sdcard、skin 都填选好后,单击“Create AVD”即可。 在 cmd 窗口中,进入到目录下,执行下面的命令。

emulator avd avdname

经过上述操作后,模拟器就运行起来了。

如果没有需要的 JDK、Eclipse 和 Android SDK,在 Linux 下也需要分别下载它 注意 们,只是在下载时选择 Linux 的资源即可,整个安装顺序和 Windows 下的大同小异。

3.8.2 模拟器辅助工具——ADB ADB(Android Debug Bridge)是 Android 提供的一个通用的调试工具,通过此工具可以管理 设备或手机模拟器的状态,另外还可以进行下面的操作: 快速更新设备或手机模拟器中的代码,如应用或 Android 系统升级; 在设备上运行 Shell 命令; 管理设备或手机模拟器上的预定端口; 在设备或手机模拟器上复制或粘贴文件。 进入 Shell 的命令如下所示。

adb shell

通过上面的命令,就可以进入设备或模拟器的 Shell 环境中,在这个 Linux Shell 中,你可以执 行各种 Linux 的命令,另外如果只想执行一条 Shell 命令,可以采用下面的方式。

adb shell [command]

例如:adb shell dmesg 会打印出内核的调试信息。(Android 的 Linux Shell 做了大量精简,很多 linux 常用指令都不支持。) 上传文件命令如下所示。

adb push

下载文件命令如下所示。

adb pull

安装程序命令如下所示。

adb install

59 Android 驱动开发与移植实战详解

卸载软件命令如下所示。

adb shell rm /data/app/

补充一点,通过 ADB 安装的软件(*.apk)都在“/data/app/”目录下,所以安装时不必制定路 径,卸载只需要简单地执行“rm”就行。 结束 ADB 命令如下所示。

adb kill-server

显示 Android 模拟器状态的命令如下所示。

adb devices (端口信息) adb get-product (设备型号) adb get-serialno (序列号)

等待正在运行的设备命令如下所示。

adb wait-for-device

端口转发命令如下所示。

adb forward adb forward tcp:5555 tcp:1234(将默认端口 TCP 5555 转发到 1234 端口上)

查看 bug 报告的命令如下所示。

adb bugreport

访问数据库 SQLite3:

adb shell sqlite3

记录无线通信日志的命令如下所示。

adb shell logcat -b radio

一般来说,无线通信中的日志非常多,在运行时没必要去记录。我们可以通过命令设置记录应 用程序配置文件,在文件 AndroidManifest. 中编写如下代码:

这样就可以设置应用程序是否显示在 Panel 上。 (1)在 Shell 内可以使用 am 指令来加载 Android 应用。

am [start|instrument] am start [-a ] [-d ] [-t ] [-c [-c ] ...] [-e [-e ...] [-n ] [-D] []

60 第 3 章 开始分析 Android 源码

am instrument [-e ] [-p ] [-w]

启动浏览器指令如下所示。

am start -a android.intent.action.VIEW -d http://www.google.cn/

拨打电话指令如下所示。

am start -a android.intent.action.CALL -d tel:10086

启动 map 直接定位到北京指令:

am start -a android.intent.action.VIEW geo:00?q=beijing

(2)为模拟器加上 SD 卡指令。

emulator -sdcard dcard.img

下面介绍如何创建 sdcard.img 文件,用它来创建一个 SD 卡的命令如下所示。

mksdcard 1024M D:dcard.img

这样就创建了一个容量为 1G 的 SD 卡。 (3)Android 模拟器打电话发短信。 GPhone 的模拟器有个特有的号码:15555218135,这就类似我们实体手机的 SIM 卡号码。需 要如下 3 个步骤实现拨号操作。 第 1 步:打开终端。 第 2 步:连接。

telnet localhost 5554

5554 是打开模拟器后在上面显示的数字,表示模拟器的名称。 第 3 步:实现拨号命令。

gsm call 15555218135

由此看出,接听/挂断过程和实体手机完全一样。同样发送短信也一样简单,只需重复上面 1、 2 两步,第 3 步命令如下所示。

sms send 15555218135 Hellothis is a Message

61

第 4 章 驱动移植

开发的驱动程序是基于 Linux 内核的,要想能够在 Android 系统中使用,需要先实现系统移植。 在本章将详细介绍移植 Android 系统的基本知识,详细讲解系统移植的基本原理,为读者步入本书 后面知识的学习打下坚实的基础。

4.1 Android 移植

本书讲解的是驱动开发。驱动开发是 Android 开发系统中最底层的应用,属于 Linux 内核层的工 作。因为驱动是系统和硬件之间的载体,涉及了不同硬件的应用问题,所以需要做系统移植的工作。

4.1.1 移植的任务 Android 移植开发的最终目的是为了开发手机产品,从开发者的角度来看,这种类型的开发以 具有硬件系统为前提,在硬件系统的基础上构建 Android 软件系统。这种类型的开发工作在 Android 系统的底层。在软件系统方面,主要工作集中在如下两个方面。 (1)Linux 中的相关设备驱动程序。 驱动程序是硬件和上层软件的接口,在 Android 手机系统中,需要基本的屏幕、触摸屏、键盘 等驱动程序,以及音频、摄像头、电话的 Modem、Wi-Fi、蓝牙等多种设备驱动程序。 (2)Android 本地框架中的硬件抽象层。 在 Android 中,硬件抽象层工作在用户空间,介于驱动程序和 Android 系统之间。Android 系 统对硬件抽象层通常都有标准的接口定义,在开发过程中,实现这些接口也就给 Android 系统提供 了硬件抽象层。 上述两个部分相互结合,共同完成了 Android 系统的软件移植。移植成功与否取决于驱动程序 的品质和对 Android 硬件抽象层接口的理解程度。Android 移植开发的工作由核心库、Dalvik 虚拟 机、硬件抽象层、Linux 内核层和硬件系统协同完成,具体结构如图 4-1 所示。

4.1.2 移植的内容 在移植过程中主要移植驱动方面的内容,具体来说,Android 的移植主要分为下面的几个类型。 (1)基本图形用户界面(GUI)部分:包括显示部分、用户输入部分和硬件相关的加速部分, 还包括媒体编解码和 OpenGL 等。

第 4 章 驱动移植

(2)音视频输入输出部分:包括音频、视频输出和摄像头等。 (3)连接部分:包括无线局域网、蓝牙、GPS 等。 (4)电话部分:包括通话、GSM 等。 (5)附属部件:包括传感器、背光、振动器等。

核心库 Dalvik虚拟机

硬件抽象层

Linux内核层(各种驱动)

硬件系统 移植

▲图 4-1 Android 移植结构

上述描述比较笼统,具体需要移植的内容如下所示。 (1)Display 显示部分:包括 FrameBuffer 驱动和 Gralloc 模块。 (2)Input 用户输入部分:包括 Event 驱动和 EventHub。 (3)Codec 多媒体编解码:包括硬件 Codec 驱动和 Codec 插件,例如 OpenMax。 (4)3D Accelerator(3D 加速器)部分:包括硬件 OpenGL 驱动和 OpenGL 插件。 (5)Audio 音频部分:包括 Audio 驱动和 Audio 硬件抽象层。 (6)Video Out 视频输出部分:包括视频显示驱动和 Overlay 硬件抽象层。 (7)Camera 摄像头部分:包括 Camera 驱动(通常是 v4l2)和 Camera 硬件抽象层。 (8)Phone 电话部分:包括 Modem 驱动程序和 RIL 库。 (9)GPS 全球定位系统部分:包括 GPS 驱动(例如串口)和 GPS 硬件抽象层。 (10)Wi-Fi 无线局域网部分:包括 Wlan 驱动和协议以及 Wi-Fi 的适配层。 (11)Blue Tooth 蓝牙部分:包括 BT 驱动和协议以及 BT 的适配层。 (12)Sensor 传感器部分:包括 Sensor 驱动以及 Sensor 硬件抽象层。 (13)Vibrator 震动器部分:包括 Vibrator 驱动和 Vibrator 硬件抽象层。 (14)Light 背光部分:包括 Light 驱动和 Light 硬件抽象层。 (15)Alarm 警告器部分:包括 Alarm 驱动和 RTC 系统以及用户空间调用。 (16)Battery 电池部分:包括电池部分驱动和电池的硬件抽象层。 Android 系统有很多组件,但并不是每一个部件都需要移植,例如浏览器引擎虽然需要下层的 网络支持,但实际上并不需要直接为其移植网络接口,而是通过无线局域网或者电话系统数据连接 来完成标准的网络接口,所以就不需要移植。

4.1.3 驱动开发需要做的工作 驱动开发的任务是,为某一个将要在 Android 系统上使用的硬件开发一个驱动程序。因为

63 Android 驱动开发与移植实战详解

Android 是基于 Linux 的,所以开发 Android 驱动其实就是开发 Linux 驱动。 对于大部分子系统来说,硬件抽象层和驱动程序都需要根据实际系统的情况来实现,例如传感 器部分、音频部分、视频部分、摄像头部分和电话部分。另外也有一些子系统的硬件抽象层是标准 的,只需要实现 Linux 内核中的驱动程序即可,例如输入部分、振动器部分、无线局域网部分和蓝 牙部分等。对于有标准的硬件抽象层的系统,有的时候通常也需要做一些配置工作。 随着 Android 系统的更新和发展,它已经不仅仅是一个移动设备的平台,而且可以用于消费类 电子和智能家电,例如从 3.0 以后的版本主要是针对平板电脑的,另外电子书、数字电视、机顶盒、 固定电话等都逐渐使用 Android 系统。在这些平台上,通常需要实现比移动设备更少的部件。一般 来说,包括显示和用户输入的基本用户界面部分是需要移植的,其他部分是可选的。例如电话系统、 振动器、背光、传感器等一般不需要在非移动设备系统来实现,一些固定位置设备通常不需要实现 GPS 系统。

4.2 Android 对 Linux 的改造

Android 内核是基于 Linux 2.6 内核的,这是一个增强内核版本,除了修改部分 Bug 外,还提 供了用于支持 Android 平台的设备驱动。Android 不但使用了 Linux 内核的基本功能,而且对 Linux 进行了改造,以实现更为强大的通信功能。

4.2.1 Android 的核心驱动 Android 中的驱动程序代码都包在“drivers”目录下,主要包括如下几个核心驱动。 (1)Android Binder:基于 OpenBinder 框架的一个驱动,用于提供 Android 平台的进程间通信 (IPC,inter-process communication)。 源代码位于 drivers/staging/android/binder.c。 (2)Android 电源管理(PM):一个基于标准 Linux 电源管理系统的轻量级的 Android 电源管 理驱动,针对嵌入式设备做了很多优化。 源代码位于文件 kernel/power/earlysuspend.c、kernel/power/consoleearlysuspend.c、kernel/power/ fbearlysuspend.c、kernel/power/wakelock.c、kernel/power/userwakelock.c。 (3)低内存管理器(Low Memory Killer):和 Linux 标准 OOM(Out Of Memory)机制相比更 加灵活,它可以根据需要杀死进程来释放需要的内存。 源代码位于 drivers/staging/android/lowmemorykiller.c。 (4)匿名共享内存(ashmem):为进程间提供大块共享内存,同时为内核提供回收和管理这个 内存的机制。 源代码位于 mm/ashmem.c。 (5)Android PMEM(Physical):PMEM 用于向用户空间提供连续的物理内存区域,DSP 和某 些设备只能工作在连续的物理内存上。 源代码位于 drivers/misc/pmem.c。 (6)Android Logger:这是一个轻量级的日志设备,用于抓取 Android 系统的各种日志。

64 第 4 章 驱动移植

源代码位于 drivers/staging/android/logger.c。 (7)Android Alarm:提供了一个定时器用于把设备从睡眠状态唤醒,同时它也提供了一个即使 在设备睡眠时也会运行的时钟基准。 源代码位于 drivers/rtc/alarm.c。 (8)USB Gadget 驱动:Android 的 USB 驱动是基于 gaeget 框架的,这是一个基于标准 Linux USB gadget 驱动框架的设备驱动。 源代码位于 drivers/usb/gadget/。 (9)Android Ram Console:这是为了提供调试功能而推出的,Android 允许将调试日志信息写 入一个被称为 RAM Console 的设备里,是一个基于 RAM 的 Buffer。 源代码位于 drivers/staging/android/ram_console.c。 (10)Android timed device:提供了对设备进行定时控制功能,目前支持 vibrator 和 LED 设备。 源代码位于 drivers/staging/android/timed_output.c(timed_gpio.c)。 (11)Yaffs2 文件系统 ,Android 采用 Yaffs2 作为 MTD nand flash 文件系统,源代码位于 fs/yaffs2/目录下。Yaffs2 是一个快速稳定的应用于 NAND 和 NOR Flash 的跨平台的嵌入式设备文件 系统,同其他 Flash 文件系统相比,Yaffs2 使用更小的内存来保存其运行状态,因此它占用内存小; Yaffs2 的垃圾回收非常简单而且快速,因此能达到更好的性能;Yaffs2 在大容量的 NAND Flash 上 性能表现尤为明显,非常适合大容量的 Flash 存储。 Android 中的 Linux 内核与驱动结构如图 4-2 所示。

系统调用接口(System Call)

Linux内核 进程调度 内存管理 网络 kernel mm net

进程通信 驱动程序 虚拟系统文件VFS ipc driver 各种文件系统

Android 专用组件

移植体系结构 Linux 和处理器 标准驱动

ARM ARM ARM X86 GoldFish MSM OMAP

▲图 4-2 Android 中的 Linux 内核与驱动结构

Android 对 Linux 内核也进行了改动,这些改动主要保存在下面的文件中。

drivers/misc/kernel_debugger.c

65 Android 驱动开发与移植实战详解

drivers/misc/pmem.c drivers/misc/qemutrace/qemu_trace_sysfs.c drivers/misc/qemutrace/qemu_trace.c drivers/misc/qemutrace/qemu_trace.h drivers/misc/uid_stat.c drivers/staging/android/lowmemorykiller.c drivers/staging/android/logger.c drivers/staging/android/timed_output.h drivers/staging/android/ram_console.c drivers/staging/android/timed_gpio.c drivers/staging/android/logger.h drivers/staging/android/binder.h drivers/staging/android/binder.c drivers/staging/android/timed_output.c drivers/staging/android/timed_gpio.h drivers/rtc/alarm.c drivers/rtc/rtc-goldfish.c drivers/net/pppolac.c drivers/net/ppp_mppe.c drivers/net/pppopns.c drivers/video/goldfishfb.c drivers/switch/switch_class.c drivers/switch/switch_gpio.c drivers/char/dcc_tty.c drivers/char/goldfish_tty.c drivers/watchdog/i6300esb.c drivers/input/misc/gpio_event.c drivers/input/misc/gpio_input.c drivers/input/misc/gpio_output.c drivers/input/misc/keychord.c drivers/input/misc/gpio_axis.c drivers/input/misc/gpio_matrix.c drivers/input/keyreset.c drivers/input/keyboard/goldfish_events.c drivers/input/touchscreen/synaptics_i2c_rmi.c drivers/usb/gadget/android.c drivers/usb/gadget/f_adb.h drivers/usb/gadget/f_mass_storage.h drivers/usb/gadget/f_adb.c drivers/usb/gadget/f_mass_storage.c drivers/mmc/host/goldfish.c drivers/power/goldfish_battery.c drivers/leds/ledtrig-sleep.c drivers/mtd/devices/goldfish_nand_reg.h drivers/mtd/devices/goldfish_nand.c kernel/power/earlysuspend.c kernel/power/consoleearlysuspend.c kernel/power/fbearlysuspend.c kernel/power/wakelock.c kernel/power/userwakelock.c

66 第 4 章 驱动移植

kernel/cpuset.c kernel/cgroup_debug.c kernel/cgroup.c mm/ashmem.c include/linux/ashmem.h include/linux/switch.h include/linux/keychord.h include/linux/earlysuspend.h include/linux/android_aid.h include/linux/uid_stat.h include/linux/if_pppolac.h include/linux/usb/android.h include/linux/wifi_tiwlan.h include/linux/android_alarm.h include/linux/keyreset.h include/linux/synaptics_i2c_rmi.h include/linux/android_pmem.h include/linux/kernel_debugger.h include/linux/gpio_event.h include/linux/wakelock.h include/linux/if_pppopns.h net/ipv4/sysfs_net_ipv4.c net/ipv4/af_inet.c net/ipv6/af_inet6.c net/bluetooth/af_bluetooth.c security/commoncap.c fs/proc/base.c

4.2.2 为 Android 构建 Linux 操作系统 如果我们以一个原始的 Linux 操作系统为基础,将其改造成为一个适合于 Android 的系统,所 做的工作其实非常简单,就是增加适用于 Android 的驱动程序。在 Android 中有很多 Linux 系统的 驱动程序,将这些驱动程序移植到新系统非常简单,具体来说有以下三个步骤。 (1)编写新的源代码。 (2)在 KConfig 配置文件中增加新内容。 (3)在 Makefile 中增加新内容。 在 Android 系统中,最常用的驱动程序有 FrameBuffer 驱动、Event 驱动、Flash MTD 驱动、 WiFi 驱动、蓝牙驱动和串口等驱动程序,并且还需要音频、视频、传感器等驱动和 sysfs 接口。所 以说移植的过程就是移植上述驱动的过程,底层程序员的工作是在 Linux 下开发适用于 Android 的 驱动程序,并移植到 Android 系统。 在 Android 中添加扩展驱动程序的基本步骤介绍如下: (1)在 Linux 内核中移植硬件驱动程序,实现系统调用接口; (2)把硬件驱动程序的调用在 HAL 中封装成 Stub; (3)为上层应用的服务实现本地库,由 Dalvik 虚拟机调用本地库来完成上层 Java 代码的实现; (4)最后编写 Android 应用程序,提供 Android 应用服务和用户操作界面。

67 Android 驱动开发与移植实战详解

4.3 内核空间和用户空间接口

我们编写的驱动程序是供系统使用硬件的,驱动程序是介于系统和硬件之间的桥梁。在 Linux 环境开发这些中间桥梁的驱动程序时,需要用到内核空间和用户空间之间的接口。越来越多的应用 程序需要编写内核级和用户级的程序来一起完成具体的任务。通常采用以下模式:首先,编写内核 服务程序利用内核空间提供的权限和服务来接收、处理和缓存数据;然后编写用户程序来和先前完 成的内核服务程序交互,具体来说,可以利用用户程序来配置内核服务程序的参数,提取内核服务 程序提供的数据,当然,也可以向内核服务程序输入待处理数据。 比较典型的应用包括:Netfilter(内核服务程序:防火墙)和 Iptable(用户级程序:规则设置 程序),IPSEC(内核服务程序:VPN 协议部分)和 IKE(用户级程序:VPN 密钥协商处理),当 然还包括大量的设备驱动程序及相应的应用软件。这些应用都是由内核级和用户级程序通过相互交 换信息来一起完成特定任务的。

4.3.1 实现系统和硬件之间的交互 实现硬件和系统的交互是我们底层开发的主要任务之一,在 Linux 平台下有如下 5 种实现此功 能的方法。 (1)编写自己的系统调用。 系统调用是用户级程序访问内核最基本的方法,目前 Linux 提供了大约 200 多个标准的系统调 用,并且允许我们添加自己的系统调用来实现和内核的信息交换。假如想建立一个系统调用日志系 统,将所有的系统调用动作记录下来,以便进行入侵检测。此时可以编写一个内核服务程序,该程 序负责收集所有的系统调用请求,并将这些调用信息记录到在内核中自建的缓冲里。我们无法在内 核里实现复杂的入侵检测程序,因此必须将该缓冲里的记录提取到用户空间。最直截了当的方法是 自己编写一个新系统调用实现这种提取缓冲数据的功能。当内核服务程序和新系统调用都实现后, 就可以在用户空间里编写用户程序进行入侵检测了,入侵检测程序可以定时、轮训或在需要的时候 调用新系统调用从内核提取数据,然后进行入侵检测。 (2)编写驱动程序。 Linux/UNIX 的一个特点就是把所有的东西都看做是文件(every thing is a file)。系统定义了简 洁完善的驱动程序界面,客户程序可以用统一的方法透过这个界面和内核驱动程序交互。而大部分 系统的使用者和开发者已经非常熟悉这种界面以及相应的开发流程了。 驱动程序运行于内核空间,用户空间的应用程序通过文件系统中“/dev/”目录下的一个文件来 和它交互。这就是我们熟悉的那个文件操作流程:open()→read()→write()→ioctl()→close()。

并不是所有的内核驱动程序都是这个界面,网络驱动程序和各种协议栈的使用 注意 就不大一致,比如说套接口编程虽然也有 open()、close()等概念,但它的内核实现以 及外部使用方式都和普通驱动程序有很大差异。

这里先不谈设备驱动程序在内核中要做的中断响应、设备管理、数据处理等工作,在此先把注

68 第 4 章 驱动移植

意力集中在它与用户级程序交互这一部分。操作系统为此定义了一种统一的交互界面,就是前面所 说的 open()、read()、write()、ioctl()和 close()等。每个驱动程序按照自己的需要做独立实现,把自 己提供的功能和服务隐藏在这个统一界面下。客户级程序选择需要的驱动程序或服务(其实就是选 择“/dev/”目录下的文件),按照上述界面和文件操作流程,就可以跟内核中的驱动交互了。其实 用面向对象的概念会更容易解释,系统定义了一个抽象的界面(abstract interface),每个具体的驱 动程序都是这个界面的实现(implementation)。 由此可见,驱动程序也是用户空间和内核信息交互的重要方式之一。从本质上来说,ioctl、read、 和 write 也是通过系统调用去完成的,只是这些调用已被内核进行了标准封装和统一定义。因此用 户不必像添加新系统调用那样必须修改内核代码,重新编译新内核,使用虚拟设备只需要通过模块 方法将新的虚拟设备安装到内核中(insmod 上)就能方便使用。 可以将 Linux 中的设备大致分为如下 3 类。 字符设备:包括那些必须以顺序方式,像字节流一样被访问的设备。 块设备:是指那些可以用随机方式、以整块数据为单位来访问的设备,例如硬盘等。 网络接口:通常指网卡和协议栈等复杂的网络输入输出服务。 如果将我们的系统调用日志系统用字符型驱动程序的方式实现,整个过程就非常简单了。我们 可以将内核中收集和记录信息的那一部分编写成一个字符设备驱动程序。虽然没有实际对应的物理 设备,但是 Linux 的设备驱动程序本来就是一个软件抽象,它可以结合硬件提供服务,也完全可以 作为纯软件提供服务。在驱动程序中,可以使用 open()来启动服务,用 read()返回处理好的记录, 用 ioctl()设置记录格式等,用 close()停止服务,write()没有用到,那么我们可以不去实现它。然后 在“/dev/”目录下建立一个设备文件对应我们新加入内核的系统调用日志系统驱动程序。 (3)使用 proc 文件系统。 proc 是 Linux 提供的一种特殊的文件系统,使用它目的就是提供一种便捷的用户和内核间的交 互方式。proc 以文件系统作为使用界面,使应用程序可以以文件操作的方式安全、方便地获取系统 当前运行的状态和其他一些内核数据信息。 proc 文件系统多用于监视、管理和调试系统,平常使用的 ps 和 top 等管理工具就是利用 proc 来读取内核信息的。除了读取内核信息外,proc 文件系统还提供了写入功能。所以我们也就可以利 用它来向内核输入信息。比如通过修改 proc 文件系统下的系统参数配置文件“/proc/sys”后可以直 接在运行时动态更改内核参数。 除了系统已经提供的文件条目,通过 proc 为我们留的接口可以允许在内核中创建新的条目从 而与用户程序共享信息数据。比如可以为系统调用日志程序(无论是作为驱动程序还是作为单纯的 内核模块)在 proc 文件系统中创建新的文件条目,在此条目中显示系统调用的使用次数,每个单 独系统调用的使用频率等等。我们也可以增加另外的条目用于设置日志记录规则。 (4)使用虚拟文件系统(VFS)。 很多内核开发者认为利用 ioctl()系统调用往往会使得系统调用意义不明确,而且难控制。而将 信息放入到 proc 文件系统中会使信息组织混乱,所以不赞成过多使用此系统。他们的建议是实现 一种孤立的虚拟文件系统来代替 ioctl()和 proc。这是因为文件系统接口清楚,而且便于用户空间访 问,同时利用虚拟文件系统使得利用脚本执行系统管理任务更加方便、有效。

69 Android 驱动开发与移植实战详解

下面举例来说如何通过虚拟文件系统修改内核信息。假设我们可以实现一个名为“sagafs”的 虚拟文件系统,其中文件 log 对应内核存储的系统调用日志。此时就可以通过文件访问的普遍方法 获得日志信息,命令如下所示。

# cat /sagafs/log

使用虚拟文件系统可以更加方便、清晰地实现信息交互。但是很多程序员认为 VFS 的 API 接 口十分复杂,其实读者们无须担心,因为从 Linux 2.5 内核开始就提供了一种叫做 libfs 的例程序, 它帮助不熟悉文件系统的用户封装了实现 VFS 的通用操作。 (5)使用内存映像。 Linux 通过内存映像机制来提供用户程序对内存直接访问的能力。内存映像的意思是把内核中特 定部分的内存空间映射到用户级程序的内存空间去。也就是说,用户空间和内核空间共享一块相同的 内存。这样做有如下影响:内核在这块地址内存储变更的任何数据,用户可以立即发现和使用,根本 无须数据拷贝;在使用系统调用交互信息时,在整个操作过程中必须有一步数据拷贝的工作,或者是 把内核数据拷贝到用户缓冲区,或只是把用户数据拷贝到内核缓冲区。这样对于许多数据传输量大、 时间要求高的应用来说很不科学,因为许多应用根本就无法忍受数据拷贝所耗费的时间和资源。

4.3.2 实现内核到用户空间的数据传输 在 Linux 中,通过 Relay 实现从 Linux 内核到用户空间的高效数据传输。通过用户定义的 Relay 通道,内核空间的程序能够高效、可靠、便捷地将数据传输到用户空间。Relay 适用于内核空间有 大量数据需要传输到用户空间的情形,目前已经广泛应用在内核调试工具,例如 SystemTap 中。

1.Relay 发展

Relay 的前身是 RelayFS,即作为 Linux 的一个新型文件系统。2003 年 3 月,RelayFS 的第一 个版本的代码被开发出来,在 7 月 14 日,第一个针对 2.6 内核的版本也开始提供下载。经过广泛 的试用和改进,直到 2005 年 9 月,RelayFS 才加入 mainline 内核(2.6.14)。同时,RelayFS 也被移 植到 2.4 内核中。在 2006 年 2 月,从 2.6.17 开始,RelayFS 不再作为单独的文件系统存在,而是 成为内核的一部分。它的源码也从“fs/”目录下转移到“kernel /relay.c”中,名称也从 RelayFS 改 成了 Relay。 RelayFS 目前已经被越来越多的内核工具使用,包括内核调试工具 SystemTap、LTT,以及一 些特殊的文件系统,例如 DebugFS。

2.Relay 的原理

Relay 提供了一种机制,使得内核空间的程序能够通过用户定义的 Relay 通道(channel)将大 量数据高效地传输到用户空间。一个 Relay 通道由一组和 CPU 一一对应的内核缓冲区组成。这些 缓冲区又被称为 Relay 缓冲区(buffer),其中的每一个在用户空间都用一个常规文件来表示,这被 叫做 relay 文件(file)。内核空间的用户可以利用 Relay 提供的 API 接口来写入数据,这些数据会 被自动地写入当前的 CPU id 对应的那个 Relay 缓冲区;同时,这些缓冲区从用户空间看来,是一

70 第 4 章 驱动移植

组普通文件,可以直接使用 read()进行读取,也可以使用 mmap()进行映射。Relay 并不关心数据的 格式和内容,这些完全依赖于使用 Relay 的用户程序。Relay 的目的是提供一个足够简单的接口, 从而使得基本操作尽可能地高效。 Relay 将数据实现了读和写的分离,使得突发性大量数据写入的时候,不需要受限于用户空间 相对较慢的读取速度,从而大大提高了效率。Relay 作为写入和读取的桥梁,也就是将内核用户写 入的数据缓存并转发给用户空间的程序。这种转发机制也正是 Relay 这个名称的由来。

3.Relay 的 API

在 Relay 中提供了许多 API 来支持用户程序完整地使用 Relay。这些 API 主要分为两大类,分 别是按照面向用户空间和面向内核空间,具体说明如下所示。 (1)面向用户空间的 API。 此类 API 编程接口向用户空间程序提供了访问 Relay 通道缓冲区数据的基本操作的入口,主要 包括如下方法。 open():允许用户打开一个已经存在的通道缓冲区。 mmap():使通道缓冲区被映射到位于用户空间的调用者的地址空间。要特别注意的是,我 们不能仅对局部区域进行映射。也就是说,必须映射整个缓冲区文件,其大小是 CPU 的个数和单 个 CPU 缓冲区大小的乘积。 read():读取通道缓冲区的内容。这些数据一旦被读出,就意味着它们被用户空间的程序消 费掉了,也就不能被之后的读操作看到。 sendfile():将数据从通道缓冲区传输到一个输出文件描述符。其中可能的填充字符会被自 动去掉,不会被用户看到。 poll():支持 POLLIN/POLLRDNORM/POLLERR 信号。每次子缓冲区的边界被越过时, 等待着的用户空间程序会得到通知。 close():将通道缓冲区的引用数减 1。当引用数减为 0 时,表明没有进程或者内核用户需要 打开它,从而这个通道缓冲区被释放。 (2)面向内核空间的 API。 此类 API 接口向位于内核空间的用户提供了管理 Relay 通道、数据写入等功能,其中最为常用 的如下所示。 relay_open():创建一个 Relay 通道,包括创建每个 CPU 对应的 Relay 缓冲区。 relay_close():关闭一个 Relay 通道,包括释放所有的 Relay 缓冲区,在此之前会调用 relay_switch()来处理这些 Relay 缓冲区以保证已读取但是未满的数据不会丢失。 relay_write():将数据写入到当前 CPU 对应的 Relay 缓冲区内。由于它使用了 local_irqsave() 保护,因此也可以在中断上下文中使用。 relay_reserve():在 Relay 通道中保留一块连续的区域来留给未来的写入操作。这通常用于 那些希望直接写入到 Relay 缓冲区的用户。考虑到性能或者其他因素,这些用户不希望先把数据写 到一个临时缓冲区中,然后再通过 relay_write()进行写入。

71 Android 驱动开发与移植实战详解

4.使用 Relay

在下面的内容中,将通过一个最简单的例子来介绍使用 Relay 的方法。本实例由如下两部分组成。 位于内核空间将数据写入 Relay 文件的程序,使用时需要作为一个内核模块被加载; 位于用户空间从 Relay 文件中读取数据的程序,使用时作为普通用户态程序运行。 (1)实现内核空间。 内核空间程序的主要操作如下所示。 当加载模块时,打开一个 Relay 通道,并且往打开的 Relay 通道中写入消息; 当卸载模块时,关闭 Relay 通道。 文件 hello-mod.c 的具体实现代码如下所示。

#include #include static struct rchan *hello_rchan; int init_module(void) { const char *msg="Hello world\n"; hello_rchan = relay_open("cpu", NULL, 8192, 2, NULL); if(!hello_rchan){ printk("relay_open() failed.\n"); return -ENOMEM; } relay_write(hello_rchan, msg, strlen(msg)); return 0; } void cleanup_module(void) { if(hello_rchan) { relay_close(hello_rchan); hello_rchan = NULL; } return; } MODULE_LICENSE ("GPL"); MODULE_DESCRIPTION ("Simple example of Relay");

(2)实现用户空间。 用户空间的函数主要操作过程如下所示。 如果 relayfs 文件系统还没有被 umount(是一个操作命令,功能是卸除目前挂在 Linux 目录中 的文件系统),则将其 umount 到“/mnt/relay”目录上。首先遍历每一个 CPU 对应的缓冲文件,然 后打开文件,接着读取所有文件内容,然后关闭文件,最后,umount 掉 Relay 文件系统。 文件 audience.c 的具体实现代码如下所示。

#include #include

72 第 4 章 驱动移植

#include #include #include #include #include #define MAX_BUFLEN 256 const char filename_base[]="/mnt/relay/cpu"; //在汇编前实现方法 get_cputotal() static int get_cputotal(void); int main(void) { char filename[128]={0}; char buf[MAX_BUFLEN]; int fd, c, i, bytesread, cputotal = 0; if(mount("relayfs", "/mnt/relay", "relayfs", 0, NULL) && (errno != EBUSY)) { printf("mount() failed: %s\n", strerror(errno)); return 1; } cputotal = get_cputotal(); if(cputotal <= 0) { printf("invalid cputotal value: %d\n", cputotal); return 1; } for(i=0; i 0) { buf[bytesread] = '\0'; puts(buf); bytesread = read(fd, buf, MAX_BUFLEN); }; // 关闭文件 if(fd > 0) { close(fd); fd = 0; } } if(umount("/mnt/relay") && (errno != EINVAL)) { printf("umount() failed: %s\n", strerror(errno)); return 1; }

73 Android 驱动开发与移植实战详解

return 0; }

通过上述实例演示了使用 Relay 的过程,虽然上述代码并没有实际用处,但是形象地描述了从 用户空间和内核空间两个方面使用 Relay 的基本流程。实际应用中对 Relay 的使用当然要比这复杂 得多,有关更多用法的实例请参考 Relay 的主页。

4.4 三类驱动程序

在 Linux 系统中,主要有 3 类设备驱动,分别是字符设备驱动、块设备驱动和网络接口驱动。在 本节的内容中,将简要讲解这 3 类设备驱动的基本知识,为读者步入本书后面知识的学习打下基础。

4.4.1 字符设备驱动 字符设备是指在 I/O 传输过程中以字符为单位进行传输的设备,例如键盘、打印机等。以字符 为单位并不一定意味着是以字节为单位,因为有的编码规则规定,1 个字符占 16 比特,合 2 个字 节。字符设备驱动程序的结构如图 4-3 所示。

用户空间

模块加载函数 初始化 添加 调用 字符设备 对应 cdev 绑定 删除 Linux系统调用 模块卸载函数 file_operations dev_t 调用

read() write() ioctl() ...

字符设备驱动

▲图 4-3 字符设备驱动程序的结构

在 Linux 系统中,字符设备以特别文件方式在文件目录树中占据位置并拥有相应的 i 结点。在 i 结点中的文件类型指明该文件是字符设备文件。可以使用与普通文件相同的文件操作命令对字符 设备文件进行操作,例如打开、关闭、读、写等。概括来说,字符设备驱动主要要做如下 3 件事。 定义一个结构体 static struct file_operations 变量,在里面定义一些设备的打开、关闭、读、 写、控制函数。 在结构体外分别实现结构体中定义的这些函数。 向内核中注册或删除驱动模块。 由此可见,实现字符设备驱动的首要任务是定义一个结构体。字符设备提供给应用程序流的控 制接口有 open、close、read、write 和 ioctl,添加一个字符设备驱动程序,实际上是给上述操作添

74 第 4 章 驱动移植

加对应的代码,Linux 对这些操作统一做了抽象。 结构体 file_operations 定义格式如下所示。

static struct file_operations myDriver_fops = { owner: THIS_MODULE, write: myDriver_write, read: myDriver_read, ioctl: myDriver_ioctl, open: myDriver_open, release: myDriver_release, };

在此结构体中规定了驱动程序向应用程序提供的操作接口,主要有实现以下几个功能的接口。 (1)实现 write 操作,就是从应用程序接收数据送到硬件。例如下面的代码。

static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos){ size_t fill_size = count; PRINTK("myDriver write called!\n"); PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos); if(*f_pos >= sizeof(myDriver_Buffer)) { PRINTK("[myDriver write]Buffer Overlap\n"); *f_pos = sizeof(myDriver_Buffer); return 0; } if((count + *f_pos) > sizeof(myDriver_Buffer)) { PRINTK("count + f_pos > sizeof buffer\n"); fill_size = sizeof(myDriver_Buffer) - *f_pos; } copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size); *f_pos += fill_size; return fill_size; }

在上述代码中,函数 u_long copy_from_user(void *to, const void *from, u_long len)用于把用户态 的数据拷到内核态,实现数据的传送。 (2)实现 read 操作,即从硬件读取数据并交给应用程序。例如下面的代码。

static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos){ size_t read_size = count; PRINTK("myDriver read called!\n"); PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos); if(*f_pos >= sizeof(myDriver_Buffer)) { PRINTK("[myDriver read]Buffer Overlap\n"); *f_pos = sizeof(myDriver_Buffer); return 0;

75 Android 驱动开发与移植实战详解

} if((count + *f_pos) > sizeof(myDriver_Buffer)) { PRINTK("count + f_pos > sizeof buffer\n"); read_size = sizeof(myDriver_Buffer) - *f_pos; } copy_to_user(buf, &myDriver_Buffer[*f_pos], read_size); *f_pos += read_size; return read_size; }

在上述代码中,函数 u_long copy_to_user(void * to, const void *from, u_long len)用于实现把内核 态的数据拷到用户态下。 (3)实现 ioctl 操作,也就是为应用程序提供对硬件行为的控制。例如下面的代码。

static int myDriver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ PRINTK("myDriver ioctl called(%d)!\n", cmd); if(_IOC_TYPE(cmd) != TSTDRV_MAGIC) { return -ENOTTY; } if(_IOC_NR(cmd) >= TSTDRV_MAXNR) { return -ENOTTY; } switch(cmd) { case MYDRV_IOCTL0: PRINTK("IOCTRL 0 called(0x%lx)!\n", arg); break; case MYDRV_IOCTL1: PRINTK("IOCTRL 1 called(0x%lx)!\n", arg); break; case MYDRV_IOCTL2: PRINTK("IOCTRL 2 called(0x%lx)!\n", arg); break; case MYDRV_IOCTL3: PRINTK("IOCTRL 3 called(0x%lx)!\n", arg); break; } return 0; }

(4)实现 open 操作。 当应用程序打开设备时对设备进行初始化,使用 MOD_INC_USE_COUNT 增加驱动程序的使 用次数。例如下面的代码。

static int myDriver_open(struct inode *inode, struct file *filp){

76 第 4 章 驱动移植

MOD_INC_USE_COUNT; PRINTK("myDriver open called!\n"); return 0; }

(5)实现 release 操作。 当应用程序关闭设备时处理设备的关闭操作。使用 MOD_DEC_USE_COUNT 来增加驱动程序 的使用次数。例如下面的代码。

static int myDriver_release(struct inode *inode, struct file *filp){ MOD_DEC_USE_COUNT; PRINTK("myDriver release called!\n"); return 0; }

(6)驱动程序初始化函数。 Linux 在加载内核模块时会调用初始化函数,初始化驱动程序本身使用 register_chrdev 向内核 注册驱动程序,该函数的第三个指向包含有驱动程序接口函数信息的 file_operations 结构体。例如 下面的代码。

#ifdef CONFIG_DEVFS_FS devfs_handle_t devfs_myDriver_dir; devfs_handle_t devfs_myDriver_raw; #endif static int __init myModule_init(void) { /* 模块初始化 */ PRINTK("myModule_init\n"); /* 注册驱动 */ myDriver_Major = register_chrdev0,DRIVER_NAME,&myDriver_fops); if(myDriver_Major < 0) { PRINTK("register char device fail!\n"); return myDriver_Major; } PRINTK("register myDriver OK! Major = %d\n", myDriver_Major); #ifdef CONFIG_DEVFS_FS devfs_myDriver_dir = devfs_mk_dir(NULL, "myDriver", NULL); devfs_myDriver_raw = devfs_register(devfs_myDriver_dir, "raw0", DEVFS_FL_DEFAULT, myDriver_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &myDriver_fops, NULL); PRINTK("add dev file to devfs OK!\n"); #endif return 0; }

在上述代码中,函数 module_init()用于向内核声明当前模块的初始化函数。 (7)驱动程序退出函数。 Linux 在卸载内核模块时会调用退出函数释放驱动程序使用的资源,使用 unregister_chrdev 从

77 Android 驱动开发与移植实战详解

内核中卸载驱动程序。将驱动程序模块注册到内核,内核需要知道模块的初始化函数和退出函数, 才能将模块放入自己的管理队列中。例如下面的代码。

static void __exit myModule_exit(void){ /* 退出模块代码 */ PRINTK("myModule_exit\n"); /* 卸载驱动 */ if(myDriver_Major > 0) { #ifdef CONFIG_DEVFS_FS devfs_unregister(devfs_myDriver_raw); devfs_unregister(devfs_myDriver_dir); #endif unregister_chrdev(myDriver_Major, DRIVER_NAME); } return; }

在上述代码中,函数 module_exit()用于向内核声明当前模块的退出函数。 经过上述分析,可以总结出开发字符设备驱动程序的基本步骤如下所示。 确定主设备号和次设备号。 实现字符驱动程序,先实现 file_operations 结构体,然后实现初始化函数并注册字符设备, 接下来实现销毁函数并释放字符设备。 创建设备文件节点。 接下来给出一个通用的字符设备驱动程序,此程序由如下两个文件构成。其中文件 tst-driver.h 的实现代码如下所示。

#ifndef __TST_DRIVER_H__ #define __TST_DRIVER_H__ #define TSTDRV_MAGIC 0xd0 #define GPIO_IN 0 #define GPIO_OUT 1//_IO(TSTDRV_MAGIC, 1) #define GPIO_SET_BIT 2//_IO(TSTDRV_MAGIC, 2) #define GPIO_CLR_BIT 3//_IO(TSTDRV_MAGIC, 3) #define TSTDRV_MAXNR 4 #endif //#ifndef __TST_DRIVER_H__

另一个构成文件 tst-driver.c 的实现代码如下所示。

#ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #include #include #include /* printk() */

78 第 4 章 驱动移植

#include #include #include //#include //#include #include #include #include "tst-driver.h" #define DRIVER_NAME "myDriver" //#undef CONFIG_DEVFS_FS #ifdef DEBUG #define PRINTK(fmt, arg...) printk(KERN_NOTICE fmt, ##arg) #else #define PRINTK(fmt, arg...) #endif /* KERN_EMERG 用于紧急事件,一般是系统崩溃前的提示信息 KERN_ALERT 用于需要立即采取动作的场合 KERN_CRIT 临界状态,通常设计验证的硬件或软件操作失败 KERN_ERR 用于报告错误状态,设备驱动程序通常会用它报告来自硬件的问题 KERN_WARNING 就可能出现的问题提出警告,这些问题通常不会对系统造成严重破坏 KERN_NOTICE 有必要提示的正常情况,许多安全相关的情况用这个级别汇报 KERN_INFO 提示性信息,有很多驱动程序在启动时用这个级别打印相关信息 KERN_DEBUG 用于调试的信息 */ static int myDriver_Major = 0; /* 驱动器号 */ /*虚拟驱动缓冲器*/ static unsigned char myDriver_Buffer[1024*1024]; /* 定义驱动操作 */ static int myDriver_open(struct inode *inode, struct file *filp) { // int Minor = MINOR(inode->i_rdev); // filp->private_data = 0; MOD_INC_USE_COUNT; PRINTK("myDriver open called!\n"); return 0; } static int myDriver_release(struct inode *inode, struct file *filp) { // int Minor = MINOR(inode->i_rdev); MOD_DEC_USE_COUNT; PRINTK("myDriver release called!\n"); return 0; } static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { size_t read_size = count; PRINTK("myDriver read called!\n"); PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos); if(*f_pos >= sizeof(myDriver_Buffer)) {

79 Android 驱动开发与移植实战详解

PRINTK("[myDriver read]Buffer Overlap\n"); *f_pos = sizeof(myDriver_Buffer); return 0; } if((count + *f_pos) > sizeof(myDriver_Buffer)) { PRINTK("count + f_pos > sizeof buffer\n"); read_size = sizeof(myDriver_Buffer) - *f_pos; } copy_to_user(buf, &myDriver_Buffer[*f_pos], read_size); *f_pos += read_size; return read_size; } static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { size_t fill_size = count; PRINTK("myDriver write called!\n"); PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos); if(*f_pos >= sizeof(myDriver_Buffer)) { PRINTK("[myDriver write]Buffer Overlap\n"); *f_pos = sizeof(myDriver_Buffer); return 0; } if((count + *f_pos) > sizeof(myDriver_Buffer)) { PRINTK("count + f_pos > sizeof buffer\n"); fill_size = sizeof(myDriver_Buffer) - *f_pos; } copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size); *f_pos += fill_size; return fill_size; } static int myDriver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { PRINTK("myDriver ioctl called(%d)!\n", cmd); if(_IOC_TYPE(cmd) != TSTDRV_MAGIC) { return -ENOTTY; } if(_IOC_NR(cmd) >= TSTDRV_MAXNR) { return -ENOTTY; } switch(cmd) { case MYDRV_IOCTL0: PRINTK("IOCTRL 0 called(0x%lx)!\n", arg);

80 第 4 章 驱动移植

break; case MYDRV_IOCTL1: PRINTK("IOCTRL 1 called(0x%lx)!\n", arg); break; case MYDRV_IOCTL2: PRINTK("IOCTRL 2 called(0x%lx)!\n", arg); break; case MYDRV_IOCTL3: PRINTK("IOCTRL 3 called(0x%lx)!\n", arg); break; } return 0; } /* 驱动操作结构 */ static struct file_operations myDriver_fops = { owner: THIS_MODULE, write: myDriver_write, read: myDriver_read, ioctl: myDriver_ioctl, open: myDriver_open, release: myDriver_release, }; /*模块初始化/退出功能*/ #ifdef CONFIG_DEVFS_FS devfs_handle_t devfs_myDriver_dir; devfs_handle_t devfs_myDriver_raw; #endif static int __init myModule_init(void) { /* 模块初始化 */ PRINTK("myModule_init\n"); /* 注册驱动 */ myDriver_Major = register_chrdev(0, DRIVER_NAME, &myDriver_fops); if(myDriver_Major < 0) { PRINTK("register char device fail!\n"); return myDriver_Major; } PRINTK("register myDriver OK! Major = %d\n", myDriver_Major); #ifdef CONFIG_DEVFS_FS devfs_myDriver_dir = devfs_mk_dir(NULL, "myDriver", NULL); devfs_myDriver_raw = devfs_register(devfs_myDriver_dir, "raw0", DEVFS_FL_DEFAULT, myDriver_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &myDriver_fops, NULL); PRINTK("add dev file to devfs OK!\n"); #endif return 0; } static void __exit myModule_exit(void) { /* 退出模块 */

81 Android 驱动开发与移植实战详解

PRINTK("myModule_exit\n"); /* 卸载驱动 */ if(myDriver_Major > 0) { #ifdef CONFIG_DEVFS_FS devfs_unregister(devfs_myDriver_raw); devfs_unregister(devfs_myDriver_dir); #endif unregister_chrdev(myDriver_Major, DRIVER_NAME); } return; } MODULE_AUTHOR("SXZ"); MODULE_LICENSE("Dual BSD/GPL"); module_init(myModule_init); module_exit(myModule_exit);

4.4.2 块设备驱动 块设备 IO 与字符设备操作的主要区别有如下三条。 块设备只能以块为单位接收输入返回输出,而字符设备则以 Byte 为单位。大多数设备是字 符设备,它们不需要缓冲并且不以固定块大小进行操作。 块设备对于 IO 请求有对应的缓冲区,所以它们可以选择以什么顺序进行响应。字符设备无 须缓冲且被直接读写。 字符设备只能被顺序读写,块设备可以随机访问。 (1)结构体 block_device_operations。 在文件 include/linux/fs.h 中定义了结构体 block_device_operations,此结构体描述了对块设备的 操作的集合,具体代码如下所示。

struct block_device_operations { int (*open) (struct inode *, struct file *);/*打开*/ int (*release) (struct inode *, struct file *);/*释放*/ int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned, unsigned long); long (*compat_ioctl) (struct file *, unsigned, unsigned long); int (*direct_access) (struct block_device *, sector_t, unsigned long *); int (*media_changed) (struct gendisk *);/*介质被改变?*/ int (*revalidate_disk) (struct gendisk *);/*使介质改变*/ int (*getgeo)(struct block_device *, struct hd_geometry *);/*填充驱动器信息*/ struct module *owner;/*模块拥有者,一般初始化为 THIS_MODULE*/ };

(2)结构体 gendisk。 结构体 gendisk 用来描述一个独立的磁盘设备或分区,具体代码如下所示。

struct gendisk{ /*前三个元素共同表征了一个磁盘的主、次设备号,同一个磁盘的各个分区共享一个主设备号*/ int major;/*主设备号*/

82 第 4 章 驱动移植

int first_minor;/*第一个次设备号*/ int minors;/*最大的次设备数,如果不能分区,则为 1*/ char disk_name[32]; struct hd_struct** part;/*磁盘上的分区信息*/ struct block_device_operations* fops;/*块设备操作,block_device_operations*/ struct request_queue* queue;/* 请求队列,用于管理该设备 IO 请求队列的指针*/ void* private_data;/*私有数据*/ sector_t capacity;/*扇区数,512 字节为 1 个扇区,描述设备容量*/ //...... };

(3)结构体 request 和 bio。 ‰ 请求 request。 结构体 request 和 request_queue 在 Linux 块设备驱动中,使用 request 结构体来表征等待进行的 IO 请求,用 request_queue 来表征一个块 IO 请求队列。定义上述两个结构体的代码如下所示。

struct request{ struct list_head queuelist; unsigned long flags; sector_t sector;/*要传输的下一个扇区*/ unsigned long nr_sectors;/*要传送的扇区数目*/ unsigned int current_nr_sector;/*当前要传送的扇区*/ sector_t hard_sector;/*要完成的下一个扇区*/ unsigned long hard_nr_sectors;/*要被完成的扇区数目*/ unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/ struct bio* bio;/*请求的 bio 结构体的链表*/ struct bio* biotail;/*请求的 bio 结构体的链表尾*/ /*请求在屋里内存中占据的不连续的段的数目* / unsigned short nr_phys_segments; unsigned short nr_hw_segments; int tag; char* buffer;/*传送的缓冲区,内核的虚拟地址*/ int ref_count;/*引用计数*/ ... };

‰ 请求队列 request_queue。 请求队列跟踪等候的块 IO 请求,它存储用于描述这个设备能够支持的请求的类型信息。请求 队列还要实现一个插入接口,这个接口允许使用多个 IO 调度器,IO 调度器以最优性能的方式向驱 动提交 IO 请求。大部分 IO 调度器是积累批量的 IO 请求,并将其排列为递增/递减的块索引顺序后 提交给驱动。另外 IO 调度器还负责合并邻近的请求,当一个新的 IO 请求被提交给调度器后,它 会在队列里搜寻包含邻近的扇区的请求。如果找到一个并且此请求合理,则调度器会将这两个请求 合并。 定义结构体 request_queue 的代码如下所示。

struct request_queue{ …

83 Android 驱动开发与移植实战详解

/*自旋锁,保护队列结构体*/ spinlock_t __queue_lock; spinlock_t* queue_lock; struct kobject kobj;/*队列 kobject*/ /*队列设置*/ unsigned long nr_requests;/*最大的请求数量*/ unsigned int nr_congestion_on; unsigned int nr_congestion_off; unsigned int nr_batching; unsigned short max_sectors;/*最大扇区数*/ unsigned short max_hw_sectors; unsigned short max_phys_sectors;/*最大的段数*/ unsigned short max_hw_segments; unsigned short hardsect_size;/*硬件扇区尺寸*/ unsigned int max_segment_size;/*最大的段尺寸*/ unsigned long seg_boundary_mask;/*段边界掩码*/ unsigned int dma_alignment;/*DMA 传送内存对齐限制*/ struct blk_queue_tag* queue_tags; atomic_t refcnt;/*引用计数*/ unsigned int in_flight; unsigned int sg_timeout; unsigned int sg_reserved_size; int node; struct list_head drain_list; struct request* flush_rq; unsigned char ordered; };

在块设备模块中,还可以使用函数实现块设备驱动的模块卸载、加载、打开与释放操作,相关 知识请参阅相关资料。 Android 的块设备驱动保存在目录“/dev/block”中,其主要内容如下所示。

brw------root root 179, 2 2010-03-29 23:33 mmcblk0p2 brw------root root 179, 1 2010-03-29 23:33 mmcblk0p1 brw------root root 179, 0 2010-03-29 23:33 mmcblk0 brw------root root 31, 6 2010-03-29 23:33 mtdblock6 brw------root root 31, 5 2010-03-29 23:33 mtdblock5 brw------root root 31, 4 2010-03-29 23:33 mtdblock4 brw------root root 31, 3 2010-03-29 23:33 mtdblock3 brw------root root 31, 2 2010-03-29 23:33 mtdblock2 brw------root root 31, 1 2010-03-29 23:33 mtdblock1 brw------root root 31, 0 2010-03-29 23:33 mtdblock0 brw------root root 7, 7 2010-03-29 23:33 loop7 brw------root root 7, 6 2010-03-29 23:33 loop6 brw------root root 7, 5 2010-03-29 23:33 loop5 brw------root root 7, 4 2010-03-29 23:33 loop4 brw------root root 7, 3 2010-03-29 23:33 loop3 brw------root root 7, 2 2010-03-29 23:33 loop2 brw------root root 7, 1 2010-03-29 23:33 loop1 brw------root root 7, 0 2010-03-29 23:33 loop0

84 第 4 章 驱动移植

brw------root root 1, 0 2010-03-29 23:33 ram0 brw------root root 1, 0 2010-03-29 23:33 ram1

在上述内容中,主设备号为 1 的是各个内存块设备,主设备号为 7 的是各个回环块设备,主设 备号为 31 的是 mtd 设备中的块设备,mmcblk0 表示 SD 卡的块设备。 在 Android 系统中,可以使用 mount 命令来查看系统中被挂起的文件系统。使用 mount 命令的 格式如下所示。

mount [-t vfstype] [-o options] device dir

上述命令格式中的主要参数的具体说明如下所示。 (1)-t vfstype:指定文件系统的类型,通常不必指定,mount 会自动选择正确的类型。有如下 六种常用类型。 光盘或光盘镜像:iso9660。 DOS fat16 文件系统:msdos。 Windows 9x fat32 文件系统:vfat。 Windows NT ntfs 文件系统:ntfs。 Mount Windows 文件网络共享:smbfs。 UNIX(LINUX) 文件网络共享:nfs。 (2)-o options:主要用来描述设备或档案的挂接(mount)方式,有如下四类常用的参数。 loop:用来把一个文件当成硬盘分区挂接上系统。 ro:采用只读方式挂接设备。 rw:采用读写方式挂接设备。 iocharset:指定访问文件系统所用字符集。 (3)device:要挂接的设备。 (4)dir:设备在系统上的挂接点(mount point)。 另外,在 Android 系统中可以使用 df 命令来查看系统中各个盘的使用情况。使用 df 命令的格 式如下所示。

df [options]

参数 options 常用取值的具体说明如下所示。 -s:对每个 Names 参数只给出占用的数据块总数。 -a:递归地显示指定目录中各文件及子目录中各文件占用的数据块数。若既不指定-s, 也不指定-a,则只显示 Names 中的每一个目录及其中的各子目录所占的磁盘块数。 -k:以 1024 字节为单位列出磁盘空间使用情况。 -x:跳过在不同文件系统上的目录不予统计。 -l:计算所有的文件大小,对硬链接文件则计算多次。 -i:显示 inode 信息而非块使用量。 -h:以容易理解的格式印出文件系统大小,例如 136KB、254MB、21GB。

85 Android 驱动开发与移植实战详解

-P:使用 POSIX 输出格式。 -T:显示文件系统类型。

4.4.3 网络设备驱动 Linux 网络设备驱动程序由 4 部分组成,分别是网络设备媒介层、网络设备驱动层、网络设备 接口层以及网络协议接口层。网络设备媒介层包括各种物理网络设备和传输媒介。对于网络设备接 口层,Linux 系统用 Net_device 结构表示网络设备接口。Net_device 结构保存所有与硬件有关的接 口信息,各协议软件主要通过 Net_device 结构来完成与硬件的交互。网络设备驱动层主要包括网络 设备的初始化、数据包的发送和接收。网络协议接口层提供网络接口驱动程序的抽象接口。 Linux 网络驱动程序中的常用方法如下所示。 (1)初始化(initialize):检测设备,配置和初始化硬件,初始化 net_device 结构,注册设备。 (2)打开(open):这个方法在网络设备被激活的时候被调用。进行资源的申请和硬件的激活 等。open 方法另一个作用是如果驱动程序作为一个模块被装入,则要防止模块卸载时设备处于打 开状态。在 open 方法里要调用 MOD_INC_USE_COUNT 宏。 (3)关闭(close):释放某些系统资源。如果是作为模块装入的驱动程序,close 里应该调用 MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。 (4)发送(hard_start_xmit):网络设备驱动程序发送数据时,系统调用 dev_queue_xmit 函数, 发送的数据放在一个 sk_buff 结构中。一般的驱动程序将数据传输到硬件发出去,特殊的设备如 loopback 把数据组成一个接收数据再回送给系统,或如 dummy 设备直接丢弃数据。如果发送成功, 则在 hard_start_xmit 方法里释放 sk_buff,返回 0,否则返回 1。 (5)接收(reception):驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知 系统的。一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块 sk_buff, 从硬件读出数据放置到申请好的缓冲区里。接下来填充 sk_buff 中的一些信息。最后调用 netif_rx() 把数据传送给上层协议层处理。 在 Android 系统中,可以使用 ifconfig 命令来查询系统中的网络设备,另外使用此命令也可以 获取 Wi-Fi 网络和电话网络的信息。

86

第 5 章 深入详解 HAL 层

HAL 层又被称为硬件抽象层,HAL 在 Android 体系中有着深远的意义,因为 Android 究竟是 完全开源还是完全不开源的秘密就在这一层。因为 HAL 层的代码没有开源,所以 Google 将硬件厂 商的驱动程序放在 HAL 层。也正是这个原因,所以 Android 被 Linux 家族删除。

5.1 初识 HAL 层

HAL 层(硬件抽象层)是位于操作系统内核与硬件电路之间的接口层,其目的在于将硬件抽 象化。它隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性, 这样就可以在多种平台上进行移植。从软硬件测试的角度来看,软硬件的测试工作都可分别基于硬 件抽象层来完成,从此使软硬件测试工作的并行进行成为可能。HAL 层的位置结构如图 5-1 所示。

库 Android运行环境

HAL层(包括GPS、Wi-Fi、Audio、Radio等驱动代码)

Linux内核

▲图 5-1 HAL 层结构

从图 5-1 所示的结构图可以看出,HAL 的功能是把 Android Framework(Android 框架)与 Linux 内核隔离。这样做的目的是让 Android 不过度依赖 Linux Kernel,从而让 Android Framework 开发 可以在不考虑驱动程序的前提下进行。在 HAL 层主要包含了 GPS、Vibrator、Wi-Fi、Copybit、Audio、 Camera、Lights、Ril、Overlay 等模块。

5.1.1 HAL 层简介 Android 硬件抽象层可以分为如下六种 HAL。 上层软件。

Android 驱动开发与移植实战详解

内部以太网。 内部通信 CLIENT。 用户接入口。 虚拟驱动,设置管理模块。 内部通信 SERVER。 定义硬件抽象层接口的代码具有以下五个特点。 硬件抽象层具有与硬件的密切相关性。 硬件抽象层具有与操作系统无关性。 接口定义的功能应包含硬件或系统所需硬件支持的所有功能。 接口定义简单明了,太多接口函数会增加软件模拟的复杂性。 具有可测性的接口设计有利于系统的软硬件测试和集成。 在 Android 源码中,HAL 主要被保存在下面的目录中。 libhardware_legacy:过去的目录,采取了链接库模块观念来架构。 libhardware:新版的目录,被调整为用 HAL stub 观念来架构。 ril:是 Radio 接口层。 msm7k:和 QUAL 平台相关的信息。 接下来将对目前 HAL 的现况做一个简单的分析。另外,目前 Android 的 HAL 层仍旧散布在不 同的地方,例如分为 Camera、Wi-Fi 等,因此上述的目录并不包含所有的 HAL 程序代码。在 HAL 架构成熟前的结构如图 5-2 所示,现在 HAL 层的结构如图 5-3 所示。

▲图 5-2 成熟前的 HAL 架构 ▲图 5-3 现在的 HAL 架构

从现在 HAL 层的结构可以看出,当前的 HAL stub 模式是一种代理人(proxy)的概念,虽然 stub 仍以“*.so”档的形式存在,但是 HAL 已经将“*.so”档隐藏了。Stub 向 HAL 提供了功能强 大的操作函数(operation),而 runtime 则从 HAL 获取特定模块(stub)的函数,然后再回调这些 操作函数。这种以 Indirect Function Call 模式的架构,让 HAL stub 变成了一种“包含”关系,也就 是说在 HAL 里包含了许多 stub(模块)。Runtime 只要说明 module ID(类型)就可以取得操作函

88 第 5 章 深入详解 HAL 层

数。在当前的 HAL 模式中,Android 定义了 HAL 层结构框架,这样通过接口访问硬件时就形成了 统一的调用方式。

5.1.2 比较 HAL_legacy 和 HAL 为了使读者明白过去结构和现在结构的差别,接下来对 HAL_legacy 和 HAL 做一个简单的比较。 (1)HAL_legacy:这是过去 HAL 的模块,采用共享库形式,在编译时会调用到。由于采用 function call 形式来调用,因此可被多个进程使用,但会被映射到多个进程空间中造成浪费,同时需要考虑 代码能否安全重入的问题(thread safe)。 (2)HAL:这是新式的 HAL,采用了 HAL module 和 HAL stub 结合形式。HAL stub 不是一个 共享库,在编译时上层只拥有访问 HAL stub 的函数指针,并不需要 HAL stub。在上层通过 HAL module 提供的统一接口获取并操作 HAL stub,所以文件只会被映射到一个进程,而不会存在重复 映射和重入问题。

5.2 分析 HAL 层源码

在 Linux Kernel 和用户空间之间提供了一个 HAL 层,即硬件抽象层。通过 HAL 层,使 Android 中的 Framework 只需要关心 HAL 中的内容,而不用关心具体的硬件实现。

5.2.1 分析 HAL module 在 HAL module 中主要有如下三个结构。 struct hw_module_t。 struct hw_module_methods_t。 struct hw_device_t。 上述三个结构的继承关系如图 5-4 所示。 以上三个抽象概念在文件 hardware.c 中进行了描述,而 HAL 模块的源代码保存在“harware” 目录中。对于不同硬件的 HAL,对应的 lib 命名规则是“id.variant.so”,比如 gralloc.msm7k.so 表 示其 id 是 gralloc,msm7k 是 variant。variant 的取值范围是在该文件中的定义的 variant_keys 对应 的值。 接下来将分析文件 hardware.c 源码。 (1)函数 hw_get_module()。 函数 hw_get_module()能够根据模块 ID 寻找硬件模块动态链接库的地址,然后调用 load 打开 动态链接库从中获取硬件模块结构体地址。执行后首先是根据固定的符号 HAL_MODULE_INFO_SYM 寻找到 hw_module_t 结构体,然后是在 hw_moule_t 中 hw_module_methods_t 结构体成员函数提供的结构 open 打开相应的模块,并同时进行初始化操作。 因为用户在调用 open()时通常会传入一个指向 hw_device_t 指针的指针。这样函数 open()将对模块 的操作函数结构保存到结构体 hw_device_t 中,用户通过它可以和模块进行交互。

89 Android 驱动开发与移植实战详解

▲图 5-4 Android HAL 结构的继承关系

函数 hw_get_module()的代码如下。

代码@/hardware/libhardware/hardware.c int hw_get_module(const char *id, const struct hw_module_t **module) 120 { 121 int status; 122 int i; 123 const struct hw_module_t *hmi = NULL; 124 char prop[PATH_MAX]; 125 char path[PATH_MAX]; /* 通过配置变量循环寻找一个模式 */ 135 for (i=0 ; i

(2)数组 variant_keys。 函数 hw_get_module()需要用到数组 variant_keys,因为 HAL_VARIANT_KEYS_COUNT 表示 数组 variant_keys 的大小。定义此数组的代码如下。

*44 static const char *variant_keys[] = { * 45 "ro.hardware", /* 能够在模拟器中获得 * 46 不同的文件. */ * 47 "ro.product.board", * 48 "ro.board.platform", * 49 "ro.arch" * 50 };

90 第 5 章 深入详解 HAL 层

然后通过此数组,并使用如下代码得到操作权限。

136 if (i < HAL_VARIANT_KEYS_COUNT) { 137 if (property_get(variant_keys[i], prop, NULL) == 0) { 138 continue; 139 }

此处的 variant_keys[i]对应有三个值,分别是 trout、msm7k 和 ARMV6。 (3)将路径和文件名保存到 path。 通过如下代码将路径和文件名保存到 path。

140 snprintf(path, sizeof(path), "%s/%s.%s.so", 141 HAL_LIBRARY_PATH, id, prop);

通过上述代码,把“HAL_LIBRARY_PATH/id.***.so”保存到 path 中,其中“***”就是上面 variant_keys 中各个元素所对应的值。 (4)载入相应的库,并把它们的 HMI 保存到 module 中。具体代码如下。

142 } else { 143 snprintf(path, sizeof(path), "%s/%s.default.so", 144 HAL_LIBRARY_PATH, id); 145 } 146 if (access(path, R_OK)) { 147 continue; 148 } 149 /*发现 library 匹配这个编号/变量*/ 150 break; 151 } 152 153 status = -ENOENT; 154 if (i < HAL_VARIANT_KEYS_COUNT+1) { 155 /*加载模块,如果失败,则不应该试图加载不同的变量. */ 157 status = load(id, path, module);//load 相应库,并把它们的 HMI 保存到 module 中 158 } 159 return status; 161 }

(5)打开相应的库并获得 hw_module_t 结构体,具体代码如下。

60 static int load(const char *id, 61 const char *path, 62 const struct hw_module_t **pHmi) 63 { 64 int status; 65 void *handle; 66 struct hw_module_t *hmi; handle = dlopen(path, RTLD_NOW);//打开相应的库 74 if (handle == NULL) { 75 char const *err_str = dlerror();

91 Android 驱动开发与移植实战详解

76 LOGE("load: module=%s\n%s", path, err_str?err_str:"unknown"); 77 status = -EINVAL; 78 goto done; 79 }

82 const char *sym = HAL_MODULE_INFO_SYM_AS_STR; 83 hmi = (struct hw_module_t *)dlsym(handle, sym);//获得 hw_module_t 结构体 84 if (hmi == NULL) { 85 LOGE("load: couldn't find symbol %s", sym); 86 status = -EINVAL; 87 goto done; 88 } 89 90 /*检查该 id 是否匹配*/ 91 if (strcmp(id, hmi->id) != 0) {//只是一个 check 92 LOGE("load: id=%s != hmi->id=%s", id, hmi->id); 93 status = -EINVAL; 94 goto done; 95 } 96 97 hmi->dso = handle; 98 99 /* 成功 */ 100 status = 0; done: 103 if (status != 0) { 104 hmi = NULL; 105 if (handle != NULL) { 106 dlclose(handle); 107 handle = NULL; 108 } 109 } else { 110 LOGV("loaded HAL id=%s path=%s hmi=%p handle=%p", 111 id, path, *pHmi, handle); 112 } 113 114 *pHmi = hmi;//得到 hw_module_t 115 116 return status; 117 }

5.2.2 分析 mokoid 工程 在 mokoid 工程中提供了一个 LedTest 示例程序,通过了解这个工程源码,对理解 Android 层 次结构、Hal 编程方法非常有意义。我们可以从网络中获取 LedTest 示例程序的源码,在 Linux 中 使用下面的命令下载获取:

#svn checkout http://mokoid.googlecode.com/svn/trunk/mokoid-read-only

下载 mokoid 工程文件后,发现其目录结构如图 5-5 所示。

92 第 5 章 深入详解 HAL 层

▲图 5-5 mokoid 工程的目录结构

需要通过 JNI(Java Native Interface)实 现 Android 的 HAL,JNI 就是 Java 程序可以调用 C/C++ 写的动态链接库,所以 HAL 可以使用 C/C++语言编写,这样效率更高。在 Android 下有如下两种 访问 HAL 的方式。 (1)Android 的 app 直接通过 service 调用“.so”格 式 的 JNI:此方法比较简单高效,但是不正规。 (2)经过 Manager 调用 Service:此方法实现起来比较复杂,但更符合目前的 Android 框架。 在此方法中,在进程 LegManager 和 LedService(Java)中需要通过进程通信的方式实现通信。 在 mokoid 工程中分别实现了上述两种方法,接下来将详细介绍这两种方法的实现原理。

1.直接调用 service 方法的实现代码

(1)HAL 层的实现代码。 文件“hardware/modules/led/led.c”的实现代码如下。

#define LOG_TAG "MokoidLedStub" #include #include #include #include #include #include /*****************************************************************************/ int led_device_close(struct hw_device_t* device) {

93 Android 驱动开发与移植实战详解

struct led_control_device_t* ctx = (struct led_control_device_t*)device; if (ctx) { free(ctx); } return 0; } int led_on(struct led_control_device_t *dev, int32_t led) { LOGI("LED Stub: set %d on.", led); return 0; } int led_off(struct led_control_device_t *dev, int32_t led) { LOGI("LED Stub: set %d off.", led); return 0; } static int led_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) { struct led_control_device_t *dev; dev = (struct led_control_device_t *)malloc(sizeof(*dev)); memset(dev, 0, sizeof(*dev)); dev->common.tag = HARDWARE_DEVICE_TAG; dev->common.version = 0; dev->common.module = module; dev->common.close = led_device_close; dev->set_on = led_on;//实例化支持的操作 dev->set_off = led_off; *device = &dev->common;//将实例化的 led_control_device_t 地址返回给 JNI 层 success: return 0; } static struct hw_module_methods_t led_module_methods = { open: led_device_open }; const struct led_module_t HAL_MODULE_INFO_SYM = { //定义此对象相当于向系统注册了一个 ID 为 LED_HARDWARE_MODULE_ID 的 stub common: { tag: HARDWARE_MODULE_TAG, version_major: 1, version_minor: 0, id: LED_HARDWARE_MODULE_ID, name: "Sample LED Stub", author: "The Mokoid Open Source Project", methods: &led_module_methods,//实现了一个 open 的方法供 JNI 层调用 } /* 此处支持 API */ };

(2)JNI 层的实现代码。

94 第 5 章 深入详解 HAL 层

文件“frameworks/base/service/jni/com_mokoid_server_LedService.cpp”的实现代码如下。 struct led_control_device_t *sLedDevice = NULL; static jboolean mokoid_setOn(JNIEnv* env, jobject thiz, jint led) { LOGI("LedService JNI: mokoid_setOn() is invoked."); if (sLedDevice == NULL) { LOGI("LedService JNI: sLedDevice was not fetched correctly."); return -1; } else { return sLedDevice->set_on(sLedDevice, led);//调用 HAL 层的注册方法 } } static jboolean mokoid_setOff(JNIEnv* env, jobject thiz, jint led) { LOGI("LedService JNI: mokoid_setOff() is invoked."); if (sLedDevice == NULL) { LOGI("LedService JNI: sLedDevice was not fetched correctly."); return -1; } else { return sLedDevice->set_on(sLedDevice, led); //调用 HAL 层的注册方法 } } /** JNI 通过 LED_HARDSOFTWARE_MODULE_ID 找到对应的 stub */ static inline int led_control_open(const struct hw_module_t* module, struct led_control_device_t** device) { return module->methods->open(module, LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device); } static jboolean mokoid_init(JNIEnv *env, jclass clazz) { led_module_t* module; if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) { LOGI("LedService JNI: LED Stub found."); if (led_control_open(&module->common, &sLedDevice) == 0) { LOGI("LedService JNI: Got Stub operations."); return 0; } } LOGE("LedService JNI: Get Stub operations failed."); return -1; } /* *阵列方法. */ // JNINativeMethod 是 JNI 层的注册方法 static const JNINativeMethod gMethods[] = { {"_init", "()Z",//Framework 层调用_init 时促发 (void*)mokoid_init}, { "_set_on", "(I)Z", (void*)mokoid_setOn },

95 Android 驱动开发与移植实战详解

{ "_set_off", "(I)Z", (void*)mokoid_setOff }, }; static int registerMethods(JNIEnv* env) { static const char* const kClassName = "com/mokoid/server/LedService"; jclass clazz; /* 寻找 */ clazz = env->FindClass(kClassName); if (clazz == NULL) { LOGE("Can't find class %s\n", kClassName); return -1; } /*注册所有方法*/ if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) { LOGE("Failed registering methods for %s\n", kClassName); return -1; } return 0; } ////Framework 层加载 JNI 库时调用 jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed\n"); goto bail; } assert(env != NULL); if (registerMethods(env) != 0) { LOGE("ERROR: PlatformLibrary native registration failed\n"); goto bail; } /* 成功*/ result = JNI_VERSION_1_4; bail: return result; }

(3)Service 的实现代码。 此处的 Service 属于 Framework 层,文件“frameworks\base\service\java\com\mokoid\server\ LedService. java”的实现代码如下。

package com.mokoid.server;

import android.util.Config; import android.util.Log; import android.content.Context;

96 第 5 章 深入详解 HAL 层

import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.os.IBinder; import mokoid.hardware.ILedService;

public final class LedService extends ILedService.Stub {

static { System.load("/system/lib/libmokoid_runtime.so");//加载 JNI 动态库 }

public LedService() { Log.i("LedService", "Go to get LED Stub..."); _init(); } public boolean setOn(int led) { Log.i("MokoidPlatform", "LED On"); return _set_on(led); } public boolean setOff(int led) { Log.i("MokoidPlatform", "LED Off"); return _set_off(led); } private static native boolean _init();//声明 JNI 库可以提供的方法 private static native boolean _set_on(int led); private static native boolean _set_off(int led); }

(4)APP 测试程序的实现代码。 此处的测试程序属于 APP 层,文件“apps/LedClient/src/com/mokoid/LedClient /LedClient.java” 的实现代码如下。

package com.mokoid.LedClient; import com.mokoid.server.LedService;//导入 Framework 层的 LedService

import android.app.Activity; import android.os.Bundle; import android.widget.TextView;

public class LedClient extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

// 在库中召集一个 API LedService ls = new LedService();//实例化 LedService ls.setOn(1);//通过 LedService 提供的方法控制底层硬件

TextView tv = new TextView(this);

97 Android 驱动开发与移植实战详解

tv.setText("LED 0 is on."); setContentView(tv); } }

2.通过 Manager 调用 Service 的实现代码

(1)Manager 的实现代码。 APP 通过 Manager 和 Service 实现通信功能,实现文件“frameworks/base/core/java/mokoid/ hardware/LedManager.java”的代码如下。

package mokoid.hardware;

import android.content.Context; import android.os.Binder; import android.os.Bundle; import android.os.Parcelable; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.Handler; import android.os.Message; import android.os.ServiceManager; import android.util.Log; import mokoid.hardware.ILedService;

/** *此类可以访问 mokoid ledservice. */ public class LedManager { private static final String TAG = "LedManager"; private ILedService mLedService;

public LedManager() {

mLedService = ILedService.Stub.asInterface( ServiceManager.getService("led"));

if (mLedService != null) { Log.i(TAG, "The LedManager object is ready."); } }

public boolean LedOn(int n) { boolean result = false;

try { result = mLedService.setOn(n); } catch (RemoteException e) {

98 第 5 章 深入详解 HAL 层

Log.e(TAG, "RemoteException in LedManager.LedOn:", e); } return result; }

public boolean LedOff(int n) { boolean result = false;

try { result = mLedService.setOff(n); } catch (RemoteException e) { Log.e(TAG, "RemoteException in LedManager.LedOff:", e); } return result; } }

因为 LedService 和 LedManager 分别属于不同的进程,所以在此需要考虑不同进程之间的通信 问题。此时在 Manager 中可以增加一个 aidl 文件来描述通信接口,文件 “frameworks/base/core/java/mokoid/ hardware/ILedService.aidl”的实现代码如下。

package mokoid.hardware;

interface ILedService { boolean setOn(int led); boolean setOff(int led); }

(2)SystemServer 的实现代码。 SystemServer属于 APP 层,实现文件“apps/LedTest/src/com /mokoid/LedTest/LedSystemServer.java” 的主要代码如下。

package com.mokoid.LedTest;

import com.mokoid.server.LedService;

import android.os.IBinder; import android.os.ServiceManager; import android.util.Log; import android.app.Service; import android.content.Context; import android.content.Intent;

public class LedSystemServer extends Service { //代表一个后台进程 @Override public IBinder onBind(Intent intent) { return null;

99 Android 驱动开发与移植实战详解

} public void onStart(Intent intent, int startId) { Log.i("LedSystemServer", "Start LedService..."); /* 请参阅文件 systemserver.java 中列出的信息. */ LedService ls = new LedService(); try { ServiceManager.addService("led", ls); } catch (RuntimeException e) { Log.e("LedSystemServer", "Start LedService failed."); } } }

(3)APP 测试程序。 此处的测试程序属于 APP 层,文件“mokoid-read-only/apps/LedTest/src/com /mokoid/LedTest/LedTest. java”的实现代码如下。

package com.mokoid.LedTest; import mokoid.hardware.LedManager; import com.mokoid.server.LedService;

import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import android.widget.Button; import android.content.Intent; import android.view.View;

public class LedTest extends Activity implements View.OnClickListener { private LedManager mLedManager = null;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //开始 ledservice 的分离过程. startService(new Intent("com.mokoid.systemserver"));

// 下面一行只用于测试!! //LedService ls = new LedService(); Button btn = new Button(this); btn.setText("Click to turn LED 1 On"); btn.setOnClickListener(this); setContentView(btn); } public void onClick(View v) { // 得到 LedManager if (mLedManager == null) { Log.i("LedTest", "Creat a new LedManager object."); mLedManager = new LedManager();

100 第 5 章 深入详解 HAL 层

} if (mLedManager != null) { Log.i("LedTest", "Got LedManager object."); } /**通过 LedManager 得到代理对象. */ mLedManager.LedOn(1); TextView tv = new TextView(this); tv.setText("LED 1 is On."); setContentView(tv); } }

5.3 Sensor 在 HAL 层的表现

人们在日常的生活中会经常用到 Sensor(传感器),例如楼宇的楼梯灯、马路上的路灯等。那 么我们手机中的传感器又可以起到什么作用呢?在 Android 手机中提供了加速度传感器、磁场、方 向、陀螺仪、光线、压力、温度等传感器。在 Android 系统中,传感器的代码分布信息如下。 (1)传感器系统的 Java 部分,实现文件为 Sensor*.java。 源码路径:frameworks/base/include/core/java /android/hardware。 (2)传感器系统的 JNI 部分,此部分演示了 android.hardware.Sensor.Manager 类的本质支持。 源码路径:frameworks/base/core/jni /android_hardware_SensorManager.cpp。 (3)传感器系统 HAL 层,演示了传感器系统的硬件抽象层需要具体的实现。 头文件路径:hardware/libhardware/include/hardware /sensors.h。 (4)驱动层。 源码路径:kernel/driver/hwmon/$(PROJECT)/sensor。

5.3.1 HAL 层的 Sensor 代码 (1)文件 Android.mk。 HAL 层的代码都是“.c”或“.cpp”格式,保存在 “hardware/$(PROJECT)/sensor/”目录中。 文件 Android.mk 的主要代码如下。

LOCAL_PATH:= $(call my-dir) LOCAL_PRELINK_MODULE := false LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw LOCAL_SHARED_LIBRARIES := libcutils libc liblog LOCAL_SRC_FILES := 适配文件 LOCAL_MODULE := sensors.$(PROJECT) include $(BUILD_SHARED_LIBRARY)

在这里要注意 LOCAL_MODULE 的赋值,这里的模块名字都是预先定义好的,具体可以参考 “hardware/libhardware/hardware.c”。 这里也可以看到加载的顺序,在加载 Sensor 的时候,依次去查找这些“so”是不是存在,然

101 Android 驱动开发与移植实战详解

后就可以加载对应的适配器。 (2)填充的结构体。 在 HAL 层中需要注意下面的填充结构体。 下面是定义 Sensor 模块的代码。

struct sensor_module_t { struct hw_module_t common; int (*get_sensors_list) (struct sensors_module_t *module, struct sensor_t const**list);

};

其中的 get_sensors_list()表示用来获得传感器列表。 使用 sensor_t 表示一个传感器的描述。

struct sensor_t{ const char* name; //传感器名称 const char* vendor; //传感器的 Vendor int version; //传感器的版本 int handle; //传感器的句柄 int type; //传感器类型 float maxRange; //传感器的最大范围 float resolution; //传感器的解析度 float power; //传感器的耗能,单位为 mA void* reserved[9]; }

下面是定义结构体和联合的代码。

typedef struct{ int sensor; //sensor 标识符 unio{ sensors_vec_t vector; //x,y,z 矢量 sensors_vec_t orientation; //方向值,单位为角度 sensors_vec_t acceleration; //加速度值,单位为 m/s2 sensors_vec_t magnetic; //磁矢量,单位 uT float temperature; //温度,单位℃ float distance; //距离,单位 cm float light; //光线亮度,单位 lux } int64_t time; //ns uint32_t reserved; }sensors_data_t;

(3)适配层函数接口。 在 HAL 层中需要注意如下函数。

static int s_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device)

102 第 5 章 深入详解 HAL 层

并且需要特别注意下面的赋值。

if (!strcmp(name, SENSORS_HARDWARE_CONTROL)) { //命令通路 ...... dev->device.common.close = dev_control_close; dev->device.open_data_source = open_data_source; dev->device.activate = activate; dev->device.set_delay = set_delay; ...... } else if (!strcmp(name, SENSORS_HARDWARE_DATA)) { //数据通路 ...... dev->device.common.close = dev_data_close; dev->device.data_open = data_open; dev->device.data_close = data_close; dev->device.poll = poll; ...... }

在驱动中提供的代码要实现 file_operation。在驱动代码中获得的 Sensor 寄存器中的值并不一定 是我们实际要上报给应用的值,比如 g-sensor,则各个方向不应该大于 10,一定注意也要考虑其他 的因素。

5.3.2 总结 Sensor 编程的流程 根据亲身体验,可以总结出如下 Sensor 编程的基本流程。 (1)获取系统服务(SENSOR_SERVICE)返回一个 SensorManager 对象。

sensormanager = (SensorManager)getSystemSeriver(SENSOR_SERVICE);

(2)通过 SensorManager 对象获取相应的 Sensor 类型的对象。

sensorObject = sensormanager.getDefaultSensor(sensor Type);

(3)声明一个 SensorEventListener 对象用于侦听 Sensor 事件,并重载 onSensorChanged 方法。

SensorEventListener sensorListener = new SensorEventListener(){ };

(4)注册相应的 SensorService。

sensormanager.registerListener(sensorListener, sensorObject, Sensor TYPE);

(5)销毁相应的 SensorService。

sensormanager.unregisterListener(sensorListener, sensorObject);

此处的 SensorListener 接口是整个传感器应用程序的核心,它包括如下两个必需的方法。 onSensorChanged(int sensor,float values[]):此方法在传感器值更改时调用,该方法只对受此 应用程序监视的传感器调用(更多内容见下文)。该方法包括如下两个参数。

103 Android 驱动开发与移植实战详解

¾ 一个整数:指示更改的传感器。 ¾ 一个浮点值数组:表示传感器数据本身。

有些传感器只提供一个数据值,另一些则提供三个浮点值。方向和加速表传感 注意 器都提供三个数据值。

onAccuracyChanged(int sensor,int accuracy):当传感器的准确性更改时将调用此方法,此方 法的参数包括两个整数,一个表示传感器,另一个表示该传感器新的准确值。

5.4 移植总结

学习 Android 驱动开发离不开移植技术,移植技术和 Linux 内核技术是 Android 驱动开发的 最核心基础。接下来将对 Android 移植的基本知识进行总结,为读者步入本书后面知识的学习打 下基础。

5.4.1 移植各个 Android 部件的方式 在 Android 系统中,不同子系统的具体移植方法不同。下面是移植各个系统的具体说明方式。 显示系统:使用 Framebuffer 标准或其他驱动程序,对应的硬件抽象层是 Gralloc。 用户输入系统:使用 Event 设备的驱动程序,对应的硬件抽象层是 EventHub。 3D 加速系统:使用非标准的驱动程序,对应的硬件抽象层是 OpenGL。 音频系统:使用非标准的驱动程序,对应的是 C++继承的硬件抽象层。 视频输出系统:使用非标准的驱动程序,对应的硬件抽象层是 overlay 模块。 摄像头系统:使用非标准的驱动程序,对应的是 C++继承的硬件抽象层。 多媒体解码系统:使用非标准的驱动程序,对应的硬件抽象层是 Skia 和 OpenMax 插件。 电话系统:使用非标准的驱动程序,对应的硬件抽象层是动态开发插件库。 GPS 定位系统:使用非标准的驱动程序,对应的硬件抽象层通常是直接接口。 无线局域网:使用 Wlan 驱动程序,对应的硬件抽象层分别是 Linux 下的 Wpa 和 Android 下的 Wi-Fi。 蓝牙系统:使用 Bluetooth 驱动程序,对应的硬件抽象层分别是 Linux 下的 Bluez 和 Android 下的 Bluedroid。 传感器系统:使用非标准的驱动程序,对应的硬件抽象层是 Sensor 硬件模块。 振动器系统:使用 Sys 文件系统中固定位置的驱动程序,对应的硬件抽象层是 Android 标 准的直接接口。 背光和指示灯系统:使用非标准的驱动程序,对应的硬件抽象层是 Light 硬件模块。 警告器系统:使用 Misc 驱动程序,对应的硬件抽象层是 Android 标准的 JNI 层。 电池管理系统:使用 Sys 文件系统中固定位置的驱动程序,对应的硬件抽象层是 Android 标准的直接接口。

104 第 5 章 深入详解 HAL 层

5.4.2 辅助工作 在 Android 系统中除了移植驱动程序和硬件抽象层之外,还需要实现如下所示的辅助工作。 (1)设置设备权限。 当 Android 系统启动时,在内核引导参数上一般都会设置“init=/init”,此时如果内核成功挂载 了这个文件系统之后,首先运行的就是这个根目录下的 init 程序。这个 init 程序是 Android 系统运 行后的第一个用户空间的程序,它以守护进程的方式运行。 当我们需要增加驱动程序的设备节点时,我们需要随之更改这些设备节点的属性,这些更改内 容被保存在文件“system/core/init/devices.c”中。此文件代码比较冗长,接下来将只对和权限有关 的代码进行讲解。 定义 perms_表示设备的类型,具体代码如下。

struct perms_ { char *name; mode_t perm; unsigned int uid; unsigned int gid; unsigned short prefix; };

定义数组 devperms 表示系统中的设备,具体代码如下。

static struct perms_ devperms[] = { { "/dev/null", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/zero", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/full", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/ptmx", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/tty", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/random", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/urandom", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/ashmem", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/binder", 0666, AID_ROOT, AID_ROOT, 0 },

/* logger should be world writable (for logging) but not readable */ { "/dev/log/", 0662, AID_ROOT, AID_LOG, 1 },

/* these should not be world writable */ { "/dev/android_adb", 0660, AID_ADB, AID_ADB, 0 }, { "/dev/android_adb_enable", 0660, AID_ADB, AID_ADB, 0 }, { "/dev/ttyMSM0", 0600, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, { "/dev/ttyHS0", 0600, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, { "/dev/uinput", 0600, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, { "/dev/alarm", 0664, AID_SYSTEM, AID_RADIO, 0 }, { "/dev/tty0", 0660, AID_ROOT, AID_SYSTEM, 0 }, { "/dev/graphics/", 0660, AID_ROOT, AID_GRAPHICS, 1 }, { "/dev/hw3d", 0660, AID_SYSTEM, AID_GRAPHICS, 0 }, { "/dev/input/", 0660, AID_ROOT, AID_INPUT, 1 },

105 Android 驱动开发与移植实战详解

{ "/dev/eac", 0660, AID_ROOT, AID_AUDIO, 0 }, { "/dev/cam", 0660, AID_ROOT, AID_CAMERA, 0 }, { "/dev/pmem", 0660, AID_SYSTEM, AID_GRAPHICS, 0 }, { "/dev/pmem_gpu", 0660, AID_SYSTEM, AID_GRAPHICS, 1 }, { "/dev/pmem_adsp", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/pmem_camera", 0660, AID_SYSTEM, AID_CAMERA, 1 }, { "/dev/oncrpc/", 0660, AID_ROOT, AID_SYSTEM, 1 }, { "/dev/adsp/", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/mt9t013", 0660, AID_SYSTEM, AID_SYSTEM, 0 }, { "/dev/akm8976_daemon",0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/akm8976_aot", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/akm8976_pffd", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/msm_pcm_out", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_pcm_in", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_pcm_ctl", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_snd", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_mp3", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_audpre", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/htc-acoustic", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/smd0", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi0", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi1", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi2", 0640, AID_RADIO, AID_RADIO, 0 }, { NULL, 0, 0, 0, 0 }, };

在上述数组中分别设置了设备的权限、所属用户和所属组,各个权限值的含义和 Linux 中的完 全一致。三个数组分别表示所属用户、所属组和其他人的权限,其中 4 表示可读,2 表示可写,1 表示可执行。例如数组内的首行代码如下。

{ "/dev/null", 0666, AID_ROOT, AID_ROOT, 0 },

“/dev/null”是一个标准的设备,其权限是 0666,表示任何用户可以对其进行读写操作。如果 需要增加一个新的设备节点文件,需要在数组 devperms 中新增加一行内容。 两个函数。 在文件中有两个比较重要的函数:handle_device_event()和 make_device()。

static void handle_device_event(struct uevent *uevent) { ... /* are we block or char? where should we live? */ if(!strncmp(uevent->path, "/block", 6)) { block = 1; base = "/dev/block/";//根据 uevent 路径改变该节点路径 mkdir(base, 0755); } else { block = 0; /* this should probably be configurable somehow */

106 第 5 章 深入详解 HAL 层

if(!strncmp(uevent->path, "/class/graphics/", 16)) { base = "/dev/graphics/";//根据 uevent 路径改变该 uevent 需要创建节点的路径 mkdir(base, 0755); } else if (!strncmp(uevent->path, "/class/oncrpc/", 14)) { base = "/dev/oncrpc/"; mkdir(base, 0755); } else if (!strncmp(uevent->path, "/class/adsp/", 12)) { base = "/dev/adsp/"; mkdir(base, 0755); } else if(!strncmp(uevent->path, "/class/input/", 13)) { base = "/dev/input/";//根据 uevent 路径改变该 uevent 需要创建节点的路径 mkdir(base, 0755); } else if(!strncmp(uevent->path, "/class/sensors/", 15)) { base = "/dev/sensors/"; mkdir(base, 0755); } else if(!strncmp(uevent->path, "/class/mtd/", 11)) { base = "/dev/mtd/";//根据 uevent 路径改变该 uevent 需要创建节点的路径 mkdir(base, 0755); } else if(!strncmp(uevent->path, "/class/misc/", 12) && !strncmp(name, "log_", 4)) { base = "/dev/log/";//根据 uevent 路径改变该 uevent 需要创建节点的路径 mkdir(base, 0755); name += 4; } else if(!strncmp(uevent->path, "/class/sound/", 13)) { base = "/dev/snd/"; mkdir(base, 0755); } else base = "/dev/"; }

snprintf(devpath, sizeof(devpath), "%s%s", base, name);

if(!strcmp(uevent->action, "add")) { make_device(devpath, block, uevent->major, uevent->minor);//创建节点文件文件 devpath return; } ... } static void make_device(const char *path, int block, int major, int minor) { unsigned uid; unsigned gid; mode_t mode; dev_t dev; if(major > 255 || minor > 255) return; mode = get_device_perm(path, &uid, &gid) | (block ? S_IFBLK : S_IFCHR);//获取将要创建 的节点是否需要重设它的 mode 数值 dev = (major << 8) | minor; mknod(path, mode, dev);

107 Android 驱动开发与移植实战详解

chown(path, uid, gid); }

函数 get_device_perm()会验证路径 path 是否和前面介绍的 devperms[]数组中的 inode 路径相同, 如果相同则返回 devperms[]数组中指定的 uid、gid 和 mode 数值。这样 make_device 会向“/dev”创 建 inode 节点,并同时改变该 inode 的 uid 和 gid.[luther.gliethtt]。 ‰ 和用户名相关的名称。 和用户名相关的名称被定义在文件“system/core/include/private/android_filesystem_config.h”中, 其中用 android_id_info 表示用户名 id 的属性。

struct android_id_info { const char *name; unsigned aid; };

各个用户名 id 被定义在数组 android_ids[]中,此数组表示了一个映射关系,能够将字符串和整 数值对应起来。定义数组 android_ids[]的代码如下。

static struct android_id_info android_ids[] = { { "root", AID_ROOT, }, { "system", AID_SYSTEM, }, { "radio", AID_RADIO, }, { "bluetooth", AID_BLUETOOTH, }, { "graphics", AID_GRAPHICS, }, { "input", AID_INPUT, }, { "audio", AID_AUDIO, }, { "camera", AID_CAMERA, }, { "log", AID_LOG, }, { "compass", AID_COMPASS, }, { "mount", AID_MOUNT, }, { "wifi", AID_WIFI, }, { "dhcp", AID_DHCP, }, { "adb", AID_ADB, }, { "install", AID_INSTALL, }, { "media", AID_MEDIA, }, { "shell", AID_SHELL, }, { "cache", AID_CACHE, }, { "diag", AID_DIAG, }, { "net_bt_admin", AID_NET_BT_ADMIN, }, { "net_bt", AID_NET_BT, }, { "inet", AID_INET, }, { "net_raw", AID_NET_RAW, }, { "misc", AID_MISC, }, { "nobody", AID_NOBODY, }, };

(2)init.rc 实现初始化操作。 文件“system/core/rootdir/init.rc”可以实现一些简单的初始化操作,Android 中的 init.rc 是启动

108 第 5 章 深入详解 HAL 层

脚本。init.rc 脚本被直接安装到目标系统的根目录下,并被 init 可执行的程序解析。 init.rc 是在 init 启动后被执行的启动脚本,主要包含下面的内容。 Commands:命令。 Actions:动作。 Triggers:触发条件。 Services:服务。 Options:选项。 Properties:属性。 其中 Commands 是一些基本的操作命令,例如:

mkdir /sdcard 0000 system system mkdir /system mkdir /data 0771 system system mkdir /cache 0770 system cache mkdir /config 0500 root root mkdir /sqlite_stmt_journals 01777 root root mount tmpfs tmpfs /sqlite_stmt_journals size=4m

这些命令在 init 可执行程序中被解析,然后可以调用相关的函数来实现。 Actions(动作)是表示一系列动作的命令,通常在 Triggers(触发条件)中被调用,动作 和触发条件的形式如下。

on

例如下面是使用动作的示例代码。

on init export PATH /sbin:/system/sbin:/system/bin:/system/xbin mkdir /system

init 表示一个触发条件,当这个触发事件发生后会设置环境变量并建立一个目录,上述操作就 被称为一个“动作”。 Services(服务):通常表示启动一个可执行程序,用 Options(选项)来表示服务的附加内 容以配合服务使用。具体代码如下。

service vold /system/bin/vold socket vold stream 0660 root mount service bootsound /system/bin/playmp3 user media group audio oneshot

其中 vold 和 bootsound 分别表示为两个服务的名称,“/system/bin /vold”和“/system /bin/playmp3”

109 Android 驱动开发与移植实战详解

分别是它们所对应的可执行程序。 socket、user、group 和 oneshot 是配合服务使用的选项,其中 oneshot 选项表示该服务只启动一 次;如果没有 oneshot 选项,则这个可执行程序会一直存在;如果可执行程序被杀死,则会重新启动。 (3)更改配置文件。 在 Android 硬件抽象层的移植过程中,经常需要向系统中加入运行时的配置文件以配置系统的 功能。etc 文件即运行时的配置文件,例如配置信息通常被保存在如下文件中。 /system/etc/apns-conf.xml APN:接入点配置文件 /system/etc/AudioFilter.csv:音频过滤器配置文件 /system/etc/bookmarks.xml:书签数据库 /system/etc/dbus.conf:总线监视配置文件 /system/etc/favorites.xml:收藏夹 /system/etc/firmware:固件信息 /system/etc/gps.conf GPS:设置文件 /system/etc/hcid.conf:内核 HCID 配置文件 /system/etc/hosts:网络 DNS 缓存 (4)文件系统的属性。 在文件“system/core/include/private/android_filesystem_config.h”中定义了各个文件的属性,其 中 fs_path_config 表示文件系统路径的属性,定义代码如下。

struct fs_path_config { unsigned mode;//模式 unsigned uid;//用户 id unsigned gid;//组 id const char *prefix;//目录前缀 };

在数组 android_dirs[]中定义了子目录的属性,定义代码如下。

static struct fs_path_config android_dirs[] = { { 00770, AID_SYSTEM, AID_CACHE, "cache" }, { 00771, AID_SYSTEM, AID_SYSTEM, "data/app" }, { 00771, AID_SYSTEM, AID_SYSTEM, "data/app-private" }, { 00771, AID_SYSTEM, AID_SYSTEM, "data/dalvik-cache" }, { 00771, AID_SYSTEM, AID_SYSTEM, "data/data" }, { 00771, AID_SHELL, AID_SHELL, "data/local/tmp" }, { 00771, AID_SHELL, AID_SHELL, "data/local" }, { 01771, AID_SYSTEM, AID_MISC, "data/misc" }, { 00770, AID_DHCP, AID_DHCP, "data/misc/dhcp" }, { 00771, AID_SYSTEM, AID_SYSTEM, "data" }, { 00750, AID_ROOT, AID_SHELL, "sbin" }, { 00755, AID_ROOT, AID_SHELL, "system/bin" }, { 00755, AID_ROOT, AID_SHELL, "system/xbin" }, { 00777, AID_ROOT, AID_ROOT, "system/etc/ppp" }, /* REMOVE */ { 00777, AID_ROOT, AID_ROOT, "sdcard" },

110 第 5 章 深入详解 HAL 层

{ 00755, AID_ROOT, AID_ROOT, 0 }, };

然后在数组 android_files[]中定义了默认文件的属性,下面是定义此数组的代码。 static struct fs_path_config android_files[] = { { 00555, AID_ROOT, AID_ROOT, "system/etc/ppp/ip-up" }, { 00555, AID_ROOT, AID_ROOT, "system/etc/ppp/ip-down" }, { 00440, AID_ROOT, AID_SHELL, "system/etc/init.goldfish.rc" }, { 00550, AID_ROOT, AID_SHELL, "system/etc/init.goldfish.sh" }, { 00440, AID_ROOT, AID_SHELL, "system/etc/init.trout.rc" }, { 00550, AID_ROOT, AID_SHELL, "system/etc/init.ril" }, { 00550, AID_ROOT, AID_SHELL, "system/etc/init.testmenu" }, { 00550, AID_ROOT, AID_SHELL, "system/etc/init.gprs-pppd" }, { 00550, AID_DHCP, AID_SHELL, "system/etc/dhcpcd/dhcpcd-run-hooks" }, { 00440, AID_BLUETOOTH, AID_BLUETOOTH, "system/etc/dbus.conf" }, { 00440, AID_BLUETOOTH, AID_BLUETOOTH, "system/etc/bluez/hcid.conf" }, { 00440, AID_BLUETOOTH, AID_BLUETOOTH, "system/etc/bluez/input.conf" }, { 00440, AID_BLUETOOTH, AID_BLUETOOTH, "system/etc/bluez/audio.conf" }, { 00440, AID_RADIO, AID_AUDIO, "/system/etc/AudioPara4.csv" }, { 00644, AID_SYSTEM, AID_SYSTEM, "data/app/*" }, { 00644, AID_SYSTEM, AID_SYSTEM, "data/app-private/*" }, { 00644, AID_APP, AID_APP, "data/data/*" }, { 02755, AID_ROOT, AID_NET_RAW, "system/bin/ping" }, { 02755, AID_ROOT, AID_INET, "system/bin/netcfg" }, { 06755, AID_ROOT, AID_ROOT, "system/xbin/su" }, { 06755, AID_ROOT, AID_ROOT, "system/xbin/librank" }, { 06755, AID_ROOT, AID_ROOT, "system/xbin/procrank" }, { 06755, AID_ROOT, AID_ROOT, "system/xbin/procmem" }, { 00755, AID_ROOT, AID_SHELL, "system/bin/*" }, { 00755, AID_ROOT, AID_SHELL, "system/xbin/*" }, { 00750, AID_ROOT, AID_SHELL, "sbin/*" }, { 00755, AID_ROOT, AID_ROOT, "bin/*" }, { 00750, AID_ROOT, AID_SHELL, "init*" }, { 00644, AID_ROOT, AID_ROOT, 0 }, };

111

第 6 章 常见的驱动平台

Android 驱动分为专用驱动和设备驱动两大类,其中 Android 的专用驱动不是 Linux 中的标准 内容,而是与体系结构和硬件平台无关的纯软件。Android 的专用驱动和 Linux 中的内存驱动类似, 主要被保存在“drivers/staging/android”目录中,只有极少数的驱动被保存在其他目录中。在移植 Android 专用驱动时,无须作出任何更改即可进行配置,并可以灵活选择是否使用驱动程序。另外, 在移动手机的发展历程中,有三个主流的处理器平台一直伴随着移动通信技术的发展而发展,分别 是 Goldfish、MSM 和 OMAP。基于这三个平台的产品都取得了不错的成绩。本章将分析这三种平 台内核中的标准驱动程序,为读者自行开发驱动程序做好准备。

6.1 专用驱动

Android 系统中专用驱动所包含的类别结构如图 6-1 所示。 Android 系统的专用驱动程序主要保存在“drivers/staging/android”目录,此目录是 Android 的 特有的目录,在里面还包含了常用的 Kconfig 文件和 Makefile 文件。其中 Makefile 的内容如下所示。

obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o obj-$(CONFIG_ANDROID_LOGGER) += logger.o obj-$(CONFIG_ANDROID_RAM_CONSOLE) += ram_console.o obj-$(CONFIG_ANDROID_TIMED_OUTPUT) += timed_output.o obj-$(CONFIG_ANDROID_TIMED_GPIO) += timed_gpio.o obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o

binder 和 logger:两个普通的 misc 驱动程序。 timed_output:一种 Android 特有的驱动程序框架。 timed_gpio:基于 timed_output 的一个驱动程序。 lowmemorykiller:内存管理的组件。 ram_console:一个利用控制台驱动的框架。

6.1.1 Binder 驱动程序 虽然应用程序是以独立的进程来运行的,但是在进程之间还需要实现相互通信。例如在多进程 的环境下,应用程序和后台服务通常会运行在不同的进程中,有独立的地址空间。但是因为需要相

第 6 章 常见的驱动平台

互协作,彼此之间必须进行通信并实现数据共享,此时需要进程通信来完成。

Binder驱动

Logger驱动

staging驱动 Lowmemorykiller 组件

Timed Gpio驱动

Ram控制台

workelock 驱动

earlysuspend 驱动

专用驱动 Ashmem驱动

Alarm驱动

Pmem驱动

USB Gadget 驱动

Android Paranoid 网络 ▲图 6-1 Android 专用驱动的类别结构

在 Linux 系统中的进程间通信方式有很多种,与此相对应的是,Android 可以选择的进程间通 信的方式也很多。具体来说主要包括以下三种方式。 标准 Linux Kernel IPC 接口。 标准 D-BUS 接口。 Binder 接口。 在上述三种方式中,Android 使用得最多也最被认可的还是 Binder 机制。这是因为 Binder 更加 简洁和快速,并且消耗的内存资源更少。另外 Binder 还解决和避免了传统的进程间通信可能会增 加进程的开销的问题,也避免了进程过载和安全漏洞等方面的风险。Binder 主要实现以下功能。 用驱动程序来推进进程间的通信。 通过共享内存来提高性能。 为进程请求分配每个进程的线程池。 针对系统中的对象引入了引用计数和跨进程的对象引用映射。

113 Android 驱动开发与移植实战详解

进程间同步调用。

1.Binder 驱动的原理

为了完成进程间通信,Binder 采用了 AIDL(Android Interface Definition Language)来描述进 程间的接口。在实际的实现中,Binder 是作为一个特殊的字符型设备而存在的,其设备节点为 “/dev/binder”。Binder 驱动程序的实现代码由如下两个文件组成。

kernel/drivers/staging/binder.h kernel/drivers/staging/binder.c

在其驱动的实现过程中,使用 binder_ioctl() 函数与用户空间进程交换数据。 BINDER_WRITE_READ 用来读写数据,数据包中的 cmd 域用于区分不同的请求。使用 binder_thread_write()函数来发送请求或返回结果,使用 binder_thread_read()函数来读取结果。在 binder_thread_write()函数中,通过调用 binder_transaction()函数来转发请求并返回结果。当收到请 求时,binder_transaction()函数会通过对象的 handle 找到对象所在的进程。如果 handle 结果为空, 则认为此对象是 context_mgr,然后把请求发给 context_mgr 所在的进程。并将请求中所有的 Binder 对象全部放到一个 RB 树中,最后把请求放到目标进程的队列中以等待目标进程读取。在函数 binder_parse()中实现数据解析工作。

2.Binder 的工作流程

作为 Android 系统的核心机制,Binder 几乎贯穿整个 Android 系统,Binder 的工作流程如下所示。 (1)客户端首先获得服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务 端的“引用”,该代理对象具有服务端的功能,使其在客户端访问服务端的方法就像访问本地方法 一样。 (2)客户端通过调用服务器代理对象的方式向服务器端发送请求。 (3)代理对象将用户请求通过 Binder 驱动发送到服务器进程。 (4)服务器进程处理用户请求,并通过 Binder 驱动返回处理结果给客户端的服务器代理对象。 (5)客户端收到服务器端的返回结果。 Binder 经过上述流程处理实现了一次通信处理。

3.实现 Binder 驱动

Binder 的实质就是要把对象从一个进程映射到另一个进程中,而不管这个对象是本地的还是远 程的。接下来我们先介绍 Binder 机制中所使用的数据结构体,然后再对整个流程进行分析。 (1)binder_work。 结构体 binder_work 是一个最简单也是最基础的结构体,下面是定义此结构体的代码。

struct binder_work { struct list_head entry; enum { BINDER_WORK_TRANSACTION = 1,

114 第 6 章 常见的驱动平台

BINDER_WORK_TRANSACTION_COMPLETE, BINDER_WORK_NODE, BINDER_WORK_DEAD_BINDER, BINDER_WORK_DEAD_BINDER_AND_CLEAR, BINDER_WORK_CLEAR_DEATH_NOTIFICATION, } type; };

在上述代码中,entry 被定义为 list_head,功能是实现一个双向链表,存储所有 binder_work 的 队列;enum 类型的 type 的功能是表示 binder_work 的类型。 (2)Binder 类型。 在头文件 binder.h 中使用 enum 表示 Binder 类型的定义代码。

#define B_PACK_CHARS(c1, c2, c3, c4) \ ((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4)) #define B_TYPE_LARGE 0x85 enum { BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE), BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE), BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE), BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE), BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE), };

在上述代码中,Binder 被分成了下面 3 个不同类。 本地对象:BINDER_TYPE_BINDER、 BINDER_TYPE_WEAK_BINDER。 远程对象的“引用”:BINDER_TYPE_HANDLE、 BINDER_TYPE_WEAK_HANDLE。 文件:BINDER_TYPE_FD。 其中文件(BINDER_TYPE_FD)类型非常重要,如果传递的是 BINDER_TYPE_FD 类型,其 实还是会将文件映射到句柄上,根据此 fd 可以找到对应的文件,然后在目标进程中分配一个 fd, 最后把这个 fd 赋值给返回的句柄。 (3)Binder 对象。 通常把进程之间传递的数据称为 Binder 对象(Binder Object),在源码中使用 flat_binder_object 结构体(位于 binder.h 文件中)来表示对象,下面是定义此结构体的代码。

struct flat_binder_object { unsigned long type; unsigned long flags; union { void *binder; signed long handle; }; void *cookie; }; (4)结构体 binder_transaction_data。 因为 Binder 对象所传递的实际内容是通过另外一个结构体 binder_transaction_data 来表示的,所

115 Android 驱动开发与移植实战详解

以从结构体 flat_binder_object 中看不到 Binder 对象所传递的实际内容,下面是定义此结构体的代码。

struct binder_transaction_data { union { size_t handle; void *ptr; } target; void *cookie; unsigned int code; unsigned int flags; pid_t sender_pid; uid_t sender_euid; size_t data_size; size_t offsets_size; union { struct { const void *buffer; const void *offsets; } ptr; uint8_t buf[8]; } data; };

结构体 binder_transaction_data 是 Binder 驱动的核心,各个字段的具体说明如下所示。 target 字段:是一个复合联合体对象,target 字段中的 handle 是要处理此事件的目标对象的 句柄。Binder 驱动根据此 handle 可以找到应该由哪个进程处理此事件,并且把此事件的任务分发 给一个线程,而那个线程也正在执行 ioctl 的 BINDER_WRITE_READ 操作,即正在等待一个请求。 target 的 ptr 字段与 handle 对应,请求方使用 handle 来指出远程对象;响应方使用 ptr 来寻址,这样 可以找到需要处理此事件的对象。由此可见 handle 和 ptr 是一个事物的两种表达形式,handle 和 ptr 之间的解析关系就是 Binder 驱动需要维护的任务。 cookie 字段:表示 target 对象所附加的额外数据。 code:是一个命令,用于描述请求 Binder 对象执行的操作。 flags 字段:用于描述传输方式,与 flat_binder_object 中的 flags 字段对应。 sender_pid 和 sender_euid:表示该进程的 pid 和 uid。 data_size:表示数据的大小字节数。 offsets_size:表示数据的偏移量字节数。 union 数据:表示真正的数据,其中 ptr 表示与 target->ptr 对应的对象的数据,buf 表示与 handle 对象对应的数据,data 中的 ptr 中的 buffer 表示实际的数据,而 offsets 则表示其偏移量。 (5)binder_proc。 结构体 binder_proc 的功能是保存调用 Binder 的各个进程或线程的信息,例如线程 ID、进程 ID、 Binder 状态信息等。下面是定义此结构体的代码。

struct binder_proc { //实现双向链表

116 第 6 章 常见的驱动平台

struct hlist_node proc_node; //线程队列、双向链表、所有的线程信息 struct rb_root threads; struct rb_root nodes; struct rb_root refs_by_desc; struct rb_root refs_by_node; //进程 ID int pid; struct vm_area_struct *vma; struct task_struct *tsk; struct files_struct *files; struct hlist_node deferred_work_node; int deferred_work; void *buffer; ptrdiff_t user_buffer_offset; struct list_head buffers; struct rb_root free_buffers; struct rb_root allocated_buffers; size_t free_async_space; struct page **pages; size_t buffer_size; uint32_t buffer_free; struct list_head todo; //等待队列 wait_queue_head_t wait; //Binder 状态 struct binder_stats stats; struct list_head delivered_death; //最大线程 int max_threads; int requested_threads; int requested_threads_started; int ready_threads; //默认优先级 long default_priority; };

(6)binder_node。 结构体 binder_node 表示一个 Binder 节点,下面是定义此结构体的代码。 struct binder_node { int debug_id; struct binder_work work; union { struct rb_node rb_node; struct hlist_node dead_node; }; struct binder_proc *proc; struct hlist_head refs; int internal_strong_refs;

117 Android 驱动开发与移植实战详解

int local_weak_refs; int local_strong_refs; void __user *ptr; void __user *cookie; unsigned has_strong_ref : 1; unsigned pending_strong_ref : 1; unsigned has_weak_ref : 1; unsigned pending_weak_ref : 1; unsigned has_async_transaction : 1; unsigned accept_fds : 1; int min_priority : 8; struct list_head async_todo; };

(7)binder_thread。 结构体 binder_thread 的功能是存储每一个单独的线程的信息,下面是定义此结构体的代码。

struct binder_thread { struct binder_proc *proc; struct rb_node rb_node; int pid; int looper; struct binder_transaction *transaction_stack; struct list_head todo; uint32_t return_error; uint32_t return_error2; wait_queue_head_t wait; struct binder_stats stats; };

proc 字段:表示当前线程属于哪一个 Binder 进程(binder_proc 指针)。 rb_node:表示一个红黑树节点。 pid:表示线程的 pid。 looper:表示线程的状态信息。 transaction_stack:定义了要接收和发送的进程和线程信息,其结构体为 binder_transaction。 todo:用于创建一个双向链表。 return_error 和 return_error2:是返回的错误信息代码。 wait:是一个等待队列头。 stats:用于表示 Binder 状态信息。 (8)binder_transaction。 结构体 binder_transaction 的功能是中转请求和返回结果,并保存接收和要发送的进程信息。下 面是定义此结构体的代码。

struct binder_transaction { int debug_id;//调试相关 struct binder_work work; struct binder_thread *from;

118 第 6 章 常见的驱动平台

struct binder_transaction *from_parent; struct binder_proc *to_proc; struct binder_thread *to_thread; struct binder_transaction *to_parent; unsigned need_reply : 1; struct binder_buffer *buffer; unsigned int code; unsigned int flags; long priority; long saved_priority; uid_t sender_euid; };

work:是一个 binder_work。 from 和 to_thread:都是一个 binder_thread 对象,用于表示接收和要发送的进程信息,另外 还包括了接收和发送进程信息的父节点 from_parent 和 to_thread。 to_proc:是一个 binder_proc 类型的结构体。其中还包括 flags、need_reply、优先级(priority) 等数据。 buffer:用来表示 binder 的缓冲区信息。 (9)初始化函数 binder_init()。 Android 中的驱动操作从初始化操作开始,在文件 binder.c 中可以找到如下初始化函数 binder_init()的实现代码。

static int __init binder_init(void) { int ret; //创建文件系统根节点 binder_proc_dir_entry_root = proc_mkdir("binder", NULL); //创建 proc 节点 if (binder_proc_dir_entry_root) binder_proc_dir_entry_proc = proc_mkdir("proc", binder_proc_dir_entry_root); //注册 Misc 设备 ret = misc_register(&binder_miscdev); //创建各文件 if (binder_proc_dir_entry_root) { create_proc_read_entry("state", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_state, NULL); create_proc_read_entry("stats", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_stats, NULL); create_proc_read_entry("transactions", S_IRUGO, binder_proc_dir_entry_ root, binder_read_proc_transactions, NULL); create_proc_read_entry("transaction_log", S_IRUGO, binder_proc_dir_entry_ root, binder_read_proc_transaction_log, &binder_transaction_log); create_proc_read_entry("failed_transaction_log", S_IRUGO, binder_proc_dir_entry_root, binder_read_proc_transaction_log, &binder_transaction_log_failed); } return ret;

119 Android 驱动开发与移植实战详解

} device_initcall(binder_init);

函数 binder_init()是 Binder 驱动的初始化函数,初始化函数一般需要设备驱动接口来调用, Android Binder 设备驱动接口函数是 device_initcall()。初始化函数使用 proc_mkdir()创建了一个 Binder 的 proc 文件系统的根节点(binder_ proc_dir_entry_root,/proc/binder),如果根节点创建成功,则接着 为 Binder 创建 binder proc 节点(binder_proc_dir_entry_proc,/proc/binder/proc);然后,驱动 Binder 使用 misc_register 把自己注册为一个 Misc 设备,其设备节点位于/dev/binder,该节点由 init 进程在 handle_device_fd(device_fd)函数中调用 handle_device_event(&uevent)函数执行,其中 uevent-netlink 事 件在“/dev/”目录下创建。最后调用 create_proc_read_entry 创建下面的只读 proc 文件。

/proc/binder/state /proc/binder/stats /proc/binder/transactions /proc/binder/transaction_log /proc/binder/failed_transaction_log

在注册 Binder 驱动为 Misc 设备时,需要用下面的代码指定 Binder 驱动的 miscdevice。

static struct miscdevice binder_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "binder", .fops = &binder_fops };

Binder 设备的主设备号为 10,此设备号需要动态获得,“.minor”被设置为动态获得设备号 MISC_DYNAMIC_MINOR,用“.name ”来表示设备名称。在最后需要指定该设备的 file_operations 结构体,下面是定义此结构体的代码。

//Binder 文件操作结构体 static struct file_operations binder_fops = { .owner = THIS_MODULE, .poll = binder_poll, .unlocked_ioctl = binder_ioctl, .mmap = binder_mmap, .open = binder_open, .flush = binder_flush, .release = binder_release, };

任何驱动程序都有向用户空间的程序提供操作接口这一主要功能,这个接口是标准的。 (10)函数 binder_open()。 函数 binder_open()的功能是打开 Binder 设备文件“/dev/binder”,在 Android 驱动中任何一个进 程及其中的所有线程都可以打开一个 Binder 设备,下面是打开过程的实现代码。

static int binder_open(struct inode *nodp, struct file *filp) {

120 第 6 章 常见的驱动平台

struct binder_proc *proc; if (binder_debug_mask & BINDER_DEBUG_OPEN_CLOSE) printk(KERN_INFO "binder_open: %d:%d\n", current->group_leader->pid, current->pid); //为 binder_proc 分配空间 proc = kzalloc(sizeof(*proc), GFP_KERNEL); if (proc == NULL) return -ENOMEM; //增加引用计数 get_task_struct(current); //保存其引用计数 proc->tsk = current; //初始化 binder_proc 队列 INIT_LIST_HEAD(&proc->todo); init_waitqueue_head(&proc->wait); proc->default_priority = task_nice(current); mutex_lock(&binder_lock); //增加 BINDER_STAT_PROC binder_stats.obj_created[BINDER_STAT_PROC]++; //添加到 binder_proc 哈希表 hlist_add_head(&proc->proc_node, &binder_procs); //保存 pid 和 private_data 等数据 proc->pid = current->group_leader->pid; INIT_LIST_HEAD(&proc->delivered_death); filp->private_data = proc; mutex_unlock(&binder_lock); //创建只读文件"/proc/binder/proc/$pid" if (binder_proc_dir_entry_proc) { char strbuf[11]; snprintf(strbuf, sizeof(strbuf), "%u", proc->pid); remove_proc_entry(strbuf, binder_proc_dir_entry_proc); create_proc_read_entry(strbuf, S_IRUGO, binder_proc_dir_entry_proc, binder_read_proc_proc, proc); } return 0; }

(11)函数 binder_release()。 函数 binder_release()与函数 binder_open()的功能相反,当 Binder 驱动退出时,需要使用它释放 在打开以及其他操作过程中分配的空间并清理相关的数据信息。 (12)binder_flush。 在关闭一个设备文件描述符复制时调用 flush 操作接口。通过调用一个 workqueue 来执行 BINDER_DEFERRED_FLUSH 操作以完成该 flush 操作,将最终处理交给 binder_defer_work()函数。 (13)binder_poll。 函数 poll()是非阻塞型 IO 的内核驱动实现,所有支持非阻塞 IO 操作的设备驱动都需要实现 poll() 函数。Binder 的 poll()函数仅支持设备是否可以非阻塞地读(POLLIN),在此有 proc_work 和 thread_work 两种等待任务。其他驱动的 poll 实现一样,也需要通过调用 poll_wait()函数来实现。

121 Android 驱动开发与移植实战详解

(14)函数 binder_get_thread()。 该功能是在 threads 队列中查找当前的进程信息。 (15)binder_mmap。 功能是把设备内存映射到用户进程地址空间中,这样就可以像操作用户内存那样操作设备内存。 (16)binder_ioctl。 此部分是 Binder 的最核心内容,Binder 的功能就是通过 ioctl 命令来实现的。在 Binder 中一共 有 7 个 ioctl 命令,被定义在 ioctl.h 头文件中,下面是定义代码。

#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read) #define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, int64_t) #define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t) #define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, int) #define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int) #define BINDER_THREAD_EXIT _IOW('b', 8, int) #define BINDER_VERSION _IOWR('b', 9, struct binder_version)

命令 ioctl 是设备驱动程序中对设备的 I/O 通道进行管理的函数。I/O 通道管理是指对设备的一 些特性进行控制,例如串口的传输波特率、马达的转速等。其调用函数是“int ioctl(int fd, ind cmd, ...);”,其中,fd 是用户程序打开设备时使用 open()函数返回的文件标识符,cmd 是用户程序 对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有与 cmd 的 意义相关。Ioctl()函数是文件结构中的一个属性分量,也就是说,如果你的驱动程序提供了对 ioctl 的支持,用户就可以在用户程序中使用 ioctl()函数控制设备的 I/O 通道。 其实不用 ioctl 命令也可以控制设备的 I/O 通道,但是这样会十分复杂。例如可以在驱动程序 中实现 write 写操作时检查是否有特殊约定的数据流通过,如果有则在后面附加控制命令(一般在 socket 编程中常常这样做)。此时会导致代码分工不明确,引发程序结构混乱。所以最好还是建议 使用 ioctl 实现控制的功能。用户程序所做的只是通过命令码告诉驱动程序它想做什么,至于怎么 解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

6.1.2 Logger 驱动 Logger 驱动是一个轻量级的 Log 驱动,能够为用户层程序提供 Log 支持。在 Logger 驱动中有 如下三个设备节点。 /dev/log/main:主要的 log。 /dev/log/event:事件的 log。 /dev/log/radio:Modem 部分的 log。 Logger 驱动为用户空间提供了 ioctl 接口、read 接口和异步 write 接口,其主设备号为 10(Misc Driver)。在如下源文件中定义了 Logger 驱动实现源码。

kernel/include/linux/logger.h kernel/drivers/misc/logger.c 对于非本用户或本组来说,Logger 驱动程序的设备节点是可写而不可读的。在 Android 用户空 间中,使用库 liblog 封装了 Logger 驱动程序,路径是“system/core/liblog”。logcat 程序负责调用

122 第 6 章 常见的驱动平台

Logger 驱动,此程序是一个可知性程序,是当用户取出系统 log 信息后在系统中使用的一个辅助工 具,logcat 程序的代码路径为“system/core/logcat”。

6.1.3 组件 Lowmemorykiller 组件 Lowmemorykiller 是一个内存管理的组件,可以根据需要结束进程来释放需要的内存。当 内存不够用时,该策略会试图结束一个进程。组件 Lowmemorykiller 通过调用 Linux 内存管理系统 的接口来注册一个 shrinker,此处的 shrinker 是通过 Lowmemorykiller 实现的。 组件 Lowmemorykiller 的源码位于文件“drivers/staging/android /lowmemorykiller.c”中,下面 是其核心部分代码。

static struct shrinker lowmem_shrinker = { .shrink = lowmem_shrink, .seeks = DEFAULT_SEEKS * 16 }; module_param_named(cost, lowmem_shrinker.seeks, int, S_IRUGO | S_IWUSR); module_param_array_named(adj, lowmem_adj, int, &lowmem_adj_size, S_IRUGO | S_IWUSR); module_param_array_named(minfree, lowmem_minfree, uint, &lowmem_minfree_size, S_IRUGO | S_IWUSR); module_param_named(debug_level, lowmem_debug_level, uint, S_IRUGO | S_IWUSR);

module_init(lowmem_init); module_exit(lowmem_exit);

MODULE_LICENSE("GPL");

在下面的两个文件中设置了组件 Lowmemorykiller 配置系统的相关参数。

/sys/module/lowmemorykiller/parameters/adj /sys/module/lowmemorykiller /parameters/minfree

Android 的 Lowmemorykiller 和标准的 oom killer 有相似的思路,只不过 Lowmemorykiller 作为 一个 shrinker 实现,而 oom killer 则在分配内存时被调用(如果内存资源很紧张)。Android 的 lowmemorykiller 实现较为简洁,这点从代码尺寸就能看到,但并不觉得比 oom killer 更为灵活。它 只不过是另一种 oom killer。

6.1.4 Timed Output 驱动程序 Timed Output 驱动是 Android 中一个很重要的框架,例如经常通过 Timed Output 驱动程序框架 来实现 Vibrator(震动)的驱动程序。Timed Output 驱动是基于 sys 文件系统来完成的,能够对设 备进行定时控制功能,目前支持设备有 Vibrator(震动)和 LED(闪光灯)设备。 Timed Output 驱动会注册“sys/class/timed_output/”目录,每一个注册实现的 Timed Output 设 备(例如 Vibrator 和 LED)将会在“sys/class/timed_output/”目录中新建一个和设备同名的子目录。 在子目录中有一个 enable 文件,通过对此文件的读写实现对设备的控制和显示。

123 Android 驱动开发与移植实战详解

在如下两个文件中实现了 Timed Output 驱动。

drivers/staging/android/timed_output.c drivers/staging/android/timed_output.h

文件 timed_output.h 定义了结构体 timed_output_dev,使设备设置定时器功能,并设置返回定 时器的剩余时间。文件 timed_output.h 的实现代码如下所示。

struct timed_output_dev { const char *name;

/*使其输出和设定定时器*/ void (*enable)(struct timed_output_dev *sdev, int timeout);

/*返回其余的定时器的当前毫秒数*/ int (*get_time)(struct timed_output_dev *sdev);

/*私有数据*/ struct device *dev; int index; int state; }; extern int timed_output_dev_register(struct timed_output_dev *dev); extern void timed_output_dev_unregister(struct timed_output_dev *dev);

在文件 timed_output.c 中,定义函数 timed_output_dev_register()实现 Timed Output 设备的注册, 下面是此函数的实现代码。

int timed_output_dev_register(struct timed_output_dev *tdev) { int ret; if (!tdev || !tdev->name || !tdev->enable || !tdev->get_time) return -EINVAL; ret = create_timed_output_class(); if (ret < 0) return ret; tdev->index = atomic_inc_return(&device_count); tdev->dev = device_create(timed_output_class, NULL, MKDEV(0, tdev->index), NULL, tdev->name); if (IS_ERR(tdev->dev)) return PTR_ERR(tdev->dev); ret = device_create_file(tdev->dev, &dev_attr_enable); if (ret < 0) goto err_create_file; dev_set_drvdata(tdev->dev, tdev); tdev->state = 0; return 0; err_create_file: device_destroy(timed_output_class, MKDEV(0, tdev->index)); printk(KERN_ERR "timed_output: Failed to register driver %s\n",

124 第 6 章 常见的驱动平台

tdev->name); return ret; } EXPORT_SYMBOL_GPL(timed_output_dev_register);

在文件 timed_output.c 中通过函数 timed_output_dev_unregister()注销 Timed Output 设备,下面 是此函数的实现代码。

void timed_output_dev_unregister(struct timed_output_dev *tdev) { device_remove_file(tdev->dev, &dev_attr_enable); device_destroy(timed_output_class, MKDEV(0, tdev->index)); dev_set_drvdata(tdev->dev, NULL); }

在文件 timed_output.c 中定义了两个函数:enable_show ()和 enable_store(),这两个函数通过调 用 sys 文件系统来实现驱动功能。文件 enable 是每个 Timed Output 设备中都有的文件,在写这个文 件的时候表示设置定时器时间并启动定时器,下面是这两个函数的实现代码。

static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct timed_output_dev *tdev = dev_get_drvdata(dev); int remaining = tdev->get_time(tdev); return sprintf(buf, "%d\n", remaining); } static ssize_t enable_store( struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct timed_output_dev *tdev = dev_get_drvdata(dev); int value; if (sscanf(buf, "%d", &value) != 1) return -EINVAL; tdev->enable(tdev, value); return size; }

6.1.5 Timed Gpio 驱动程序 Timed Gpio 是基于 Timed_Output 的驱动程序,能够定时控制 GPIO。Timed Gpio 可以调用 Timed Output 框架注册一个驱动程序。在如下两个文件中保存了定义 Timed Gpio 驱动程序的源码。

drivers/staging/android/timed_gpio.h drivers/staging/android/timed_gpio.c

在文件 timed_gpio.h 中定义了 Timed Gpio 驱动的名称,并设置结构体 timed_gpio 作为驱动的 私有结构体。下面是此文件的实现代码。

#ifndef _LINUX_TIMED_GPIO_H

125 Android 驱动开发与移植实战详解

#define _LINUX_TIMED_GPIO_H #define TIMED_GPIO_NAME "timed-gpio"//定义 Timed Gpio 驱动的名称 struct timed_gpio { const char *name; unsigned gpio; int max_timeout; u8 active_low; }; struct timed_gpio_platform_data { int num_gpios; struct timed_gpio *gpios; }; #endif

在文件 timed_gpio.c 中,通过下面的两个函数分别实现对驱动设备的注册和注销。

int timed_output_dev_register(struct timed_output_dev *tdev) void timed_output_dev_unregister(struct timed_output_dev *tdev)

6.1.6 唤醒和休眠

wakelock 和 early_suspend 是 Android 系统中的一种特殊机制,能够实现“唤醒”和“休眠” 功能,并获取系统资源的信息,例如电源信息和 CPU 信息等。

1.wakelock 和 early_suspend 的原理

(1)wakelock。 wakelock 在 Android 的电源管理系统中扮演一个核心角色。wakelock 采用了“锁”机制,只要 有人拿着这个锁,系统就无法进入休眠状态。这个锁可以是有超时的或者是没有超时的,超时的锁 会在时间过去以后自动解锁。如果没有锁了或者超时了,内核就会启动休眠的那套机制来进入休眠。 当系统启动完毕后,会自己加一把名为“main”的锁,而当系统有意愿去睡眠时则会先去释放 这把“main”锁。在 Android 中,在 early_suspend 的最后一步会释放“main”锁(wake_unlock: main)。 当释放完后会检查是否还有其他存在的锁,如果没有则直接进入睡眠过程。 wakelock 的缺点是,如果有某一应用获锁而不释放,或者因为一直在执行某种操作而没时间来 释放,则会导致系统一直进入不了睡眠状态,这样功耗会过大。 在 wakelock 中有 3 种类型,其中最常用的是 WAKE_LOCK_SUSPEND,作用是防止系统进入 睡眠。文件 wakelock.c 中定义了 wakelock 的接口,下面是定义代码。

enum { WAKE_LOCK_SUSPEND, /* 防止暂停 */ WAKE_LOCK_IDLE, /*防止低功耗空闲*/ WAKE_LOCK_TYPE_COUNT };

在 wakelock 中,通过如下两个位置让系统从 early_suspend 进入 suspend 状态。 在 wake_unlock 中,当解锁后,如果没有其他的 wakelock,则进入 suspend。

126 第 6 章 常见的驱动平台

当超时锁的定时器超时后,定时器的回调函数会判断有没有其他的 wakelock,若没有则进 入 suspend。 (2)early_suspend。 early_suspend 在 Linux 内核的睡眠过程前被调用。因为背光需要的能耗过大,所以常采用此类 方法在手机系统的设计中操作背光。如在一些在内核中的预先进行处理的事件可以先注册上 early_suspend 函数,这样当系统要进入睡眠之前会首先调用这些注册的函数。 下面是和 Android 休眠唤醒相关的实现文件。

linux_source/kernel/power/main.c linux_source/kernel/power/earlysuspend.c linux_source/kernel/power/wakelock.c linux_source/kernel/power/process.c linux_source/driver/base/power/main.c linux_source/arch/xxx/mach-xxx/pm.c 或 linux_source/arch/xxx/plat-xxx/pm.c

2.Android 休眠

当用户读写“/sys/power/state”时,会调用文件“linux_source/kernel/power/main.c”中的 state_store() 函数。其中,Android 的 early_suspend 会执行:

request_suspend_state(state);

标准的 Linux 休眠会执行:

error = enter_state(state);

函数 state_store()的原型如下所示。

static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n)

函数 request_suspend_state()会调用 early_suspend_work 的工作队列,目的是进入 early_suspend() 函数中。下面是函数 request_suspend_state()的原型。

void request_suspend_state(suspend_state_t new_state)

函数 early_suspend()需要先判断当前请求的状态是否还是 suspend。如果不是则直接退出,如 果是则函数会调用已经注册的 early_suspend 的函数。然后同步文件系统,最后释放 main_wake_lock。 下面是函数 early_suspend()的原型。

static void early_suspend(struct work_struct *work)

在函数 wake_unlock()中删除链表中的 wake_lock 节点,目的是判断当前是否存在 wake_lock。 如果 wake_lock 的数目为 0,则调用工作队列 suspend_work,然后进入 suspend 状态。下面是函数 wake_unlock()的原型。

void wake_unlock(struct wake_lock *lock)

127 Android 驱动开发与移植实战详解

在函数 suspend()中,首先判断当前是否有 wake_lock,如果有则退出;然后同步文件系统,最 后调用 pm_suspend()函数。下面是函数 suspend()的原型。

static void suspend(struct work_struct *work)

在函数 pm_suspend()中调用 enter_state()函数,这样就进入了标准 Linux 的休眠过程。下面是函 数 pm_suspend()的原型。

int pm_suspend(suspend_state_t state)

函数 enter_state()首先检查一些状态参数,再同步文件系统,然后调用 suspend_prepare()冻结进 程,最后调用 suspend_devices_and_enter()让外设进入休眠。下面是函数 enter_state()的原型。

static int enter_state(suspend_state_t state)

函数 suspend_prepare()先通过“pm_prepare_console();”给 suspend 分配一个虚拟终端来输出信 息,再广播一个系统进入 suspend 的通报,关闭用户态的 helper 进程,接下来调用函数 suspend_freeze_processes()来冻结进程,最后会尝试释放一些内存。函数 suspend_prepare()的原型如 下所示。

static int suspend_prepare(void)

在函数 suspend_freeze_processes()中调用 freeze_processes()函数,而在 freeze_processes()函数中 调用 try_to_freeze_tasks()函数完成冻结任务。在冻结过程中,会判断当前进程是否有 wake_lock, 如果有则冻结失败,函数会放弃冻结。下面是函数 freeze_processes()的原型。

static int try_to_freeze_tasks(bool sig_only)

到此为止,所有的进程都已经停止了,内核态进程有可能在停止的时候握有一些信号量,如果 这时在外部设备解锁这个信号量可能会发生死锁,所以建议不要在外部设备的 suspend()里面等待 锁。而且在 suspend 的过程中,很多 log 是无法输出的,所以一旦出现问题就非常难以调试。 接下来回到 enter_state()函数中,当冻结进程完成后调用 suspend_devices_and_enter()函数让外 设进入休眠。在该函数中,首先休眠串口,然后通过 device_suspend()函数调用各驱动的 suspend 函数。 当外设进入休眠后调用“suspend_ops->prepare()“,suspend_ops 是板级的 PM 操作,假如是 s3c6410 ,则被注册在文件“linux_source/arch/arm/plat-s3c64xx/pm.c ”中,在里面只定义了 suspend_ops->enter()函数。

static struct platform_suspend_ops s3c6410_pm_ops = { .enter = s3c6410_pm_enter, .valid = suspend_valid_only_mem, };

然后通过如下代码在多 CPU 中关闭非启动 CPU。

int suspend_devices_and_enter(suspend_state_t state)

128 第 6 章 常见的驱动平台

{ int error;

if (!suspend_ops) return -ENOSYS;

if (suspend_ops->begin) { error = suspend_ops->begin(state); if (error) goto Close; } suspend_console(); suspend_test_start(); error = device_suspend(PMSG_SUSPEND); if (error) { printk(KERN_ERR "PM: Some devices failed to suspend\n"); goto Recover_platform; } suspend_test_finish("suspend devices"); if (suspend_test(TEST_DEVICES)) goto Recover_platform;

if (suspend_ops->prepare) { error = suspend_ops->prepare(); if (error) goto Resume_devices; }

if (suspend_test(TEST_PLATFORM)) goto Finish;

error = disable_nonboot_cpus(); if (!error && !suspend_test(TEST_CPUS)) suspend_enter(state);

enable_nonboot_cpus(); Finish: if (suspend_ops->finish) suspend_ops->finish(); Resume_devices: suspend_test_start(); device_resume(PMSG_RESUME); suspend_test_finish("resume devices"); resume_console(); Close: if (suspend_ops->end) suspend_ops->end(); return error;

Recover_platform:

129 Android 驱动开发与移植实战详解

if (suspend_ops->recover) suspend_ops->recover(); goto Resume_devices; }

接下来调用函数 suspend_enter(),该函数将首先关闭 IRQ,然后调用 device_power_down(),此 函数会调用 suspend_late()函数。此函数是系统真正进入休眠最后调用的函数,通常会在这个函数中 作最后的检查,接下来休眠所有的系统设备和总线。最后调用 suspend_pos->enter() 使 CPU 进入 省电状态。此时整个休眠过程完成了。函数 suspend_enter()的原型如下所示。

static int suspend_enter(suspend_state_t state)

3.Android 唤醒

如果在休眠中系统被中断或者被其他事件唤醒,接下来的代码就从 suspend 完成的地方开始执 行,以 s3c6410 为例,即在文件 pm.c 中的 s3c6410_pm_enter()中的 cpu_init(),然后执行 suspend_enter() 的 sysdev_resume()函数,唤醒系统设备和总线,使能系统中断。 然后回到 suspend_devices_and_enter()函数中,使能休眠时候停止掉的非启动 CPU,并且继续 唤醒每个设备。 当函数 suspend_devices_and_enter()被执行完成后,系统外设已经唤醒,但进程依然是冻结的状 态,返回到 enter_state 函数中,调用 suspend_finish()函数。 在函数 suspend_finish()中解冻进程和任务,这样可以释放更多的用户空间来帮助进程,从而广 播一个系统从 suspend 状态退出的 notify。

6.1.7 Ashmem 驱动程序 Ashmem 是 Android 的内存分配/共享机制,经常被称为匿名共享内存。在 dev 目录下对应的设 备是“/dev/ashmem”。和传统的内存分配机制相比,malloc、anonymous/named mmap,Ashmem 等 的好处是提供了辅助内核内存回收算法的“pin/unpin”机制。

1.Ashmem 驱动的原理

Ashmem 基于 MMAP 系统调用,不同的进程可以将同一段物理内存映射到各自的虚拟地址控 制,这样可以实现共享。假如进程 A 的内存空间范围是 0X0000~0XFFFF,进程 B 的内存空间范 围是 0X0000~0XFFFF,这两个进程想共享一个文件或一段空间时,可以使用 MMAP(比如都想读 取硬盘上的 c.txt,txt 内容为“123”),首先另外开辟第三个内存空间(3 个字节),将硬盘上的“c.txt” 映射到此内存空间中,使此内存空间拥有这个 c.txt,再将 A、B 进程分别映射至这个内存空间,则 现在 A 进程的内核空间范围为 0X0000~0XFFFF+4,B 进程的内核空间范围为 0X0000~0XFFFF+4。 此时进程 A 和 B 都拥有了共同的内存空间,即可以互相共享共同内存空间里的内容了。在创建 mmap 时还可以指定是否可读写,如果 A 或 B 改变了共同内存空间的值,将 c.txt 内容改为了“234”的 话,硬盘上的 c.txt 内容仍然为 123,若想改变,则需要调用 msync 实现硬盘和共享内存区的同步, 而 Ashmem 与 mmap 稍有不同的是,Ashmem 与 cache shrinker 关联起来,可以在适当时机去回收

130 第 6 章 常见的驱动平台

这些共享内存,这点比较智能,而 mmap 则不能。

2.分析 Ashmem 源码

Ashmem 的源代码保存在内存管理的 mm 目录中,实现文件是“android/mydroid/kernel/mm/ ashmem.c”,Ashmem 的头文件是“include/linux/ashmem.h”。 (1)文件 ashmem.h。 在文件 ashmem.h 中定义了 ioctl 命令,例如下面的代码。

#define __ASHMEMIOC 0x77 #define ASHMEM_SET_NAME _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN]) #define ASHMEM_GET_NAME _IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN]) #define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t) #define ASHMEM_GET_SIZE _IO(__ASHMEMIOC, 4) #define ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned long) #define ASHMEM_GET_PROT_MASK _IO(__ASHMEMIOC, 6) #define ASHMEM_PIN _IOW(__ASHMEMIOC, 7, struct ashmem_pin) #define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin) #define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) #define ASHMEM_PURGE_ALL_CACHES _IO(__ASHMEMIOC, 10) #endif /* _LINUX_ASHMEM_H */

(2)文件 ashmem.c。 在文件 ashmem.c 中定义了 Ashmem 类,通过注册 cache shrinker 实现内存回收,通过注册 misc 提供 mmap 接口。Ashmem 使用如下类实现维护工作。 ashmem_area:代表共享内存的区域,在里面有一个 unpinned_list 成员,可以回收挂在这个 list 上的 range。 ashmem_range:将这段区域以页为单位分为多个 range,在里面有一个 LRU 链表,当 cache shrink 回收一个 ashmem_area 的某段内存时,根据 LRU 的原则来选择哪些页面优先被回收。 在文件 ashmem.c 中需要用到两个数据结构,下面是数据结构 ashmem_area 的代码。

struct ashmem_area { char name[ASHMEM_NAME_LEN]; /* 申请的一块 ashmem 的名称 */ struct list_head unpinned_list; /* ashmem 区的链表,用来把所有区连接起来 */ struct file *file; /* 支持 shmem-based 的文件, struct file 结构体定义 */ size_t size; /* 内存映射的大小,以 bytes 为单位 */ unsigned long prot_mask; /* 一个类似于标志位的变量 */ };

上述结构体是 Android 共享内存区,当父进程调用函数 open()时有效,在调用函数 release()时 消亡,通过 ashmem_mutex 来保护其互斥性。 数据结构 struct ashmem_range 表示 unpinned 页的区间,生命周期从 unpin 到 pin,也需要用 “ashmem_mutex”来保证其互斥性。下面是定义此数据结构的代码。

struct ashmem_range { struct list_head lru; /* LRU (最近最少使用)链表 */

131 Android 驱动开发与移植实战详解

struct list_head unpinned; /* unpinned 区域链表 */ struct ashmem_area *asma; /* 相关的 ashmem 区域 */ size_t pgstart; /* 页开始位置(包含在页内) */ size_t pgend; /* 页结束位置(包含在页内) */ unsigned int purged; /* 标志变量, ASHMEM_NOT 或 ASHMEM_WAS_PURGED */ };

接下来定义全局变量 LRU 链表和 LRU 链表的计数器。

/*定义的几个全局变量*/ static LIST_HEAD(ashmem_lru_list); /* LRU 链表的定义 */ static unsigned long lru_count; / * LRU 链表计数器 */ static DEFINE_MUTEX(ashmem_mutex);

/* 用来保证互斥性的 ashmem_mutex 初始化 */ static struct kmem_cache *ashmem_area_cachep __read_mostly; / 指向 ashmem_area 的指针变量 */ static struct kmem_cache *ashmem_range_cachep __read_mostly; /* 用来返回 unpinned 页的 size */ #define range_on_lru(range) ((range)->purged == ASHMEM_NOT_PURGED) /* 修改标志 purged 的值为 ASHMEM_NOT_PURGED ,表示这段内存没有清空 */ #define page_range_subsumes_range(range, start, end) (((range)->pgstart>= (start)) && ((range)->pgend <= (end))) /*判断是否在页外 */ #define page_in_range(range, page) (((range)->pgstart <= (page)) && ((range)->pgend >= (page))) /* 判断是否在页内,参数为 range 结构体、页号 */ #define page_range_in_range(range, start, end) \ (page_in_range(range, start) || page_in_range(range, end) || \ page_range_subsumes_range(range, start, end)) /* 判断是否在页内,参数为 range 结构体、起始地址、结束地址 */ #define range_before_page(range, page) \ ((range)->pgend < (page)) #define PROT_MASK (PROT_EXEC | PROT_READ | PROT_WRITE)

接下来开始看实现函数,下面列出了在文件 ashmem.c 中定义的实现函数。

/* 对 LRU 链表进行添加结点 */ static inline void lru_add( struct ashmem_range *range) /* 对 LRU 链表进行删除结点 */ static inline void lru_del( struct ashmem_range *range) /* 修改 range 结构体定义的内存的大小,可以进行缩短 */ static inline void range_shrink( struct ashmem_range *range, size_t start, size_t end) /* 初始化一个 ashmem_area 结构体 */ static int range_alloc( struct ashmem_area *asma, struct ashmem_range *prev_range, unsigned int purged, size_t start, size_t end) static void range_del( struct ashmem_range *range) /* 删除一个 ashmem_area,就是从链表中删除 */

132 第 6 章 常见的驱动平台

static void range_del( struct ashmem_range *range)

然后用下面的代码定义数据结构 file_operations ashmem_fops。

static struct file_operations ashmem_fops = {  .owner = THIS_MODULE,  .open = ashmem_open,  .release = ashmem_release,  .mmap = ashmem_mmap,  .unlocked_ioctl = ashmem_ioctl,  .compat_ioctl = ashmem_ioctl, }; 下面是和上述数据结构有关的成员函数。

/* 创建一个文件对象,也就是为一个 ashmem_area 结构体申请一段空间 */ static int ashmem_open(struct inode *inode, struct file *file) /* 释放文件对象,也就是释放 ashmem_area 结构体 */ static int ashmem_release(struct inode *ignored, struct file *file) /* 将制定的文件映射到制定的进程区域中 */ static int ashmem_mmap( struct file *file, struct vm_area_struct *vma)

3.实现 Ashmem

在实现 Ashmem 时,先打开设备文件,然后实现 mmap 映射。下面是具体实现流程。 (1)调用函数 ashmem_create_region(),此函数需要完成以下三个工作。 fd = open("/dev/ashmem", O_RDWR); ioctl(fd, ASHMEM_SET_NAME, region_name); // 这一步可选 ioctl(fd, ASHMEM_SET_SIZE, region_size); (2)应用程序调用 mmap 来把 ashmem 分配的空间映射到进程空间。

mapAddr = mmap(NULL, pHdr->mapLength, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);

应用程序可以通过 ioctl 来 pin 和 unpin 某一段映射的空间,目的是提示内核的 page cache 算法 可以把哪些页面回收,这是一般 mmap 所不能做到的。 由此可见,ashmem 以较小的代价(用户需进行额外的 ioctl 调用来设置名字、大小、pin 和 unpin) 获得了一些内存使用的智能性。并且 Ashmem 借助了内核已经有的工具,所以本身实现很小巧, 只有不到 700 行的代码。如果 Ashmem 不使用内核驱动实现,则 pin/unpin 的语义比较难以实现, 即便实现了,效率也不会很高。

6.1.8 Pmem 驱动程序 Pmem 与 Ashmem 都通过 mmap 实现共享,区别是 Pmem 的共享区域是一段连续的物理内存, 而 Ashmem 的共享区域在虚拟空间是连续的,物理内存却不一定连续。

1.Pmem 的原理

在“drivers/misc/pmem.c”中保存了 Pmem 的源代码,Pmem 驱动依赖于 Linux 的 misc device

133 Android 驱动开发与移植实战详解

和 platform driver 框架,一个系统可以有多个 Pmem,默认的是最多 10 个。Pmem 显现了如下操作。 platform driver 的 probe 和 remove 操作。 misc device 的 fops 接口和 vm_ops 操作。 Pmem 在模块初始化时会注册一个 platform driver,在后面的 probe 时会创建 misc 设备文件, 分配内存,完成初始化工作。 Pmem 通过如下三个结构体来维护分配的共享内存。 pmem_info:代表一个 Pmem 设备分配的内存块。 pmem_data:代表该内存块的一个子块。 pmem_region:负责把每个子块分成多个区域。 其中 pmem_data 是分配的基本单位,即每次应用层要分配一块 Pmem 内存,就会有一个 pmem_data 来表示这个被分配的内存块,实际上在 open 的时候,并不是 open 一个 pmem_info 表示 的整个 Pmem 内存块,而是创建一个 pmem_data 以备使用。一个应用可以通过 ioctl 来分配 pmem_data 中的一个区域,并可以把它 map 到进程空间;并不一定每次都要分配和 map 整个 pmem_data 内存块。

2.用户接口

一个进程首先打开 Pmem 设备,通过 ioctl(PMEM_ALLOCATE)分配内存,当 mmap 这段内存 到自己的进程空间后,该进程成为 master 进程。其他进程可以重新打开这个 pmem 设备,并且可 以通过调用 ioctl(PMEM_CONNECT)将自己的 pmem_data 与 master 进程的 pmem_data 建立连接关 系,此时这个进程就成为 client 进程。client 进程可以通过 mmap 将 master Pmem 中的一段或全部 重新映射到自己的进程空间,这样就实现了共享 Pmem 内存。如果是 GPU 或 DSP 则可以通过 ioctl(PMEM_GET_PHYS)获取物理地址进行操作。

6.1.9 Alarm 驱动程序 Alarm 就是一个硬件时钟,前面我们已经知道它提供了一个定时器,用于把设备从睡眠状态唤 醒,同时它也提供了一个在设备睡眠时仍然会运行的时钟基准。在应用层上,有关时间的应用都需 要 Alarm 的支持,源代码位于“drivers/rtc/alarm.c”。

1.Alarm 基础

Alarm 的设备名为“/dev/alarm”,此设备的实现非常简单,打开源码后首先看到:

include

在里面定义了一些和 Alarm 相关的信息,主要包括如下 5 种类型的 Alarm。 _WAKEUP 类型:表示在触发 Alarm 时需要唤醒设备,反之则不需要唤醒设备。 ANDROID_ALARM_RTC 类型:表示在指定的某一时刻触发 Alarm。 ANDROID_ALARM_ELAPSED_REALTIME:表示在设备启动后,流逝的时间达到总时间 之后触发 Alarm。

134 第 6 章 常见的驱动平台

ANDROID_ALARM_SYSTEMTIME 类型:表示系统时间。 ANDROID_ALARM_TYPE_COUN:表示 Alarm 类型的计数。

2.实现 Alarm 驱动

Alarm 返回标记随着 Alarm 的类型而改变。通过定义的宏实现禁用 Alarm、Alarm 等待、设置 Alarm 等功能。下面我们开始分析 Alarm 驱动的具体实现。 (1)alarm_init。 在初始化过程中,首先需要初始化系统时间,通过 platform_driver_register 函数来注册 Alarm 驱动的相关参数,具体代码如下所示。

static struct platform_driver alarm_driver = { .suspend = alarm_suspend, .resume = alarm_resume, .driver = { .name = "alarm" } };

上述代码主要指定了当系统挂起(suspend)和恢复(resume)时所需要的实现分别是 alarm_suspend 和 alarm_resume,同时将 Alarm 设备驱动的名称设置为了“alarm”。 (2)alarm_late_init。 当启动 Alarm 后需要读取当前的 RCT 和系统时间,由于需要确保在这个操作过程中不被中断, 或者在中断之后能告诉其他进程该过程没有读取完成,不能被请求,因此这里需要通过 spin_lock_irqsave 和 spin_unlock_irqrestore 来对其执行锁定和解锁操作。实现代码如下所示。

static int __init alarm_late_init(void) { unsigned long flags; struct timespec system_time; spin_lock_irqsave(&alarm_slock, flags); getnstimeofday(&elapsed_rtc_delta); ktime_get_ts(&system_time); elapsed_rtc_delta = timespec_sub(elapsed_rtc_delta, system_time); spin_unlock_irqrestore(&alarm_slock, flags); ANDROID_ALARM_DPRINTF(ANDROID_ALARM_PRINT_INFO, "alarm_late_init: rtc to elapsed realtime delta %ld.%09ld\n", elapsed_rtc_delta.tv_sec, elapsed_rtc_delta.tv_nsec); return 0; }

(3)alarm_exit。 当 Alarm 退出时需要通过 class_interface_unregiste()函数卸载在初始化时注册的 Alarm 接口, 通过 wake_lock_destroy()函数销毁 SUSPEND lock,并通过 platform_driver_unregister()函数卸载 Alarm 驱动。具体实现代码如下所示。

static void __exit alarm_exit(void) {

135 Android 驱动开发与移植实战详解

class_interface_unregister(&rtc_alarm_interface); wake_lock_destroy(&alarm_rtc_wake_lock); wake_lock_destroy(&alarm_wake_lock); platform_driver_unregister(&alarm_driver); }

(4)添加和移除设备。 接下来讲解函数 rtc_alarm_add_device()和函数 rtc_alarm_remove_device()的实现。在添加设备 时,首先将设备转换成 rtc_device 类型,然后通过 misc_register()函数将自己注册成为一个 Misc 设 备。在此功能阶段的主要对应代码如下所示。

static struct file_operations alarm_fops = { .owner = THIS_MODULE, .unlocked_ioctl = alarm_ioctl, .open = alarm_open, .release = alarm_release, }; static struct miscdevice alarm_device = { .minor = MISC_DYNAMIC_MINOR, .name = "alarm", .fops = &alarm_fops, };

其中 alarm_device 中的“.name”表示设备文件名称,alarm_fops 定义了 Alarm 的常用操作, 包括打开、释放和 I/O 控制。另外还需要通过 rtc_irq_register()函数注册一个 rtc_task,用来处理 Alarm 触发的方法。

6.1.10 USB Gadget 驱动程序 USB Gadget 是 Linux 系统中的 USB 驱动程序,在 Android 系统中,新增了 ADB Garget 驱动来 实现驱动功能。当使用 Garget 驱动时,Android 将作为一个 USB 设备而提供一个 ADB 接口。 在 Linux 系统中,ADB Garget 的功能主要体现在设备端,并且每一个硬件只能选一个。在 ADB Garget 里面包含了 ADB 的调试功能和大容量存储器的功能。 ADB Garget 驱动程序的源码保存在“/drivers/usb/gadget”目录下,具体来说有三个文件实现, 分别是 android.c、f_adb.c 和 f_mass_storage.c。其中 g_android.ko 是由这三个文件编译而来, android.c 依赖于 f_adb.c 和 f_mass_storage.c(注意 f_adb.c 和 f_mass_storage.c 两个文件之间无依赖关 系)。f_adb.c 是实现 ADB 功能的文件,f_mass_storage.c 是标准的文件,包含此文件的目的是为了 同时实现大容量存储器的功能。 在文件 android.c 中注册了一个 MISC 设备“dev/android_adb_enable”,当打开这个设备时表示 用 ADB Garget 的功能。在文件 android.c 中分别注册 adb 和 mass storage 的代码如下所示。

static int __init android_bind_config(struct usb_configuration *c) { struct android_dev *dev = _android_dev; int ret;

136 第 6 章 常见的驱动平台

printk(KERN_DEBUG "android_bind_config\n"); ret = mass_storage_function_add(dev->cdev, c, dev->nluns); if (ret) return ret; return adb_function_add(dev->cdev, c); }

在文件 f_afb.c 中也注册了一个 MISC 设备“dev/android_adb”,此设备支持读写功能。

6.1.11 Android Paranoid 驱动程序 Android Paranoid 是一个网络驱动,Android 对 Linux 内核的网络部分进行了改动,通过改动后 增加了网络认证机制。上述改动特性是通过宏 ANDROID_PARANOID_NETWORK 实现的。此修 改涉及了 Linux 源码中的以下文件。 net/ipv4/af_inet.c:IPV4 协议文件。 net/ipv6/af_inet6.c:IPV6 协议文件。 net/bluetooth/af_bluetooth.c:蓝牙协议文件。 security/commoncap.c:安全性文件。 在上述文件中,前三个是三种不同网络协议中处理协议方面的文件,在逻辑上是并列的关系。 有关网络部分 AID 的定义是在文件“include/linux/android_aid.h”中实现的,对应代码如下所示。

#ifndef _LINUX_ANDROID_AID_H #define _LINUX_ANDROID_AID_H /*不同的 AID 处理不同的内核*/ #define AID_NET_BT_ADMIN 3001 #define AID_NET_BT 3002 #define AID_INET 3003 #define AID_NET_RAW 3004 #endif

在文件 af_inet.c 中会进一步检查 AID,只有符合时才返回 1,如果没有附加此特性则直接返回 1。在文件 commoncap.c 中与之相关的代码如下所示。

int cap_capable(struct task_struct *tsk, const struct cred *cred, struct user_namespace *targ_ns, int cap, int audit) { for (;;) { if (targ_ns != &init_user_ns && targ_ns->creator == cred->user) return 0;

/* 需要必要的能力? */ if (targ_ns == cred->user->user_ns) return cap_raised(cred->cap_effective, cap) ? 0 : -EPERM;

/*尝试了所有父母的 namespaces? */ if (targ_ns == &init_user_ns) return -EPERM;

137 Android 驱动开发与移植实战详解

targ_ns = targ_ns->creator->user_ns; }

通过上述代码实现了对 AID 的判断,如果 AID 符合要求则返回 0,并不会再使用函数 return cap_raised()进行处理。

6.2 Goldfish 设备驱动

在 Goldfish 处理平台中的设备驱动的类别如图 6-2 所示。

Framebuffer驱动

键盘驱动

实时时钟驱动

设备驱动 TTY终端驱动

NandFlash 驱动

MMC驱动

电池驱动

▲图 6-2 设备驱动类别结构

1.Framebuffer 驱动

在 Android 中使用 SurfaceFlinger 作为屏幕合成引擎,功能是管理来自各个窗口的 Surface objects,然后将其写入到 framebuffer 去。每一个 Surface 都是双缓冲的,SurfaceFlinger 使用前 buffer 负责合成,使用后 buffer 负责绘制。一旦绘制完成,Android 通过页翻转操作,交换 Y 轴坐标的偏 移量,选择不同 buffer。在 EGL 显示服务初始化时,如果虚拟 Y 轴分辨率大于实际 Y 轴分辨率, 说明 framebuffer 可以直接使用双缓冲。否则,后 buffer 要复制到前 buffer,这样会导致页交换延迟。 为了提高系统性能,Framebuffer 驱动最好提供双缓冲机制。 Goldfish 的 Framebuffer 驱动中提供了双缓冲机制,用于 Android SDK 中基于 QEMU 的模拟器。 在“linux/drivers /video/”目录下保存了 Framebuffer 对应的源文件。总体抽象设备文件为 fbcon.c, 在此目录下还有与各种显卡驱动相关的源文件。FrameBuffer 设备驱动基于下面的文件。 linux/include/linux/fb.h:定义一些变量结构和宏。

138 第 6 章 常见的驱动平台

linux/drivers/video/fbmem.c:实现设备入口和初始化。 xxxfb.c: 自己添加的设备驱动文件,例如 struct fb_info,有两个实现入口点函数,分别是 xxxfb_init()和 xxxfb_setup()。 (1)文件 fb.h。 Framebuffer 设备在很大程度上依靠了下面的数据结构。 Struct fb_var_screeninfo:描述图形卡的特性的。通常是被用户设置的。 Struct fb_fix_screeninfo:定义了图形卡的硬件特性,是不能改变的。 Struct fb_cmap :描述了和设备无关的颜色映射信息。可以通过 FBIOGETCMAP 和 FBIOPUTCMAP 对应的 ioctl 操作设定或获取颜色映射信息。 Struct fb_info:定义了当前图形卡 framebuffer 设备状态,一个图形卡可能有两个 framebuffer, 在这种情况下,就需要两个 fb_info 结构。这个结构是唯一在内核空间可见的。在这个结构中有一 个 fb_ops 指针,指向驱动设备工作所需的函数集。 Struct fb_ops:用户应用可以使用 ioctl()系统调用来操作设备,这个结构就是用以支持 ioctl() 的这些操作的。 (2)文件 fbmem.c。 文件 fbmem.c 处于 Framebuffer 设备驱动技术的中心位置,既为上层应用程序提供了系统调用, 也为下一层的特定硬件驱动提供接口。底层硬件驱动需要用到此处的接口向系统内核注册自己。文 件 fbmem.c 为所有支持 FrameBuffer 的设备驱动提供了通用的接口。接下来简单分析文件 fbmem.c 的主要内容。 全局变量。 通过两个变量记录了所有 fb_info 结构的实例,用 fb_info 结构描述了显卡的当前状态,所有设 备对应的 fb_info 结构都保存在这个数组中。当一个 FrameBuffer 设备驱动向系统注册自己时,其 对应的 fb_info 结构就会添加到这个结构中,同时 num_registered_fb 自动加 1。对应代码如下所示。

struct fb_info *registered_fb[FB_MAX]; int num_registered_fb;

函数。 在文件 fbmem.c 主要实现了如下两个函数。

register_framebuffer(struct fb_info *fb_info); unregister_framebuffer(struct fb_info *fb_info);

这两个是提供给下层 FrameBuffer 设备驱动的接口,设备驱动通过这两个函数向系统注册或注 销自己。底层设备驱动所要做的任务就是填充 fb_info 结构,然后向系统注册或注销。 (3)我们可以自行编写自己的驱动文件,例如下面的代码。

static struct fb_ops xxxfb_ops = { owner: THIS_MODULE, fb_open: xxxfb_open, /*如果只是想让其实现某功能*/ fb_release: xxxfb_release, fb_get_fix: fbgen_get_fix,

139 Android 驱动开发与移植实战详解

fb_get_var: fbgen_get_var, fb_set_var: fbgen_set_var, fb_get_cmap: fbgen_get_cmap, fb_set_cmap: fbgen_set_cmap, fb_pan_display: fbgen_pan_display, fb_ioctl: xxxfb_ioctl, };

(4)操作 Framebuffer。 操作 Framebuffer 的基本步骤如下所示。 打开一个可用的 FrameBuffer 设备。 通过 mmap 调用把显卡的物理内存空间映射到用户空间。 更改内存空间里的像素数据并显示。 退出时关闭 framebuffer 设备。 例如在下面的代码中,使用 framebuffer 画了一个渐变的进度条,实现文件 framebuf.c 的具体 代码如下所示。

#include #include #include #include #include inline static unsigned short int make16color(unsigned char r, unsigned char g, unsigned char b) { return ((((r >> 3) & 31) << 11) | (((g >> 2) & 63) << 5) | ((b >> 3) & 31) ); } int main() { int fbfd = 0; struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; long int screensize = 0; char *fbp = 0; int x = 0, y = 0; int guage_height = 20, step = 10; long int location = 0; fbfd = open("/dev/graphics/fb0", O_RDWR); if (!fbfd) { printf("Error: cannot open framebuffer device.\n"); exit(1); } printf("The framebuffer device was opened successfully.\n"); if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) { printf("Error reading fixed information.\n"); exit(2); } if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) { printf("Error reading variable information.\n");

140 第 6 章 常见的驱动平台

exit(3); } printf("sizeof(unsigned short) = %d\n", sizeof(unsigned short)); printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel ); printf("xoffset:%d, yoffset:%d, line_length: %d\n", vinfo.xoffset, vinfo.yoffset, finfo.line_length ); screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;; fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,fbfd, 0); if ((int)fbp == -1) { printf("Error: failed to map framebuffer device to memory.\n"); exit(4); } printf("The framebuffer device was mapped to memory successfully.\n"); memset(fbp, 0, screensize); y = (vinfo.yres - guage_height) / 2 - 2; for (x = step - 2; x < vinfo.xres - step + 2; x++) { location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length; *((unsigned short int*)(fbp + location)) = 255; } y = (vinfo.yres + guage_height) / 2 + 2; // Where we are going to put the for (x = step - 2; x < vinfo.xres - step + 2; x++) { location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length; *((unsigned short int*)(fbp + location)) = 255; } x = step - 2; for (y = (vinfo.yres - guage_height) / 2 - 2; y < (vinfo.yres + guage_height) / 2 + 2; y++) { location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length; *((unsigned short int*)(fbp + location)) = 255; } x = vinfo.xres - step + 2; for (y = (vinfo.yres - guage_height) / 2 - 2; y < (vinfo.yres + guage_height) / 2 + 2; y++) { location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length; *((unsigned short int*)(fbp + location)) = 255; } for ( x = step; x < vinfo.xres - step; x++ ) { for ( y = (vinfo.yres - guage_height) / 2; y < (vinfo.yres + guage_height) / 2; y++ ) { location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length; if ( vinfo.bits_per_pixel == 32 ) { *(fbp + location) = 100; // Some blue *(fbp + location + 1) = 15+(x-100)/2; // A little green *(fbp + location + 2) = 200-(y-100)/5; // A lot of red *(fbp + location + 3) = 0; // No transparency

141 Android 驱动开发与移植实战详解

} else { //assume 16bpp unsigned char b = 255 * x / (vinfo.xres - step); unsigned char g = 255; // (x - 100)/6 A little green unsigned char r = 255; // A lot of red unsigned short int t = make16color(r, g, b); *((unsigned short int*)(fbp + location)) = t; } } usleep(200); } munmap(fbp, screensize); close(fbfd); return 0; }

2.键盘驱动

Goldfish 平台中的键盘驱动是 Goldfish_events,下面是其源码路径。

drivers/input/keyboard/goldfish_events.c

下面是在文件 goldfish_events.c 中定义枚举的代码。

enum { REG_READ = 0x00, REG_SET_PAGE = 0x00, REG_LEN = 0x04, REG_DATA = 0x08, PAGE_NAME = 0x00000, PAGE_EVBITS = 0x10000, PAGE_ABSDATA = 0x20000 | EV_ABS, };

下面是定义数据结构 event_dev 的代码。

struct event_dev { struct input_dev *input; int irq; unsigned addr; char name[0]; };

下面是进行模块初始化的对应代码。

module_init(events_init); static int __devinit events_init(void){ return platform_driver_register(&events_driver); }

通过初始化实现注册功能,对应代码如下所示。

142 第 6 章 常见的驱动平台

static struct platform_driver events_driver = { .probe = events_probe, .driver = { .name = "goldfish_events", },

在函数 platform_driver_register()中会执行下面的代码。

drv->driver.bus = &platform_bus_type; if (drv->probe) drv->driver.probe = platform_drv_probe;

最后调用到函数 platform_drv_probe(),下面是定义此函数的实现代码。

static int platform_drv_probe(struct device *_dev){ struct platform_driver *drv = to_platform_driver(_dev->driver) struct platform_device *dev = to_platform_device(_dev); return drv->probe(dev); }

在上述函数代码中,“to_platform_driver(_dev->driver)”的功能是返回一个 platform_driver 型的 指针,而“to_platform_device(_dev)”的功能是返回一个 platform_device 的指针。

3.实时时钟驱动

Goldfish 平台中的实时时钟驱动就是 RTC 设备,这是 Linux 中的一种标准驱动程序,在用户空 间提供了设备节点,例如 MISC 和自定义字符设备。下面是其源码路径。

kernel/drivers/rtc/rtc-goldfish.c 其中 goldfish_rtc_read_time()是其读取时间的调用函数,下面是定义此函数的代码。

static int goldfish_rtc_read_time(struct device *dev, struct rtc_time *tm) { struct timespec now = current_kernel_time(); rtc_time_to_tm(now.tv_sec, tm); return 0; } 函数 int goldfish_rtc_probe()是一个探测函数,定义代码如下所示。

static int goldfish_rtc_probe(struct platform_device *pdev) { int ret; struct goldfish_rtc *qrtc; qrtc = kzalloc(sizeof(*qrtc), GFP_KERNEL); if(qrtc == NULL) { ret = -ENOMEM; goto err_qrtc_alloc_failed; } platform_set_drvdata(pdev, qrtc);

143 Android 驱动开发与移植实战详解

// r = platform_get_resource(pdev, IORESOURCE_MEM, 0); // if(r == NULL) { // ret = -ENODEV; // goto err_no_io_base; // } // qrtc->base = IO_ADDRESS(r->start - IO_START); // qrtc->irq = platform_get_irq(pdev, 0); // if(qrtc->irq < 0) { // ret = -ENODEV; // goto err_no_irq; // } qrtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &goldfish_rtc_ops, THIS_MODULE); if (IS_ERR(qrtc->rtc)) { ret = PTR_ERR(qrtc->rtc); goto err_rtc_device_register_failed; }

// ret = request_irq(qrtc->irq, goldfish_rtc_interrupt, 0, pdev->name, qrtc); // if(ret) // goto request_irq;

return 0;

// free_irq(qrtc->irq, qrtc); //request_irq: rtc_device_unregister(qrtc->rtc); err_rtc_device_register_failed: //err_no_irq: //err_no_io_base: kfree(qrtc); err_qrtc_alloc_failed: return ret; }

由此可见,RTC 驱动的主要功能是实现探测和读取时间,模拟实现时钟功能。

4.TTY 终端驱动

Goldfish 平台中的 TTY 终端驱动提供了虚拟串口功能,在如下文件中保存了其实现源码。

drivers/char/goldfish_tty.c

Goldfish 的 TTY 中断驱动程序在用户空间有三个设备,对应的结点分别是 dev/ttyS0、dev/ttyS1 和 dev/ttyS2。TTY 终端驱动只支持写操作,驱动程序写功能是通过文件 goldfish_tty.c 的 goldfish_tty_do_write()函数实现的,下面是定义此函数的实现代码。

static void goldfish_tty_do_write(int line, const char *buf, unsigned count) { unsigned long irq_flags;

144 第 6 章 常见的驱动平台

struct goldfish_tty *qtty = &goldfish_ttys[line]; uint32_t base = qtty->base; spin_lock_irqsave(&qtty->lock, irq_flags); writel(buf, base + GOLDFISH_TTY_DATA_PTR); writel(count, base + GOLDFISH_TTY_DATA_LEN); writel(GOLDFISH_TTY_CMD_WRITE_BUFFER, base + GOLDFISH_TTY_CMD); spin_unlock_irqrestore(&qtty->lock, irq_flags); }

5.NandFlash 驱动

Goldfish 平台中的 NandFlash 驱动提供了对 Flash 设备的支持,其实现源码被保存在如下文件中。

kernel/drivers/mtd/devices/goldfish_nand.c kernel/drivers/mtd/devices/goldfish_nand_reg.h

NandFlash 驱动程序是标准的 MTD 驱动程序,所以 Goldfish 的 Nand 驱动程序会为每一个分区 构建字符设备和块设备。在同一个分区中,可能会有两个字符设备分别用于读写操作和只读操作。

6.MMC 驱动

Goldfish 平台中的 MMC 驱动程序是标准的 MMC 主机驱动程序,在手机应用中常用于实现 SD 卡驱动,有时也被称为多媒体驱动。MMC 驱动的标准实现源码被保存在文件 kernel/drivers/mmc/host/goldfish.c 中。当有 MMC 或者 SD 卡注册时,才会使用 MMC 驱动程序。

7.电池驱动

在文件 kernel/drivers/power/goldfish_battery.c 中保存了 Goldfish 平台中的电池驱动的标准源码。 这里的电池驱动是一个 power_supply 驱动程序,能够读取电池设备的电量属性,例如剩余电量和 总电量等。获取属性功能是通过函数 goldfish_ac_get_property()实现的,下面是定义此函数的代码。

static int goldfish_ac_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct goldfish_battery_data *data = container_of(psy, struct goldfish_battery_data, ac); int ret = 0;

switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_AC_ONLINE); break; default: ret = -EINVAL; break; } return ret; }

145 Android 驱动开发与移植实战详解

6.3 MSM 内核和驱动

在本节将简要介绍 MSM 内核的基本知识,并简要讲解内核移植和各种驱动的基本知识,为读 者步入本书后面知识的学习打下坚实的基础。

6.3.1 MSM 基础 MSM 是美国高通公司的应用处理器,是 Android 系统最常用的处理器产品之一。高通公司以 其 CDMA(码分多址)数字技术为基础,开发并提供富于创意的数字无线通信产品和服务。如今, 美国高通公司正积极倡导全球快速部署 3G 网络、手机及应用。作为一项新兴技术,CDMA 正迅速 风靡全球并已占据 20%的无线市场。目前,全球 CDMA 用户已超过 2.56 亿,遍布 70 个国家的 156 家运营商已经商用 3GCDMA 业务。2002 年,高通公司芯片销售创历史佳绩;1994 年至今,高通 公司已向全球包括中国在内的众多制造商提供了累计超过 15 亿多枚芯片。

1.常见 MSM 处理器产品

MSM 处理器汇集了多款经典产品,在其发展过程中主要有如下几种处理器产品。 (1)MSM7200。 MSM7200 解决方案支持上行密集型(uplink-intensive)服务,例如 IP 语音(Vo I P )、3D 多人 无线游戏以及实时共享高质量视频和图像的一按式多媒体(push-to-multimedia)应用。此外, MSM7200 芯片组还支持大容量附件电子邮件的发送和接收,从而进一步提高企业效率。 MSM7200 芯片组支持的下行链路的数据传输速率高达 7.2 Mbps,上行链路的数据传输速率高 达 5.76 Mbps,这一速率高于有线宽带连接的速率。作为融合平台的一部分,MSM7200 还支持第 三方操作系统,从而进一步将消费类电子产品功能和无线通信功能融合在一起。 高通 MSM7200 芯片的 CPU 部分主频最高达 400MHz。采用双核构架,有一个 400MHz 的 Arm11 核心负责程序部分,一个频率为 274MHz 的 Arm9 核心负责通信,拥有高速的网络接口,可以支持 GPRS、EDGE、WCDMA、HSDPA、HSUPA 等数据连接,另外 MSM7200 还可以提供 Java 硬件加 速,拥有独立的音频处理模块,内建 Q3Dimension 3D 渲染引擎,支持 OpenGL ES 3D 图形加速, 拥有每秒 400 万多边形计算、133 万像素填充能力。从硬件上支持 H.263 以及 H.264 的视频解码。 在摄像头方面可以支持并且还内建 GPS 模块。可以说 MSM 是一块高度集成的处理器,而且性能 非常强劲。 (2)MSM7201A。 MSM7201A 是单芯片、双核的解决方案,可以提供高速数据处理功能、硬件加速多媒体功能、 3D 图形以及嵌入式多模 3G 移动宽带连接以实现完美的无线体验。 MSM7201A 芯片组内建 3D 图形处理模块,以及嵌入模式的 3G 连接。它还具备高速数据传输 以及处理功能,同时支持硬件加速技术。这样的硬件设计丰富了 Android 平台的功能,支持多样化 的应用服务,为用户带来新鲜的个性体验。这个主频为 528MHz 的 MSM7201A 芯片组已经在 HTC Touch Diamond 和 Touch Pro 这两款机器上使用了。

146 第 6 章 常见的驱动平台

MSM7201A 芯片组支持高分辨率的图像以及视频播放,流媒体功能表现也很出色,支持包括 YouTube 在内的服务。300 万像素的摄像头可以有效地扫描条形码,用户可以在网上查找到相关物 品的售价,这样方便比较同样商品的售价。SM7201A 芯片组支持 GPS 卫星定位功能。而且高通公 司透露 MSM7201A 芯片组将会在未来其他制造商的 Android 平台手机上使用。 (3)QSD8250。 QSD8250 支持 HSPA 数据传输,下行速率可达 7.2Mbps,上行达 5.76Mbps,并提供全向后兼 容(full backward compatibility)。双模的 QSD8650 支持 HSPA 及 CDMA2000 1xEV-DO Rev.B,并 提供全向后兼容。此两款解决方案均含有 1GHz 的微处理器核心,搭配高通第六代以 600MHz 运作 的 DSP 核心,可提供随开即用(instant-on)及全时连线(always-connected)的使用者体验。 Snapdragon 支持高传真影像解码、1200 万像素的照相功能、GPS、移动电视(含 MediaFLO、 DVB-H 和/或 ISDB-T 标准)、Wi-Fi 及蓝牙功能,可协助装置制造商设计即时、无缝连线的轻薄手机。 (4)QSD8650。 QSD8650 和 QSD8250 是高通为不同网络用户而设计的两个芯片解决方案,其中,QSD8650 是双模芯片解决方案,它不仅可以像 QSD8250 那样支持 HSPA 数据传输,而且支持 CDMA2000、 CDMA1×网络以及 EV-DO Rev.B 数据传输。这两个解决方案都包含 1GHz 处理器核心。 (5)高通 MSM8255。 MSM8255 采用 45 纳米级单核心技术的 CPU 芯片,这样的提升有助于省电和缩小芯片尺寸, 省电是比较重要的提升。其次,GPU 的提升也非常明显,相比 QSD8250 内建 Adreno 200 图形处理 芯片,而 MSM8255 为 Adreno 205,虽然数字只差 5,但是性能翻倍,对于 Android 这样吃 GPU 的 系统来说,高性能 GPU 就更有必要了。 MSM8255 是世界首款 1.4GHz 单核,MSM8x55 芯片组平台包括 MSM8255™和 MSM8655™,专 为高性能智能手机和平板电脑设计,以最新设计和优化的多媒体子系统及 45nm 处理技术为特色, 在低能耗的同时提供一流的单核处理性能。这一平台同时包括 APQ8055 处理器,专为平板电脑和 大型展示设备设计,无须使用 WWAN 调制解调器。 CPU(中央处理器)Scorpion 单核,高达 1.4GHz GPU(图形处理器)Adreno™ 205:高级移动 图像。 多媒体高分辨率(720p)视频录像和回放技术,每秒可达 30 帧多音频和视频编解码器,支持 高分辨率 XGA(1024x768)显示,Dolby® 5.1 环绕声,立体 3D 捕捉和回放,支持 1200 万像素双 摄像头。 (6)MSM8260。 MSM8260 是世界首款 1.5GHz 移动异步双核,MSM8x60™芯片组平台包括 MSM8260™和 MSM8660™,满足多任务,高级游戏和娱乐需要,使用低电能 45nm 处理技术,具有更高的整合度 和性能。这一平台同时包括 APQ8060 处理器,专为平板电脑和大型展示设备设计,无须使用 WWAN 调制解调器。

2.Snapdragon 内核介绍

Snapdragon 是高度集成的移动优化系统芯片(SoC),结合了业内领先的 3G/4G 移动宽带技术

147 Android 驱动开发与移植实战详解

与高通公司自有的基于 ARM 的微处理器内核、强大的多媒体功能、3D 图形功能和 GPS 引擎。 Snapdragon 芯片组系列定位 IT 与通信融合,由于具备极高的处理速度、极低的功耗、逼真的 多媒体和全面的连接性,推动了全新智能移动终端的涌现,因此可以使用户获得“永远在线、永远 激活、永远连接”的最佳体验,从而为世界各地的消费者重新定义移动性。Snapdragon 旨在支持功 能先进的智能手机和智能本,并为消费者提供了异于市场上任何其他产品的独特体验。通过更好地 优化定制 CPU 内核,Snapdragon 获得了出色的移动性,兼具前所未有的处理性能与低功耗,使制 造商能够基于 Snapdragon 推出具有全天电池使用时间的轻薄且功能强大的终端产品。 以下为高通公司 Snapdragon 的主要技术优势与特点: 具备出色移动计算性能的增强的基于 ARM 的单核或双核 CPU; 集成 3G/4G 移动宽带; 支持 Wi-Fi 和蓝牙连接; 内置 GPSOne 引擎; 高分辨率视频编解码支持; 高性能的 3D 图像功能; 高清显示屏支持; 支持高达数百万像素的摄像头; 支持包括 MediaFLO 在内的移动广播解决方案; 支持 Android、Brew MP、Chrome 和 Windows Phone 等领先操作系统。 以下为基于高通公司 Snapdragon 设计的智能手机和智能本为消费者带来的主要益处: 优化的功耗管理,电池充电一次可供全天使用; 无处不在的实时连接; 完整的网页浏览功能,呈现丰富的互联网体验; 访问实时、个性化和位置感知内容; 传输并播放储存在本机的高清晰视频内容; 高性能的 3D 用户接口、游戏、地图和其他功能; 高画质的静态图片及视频片段; 通过实时短信、视频会议及聊天功能,进入社交网络等第三方的生产力应用。

6.3.2 移植 MSM 内核 对于 MSM 处理器平台的 Linux 内核来说,和标准的 Linux 内核主要有如下三点差别。 MSM 及其板级平台机器的移植。 MSM 及其板级平台一些虚拟设备的驱动程序。 Android 中特有的驱动程序和组件。 Android 中特有的驱动程序和组件是 Android 中特有的部分,这部分内容在 Android 平台的 Linux 内核中是基本相同的。在 Android 开源网站上,使用 git 工具可以得到 MSM 内核代码。操作 命令如下所示。

$ git clone git://android.git.kernel.org/kernel/msm.git

148 第 6 章 常见的驱动平台

在大多数情况下,在 MSM 内核 git 的代码仓库中有 origin/android-msm-2.6.29 和 origin/android- msm-2.6.29-nexusone 两个分支可以选择。 选择 MSM 通用的 2.6.29 版本,进行编译的方式如下所示。

$ git checkout –b android-msm-2.6.29 origin/android-msm-2.6.29 $ git make ARCH=arm msm_defconfig .config $ git make ARCH=arm CROSS_COMPILE={path}/arm-none-linux-gnueabi-

选择 中使用的 MSM 内核版本,实现编译的方式如下所示。

$ git checkout –b android-msm-2.6.29-nexusone origin/android-msm-2.6.29-nexusone $ git make ARCH=arm msm_defconfig .config $ git make ARCH=arm CROSS_COMPILE={path}/arm-none-linux-gnueabi-

在当前有如下两种使用 MSM 平台的 Linux 内核版本。 针对 MSM7kxx 系列的处理器:config 文件的路径是“arch/arm/configs/msm_defconfig”。 针对 QSD8kxx 系列的处理器(snapdragon) : config 文件的路径为“arch/arm/configs/ mahimahi_defconfig”。 上述两个版本使用了不同的 Linux 代码和配置文件。 例如在 linux-2.6.36 内核中,下面是 MSM 源码的片段。

CONFIG_EXPERIMENTAL=y CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y CONFIG_BLK_DEV_INITRD=y CONFIG_SLAB=y # CONFIG_BLK_DEV_BSG is not set # CONFIG_IOSCHED_DEADLINE is not set # CONFIG_IOSCHED_CFQ is not set CONFIG_ARCH_MSM=y CONFIG_MACH_HALIBUT=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_PREEMPT=y CONFIG_AEABI=y # CONFIG_OABI_COMPAT is not set CONFIG_ZBOOT_ROM_TEXT=0x0 CONFIG_ZBOOT_ROM_BSS=0x0 CONFIG_CMDLINE="mem=64M console=ttyMSM,115200n8" CONFIG_PM=y CONFIG_NET=y CONFIG_UNIX=y CONFIG_INET=y # CONFIG_INET_XFRM_MODE_TRANSPORT is not set # CONFIG_INET_XFRM_MODE_TUNNEL is not set # CONFIG_INET_XFRM_MODE_BEET is not set # CONFIG_INET_DIAG is not set # CONFIG_IPV6 is not set

149 Android 驱动开发与移植实战详解

CONFIG_MTD=y CONFIG_MTD_PARTITIONS=y CONFIG_MTD_CMDLINE_PARTS=y CONFIG_MTD_CHAR=y CONFIG_MTD_BLOCK=y CONFIG_NETDEVICES=y

6.3.3 移植 MSM 在 MSM 处理器中,Linux 移植部分主要保存在如下目录中。 arch/arm/mach-msm/:MSM 平台部分移植的核心部分,其中包含了 qdsp5 和 qdsp6 这两个 目录,它们分别是 5 代 DSP 和 6 代 DSP 在应用处理器端的相关内核代码。 arch/arm/mach-msm/include/mach/:MSM 平台头文件的目录,可以在内核空间中被其他部 分引用。

1.Makefile 文件

Makefile 文件是移植的核心,在 MSM 平台中对应的是 “arch/arm/machlmsm/Makefile”,下面 是其主要代码。

obj-y += io.o irq.o timer.o dma.o memory.o obj-$(CONFIG_ARCH_QSD8X50) += sirc.o obj-y += devices.o pwrtest.o obj-y += proc_comm.o obj-y += dex_comm.o obj-y += amss_para.o obj-y += pmic_global.o obj-y += vreg.o obj-y += pmic.o obj-y += remote_spinlock.o obj-$(CONFIG_ARCH_MSM_ARM11) += acpuclock-arm11.o idle.o obj-$(CONFIG_ARCH_QSD8X50) += arch-init-scorpion.o acpuclock-scorpion.o obj-$(CONFIG_ARCH_MSM7X30) += acpuclock-7x30.o internal_power_rail.o obj-$(CONFIG_ARCH_MSM7X30) += clock-7x30.o arch-init-7x30.o socinfo.o obj-$(CONFIG_ARCH_MSM7X30) += rpc_pmapp.o smd_rpcrouter_clients.o spm.o obj-$(CONFIG_ARCH_MSM7X30) += rpc_hsusb.o obj-$(CONFIG_ARCH_MSM_SCORPION) += idle-v7.o obj-y += gpio.o generic_gpio.o obj-y += nand_partitions.o obj-y += drv_callback.o obj-$(CONFIG_ARCH_QSD8X50) += pmic.o htc_wifi_nvs.o htc_bluetooth.o obj-$(CONFIG_MSM_FIQ_SUPPORT) += fiq_glue.o obj-$(CONFIG_MACH_TROUT) += board-trout-rfkill.o obj-$(CONFIG_MSM_SMD) += smd.o smd_debug.o obj-$(CONFIG_MSM_SMD) += smd_tty.o smd_qmi.o obj-$(CONFIG_MSM_SMD) += smem_log.o obj-$(CONFIG_MSM_SMD) += last_radio_log.o obj-$(CONFIG_MSM_SMD) += htc_port_list.o ifndef CONFIG_ARCH_MSM7X30

150 第 6 章 常见的驱动平台

obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter.o else obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter-7x30.o endif obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter_device.o ifndef CONFIG_ARCH_MSM7X30 obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter_servers.o else obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter_servers-7x30.o endif obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter_xdr.o obj-$(CONFIG_MSM_RPCSERVERS) += rpc_server_dog_keepalive.o obj-$(CONFIG_MSM_RPCSERVERS) += rpc_server_time_remote.o obj-$(CONFIG_MSM_DALRPC) += dal.o obj-$(CONFIG_MSM_DALRPC_TEST) += dal_remotetest.o obj-$(CONFIG_ARCH_MSM7X30) += dal_axi.o obj-$(CONFIG_MSM_ADSP) += qdsp5/ obj-$(CONFIG_MSM_ADSP_COMP) += qdsp5_comp/ obj-$(CONFIG_MSM7KV2_AUDIO) += qdsp5v2/ obj-$(CONFIG_QSD_AUDIO) += qdsp6/ obj-$(CONFIG_MSM_HW3D) += hw3d.o obj-$(CONFIG_PM) += pm.o obj-$(CONFIG_CPU_FREQ) += cpufreq.o

obj-$(CONFIG_HTC_ACOUSTIC) += htc_acoustic.o obj-$(CONFIG_HTC_ACOUSTIC_QSD) += htc_acoustic_qsd.o obj-$(CONFIG_MSM7KV2_AUDIO) += htc_acoustic_7x30.o obj-$(CONFIG_SENSORS_AKM8976) += htc_akm_cal.o obj-$(CONFIG_MACH_HALIBUT) += board-halibut.o board-halibut-panel.o obj-$(CONFIG_MACH_HALIBUT) += board-halibut-keypad.o fish_battery.o clock.o obj-$(CONFIG_MACH_SWORDFISH) += board-swordfish.o clock.o obj-$(CONFIG_MACH_SWORDFISH) += board-swordfish-keypad.o fish_battery.o obj-$(CONFIG_MACH_SWORDFISH) += board-swordfish-panel.o obj-$(CONFIG_MACH_SWORDFISH) += board-swordfish-mmc.o obj-$(CONFIG_MACH_TROUT) += board-trout.o board-trout-gpio.o clock.o obj-$(CONFIG_MACH_TROUT) += board-trout-keypad.o board-trout-panel.o obj-$(CONFIG_MACH_TROUT) += htc_akm_cal.o htc_wifi_nvs.o htc_acoustic.o obj-$(CONFIG_MACH_TROUT) += board-trout-mmc.o board-trout-wifi.o obj-$(CONFIG_MACH_TROUT) += devices_htc.o obj-$(CONFIG_MACH_MAHIMAHI) += board-mahimahi.o board-mahimahi-panel.o clock.o obj-$(CONFIG_MACH_MAHIMAHI) += board-mahimahi-keypad.o board-mahimahi-mmc.o obj-$(CONFIG_MACH_MAHIMAHI) += board-mahimahi-rfkill.o htc_wifi_nvs.o obj-$(CONFIG_MACH_MAHIMAHI) += board-mahimahi-wifi.o board-mahimahi-audio.o obj-$(CONFIG_MACH_MAHIMAHI) += msm_vibrator.o obj-$(CONFIG_MACH_MAHIMAHI) += board-mahimahi-microp.o obj-$(CONFIG_MACH_MAHIMAHI) += htc_acoustic_qsd.o obj-$(CONFIG_MACH_MAHIMAHI) += board-mahimahi-flashlight.o

因为 MSM 处理器既有 ARM11(属于 ARMv6)体系结构的 MSM7k,也有 SCORPION 体系结 构(属于 ARMv7)的 QSD8k,所以其不同的方面在 Makefile 中对此做出了区分。在为 mahimahi

151 Android 驱动开发与移植实战详解

板构建的系统中,CONFIG ARCH_MSM_SCORPION , CONFIG_MSM_QDSP6 , CONFIG MACH_SWORDFISH 和 CONFIG- MACH_MAHIMAHI 等几个宏都为真。

2.驱动和组件

在 MSM 平台中,几乎没有专门针对 Android 的驱动和组件,这是因为其几乎都和硬件无关。 唯一的区别是在配置文件中对 Android 专用驱动和组件的选择不同。下面是文件 MSM_defconfig 的主要代码。

# Power management options CONFIG_PM=y # CONFIG_PM_DEBUG is not set CONFIG_PM_SLEEP=y CONFIG_SUSPEND=y CONFIG_SUSPEND_FREEZER=y CONFIG_HAS_WAKELOCK=y CONFIG_HAS_EARLYSUSPEND=y CONFIG_WAKELOCK=y CONFIG_WAKELOCK_STAT=y CONFIG_USER_WAKELOCK=y CONFIG_EARLYSUSPEND=y # CONFIG_NO_USER_SPACE_SCREEN_ACCESS_CONTROL is not set # CONFIG_CONSOLE_EARLYSUSPEND is not set CONFIG_FB_EARLYSUSPEND=y # CONFIG_APM_EMULATION is not set # CONFIG_PM_RUNTIME is not set CONFIG_ARCH_SUSPEND_POSSIBLE=y CONFIG_NET=y # Android CONFIG_ANDROID=y CONFIG_ANDROID_BINDER_IPC=y CONFIG_ANDROID_LOGGER=y CONFIG_ANDROID_RAM_CONSOLE=y CONFIG_ANDROID_RAM_CONSOLE_ENABLE_VERBOSE=y CONFIG_ANDROID_RAM_CONSOLE_ERROR_CORRECTION=y CONFIG_ANDROID_RAM_CONSOLE_ERROR_CORRECTION_DATA_SIZE=128 CONFIG_ANDROID_RAM_CONSOLE_ERROR_CORRECTION_ECC_SIZE=16 CONFIG_ANDROID_RAM_CONSOLE_ERROR_CORRECTION_SYMBOL_SIZE=8 CONFIG_ANDROID_RAM_CONSOLE_ERROR_CORRECTION_POLYNOMIAL=0x11d # CONFIG_ANDROID_RAM_CONSOLE_EARLY_INIT is not set CONFIG_ANDROID_TIMED_OUTPUT=y CONFIG_ANDROID_TIMED_GPIO=y CONFIG_ANDROID_LOW_MEMORY_KILLER=y

# RCU Subsystem # RTC interfaces # CONFIG_RTC_INTF_SYSFS is not set # CONFIG_RTC_INTF_PROC is not set

152 第 6 章 常见的驱动平台

# CONFIG_RTC_INTF_DEV is not set CONFIG_RTC_INTF_ALARM=y CONFIG_RTC_INTF_ALARM_DEV=y # CONFIG_RTC_DRV_TEST is not set # CONFIG_RTC_DRV_DS1307 is not set # CONFIG_RTC_DRV_DS1374 is not set # CONFIG_RTC_DRV_DS1672 is not set # CONFIG_RTC_DRV_MAX6900 is not set # CONFIG_RTC_DRV_RS5C372 is not set # CONFIG_RTC_DRV_ISL1208 is not set # CONFIG_RTC_DRV_X1205 is not set # CONFIG_RTC_DRV_PCF8563 is not set # CONFIG_RTC_DRV_PCF8583 is not set # CONFIG_RTC_DRV_M41T80 is not set # CONFIG_RTC_DRV_S35390A is not set # CONFIG_RTC_DRV_FM3130 is not set # CONFIG_RTC_DRV_RX8581 is not set # CONFIG_RTC_DRV_RX8025 is not set

# # Generic Driver Options CONFIG_UEVENT_HELPER_PATH="" # CONFIG_DEVTMPFS is not set CONFIG_STANDALONE=y CONFIG_PREVENT_FIRMWARE_BUILD=y CONFIG_FW_LOADER=y # CONFIG_FIRMWARE_IN_KERNEL is not set CONFIG_EXTRA_FIRMWARE="" # CONFIG_DEBUG_DRIVER is not set # CONFIG_DEBUG_DEVRES is not set # CONFIG_SYS_HYPERVISOR is not set # CONFIG_CONNECTOR is not set CONFIG_MTD=y # CONFIG_MTD_DEBUG is not set # CONFIG_MTD_TESTS is not set # CONFIG_MTD_CONCAT is not set CONFIG_MTD_PARTITIONS=y # CONFIG_MTD_REDBOOT_PARTS is not set

CONFIG_MTD_CMDLINE_PARTS=y # CONFIG_MTD_AFS_PARTS is not set # CONFIG_MTD_AR7_PARTS is not set

CONFIG_ANDROID_PMEM=y

CONFIG_ANDROID_PARANOID_NETWORK=y 3.设备驱动 (1)显示驱动。 在 MSM 平台中的显示驱动是 framebuffer,除此之外还调用了一些内部独有的功能。在 MSM

153 Android 驱动开发与移植实战详解

中有如下和显示相关的头文件。 arch/arm/mach-msm/include/mach/msm_fb.h:是 framebuffer 驱动程序的头文件。 include/linux/msm_mdp.h:这显示模块头文件。 在“drivers/video/”目录中,除了有和 framebuffer 驱动程序相关的通用代码之外,MSM 显示 部分的驱动程序主要被保存在“drivers/video/msm/”目录中,其中“gpu”目录为图形处理单元(Graphic Process Unit)部分相关的内容。 文件 msm_fb.c 是 framebuffer 驱动程序的入口文件,另外还有一些和 mddi(Display Digital Interface,一种串行总线,用于连接 LCD)、mdp(Display Processor,显示的主模块,为 framebuffer 核心使用)实现相关的文件。 文件 msm_mddi( mddi.c)、msm_mdp( mdp.c)和 msm_panel(msm fb.c)都是和显示部分相关的。 在文件“msm2/arch/arm/mach-msm/device.c ”中定义了对应 msm_mddi 和 msm_mdp 的 platform_device。在 mddi_client- XXX 中定义了对应 msm_panel 的 platform_device。这 3 个平台驱 动可以在 sys 文件系统的目录“/sys/bus/platform/drivers/”中找到。 MDP 还定义了一种名为 msm_mdp 的 class。在 sys 文件系统的“/sys/class/”保存了其相关信息。 (2)触摸屏驱动。 MSM 的 mahimahip 平台触摸屏的驱动程序保存在“drivers/input/touchscreen”目录中的文件 synaptics_i2c- rmi.c 和 msm_ts.c,它们分别是一个 event 设备。 文件 synaptics_i2c_rmi.c 中的驱动是一个 i2c 的触摸屏的驱动程序,其 i2c_driver 的名称为 synaptics-rmi-ts。在文件 arch/arm/mach-msm/board-mahimahi.c 中定义其对应的 i2c_device,这个驱 动在 sys 文件系统的“sys/bu s/i2c/drivers/synaptics -rmi-ts”目录中,它在 i2c-0 总线上的 id 为 0040。 文件 synaptlcs_i2c_rmi.c 对应的 event 设备是“/dev/inpuUevent2”。文件 msmts.c 是高通 MSM/QSD 触摸屏的驱动程序,在 sys 文件系统的目录“/sys/bus/platform/drivers/”中可以找到其相 关的信息,文件 msm2/arch/arm/mach-msm/device.c 定义了相对应的 platform_device。 (3)按键和轨迹球驱动。 MSM 的 mahimahip 平台系统包含了按键(有 3 个按键)和轨迹球的功能,具体功能是通过文 件 arch/arm/mach-msm/board-mahimahi-keypad.h 实现的,在此文件中注册了名为 mahimahi-keypad 的键盘设备和名称为 mahimahi-nav 的轨迹球设备,对应的设备节点分别为/dev/input/event4 和 /dev/inpuUevent5。 (4)时钟驱动。 MSM 的实时时钟的驱动程序在 drivers/rtc 的 rtc-MSM7kOOa.c 和 hctosys.c 文件中实现。文件 rtc-MSM7kOOa.c 实现了标准的实时时钟。驱动程序名称为 rs30000048:00010000,sys 文件系统可 以在“/sys/bus/platform/drivers/”中找到。 hctosys.c 文件中提供了实时时钟的初始化函数。 (5)摄像头驱动。 在 MSM 平台中,摄像头系统是由经典的“Camera 驱动+Sensor 驱动”方式构成的,此驱动程 序是基于 Video for Linux2 的摄像头驱动程序。 除了 v412 的共用部分以外,MSM 的主要文件保存在“drivers/media/video/msm/”目录中。在

154 第 6 章 常见的驱动平台

里面包含了 msm_v412.c,msm_camera.c,s5k3e2fx.c,msm_vfe8x_proc.c 等文件。 文件 msm_camera.c:是公用的库函数,功能是创建“/dev/msm_camera”中的各个设备文件。 在此主要包含了 3 个自定义的字符设备,其中用 frame0 表示帧数据设备,config0 表示配置设备, contr010 表示控制设备。 文件 include/media/msm_camera.h:是 MSM 摄像头相关的头文件,定义了各种额外的 ioctl 命令。 文件 msm_v412.c:是 v412 驱动程序的实现文件,实现了标准的 Video for Linux 2 的驱动程 序,它实际上是在调用 msm_camera.c 中内容的基础上实现的。 s5k3e2fx 是摄像头传感器的驱动程序,platform_driver 的名称为 msm_camera_s5k3e2fx,这个 名称和 board-mahimahi.c 中定义的 platform_device 相匹配。 s5k3e2fx 是连接在 i2c 总线上的,其地址为 0-0010,在 sys 文件系统中。 (6)无线局域网驱动。 MSM 平台包含了无线局域网,使用 bcm4329 实现此驱动。bcm4329 是集成了蓝牙、无线局域 网和 FM 的芯片,与之相关代码保存在“drivers/net/wireless/bcm4329”目录中。其中在文件 dhd_linux.c 中定义了 platform_driver 的名称为 bcm4329_wlan,其名称和 board-mahimahi-wifi.c 中定义的 platform_device 相匹配。 (7)蓝牙驱动。 MSM 的 mahimahip 平台的蓝牙驱动使用标准的 HCI 驱动,路径在 drivers/bluetooth 中,包括 hci 11.c,hci_h4.c 和 hci_ldisc.c,编译后将生成 hci uart.o 文件。 (8)DSP 驱动。 在 MSM 平台的 DSP(数字信号处理器)具有比较高级的功能,主要被保存在如下的目录中。 arch/arm/mach-msm/qdsp5:MSM7k 系列处理器使用的 5 代 DSP。 arch/arm/mach-msm/qdsp6:QSD8k 系列处理器使用的 6 代 DSP。 其中,在“arch/arm/mach-msm/qdsp6”中包含了如下相关文件。 dal.c:dal 协议文件。 q6audio.c:Audio 系统通用库文件。 audio_ctl.c:音频控制文件。 routing.c:音频路径控制。 pcm_in.c:PCM 输入通道。 pcm_out.c:PCM 输出通道。 mp3.c:MP3 码流直接输出通道。 msm_q6vdec.c:视频解码。 msm_q6venc.c:视频编码。 Audio 系统的头文件是“arch/arm/mach - ms m/include/mach/msm_qdsp6_audio.h”。 MSM 视频编解码的头文件在“include/linux/”目录中,由如下 2 个文件实现。 msm_q6vdec.h:视频解码器头文件。 msm_q6venc.h:视频编码器头文件。

155 Android 驱动开发与移植实战详解

q6venc 是视频编码器在用户空间的节点,是一个 MISC 字符设备,vdec 是视频解码器在用户 空间的节点,是一个自定义的字符设备。

6.3.4 高通特有的组件 高通公司处于移动处理器的领先地位,所以在 MSM 处理器中还包含了很多高通公司所独有的 组件驱动,这些驱动的实现文件被保存在“arch/arm/mach-msm/”目录中,具体来说主要包括下面 的文件。 smd_private.h:定义了共享内存相关的结构和内存区域等。 smd.c:定义了共享内存的部分底层机制的实现。 proc_comm.c:定义了处理器间简单远程命令接口实现。 smd_rpcrouter.c:定义了 ONCRPC 实现部分。 smd_rpcrouter_device.c:定义了 ONCRPC 实现部分。 smd_rpcrouter_servers.c:定义了 ONCRPC 实现部分。 (1)SMEM。 SMEM(Shared Memory)用于管理共享内存的区域。有静态和动态两种区域。静态区域一般 是定义好的,可以由两个 CPU 分别直接访问。而动态区域一般通过 smem 的分配机制来分配。 SMEM 是最基础的共享内存管理机制,所有使用共享内存的通信机制或协议都基于它来实现。 区域很多,有用于存放基本的版本等信息的,也有用于实现简单的 RPC 机制的,还有分配 Buffer 以用于大量数据传输的。 SMEM 的区域定义在 arch/arm/mach-msm/目录 smd_private.h 中,实现代码大多在该目录下的 smd.c 文件中。 (2)SMSM。 SMSM 利用 SMEM 中 SMEM_SMSM_SHARED_STATE 等区域,传送两个 CPU 的状态信息, 诸如 modem 重启、休眠等状态。 当 SMSM 信息变化后,通常通过中断来通知到另一个处理器。 (3)PROC COMM。 PROC COMM 使用 SMEM 中的最前面一个区域:SMEM_PROC_COMM。它是一套应用处理 器向 MODEM 发送简单命令的接口。 PROC COMM 能传递的信息非常有限,仅能传递两个 uint32 的数据作为参数,也只能接受两个 uint32 的数据,加一个 boolean 作为返回值。但相对于后面提到的 RPC,PROC COMM 更轻量级。 PROC COMM 定义在文件 proc_comm.c 中,通常应用处理器会使用 msm_proc_comm 接口函数 来发送命令,并通过轮询进行等待返回。注意需要支持的命令,在 Modem 设备启动时需要注册好 对应的处理程序。 常用的 PROC COMM 命令如下所示。 SMEM_PROC_COMM_GET_BAIT_LEVEL:获取电池电量级别。 SMEM_PROC_COMM_CHG_IS_CHARGING:判断是否在充电。 SMEM_PROC_COMM_POWER_ DOWN:关机。

156 第 6 章 常见的驱动平台

SMEM_PROC_COMM_RESET_MODEM:重启 modem。 (4)SMD。 SMD 用于处理器之间,是一套通过共享内存,同步大量数据的协议。目前 SMD 支持 64 个通 道,其中 36 个已经定义。分别用于蓝牙、RPC、modem 数据链接等。为了防止冲突,每个通道使 用两路连接,将发送和接收分开。 SMD 使用 SMEM 中的对应区域分配适当大小的缓冲,并定义了详细的协议,用于控制传输的 开启、停止等。控制的标记类似于 RS-232,而且支持流控。 SMD 支持 stream 模式和 packet 模式。后者会对数据进行封包,保证对端获取到的数据与传送 时分块一致。 SMD 主要是在文件 smd.c 中实现的,在里面有一整套如下函数的接口。 smd_open:打开一个 smd 通道。 smd_close:关闭一个 smd 通道。 smd_read:从一个通道中读取。 smd_write:写入到一个通道。 smd_alloc_channel:分配一个通道。 (5)ONCRPC。 RPC 的含义为 Remote Procedure Calls(远程过程调用)。此处特指处理器间的远程过程调用。 在高通平台中,这一机制又叫 ONCRPC(Open Network Computing Remote Procedure Call),以 下 提 及 ONCRPC,都是特指高通平台上的具体实现。 ONCRPC 基于共享内存上的 SMD 实现。使应用处理器端的应用程序,可以直接访问 modem 端的服务,支持的服务如下所示。 Call Manager (CM API)。 Wueless Messaging Service (WMS API)。 GSDI (Sm,USIM)。 GSTK (Toolkit)。 PDSM API (GPS)。 另外,ONCRPC 基于服务端、客户端的思想构建,代码分布在 smd_rpcrouter 开头的源码文件 中。服务端实现到 modem 的具体服务访问、而客户端暴露透明的 API 给用户程序调用。用户程序 如果需要使用 ONCRPC,需要链接 ONCRPC-shared、AMSS RPC exported 等库。

6.4 OMAP 内核和驱动

在本节将简要介绍 OMAP 内核的基本知识,并简要讲解内核移植和各种驱动的基本知识。

6.4.1 OMAP 基础 OMAP 是德州仪器(TI)公司的应用处理器,是 Android 系统最常用的处理器产品之一。在本 节将简要介绍 OMAP 处理器的基本知识,为读者步入本书后面知识的学习打下基础。

157 Android 驱动开发与移植实战详解

1.OMAP 简介

OMAP 是一款面向多操作系统(包括 PalmOS5.0、PocketPC2002 和通信领域的 Symbian)的 高性能低功耗处理器。它集成了包括一个数字协处理器在内的多媒体单元,并且加入 GSM/GPRS 接口和蓝牙无线协议等一些当前的高级功能。由于其较低的主频 150Mhz 和广泛的支持性能,OMAP 获得了 Palm 公司的认可,成为其 Palm OS5 产品的标准处理器。 OMAP 处理器的优点是支持接口全面,并且具有较低的功耗和不错的性能表现。其在 Palm OS5 系统上的运用很好地延续了 Palm 一向给人的省电、程序效率高的印象。其缺点是耗电基本和旧款 的彩色机型持平,而且面对处理 MPEG 流和一些解码动作的“硬杀伤”应用的时候,其绝对性能 不及 StrongARM 等。

2.OMAP 处理器的产品

OMAP 处理器汇集了多款经典产品,在其发展过程中主要有如下几种处理器产品。 (1)OMAP 2 系列。 早期诺基亚 S60 机型大多数使用的是 OMAP 2420 处理器,虽然在 N96、N97 等机型中分别采 用了意法半导以及飞思卡尔处理器,但在整体系统性能方面并没有提升,所以在后续推出的 N900 中还是回到了德州仪器(TI)怀抱,转而使用 OMAP 3430 型处理器。 (2)OMAP 3 系列。 早期 OMAP 3 系列处理器并非只有诺基亚一家采用,首款支持 720P 摄像的 S60 手机三星 i8910 便是采用了 OMAP 3430 型处理器(运行主频 800MHz),而首款搭载 1200 万像素拍照镜头的 S60 手机——索尼爱立信 U_1 则是采用了 OMAP 2420 型处理器(运行频率 600MHz),此外,摩托罗拉 的复兴之作 Droid 以及 Milestone 也都采用了 OMAP 3430 型处理器(运行频率 600MHz)。由此可 见,从 2009 年以来,三星、索尼爱立信、诺基亚以及摩托罗拉所推出的阶段性旗舰,都纷纷采用 了 OMAP 34x0 系处理器。 从 2010 年开始,作为 OMAP 34x0 系列处理器的升级产品,OMAP35x0 和 36x0 系列处理器成 为新的标杆产品。例如索尼爱立信 U5i 便是采用了最新的 OMAP 3630 处理器,在 45 纳米制程下 具备着更低的电力消耗以及更强的性能。 (3)OMAP 35xx 系列。 作为 OMAP 34x0 系列处理器的“正序”升级系列,德州仪器推出的 OMAP 35x0 系列中性能 指标最为抢眼的莫过于 OMAP 3530 型处理器,运行主频达到 720MHz(ARM Cortex-A8 架构),而 DSP 处理器运行频率则达到了 520MHz。同系列中的 OMAP 3525 处理器的两项指标分别为 600MHz 以及 430MHz。而定位较为大众化的 OMAP 3515 以及 3503 两款处理器则省略了 DSP 处理器,核 心运行频率为 600MHz。 OMAP3530 处理器(720MHz)的主要特性如下所示。 720 MHz ARM Cortex-A8 内核。 支持 1400 Dhrystone 每秒百万条指令(MIPS)。 520 MHz C64x+ DSP。

158 第 6 章 常见的驱动平台

用于加速 3D 图形的 POWERVR SGX 显示与游戏效果子系统支持。 (4)OMAP 36xx 系列。 这是当前德州仪器的最高级产品,最先推出的四款 OMAP 36xx 系列处理器采用了全新的 45 纳米生产工艺,能够在 12*12mm 或 14*14mm 的封装单位(BGA 封装)内集成更多晶体管,在提 供最高 1GHz 运行主频的同时,能够带来最高 75%的图形性能提升以及 25%的电力节省。

3.开发平台

德州仪器(TI)为 OMAP 处理器推出了专门的移动开发平台—— Zoom,该平台不仅可为移 动应用开发人员提供所需的所有工具,而且还有助于加速创新型应用的上市进程。这款由 Logic 公司设计、开发并制造的新型平台性能可靠,能为智能电话与移动因特网设备(MID)应用开发人 员提供无线连接技术、增强型影像、视频、显示技术及软件,以确保可靠而小巧的手持设备特性, 并提供 3G 调制解调器以及在手掌中即可支持大屏幕体验的 DLP Pico 投影技术模块等选项,充 分满足开发人员在领先的移动操作系统(如 Android Mobile Platform、Linux、LiMo、Symbian OS 以 及 Microsoft Windows Mobile 等)上实现各种先进应用的需求。

6.4.2 OMAP 内核 OMAP 是德州仪器公司的应用处理器,Android 使用的是 OMAP3 系列的处理器。在 Android 代码库中公开了对应的 MSM 的源代码,可以使用 git 工具得到 MSM 内核代码,下面是实现命令:

$ git clone git://android.git.kernel.org/kernel/omap.git

在 OMAP 处理器中,Zoom 平台的 config 路径为“arch/arm/configs/zoom2_defconfig”,下面是 此文件的主要内容。

//设置体系结构 CONFIG_ARM=y CONFIG_SYS_SUPPORTS_APM_EMULATION=y CONFIG_GENERIC_GPIO=y CONFIG_GENERIC_TIME=y CONFIG_GENERIC_CLOCKEVENTS=y CONFIG_MMU=y CONFIG_ARCH_OMAP=y //下面设置 OMAP 处理器使用的特性 # TI OMAP Implementations # CONFIG_ARCH_OMAP_OTG=y # CONFIG_ARCH_OMAP1 is not set # CONFIG_ARCH_OMAP2 is not set CONFIG_ARCH_OMAP3=y //下面设置板级类型 # # OMAP Feature Selections # # CONFIG_OMAP_DEBUG_POWERDOMAIN is not set

159 Android 驱动开发与移植实战详解

# CONFIG_OMAP_DEBUG_CLOCKDOMAIN is not set # CONFIG_OMAP_SMARTREFLEX is not set # CONFIG_OMAP_RESET_CLOCKS is not set CONFIG_OMAP_BOOT_TAG=y CONFIG_OMAP_BOOT_REASON=y # CONFIG_OMAP_COMPONENT_VERSION is not set # CONFIG_OMAP_GPIO_SWITCH is not set CONFIG_OMAP_MUX=y CONFIG_OMAP_MUX_DEBUG=y CONFIG_OMAP_MUX_WARNINGS=y CONFIG_OMAP_MCBSP=y # CONFIG_OMAP_MBOX_FWK is not set CONFIG_OMAP_IOMMU=y # CONFIG_OMAP_MPU_TIMER is not set CONFIG_OMAP_32K_TIMER=y CONFIG_OMAP_32K_TIMER_HZ=128 CONFIG_OMAP_TICK_GPTIMER=1 CONFIG_OMAP_DM_TIMER=y # CONFIG_OMAP_LL_DEBUG_UART1 is not set # CONFIG_OMAP_LL_DEBUG_UART2 is not set # CONFIG_OMAP_LL_DEBUG_UART3 is not set CONFIG_OMAP_LL_DEBUG_UART_EXT=y CONFIG_OMAP_SERIAL_WAKE=y # CONFIG_OMAP_PM_NONE is not set CONFIG_OMAP_PM_NOOP=y # CONFIG_OMAP_PM_SRF is not set CONFIG_ARCH_OMAP34XX=y CONFIG_ARCH_OMAP3430=y # # OMAP Board Type # # CONFIG_MACH_NOKIA_RX51 is not set # CONFIG_MACH_OMAP_LDP is not set CONFIG_MACH_OMAP_ZOOM2=y CONFIG_WIFI_CONTROL_FUNC=y # CONFIG_TIWLAN_SDIO is not set # CONFIG_MACH_OMAP_3430SDP is not set # CONFIG_MACH_OMAP3EVM is not set # CONFIG_MACH_OMAP3_BEAGLE is not set # CONFIG_MACH_OVERO is not set # CONFIG_MACH_OMAP3_PANDORA is not set //下面是 CPU 的设置内容 # # Processor Type # CONFIG_CPU_32=y CONFIG_CPU_32v6K=y CONFIG_CPU_V7=y CONFIG_CPU_32v7=y CONFIG_CPU_ABRT_EV7=y

160 第 6 章 常见的驱动平台

CONFIG_CPU_PABRT_IFAR=y CONFIG_CPU_CACHE_V7=y CONFIG_CPU_CACHE_VIPT=y CONFIG_CPU_COPY_V6=y CONFIG_CPU_TLB_V7=y CONFIG_CPU_HAS_ASID=y CONFIG_CPU_CP15=y CONFIG_CPU_CP15_MMU=y

6.4.3 移植 OMAP 体系结构 在 OMAP 处理器中,其 Zoom 平台的 Linux 内核比较特殊,具体来说,和标准 Linux 内核相比 主要有如下三点差别。 (1)OMAP Zoom 平台机器的移植。 (2)OMAP Zoom 平台的驱动程序。 (3)Android 中特有的驱动程序和组件。 正是因为上述三点差别,所以整个移植过程也是基于上述三个方面。在移植 OMAP 过程中主 要实现如下两个目录。 arch/arm/plat-omap:移植 OMAP 平台部分。 arch/arm/mach-omap2:移植 OMAP 处理器部分。

1.移植 OMAP 平台

在 OMAP 平台中,需要移植的内容保存在“arch/arm/plat-omap”目录中,其中 include 目录是 此平台的头文件。下面是在此目录中和移植相关的主要文件。 (1)文件 Kconfig。 在 Kconfig 文件中配置了 OMAP 平台的各种内容。

if ARCH_OMAP menu "TI OMAP Common Features" config ARCH_OMAP_OTG bool choice prompt "OMAP System Type" default ARCH_OMAP2PLUS config ARCH_OMAP1 bool "TI OMAP1" select COMMON_CLKDEV help "Systems based on omap7xx, omap15xx or omap16xx" config ARCH_OMAP2PLUS bool "TI OMAP2/3/4" select COMMON_CLKDEV help "Systems based on omap24xx, omap34xx or omap44xx"

161 Android 驱动开发与移植实战详解

(2)文件 Kconfig。 在 Makefile 文件中设置了如下通用支持内容。

# # Makefile for the linux kernel. #

# Common support obj-y := common.o sram.o clock.o devices.o dma.o mux.o gpio.o \ usb.o fb.o io.o obj-m := obj-n := obj- :=

# OCPI interconnect support for 1710, 1610 and 5912 obj-$(CONFIG_ARCH_OMAP16XX) += ocpi.o

# omap_device support (OMAP2+ only at the moment) obj-$(CONFIG_ARCH_OMAP2) += omap_device.o obj-$(CONFIG_ARCH_OMAP3) += omap_device.o obj-$(CONFIG_ARCH_OMAP4) += omap_device.o

obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o obj-$(CONFIG_OMAP_IOMMU) += iommu.o iovmm.o obj-$(CONFIG_OMAP_IOMMU_DEBUG) += iommu-debug.o

obj-$(CONFIG_CPU_FREQ) += cpu-omap.o obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o obj-$(CONFIG_OMAP_DEBUG_DEVICES) += debug-devices.o obj-$(CONFIG_OMAP_DEBUG_LEDS) += debug-leds.o i2c-omap-$(CONFIG_I2C_OMAP) := i2c.o obj-y += $(i2c-omap-m) $(i2c-omap-y)

# OMAP mailbox framework obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox.o obj-$(CONFIG_OMAP_PM_NOOP) += omap-pm-noop.o

(3)头文件。 在“arch/arm/plat-omap/include”目录下保存了 OMAP 平台的头文件,在里面有 mach 和 dspbridge 两个子目录。其中子目录“mach”比较重要,在此目录中的很多文件的功能是这个硬件平台在 Linux 移植中约定的名称,例如文件 dma.h 是表示 DMA 信息的头文件,文件 irqs.h 是和中断信息相关的 头文件。子目录 mach 下的文件结构如图 6-3 所示。 在子目录“dspbridge”中包含了和控制 DSP 相关的头文件,因为 OMAP 包含了 ARM 和 DSP 下的双核处理器,为了在 ARM 方面控制 DSP,所以德州仪器采用了 dspbridge 的方式。子目录 dspbridge 下的文件结构如图 6-4 所示。

162 第 6 章 常见的驱动平台

▲图 6-3 子目录 mach 下的文件结构 ▲图 6-4 子目录 dspbridge 下的文件结构

因为 OMAP 处理器比较复杂,所以特意将很多功能做成了库,这些库文件也被保存在此目录 下。例如在文件“arch/arm/plat-omap/include/plat/display.h”中定义了和显示子系统相关的数据结构 和接口,主要代码内容如下所示。

enum omap_color_mode { OMAP_DSS_COLOR_CLUT1 = 1 << 0, /* BITMAP 1 */ OMAP_DSS_COLOR_CLUT2 = 1 << 1, /* BITMAP 2 */ OMAP_DSS_COLOR_CLUT4 = 1 << 2, /* BITMAP 4 */ OMAP_DSS_COLOR_CLUT8 = 1 << 3, /* BITMAP 8 */ OMAP_DSS_COLOR_RGB12U = 1 << 4, /* RGB12, 16-bit container */ OMAP_DSS_COLOR_ARGB16 = 1 << 5, /* ARGB16 */ OMAP_DSS_COLOR_RGB16 = 1 << 6, /* RGB16 */ OMAP_DSS_COLOR_RGB24U = 1 << 7, /* RGB24, 32-bit container */ OMAP_DSS_COLOR_RGB24P = 1 << 8, /* RGB24, 24-bit container */ OMAP_DSS_COLOR_YUV2 = 1 << 9, /* YUV2 4:2:2 co-sited */ OMAP_DSS_COLOR_UYVY = 1 << 10, /* UYVY 4:2:2 co-sited */ OMAP_DSS_COLOR_ARGB32 = 1 << 11, /* ARGB32 */ OMAP_DSS_COLOR_RGBA32 = 1 << 12, /* RGBA32 */ OMAP_DSS_COLOR_RGBX32 = 1 << 13, /* RGBx32 */ };

163 Android 驱动开发与移植实战详解

enum omap_lcd_display_type { OMAP_DSS_LCD_DISPLAY_STN, OMAP_DSS_LCD_DISPLAY_TFT, };

enum omap_dss_load_mode { OMAP_DSS_LOAD_CLUT_AND_FRAME = 0, OMAP_DSS_LOAD_CLUT_ONLY = 1, OMAP_DSS_LOAD_FRAME_ONLY = 2, OMAP_DSS_LOAD_CLUT_ONCE_FRAME = 3, };

enum omap_dss_trans_key_type { OMAP_DSS_COLOR_KEY_GFX_DST = 0, OMAP_DSS_COLOR_KEY_VID_SRC = 1, }; struct omap_dss_board_info { int (*get_last_off_on_transaction_id)(struct device *dev); int num_devices; struct omap_dss_device **devices; struct omap_dss_device *default_device; };

2.移植 OMAP 处理器

“arch/arm/mach-omap2”是 OMAP 处理器的移植目录,在此目录中主要包括下面的文件。 (1)文件 Kconfig。 在 Kconfig 文件中配置了通用 OMAP 中的文件。

# Common support obj-y := id.o io.o control.o mux.o devices.o serial.o gpmc.o timer-gp.o pm.o

omap-2-3-common = irq.o sdrc.o hwmod-common = omap_hwmod.o \ omap_hwmod_common_data.o prcm-common = prcm.o powerdomain.o clock-common = clock.o clock_common_data.o \ clockdomain.o clkt_dpll.o \ clkt_clksel.o obj-$(CONFIG_ARCH_OMAP2) += $(omap-2-3-common) $(prcm-common) $(hwmod-common) obj-$(CONFIG_ARCH_OMAP3) += $(omap-2-3-common) $(prcm-common) $(hwmod-common) obj-$(CONFIG_ARCH_OMAP4) += $(prcm-common) $(hwmod-common) obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o # SMP support ONLY available for OMAP4 obj-$(CONFIG_SMP) += omap-smp.o omap-headsmp.o obj-$(CONFIG_LOCAL_TIMERS) += timer-mpu.o obj-$(CONFIG_HOTPLUG_CPU) += omap-hotplug.o obj-$(CONFIG_ARCH_OMAP4) += omap44xx-smc.o omap4-common.o board-zoom-peripherals.o \ board-flash.o \

164 第 6 章 常见的驱动平台

hsmmc.o \ board-zoom-debugboard.o obj-$(CONFIG_MACH_OMAP_ZOOM3) += board-zoom3.o \ board-zoom-peripherals.o \ board-flash.o \ hsmmc.o \ board-zoom-debugboard.o obj-$(CONFIG_MACH_OMAP_3630SDP) += board-3630sdp.o \ board-zoom-peripherals.o \ board-flash.o \ hsmmc.o obj-$(CONFIG_MACH_CM_T35) += board-cm-t35.o \ hsmmc.o obj-$(CONFIG_MACH_IGEP0020) += board-igep0020.o \ hsmmc.o obj-$(CONFIG_MACH_OMAP3_TOUCHBOOK) += board-omap3touchbook.o \ hsmmc.o obj-$(CONFIG_MACH_OMAP_4430SDP) += board-4430sdp.o \ hsmmc.o obj-$(CONFIG_MACH_OMAP4_PANDA) += board-omap4panda.o \ hsmmc.o obj-$(CONFIG_MACH_OMAP3517EVM) += board-am3517evm.o obj-$(CONFIG_MACH_SBC3530) += board-omap3stalker.o \ hsmmc.o # Platform specific device init code usbfs-$(CONFIG_ARCH_OMAP_OTG) := usb-fs.o obj-y += $(usbfs-m) $(usbfs-y) obj-y += usb-musb.o obj-$(CONFIG_MACH_OMAP2_TUSB6010) += usb-tusb6010.o obj-y += usb-ehci.o onenand-$(CONFIG_MTD_ONENAND_OMAP2) := gpmc-onenand.o obj-y += $(onenand-m) $(onenand-y) nand-$(CONFIG_MTD_NAND_OMAP2) := gpmc-nand.o obj-y += $(nand-m) $(nand-y) smc91x-$(CONFIG_SMC91X) := gpmc-smc91x.o obj-y += $(smc91x-m) $(smc91x-y)

(2)文件 board-zoom2.c。 文件 board-zoom2.c 是 OMAP 机器实现的核心文件。

//下面是定义机器类型 MACHINE_START(OMAP_ZOOM2, "OMAP Zoom2 board") .phys_io = ZOOM_UART_BASE, .io_pg_offst = (ZOOM_UART_VIRT >> 18) & 0xfffc, .boot_params = 0x80000100, .map_io = omap3_map_io, .reserve = omap_reserve, .init_irq = omap_zoom2_init_irq, .init_machine = omap_zoom2_init, .timer = &omap_timer,//当前机器实现的定时器

165 Android 驱动开发与移植实战详解

MACHINE_END //下面是初始化函数 static void __init omap_zoom2_init(void) { omap3_mux_init(board_mux, OMAP_PACKAGE_CBB); zoom_peripherals_init(); board_nand_init(zoom_nand_partitions, ARRAY_SIZE(zoom_nand_partitions), ZOOM_NAND_CS); zoom_debugboard_init(); } //下面是初始化板级的终端系统函数 static void __init omap_zoom2_init_irq(void) { omap2_init_common_hw(mt46h32m32lf6_sdrc_params, mt46h32m32lf6_sdrc_params); omap_init_irq(); omap_gpio_init(); }

(3)文件 devices.c。 文件 devices.c 的功能和其他平台的同名文件类似,即向系统注册各种平台设备。

//下面是 spi1 平台设备的资源和注册内容 static struct omap2_mcspi_platform_config omap2_mcspi1_config = { .num_cs = 4, }; static struct resource omap2_mcspi1_resources[] = { { .start = OMAP2_MCSPI1_BASE, .end = OMAP2_MCSPI1_BASE + 0xff, .flags = IORESOURCE_MEM, }, }; static struct platform_device omap2_mcspi1 = { .name = "omap2_mcspi", .id = 1, .num_resources = ARRAY_SIZE(omap2_mcspi1_resources), .resource = omap2_mcspi1_resources, .dev = { .platform_data = &omap2_mcspi1_config, }, }; //下面是 spi2 平台设备的资源和注册内容 static struct omap2_mcspi_platform_config omap2_mcspi2_config = { .num_cs = 2, }; static struct resource omap2_mcspi2_resources[] = { { .start = OMAP2_MCSPI2_BASE, .end = OMAP2_MCSPI2_BASE + 0xff,

166 第 6 章 常见的驱动平台

.flags = IORESOURCE_MEM, }, }; static struct platform_device omap2_mcspi2 = { .name = "omap2_mcspi", .id = 2, .num_resources = ARRAY_SIZE(omap2_mcspi2_resources), .resource = omap2_mcspi2_resources, .dev = { .platform_data = &omap2_mcspi2_config, }, }; #if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3) || \ defined(CONFIG_ARCH_OMAP4) //下面是 spi3 平台设备的资源和注册内容 static struct omap2_mcspi_platform_config omap2_mcspi3_config = { .num_cs = 2, }; static struct resource omap2_mcspi3_resources[] = { { .start = OMAP2_MCSPI3_BASE, .end = OMAP2_MCSPI3_BASE + 0xff, .flags = IORESOURCE_MEM, }, }; static struct platform_device omap2_mcspi3 = { .name = "omap2_mcspi", .id = 3, .num_resources = ARRAY_SIZE(omap2_mcspi3_resources), .resource = omap2_mcspi3_resources, .dev = { .platform_data = &omap2_mcspi3_config, }, }; #endif #if defined(CONFIG_ARCH_OMAP3) || defined(CONFIG_ARCH_OMAP4) static struct omap2_mcspi_platform_config omap2_mcspi4_config = { .num_cs = 1, }; //下面是 spi4 平台设备的资源和注册内容 static struct resource omap2_mcspi4_resources[] = { { .start = OMAP2_MCSPI4_BASE, .end = OMAP2_MCSPI4_BASE + 0xff, .flags = IORESOURCE_MEM, }, }; static struct platform_device omap2_mcspi4 = { .name = "omap2_mcspi", .id = 4,

167 Android 驱动开发与移植实战详解

.num_resources = ARRAY_SIZE(omap2_mcspi4_resources), .resource = omap2_mcspi4_resources, .dev = { .platform_data = &omap2_mcspi4_config, };

6.4.4 移植 Android 专用驱动和组件 在 OMAP 平台中,专用的 Android 驱动和组件与 Android 系统中的驱动和组件基本相同,所以 整个移植工作也比较简单,唯一的区别是在配置文件中对 Android 专用驱动和组件的选择不同。 OMAP 平台的 Zoom2 的配置文件是 zoom2_defconfig,下面列出了里面的选择内容。

# # Power management options # CONFIG_PM=y # CONFIG_PM_DEBUG is not set # CONFIG_SUSPEND is not set CONFIG_HAS_WAKELOCK=y CONFIG_HAS_EARLYSUSPEND=y CONFIG_WAKELOCK=y CONFIG_WAKELOCK_STAT=y CONFIG_USER_WAKELOCK=y CONFIG_EARLYSUSPEND=y # CONFIG_NO_USER_SPACE_SCREEN_ACCESS_CONTROL is not set # CONFIG_CONSOLE_EARLYSUSPEND is not set CONFIG_FB_EARLYSUSPEND=y # CONFIG_APM_EMULATION is not set CONFIG_ARCH_SUSPEND_POSSIBLE=y CONFIG_NET=y # # Android # CONFIG_ANDROID=y CONFIG_ANDROID_BINDER_IPC=y CONFIG_ANDROID_LOGGER=y CONFIG_ANDROID_RAM_CONSOLE=y CONFIG_ANDROID_RAM_CONSOLE_ENABLE_VERBOSE=y # CONFIG_ANDROID_RAM_CONSOLE_ERROR_CORRECTION is not set # CONFIG_ANDROID_RAM_CONSOLE_EARLY_INIT is not set CONFIG_ANDROID_TIMED_OUTPUT=y CONFIG_ANDROID_TIMED_GPIO=y CONFIG_ANDROID_LOW_MEMORY_KILLER=y # # RCU Subsystem # CONFIG_CLASSIC_RCU=y CONFIG_LOG_BUF_SHIFT=14 CONFIG_GROUP_SCHED=y CONFIG_FAIR_GROUP_SCHED=y

168 第 6 章 常见的驱动平台

CONFIG_USER_SCHED=y

CONFIG_BLK_DEV_INITRD=y CONFIG_INITRAMFS_SOURCE="" CONFIG_CC_OPTIMIZE_FOR_SIZE=y CONFIG_SYSCTL=y CONFIG_ANON_INODES=y CONFIG_PANIC_TIMEOUT=0 CONFIG_EMBEDDED=y CONFIG_UID16=y

OMAP 和 Android 系统实现了完美的融合,有关 Linux、OMAP 和 Android 之间的具体联系, 读者可以登录 http://elinux.org/Android_on_OMAP 来了解更多的信息,如图 6-5 所示。

▲图 6-5 Android on OMAP 页面

6.4.5 OMAP 的设备驱动 了解了 OMAP 内核的移植知识后,接下来开始分析 OMAP 平台中的设备驱动知识。

1.显示驱动程序

OMAP 处理器中的显示驱动就是 SOC 的 DSS 驱动程序,在里面包含了一个主显示屏和两个视

169 Android 驱动开发与移植实战详解

频叠加的显示层。显示系统的库文件被保存在“drivers/video/omap2/omapfb/dss”目录中,主要有 文件 core.c、manager.c、overlay.c、dss.c、omapdss.c、dpi.c 和 wenc.c,上述文件构成了 OMAP 显 示驱动程序的“库”。 文件 core.c 定义了 platform_driver 的名称 omapdss,主显示驱动的 FramBuffer 驱动程序的内容 比较复杂,这些驱动程序保存在“drivers/video/omap2/omapfb/”目录中的如下文件中。

omapfb-main.c omapfb-ioctl.c omapfb-sysfs.c

其中在文件 omapfb-main.c 中定义 platform_driver 的名称为 omapfb。 Framebuffer 的参数最终可以体现在 overlay 的信息中,在“/sys/devices/platform/omapdss/overlay0” 目录下有 overlay0 对应的 framebuffer 的输入图像分辨率、输出图像分辨率和屏幕宽度、对应的输出 通道名称、使用的管理器等。

2.i2c 总线驱动程序

I2C core 的功能是维护 Linux 的 I2C 核心部分,在里面提供了核心的数据结构,I2C 适配器驱 动和设备驱动的注册、注销管理等 API,同时还提供了 I2C 总线读写访问的一般接口(具体的实现 在与 I2C 控制器相关的 I2C adapter 中实现)。 I2C adapter driver 层即 I2C 适配器驱动层,每种处理器平台都有自己的适配器驱动,这些都属 于平台移植相关层。I2C 的职责是为系统中每条 I2C 总线实现相应的读写方法,但是适配器驱动本 身并不会进行任何的通信,而是等待设备驱动调用其函数。 在开机后会首先装载 I2C 适配器驱动,一个适配器驱动支持一条特定的 I2C 总线的读写。一个 适配器驱动通常需要两个模块来描述,分别是 struct i2c_adapter 和 struct i2c_algorithm。 I2C adapter 构造了对 I2C core 层接口的数据结构,并用接口函数向 I2C core 注册了一个适配 器。i2c_algorithm 实现了对 I2C 总线访问的算法,master_xfer 和 smbus_xfer 即 I2C adapter 底层对 I2C 总线读写方法的实现,下面是相关的数据结构。

struct i2c_algorithm { int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); u32 (*functionality) (struct i2c_adapter *); };

I2C 的 device driver 层可以用两个模块来描述,struct i2c_driver 和 struct i2c_client。i2c_client 和 i2c_driver 分别构造对 I2C core 层接口的数据结构,并且通过相关的接口函数向 I2C Core 注册 I2C 设备驱动,相关的数据结构是“i2c_driver”。 Driver 是为 device 服务的,在 i2c_driver 注册时会扫描 i2c bus 上的设备,并实现驱动和设备的 绑定。此处主要有 attach_adapter 和 probe 两种接口,这两种接口分别针对旧的和新的驱动。i2c_client

170 第 6 章 常见的驱动平台

对应 I2C 总线上某个特定的 slave 或者 user space 的某个用户,而此时的 slave 可以动态变化。 I2C adapter 驱动位于“drivers/i2c/busses”目录中,OMAP 的 I2C adapter 驱动程序是 i2c-omap.c。 Andrord 中 Platform device 的注册的代码位于内核中的如下文件中。

arch/arm/plat-omap/i2c.c arch/arm/mach-omap2/board- xxxx.c

Andrord 中 Platform driver 的注册的代码位于内核的如下文件中。

drivers/i2c/busses/i2c-omap.c

该驱动的注册目的是初始化 OMAP3630 的 I2C adapter,提供 I2C 总线传输的具体实现,并且 向 I2C core 注册 I2C adapter。 (1)定义 Platform driver。 下面是在文件“drivers/i2c/busses/ i2c-omap.c”中定义 Platform driver 的代码。

static struct platform_driver omap_i2c_driver = { .probe = omap_i2c_probe, .remove = omap_i2c_remove, .driver = { .name = "i2c_omap", .owner = THIS_MODULE, }, };

(2)注册 Platform driver。 下面是在文件“drivers/i2c/busses/ i2c-omap.c”中注册 platform driver 的代码。

/* I2C may be needed to bring up other drivers */ static int __init omap_i2c_init_driver(void) { return platform_driver_register(&omap_i2c_driver); } subsys_initcall(omap_i2c_init_driver);

当通过函数 platform_driver_register()注册 Platform driver omap_i2c_driver 时,会扫描 platform bus 上的所有设备,因为匹配因子是 name(即 i2c_omap),而之前已经将 name 为“i2c_omap”的 Platform device 注册到 platform bus 上,所以会匹配成功。通过调用函数 omap_i2c_probe(),可以将 设备和驱动绑定。 在文件“drivers/i2c/busses/i2c-omap.c”中涉及了数据结构 omap_i2c_dev,在此结构中定义了 OMAP 的 I2C 控制器,下面是定义此结构的代码。

struct omap_i2c_dev { struct device *dev; void __iomem *base; /*虚拟*/ int irq;

171 Android 驱动开发与移植实战详解

struct clk *iclk; /*接口*/ struct clk *fclk; /* 功能接口*/ struct completion cmd_complete; struct resource *ioarea; u32 speed; /*总线速率,单位是 Khz */ u16 cmd_err; u8 *buf; size_t buf_len; struct i2c_adapter adapter; u8 fifo_size; /*使用大小值*/ u8 rev; unsigned b_hw:1; /*坏的修正,单位 h/w */ unsigned idle:1; u16 iestate; /*保存中断寄存器*/ u16 pscstate; u16 scllstate; u16 sclhstate; u16 bufstate; u16 syscstate; u16 westate; };

3.摄像头和视频输出驱动程序

在 OMAP3 平台中,摄像头和视频输出分别属于两个不同的子系统,具体说明如下所示。 摄像头属于 ISP 子系统,连接的硬件是摄像头传感器。 视频输出属于 DSS 系统,连接的硬件是屏幕。 OMAP 平台的摄像头和视频输出都是基于 video for Linux 2 的驱动程序框架,摄像头调用显示 相应的库实现视频输入功能,而视频输出调用 DSS 相关的库实现视频输出功能。 ISP 驱动程序由“drivers/media/video”目录中的如下文件组成。

isp.c isph3a.c isppreview.c ispresizer.c

OMAP 平台摄像头驱动程序的 v412 驱动主文件是 omap34xxcam.c。 视频输出驱动程序被保存在“drivers/media/video/omap/omap-vout”目录中,其中和下面列出 的文件相关。

omapvout-dss.c omapvout-dss.h omapvout-mem.c omapvout-mem.h omapvout-vbq.c omapvout-vbq.h omapvout.c omapvout.h

172 第 6 章 常见的驱动平台

上述文件都是 v412 的驱动程序,设备节点分别是“dev/video1”和“dev/video2”。

4.触摸屏和键盘驱动程序

OMAP 的 Zoom 平台的输入设备包含了触摸屏和 Qwerty 全键盘。OMAP 的 Zoom 平台的触摸 屏驱动程序被保存在“drivers/input/touchscreen/synaptics_i2c_rmi.c”文件中。 OMAP 的 Zoom 平台的键盘驱动程序保存在文件“drivers/input/keyboard/twl4030_keypad.c”中, 因为 twl4030 使用的是 i2c 的接口,所以这个驱动程序本身经过的是一次封装。 文件 twl4030_keypad.c 中核心的内容是中断处理相关内容,函数 do_kp_irq()是一个标准的 Linux 中断的处理函数,下面是定义此函数的代码。

static irqreturn_t do_kp_irq(int irq, void *_kp) { struct twl4030_keypad *kp = _kp; u8 reg; int ret; ret = twl4030_kpread(kp, ®, KEYP_ISR1, 1); // 调用 twl4030_i2c_read if ((ret >= 0) && (reg & KEYP_IMR1_KP)) twl4030_kp_scan(kp, 0); // 非释放所有的处理 else twl4030_kp_scan(kp, 1); // 释放所有的处理 return IRQ_HANDLED; }

函数 twl4030_kp_scan()负责找到按键的行列,然后调用函数 input_report_key()来汇报信息,主 要代码如下所示。

static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all) { u16 new_state[MAX_ROWS]; int col, row; // ...... 省略部分内容 for (row = 0; row < kp->n_rows; row++) { int changed = new_state[row] ^ kp->kp_state[row]; // ...... 省略部分内容 for (col = 0; col < kp->n_cols; col++) { int key; key = twl4030_find_key(kp, col, row); // ...... 省略部分内容 input_report_key(kp->input, key, // 上报按键消息 new_state[row] & (1 << col)); } kp->kp_state[row] = new_state[row]; } input_sync(kp->input); }

5.实时时钟驱动程序

OMAP 处理器的 Zoom 平台的实时时钟是一个连接在 i2c 总线上的设备,通过 tw14030 来控制。

173 Android 驱动开发与移植实战详解

OMAP 的实时时钟驱动程序在文件“drivers/rtc/rtc-twl14030.c”中实现。其平台驱动名称是 “tw14030_rtc”,可以在 sys 文件系统的“sys/bus/platform/drivers”目录中找到。

6.音频驱动程序

OMAP 音频驱动程序使用了标准的 ALSA 驱动程序框架,ALSA 的核心是能使 CONFIG_SND 系列配置宏。OMAP 的音频驱动程序保存在“sound/soc/omap”目录,在里面最主要的实现文件是 omap-mcbsp.c 和 omap-pcm.c。

7.蓝牙驱动程序

OMAP 平台中的蓝牙设备使用标准的 HCI 驱动程序,被保存在“drivers/bluetooth”目录中, 主要文件是 hci_ll.c、hci_ldisc.c 和 hci_h4.c,编译后将生成 hci_uart.o 文件。

8.DSP 驱动程序

OMAP 处理器包含了 ARM 处理器和 DSP 处理器,其中 DSP 处理器用于辅助实现加速功能, 现在高级的处理器一般都使用双处理器来架构。ARM 和 DSP 之间用 DspBrige 桥来连接,DSP 桥 的内容保存在“drivers/dsp/bridge”目录中,在里面包含了如下目录。 dynload:实现动态加载功能。 gen:实现工具类函数。 hw:是硬件核心功能。 pmgr:能源管理器。 rmgr:资源管理器。 wmd:实现 Bridg Mini Driver 接口。

174

第 7 章 输入系统驱动

从本章内容开始,将详细讲解 Android 各种驱动的实现原理和具体移植知识。在本章将详细讲 解 Android 输入系统驱动的基本知识,详细介绍这些源码的实现原理,为读者步入本书后面知识的 学习打下基础。

7.1 输入系统介绍

Android 输入系统的结构比较简单,实现输入功能的硬件设备包括键盘、触摸屏和轨迹球等。 在 Android 的上层中,可以通过获得这些设备产生的事件,并对设备的事件作出响应。在 Java 框 架中,通常使用运动事件来获得触摸屏和轨迹球设备的信息,使用按键事件获得各种键盘的信息。 Android 输入系统的框架结构如图 7-1 所示。

Android应用

平台API 运动事件/按键事件

Java框架 输入设备管理和事件转换

Android系统

本地框架 JNI库和UI库

硬件 用户输入设备(包括键盘、触摸屏、轨迹球) 和驱动

▲图 7-1 Android 输入系统的框架结构

7.1.1 Android 输入系统结构元素介绍 Android 输入系统比较简单,从下到上分别包括驱动程序、本地库处理部分、Java 类对输入事 件的处理和 Java 程序的接口等。如图 7-2 所示。

Android 驱动开发与移植实战详解

*.kl和*.kcm

内核 需要移植的部分

▲图 7-2 用户输入系统的结构

图 7-2 中从上到下各个结构元素的具体说明如下所示。 (1)Android 应用程序层:通过重新实现 onTouchEvent 和 onTrackballEvent 等函数来接收运动 事件(MotionEvent),通过重新实现 onKeyDown 和 onKeyUp 等函数来接收按键事件(KeyEvent)。 这些类包含在 android.view 包中。 (2)Java 框架层的处理:在 Java 框架层具有 KeyInputDevice 等类用于处理由 EventHub 传送上 来的信息,通常信息由数据结构 RawInputEvent 和 KeyEvent 来表示。通常情况下,对于按键事件, 则直接使用 KeyEvent 来传送给应用程序层,对于触摸屏和轨迹球等事件,则由 RawInputEvent 经 过转换后,形成 MotionEvent 时间传送给应用程序层。 (3)EventHub:本地框架层的 EventHub 是 libui 中的一部分,它实现了对驱动程序的控制,并 从中获得信息。定义按键布局和按键字符映射需要运行时配置文件的支持,它们的后缀名分别为 “kl”和“kcm”。 (4)驱动程序:保存在“/dev/input”目录中,通常是 Event 类型的驱动程序。

7.1.2 Android 输入系统驱动 Android 的输入系统驱动用的是标准的 Linux 的 Input 子系统驱动,Input 是 Linux 上的输入驱

176 第 7 章 输入系统驱动

动程序,分成游戏杆(joystick)、鼠 标( mouse)和事件设备(Event queue)三种驱动程序,Android 目前是采用事件驱动来和用户空间进行交互的,可支持的输入设备包括键盘、鼠标、触摸屏等多种 设备。 Input 输入系统的主设备号为 13,事件设备(Event queue)的次设备号为 64~95,可以在设备 的 shell 环境中查看 Event 设备在文件系统中分配的设备节点。 进入“/dev/input/”目录,输入 ls –l 可以看到如图 7-3 所示的结果。

▲图 7-3 显示结果

由此可见,Input 输入系统的主设备号为 13,次设备号递增生成。Event 在用户空间主要通过 read、ioctl 和 poll 等文件系统的接口进行操作,read 用于读取输入信息,ioctl 用于获取和设置信息, poll 调用可以进行用户空间的阻塞,当内核有按键等中断时,通过在中断中唤醒 poll 的内核实现, 这样在用户空间的 poll 调用也可以返回。

7.1.3 Input 系统的层次结构 Input 子系统大致可以分为三层:硬件驱动层、子系统核心层、事件处理层 (1)其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱 动程序的作者来编写。 (2)子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事 件处理层的接口。 (3)事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。

7.1.4 移植工作 在移植 Android 输入系统时需要完成下面的两个工作。 (1)移植输入(input)驱动程序。 (2)在用户空间中动态配置“kl”和“kcm”文件。 因为 Android 输入系统的硬件抽象层是 libui 库中的 EventHub,此部分是系统的标准部分。所 以在实现特定硬件平台的 Android 系统的时候,通常改变输入系统硬件抽象层。 EventHub 使用 Linux 标准的输入设备作为输入设备,并且大多数使用实用的 Event 设备。 基于上述原因,为了实现 Android 系统的输入,必须使用 Linux 标准输入驱动程序作为标准的 输入。 由此可见,输入系统的标准化程度较高,在用户空间实现时一般不需要更改代码。唯一的变化 是使用不同的“kl”和“kcm”文件,使用按键的布局和按键字符映射关系。

177 Android 驱动开发与移植实战详解

7.2 Input 驱动源码分析

Input(输入)驱动程序是 Linux 输入设备的驱动程序,可以进一步分为三种驱动程序,分别是 游戏杆(joystick)、鼠标(mouse 和 mice)和事件设备(Event queue)。其中事件驱动程序是目前 通用的驱动程序,可以支持键盘、鼠标、触摸屏等多种输入设备。 Android 中 Input 子系统的主要代码路径如下:

"include/linux/Input.h"(Input 系统的头文件) "Drivers/Input/Input.c"(设备注册和加载) "Drivers/Input/Evdev.c"(Event 驱动的实现) "Drivers/Inout/Keyboard/s3c-gpio_keys.c"(键盘驱动的实现) "Drivers/Inout/Keyboard/s3c-ts.c"(触摸屏的驱动)

Input 驱动程序的主设备号是 13,每一种 Input 设备占用 5 位,所以每种设备包含的个数是 32 个。Event 设备在用户空间可以使用如下三种文件系统操作接口。 Read:读取输入信息。 Ioctl:获得和设置信息。 Poll:调用可以进行用户空间的阻塞,当内核有按键等中断时,通过在中断中唤醒 poll 的 内核实现,这样在用户空间的 poll 调用也可以返回。 Event 设备在文件系统中的设备节点是“/dev/input/eventX”目录。主设备号为 13,次设备号按 照递增顺序生成,为 64~95,各个具体的设备保存在 misc、touchscreen 和 keyboard 等目录中。 Android 输入设备驱动程序的头文件是“include/linux/input.h ”,核心文件是 “drivers/input/input.c”,Event 部分的代码文件是“drivers/input/ evdev.c”。

7.2.1 文件 input.h 在手机系统中的键盘(keyboard)和小键盘(kaypad)属于按键设备 EV_KEY,轨迹球属于相 对设备 EV_REL,触摸屏属于绝对设备 EV_ABS。下面是在文件 input.h 中定义上述按键设备数值 的代码。

#define KEY_RESERVED 0 #define KEY_ESC 1 #define KEY_1 2 #define KEY_2 3 #define KEY_3 4 #define KEY_4 5 #define KEY_5 6 #define KEY_6 7 #define KEY_7 8 #define KEY_8 9 #define KEY_9 10 #define KEY_0 11 #define KEY_MINUS 12

178 第 7 章 输入系统驱动

#define KEY_EQUAL 13 #define KEY_BACKSPACE 14 #define KEY_TAB 15 #define KEY_Q 16 #define KEY_W 17 #define KEY_E 18 #define KEY_R 19 #define KEY_T 20

在文件 input.h 中还定义结构 struct input_dev,此结构表示 Input 驱动程序的各种信息,在里面 定义并归纳了各种设备的信息,例如按键、相对设备、绝对设备、杂项设备、LED、声音设备、强 制反馈设备、开关设备等。定义结构 struct input_dev 的代码如下所示。

struct input_dev { const char *name; // 设备名称 const char *phys; // 设备在系统的物理路径 const char *uniq; // 统一的 ID struct input_id id; // 设备 ID unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 事件 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; // 按键 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; // 相对设备 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; // 绝对设备 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; // 杂项设备 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; // LED unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; // 声音设备 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; // 强制反馈设备 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; // 开关设备 unsigned int keycodemax; // 按键码的最大值 unsigned int keycodesize; // 按键码的大小 void *keycode; // 按键码 int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);

179 Android 驱动开发与移植实战详解

struct ff_device *ff; unsigned int repeat_key; struct timer_list timer; int sync; int abs[ABS_MAX + 1]; int rep[REP_MAX + 1]; unsigned long key[BITS_TO_LONGS(KEY_CNT)]; unsigned long led[BITS_TO_LONGS(LED_CNT)]; unsigned long snd[BITS_TO_LONGS(SND_CNT)]; unsigned long sw[BITS_TO_LONGS(SW_CNT)]; int absmax[ABS_MAX + 1]; // 绝对设备相关内容 int absmin[ABS_MAX + 1]; int absfuzz[ABS_MAX + 1]; int absflat[ABS_MAX + 1]; // 设备相关的操作 int (*open)(struct input_dev *dev); void (*close)(struct input_dev *dev); int (*flush)(struct input_dev *dev, struct file *file); int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle *grab; spinlock_t event_lock; struct mutex mutex; unsigned int users; int going_away; struct device dev; struct list_head h_list; struct list_head node; };

在实现 Event 驱动程序时,可以用接口通过向上通知的方式得到按键的事件。在文件 input.h 中定义实现上述接口的代码如下所示。

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value); void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value); static inline void input_report_key(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_KEY, code, !!value); } static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_REL, code, value); } static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_ABS, code, value); } static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_FF_STATUS, code, value); }

180 第 7 章 输入系统驱动 static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_SW, code, !!value); } static inline void input_sync(struct input_dev *dev) { input_event(dev, EV_SYN, SYN_REPORT, 0); }

基于文件 input.c 的原理,下面是笔者编写的 USB 输入驱动程序。

#include #include #include #include #include

#define USB_MOUSE ("/dev/input/mouse0") struct pollfd mypoll; int main(int argc, char *argv[]) { int mouseFd; struct input_event buff;

if ((mouseFd = open(USB_MOUSE, O_RDONLY)) == -1) { printf("Failed to open /dev/input/mouse0\n"); return -1; } mypoll.fd = mouseFd; mypoll.events = POLLIN; while(1) { if(poll( &mypoll, 1, 10) > 0) { unsigned char data[4] ={0}; /* data 的数据格式: data0:00xx 1xxx ----低三位是按键值---左中右分别为 01 02 04, 第 4/5 位分别代表 x、y 移动方向,右上方 x/y>0,左下方 xy<0 data1:取值范围-127~127,代表 x 轴移动偏移量 data2:取值范围-127~127,代表 y 轴移动偏移量 */ usleep(50000); read(mouseFd, data, 4); //MOUSEDEV_EMUL_PS2 方式每次采样数据为 3 个字节,多读不会出 错,只返回成功读取的数据数 printf("mouse data=%02x%02x%02x%02x\n", data[0],data[1], data[2], data[3]); } }

close(mouseFd); return 0; }

181 Android 驱动开发与移植实战详解

7.2.2 文件 KeycodeLabels.h 文件 KeycodeLabels.h 是本地框架层 libui 的头文件,功能是实现用户空间处理功能。Android 中的触摸屏和轨迹球非常简单,只需要实现坐标传递、按下、抬起等信息事件即可。而按键处理的 过程稍微复杂,键表示方式需要先后经过按键布局转换和按键码转换。 键扫描码 Scancode 是由 Linux 的输入驱动框架定义的整数类型。键扫描码 Scancode 经过一次转 化后,形成按键标签 KeycodeLabel,这是一个字符串的表示形式。按键标签 KeycodeLabel 经过转换 后,再次形成整数型按键码 keycode。在 Android 应用程序层,主要使用按键码 keycode 来区分。 在文件 KeycodeLabels.h 中,按键码是整数值的格式,在此文件中是通过枚举实现的。定义枚 举 KeyCode 的代码如下所示。

typedef enum KeyCode { kKeyCodeUnknown = 0, kKeyCodeSoftLeft = 1, kKeyCodeSoftRight = 2, kKeyCodeHome = 3, kKeyCodeBack = 4, kKeyCodeCall = 5, kKeyCodeEndCall = 6, kKeyCode0 = 7, kKeyCode1 = 8, kKeyCode2 = 9, kKeyCode3 = 10, kKeyCode4 = 11, kKeyCode5 = 12, kKeyCode6 = 13, kKeyCode7 = 14, kKeyCode8 = 15, kKeyCode9 = 16, kKeyCodeStar = 17, kKeyCodePound = 18, kKeyCodeDpadUp = 19, kKeyCodeDpadDown = 20, kKeyCodeDpadLeft = 21, kKeyCodeDpadRight = 22, kKeyCodeDpadCenter = 23, kKeyCodeVolumeUp = 24, kKeyCodeVolumeDown = 25, kKeyCodePower = 26, kKeyCodeCamera = 27, kKeyCodeClear = 28, kKeyCodeA = 29, kKeyCodeB = 30, kKeyCodeC = 31, kKeyCodeD = 32, kKeyCodeE = 33, kKeyCodeF = 34,

182 第 7 章 输入系统驱动 kKeyCodeG = 35, kKeyCodeH = 36, kKeyCodeI = 37, kKeyCodeJ = 38, kKeyCodeK = 39, kKeyCodeL = 40, kKeyCodeM = 41, kKeyCodeN = 42, kKeyCodeO = 43, kKeyCodeP = 44, kKeyCodeQ = 45, kKeyCodeR = 46, kKeyCodeS = 47, kKeyCodeT = 48, kKeyCodeU = 49, kKeyCodeV = 50, kKeyCodeW = 51, kKeyCodeX = 52, kKeyCodeY = 53, kKeyCodeZ = 54, kKeyCodeComma = 55, kKeyCodePeriod = 56, kKeyCodeAltLeft = 57, kKeyCodeAltRight = 58, kKeyCodeShiftLeft = 59, kKeyCodeShiftRight = 60, kKeyCodeTab = 61, kKeyCodeSpace = 62, kKeyCodeSym = 63, kKeyCodeExplorer = 64, kKeyCodeEnvelope = 65, kKeyCodeNewline = 66, kKeyCodeDel = 67, kKeyCodeGrave = 68, kKeyCodeMinus = 69, kKeyCodeEquals = 70, kKeyCodeLeftBracket = 71, kKeyCodeRightBracket = 72, kKeyCodeBackslash = 73, kKeyCodeSemicolon = 74, kKeyCodeApostrophe = 75, kKeyCodeSlash = 76, kKeyCodeAt = 77, kKeyCodeNum = 78, kKeyCodeHeadSetHook = 79, kKeyCodeFocus = 80, kKeyCodePlus = 81, kKeyCodeMenu = 82, kKeyCodeNotification = 83, kKeyCodeSearch = 84,

183 Android 驱动开发与移植实战详解

kKeyCodePlayPause = 85, kKeyCodeStop = 86, kKeyCodeNextSong = 87, kKeyCodePreviousSong = 88, kKeyCodeRewind = 89, kKeyCodeForward = 90, kKeyCodeMute = 91 } KeyCode;

在文件 KeycodeLabels.h 中定义数组 KEYCODES[],功能是存储从字符串到整数的映射关系。 左列的内容即表示按键标签 KeyCodeLabel,右列的内容为按键码 KeyCode(与 KeyCode 的数值对 应)。在按键信息第二次转化的时候,已经将字符串类型 KeyCodeLabel 转化成整数的 KeyCode。定 义数组 KEYCODES[]的代码如下所示。

static const KeycodeLabel KEYCODES[] = { { "SOFT_LEFT", 1 }, { "SOFT_RIGHT", 2 }, { "HOME", 3 }, { "BACK", 4 }, { "CALL", 5 }, { "ENDCALL", 6 }, { "0", 7 }, { "1", 8 }, { "2", 9 }, { "3", 10 }, { "4", 11 }, { "5", 12 }, { "6", 13 }, { "7", 14 }, { "8", 15 }, { "9", 16 }, { "STAR", 17 }, { "POUND", 18 }, { "DPAD_UP", 19 }, { "DPAD_DOWN", 20 }, { "DPAD_LEFT", 21 }, { "DPAD_RIGHT", 22 }, { "DPAD_CENTER", 23 }, { "VOLUME_UP", 24 }, { "VOLUME_DOWN", 25 }, { "POWER", 26 }, { "CAMERA", 27 }, { "CLEAR", 28 }, { "A", 29 }, { "B", 30 }, { "C", 31 }, { "D", 32 }, { "E", 33 }, { "F", 34 }, { "G", 35 },

184 第 7 章 输入系统驱动

{ "H", 36 }, { "I", 37 }, { "J", 38 }, { "K", 39 }, { "L", 40 }, { "M", 41 }, { "N", 42 }, { "O", 43 }, { "P", 44 }, { "Q", 45 }, { "R", 46 }, { "S", 47 }, { "T", 48 }, { "U", 49 }, { "V", 50 }, { "W", 51 }, { "X", 52 }, { "Y", 53 }, { "Z", 54 }, { "COMMA", 55 }, { "PERIOD", 56 }, { "ALT_LEFT", 57 }, { "ALT_RIGHT", 58 }, { "SHIFT_LEFT", 59 }, { "SHIFT_RIGHT", 60 }, { "TAB", 61 }, { "SPACE", 62 }, { "SYM", 63 }, { "EXPLORER", 64 }, { "ENVELOPE", 65 }, { "ENTER", 66 }, { "DEL", 67 }, { "GRAVE", 68 }, { "MINUS", 69 }, { "EQUALS", 70 }, { "LEFT_BRACKET", 71 }, { "RIGHT_BRACKET", 72 }, { "BACKSLASH", 73 }, { "SEMICOLON", 74 }, { "APOSTROPHE", 75 }, { "SLASH", 76 }, { "AT", 77 }, { "NUM", 78 }, { "HEADSETHOOK", 79 }, { "FOCUS", 80 }, { "PLUS", 81 }, { "MENU", 82 }, { "NOTIFICATION", 83 }, { "SEARCH", 84 }, { "MEDIA_PLAY_PAUSE", 85 },

185 Android 驱动开发与移植实战详解

{ "MEDIA_STOP", 86 }, { "MEDIA_NEXT", 87 }, { "MEDIA_PREVIOUS", 88 }, { "MEDIA_REWIND", 89 }, { "MEDIA_FAST_FORWARD", 90 }, { "MUTE", 91 }, { NULL, 0 } };

在文件“frameworks/base/core/Java/android/view/KeyEvent.Java”中定义了类 android.view.KeyEvent, 在里面定义的整数类型的数值与 KeycodeLabels.h 中定义的 KeyCode 枚举值是对应的。

7.2.3 文件 KeyCharacterMap.h 文件“frameworks/base/include/ui/KeyCharacterMap.h”是本地框架层 libui 的头文件,定义了按 键的字符映射关系。KeyCharacterMap 只是一个辅助的功能,因为按键码只是一个与 UI 无关整数, 通常用程序对其进行捕获处理,然而如果将按键事件转换为用户可见的内容,就需要经过这个层次 的转换。其中下面是定义类 KeyCharacterMap 的代码。

class KeyCharacterMap { public: ~KeyCharacterMap(); unsigned short get(int keycode, int meta); unsigned short getNumber(int keycode); unsigned short getMatch(int keycode, const unsigned short* chars, int charsize, uint32_t modifiers); unsigned short getDisplayLabel(int keycode); bool getKeyData(int keycode, unsigned short *displayLabel, unsigned short *number, unsigned short* results); inline unsigned int getKeyboardType() { return m_type; } bool getEvents(uint16_t* chars, size_t len, Vector* keys, Vector* modifiers); static KeyCharacterMap* load(int id); enum { NUMERIC = 1, Q14 = 2, QWERTY = 3 // or AZERTY or whatever }; }

在上述代码中,使用 KeyCharacterMap 将按键码映射为文本可识别的字符串。 上述关于按键码和按键字符映射的内容是在代码中实现的内容,我们还需要配合动态的配置文 件来使用。在实现 Android 系统的时候,很可能需要更改这两种文件。我们需要动态配置下面的两 个文件。 KL(Keycode Layout):后缀名为 kl 的配置文件。

186 第 7 章 输入系统驱动

KCM(KeyCharacterMap):后缀名为 kcm 的配置文件。 在 Donut 及其之前版本的配置文件路径为:

development/emulator/keymaps/

在 Eclair 及其之后版本的配置文件路径为:

sdk/emulator/keymaps/

当系统生成上述配置文件后,将会被放置在目标文件系统的“/system/usr/keylayout/”目录中或 “/system/usr/keychars/”目录中。另外,kl 文件将被直接复制到目标文件系统中;由于尺寸较大, kcm 文件放置在目标文件系统中之前,需要经过压缩处理。KeyLayoutMap.cpp 负责解析处理 kl 文 件,KeyCharacterMap.cpp 负责解析 kcm 文件。

7.2.4 Kl 格式文件 Kl 格式文件是按键布局文件,通常以原始的文本文件的形式存在,被保存在目标文件系统的 “/system/usr/keylayout/”目录或者“/system/usr/keychars/”目录中。Android 默认提供的按键布局文 件有两个,分别是 qwerty.kl 和 AVRCP.kl。其中 qwerty.kl 是全键盘的布局文件,是系统中主要按键 使用的布局文件;文件 AVRCP.kl 用于实现多媒体的控制。 文件 qwerty.kl 的主要代码如下所示。

key 399 GRAVE key 2 1 key 3 2 key 4 3 key 5 4 key 6 5 key 7 6 key 8 7 key 9 8 key 10 9 key 11 0 key 158 BACK WAKE_DROPPED key 230 SOFT_RIGHT WAKE key 60 SOFT_RIGHT WAKE key 107 ENDCALL WAKE_DROPPED key 62 ENDCALL WAKE_DROPPED key 229 MENU WAKE_DROPPED

# 省略部分按键的对应内容 key 16 Q key 17 W key 18 E key 19 R key 20 T key 115 VOLUME_UP key 114 VOLUME_DOWN

187 Android 驱动开发与移植实战详解

在上述代码中,第 1 列为按键的扫描码,是一个整数值;第 2 列为按键的标签,是一个字符串。 即完成了按键信息的第 1 次转化,将整型的扫描码,转换成字符串类型的按键标签。第 3 列表示按 键的 Flag,带有 WAKE 字符,表示此按键可以唤醒系统。 扫描码受驱动程序决定,不同的扫描码对应一个按键标签。两个手机的物理按键可以对应同一 个功能按键。假如当上面的扫描码为 158 时,对应的标签为 BACK ,经过第二次转换后,根据 KeycodeLabels.h 的 KEYCODES 数组可得出其对应的按键码是 4。

7.2.5 kcm 格式文件 kcm 格式文件是按键字符映射文件,用于表示按键字符的映射关系,功能是将整数类型按键码 (keycode)转化成可以显示的字符。kcm 文件将被 makekcharmap 工具转化成二进制的格式,放在 目标系统的/system/usr/keychars/目录中。 文件 qwerty.kcm 表示全键盘的字符映射关系,主要代码如下所示。

[type=QWERTY] # keycode display number base caps fn caps_fn A 'A' '2' 'a' 'A' '#' 0x00 B 'B' '2' 'b' 'B' '<' 0x00 C 'C' '2' 'c' 'C' '9' 0x00E7 D 'D' '3' 'd' 'D' '5' 0x00 E 'E' '3' 'e' 'E' '2' 0x0301 F 'F' '3' 'f' 'F' '6' 0x00A5 G 'G' '4' 'g' 'G' '-' '_' H 'H' '4' 'h' 'H' '[' '{' I 'I' '4' 'i' 'I' '$' 0x0302 J 'J' '5' 'j' 'J' ']' '}' K 'K' '5' 'k' 'K' '"' '~' L 'L' '5' 'l' 'L' ''' '`' M 'M' '6' 'm' 'M' '!' 0x00 N 'N' '6' 'n' 'N' '>' 0x0303

在上述代码中,第一列表示转换之前的按键码,第二列后面的分别表示转换成的显示内容 (display)和数字(number)等内容。这些转化的内容和文件 KeyCharacterMap.h 相对应,具体内容 是在此文件的 getDisplayLabel()和 getNumber()等函数定义的。 除了 QWERTY 映射类型之外,还可以映射 Q14(单键多字符对应的键盘)和 NUMERIC(12 键的数字键盘)。

7.2.6 文件 EventHub.cpp 文件 EventHub.cpp 位于 libui 库中的“frameworks/base/libs/ui”目录下,此文件是输入系统的 核心控制,整个输入系统的主要的功能都是在此文件中实现的。例如当按下电源键后,系统把 scanCode 写入对应的设备节点,文件 EventHub.cpp 会去读这个设备结点,并把 scanCode 通过 kl 文件对应成 keyCode 发送到上层。 在文件 EventHub.cpp 中需要定义设备节点所在的路径,下面是定义代码。

188 第 7 章 输入系统驱动

static const char *device_path = "/dev/input"; // 输入设备的目录

在具体处理时,在函数 openPlatformInput()中通过调用 scan_dir()函数搜索路径下面所有 Input 驱动的设备节点,函数 scan_dir()会从目录中查找设备,找到后调用 open_device()函数以打开查找 到的设备。下面是定义函数 openPlatformInput()的实现代码。

bool EventHub::openPlatformInput(void) { /* *打开平台特殊化输入装置 */ int res; mFDCount = 1; mFDs = (pollfd *)calloc(1, sizeof(mFDs[0])); mDevices = (device_t **)calloc(1, sizeof(mDevices[0])); mFDs[0].events = POLLIN; mDevices[0] = NULL; #ifdef HAVE_INOTIFY mFDs[0].fd = inotify_init(); res = inotify_add_watch(mFDs[0].fd, device_path, IN_DELETE | IN_CREATE); if(res < 0) { LOGE("could not add watch for %s, %s\n", device_path, strerror(errno)); } #else /* 我们分配它的空间并且设置它对事无效 */ mFDs[0].fd = -1; #endif res = scan_dir(device_path); if(res < 0) { LOGE("scan dir failed for %s\n", device_path); //打开目录("/dev/input/event0"); } return true; }

函数 getEvent()的功能是在一个无限循环之内调用阻塞的函数等待事件到来,下面是定义此函 数的具体代码。

bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, int32_t* outValue, nsecs_t* outWhen) { *outDeviceId = 0; *outType = 0; *outScancode = 0; *outKeycode = 0; *outFlags = 0; *outValue = 0;

189 Android 驱动开发与移植实战详解

*outWhen = 0; status_t err; fd_set readfds; int maxFd = -1; int cc; int i; int res; int pollres; struct input_event iev; while(1) { //首先,报告所有增加或被去除的所有设备 if (mClosingDevices != NULL) { device_t* device = mClosingDevices; LOGV("Reporting device closed: id=0x%x, name=%s\n", device->id, device->path.string()); mClosingDevices = device->next; *outDeviceId = device->id; if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0; *outType = DEVICE_REMOVED; delete device; return true; } if (mOpeningDevices != NULL) { device_t* device = mOpeningDevices; LOGV("Reporting device opened: id=0x%x, name=%s\n", device->id, device->path.string()); mOpeningDevices = device->next; *outDeviceId = device->id; if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0; *outType = DEVICE_ADDED; return true; }

release_wake_lock(WAKE_LOCK_ID);

pollres = poll(mFDs, mFDCount, -1);

acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);

if (pollres <= 0) { if (errno != EINTR) { LOGW("select failed (errno=%d)\n", errno); usleep(100000); } continue; } //printf("poll %d, returned %d\n", mFDCount, pollres); if(mFDs[0].revents & POLLIN) { read_notify(mFDs[0].fd); }

190 第 7 章 输入系统驱动

for(i = 1; i < mFDCount; i++) { if(mFDs[i].revents) { LOGV("revents for %d = 0x%08x", i, mFDs[i].revents); if(mFDs[i].revents & POLLIN) { res = read(mFDs[i].fd, &iev, sizeof(iev)); if (res == sizeof(iev)) { LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d", mDevices[i]->path.string(), (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value); *outDeviceId = mDevices[i]->id; if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0; *outType = iev.type; *outScancode = iev.code; if (iev.type == EV_KEY) { err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags); LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d\n", iev.code, *outKeycode, *outFlags, err); if (err != 0) { *outKeycode = 0; *outFlags = 0; } } else { *outKeycode = iev.code; } *outValue = iev.value; *outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec); return true; } else { if (res<0) { LOGW("could not get event (errno=%d)", errno); } else { LOGE("could not get event (wrong size: %d)", res); } continue; } } } } } }

在上述代码中,通过 poll()函数来阻塞程序的运行,此时为等待状态,不会开销内存。当 Input 设备的相应事件发生后会将 poll()函数返回,然后通过 read()函数读取 Input 设备发生的事件代码。

7.3 Input 设备的运作过程

了解了 Android 系统的 Input 系统的源码之后,接下来开始讲解 Input 设备的具体运作过程。

191 Android 驱动开发与移植实战详解

7.3.1 Input 设备的注册(硬件驱动层) 在 Android 中 input 设备会通过模块初始化的方式将设备注册到 input 系统中,下面以触摸屏为 例来介绍触摸屏设备的注册加载过程。 在 S3c-ts.c 中,在模块加载的时候注册三星的触摸屏驱动 s3c_ts_driver,s3c_ts_driver 在 S3c-ts.c 中定义代码如下所示:

static struct platform_driver s3c_ts_driver = { .probe = s3c_ts_probe, .remove = s3c_ts_remove, .suspend = s3c_ts_suspend, .resume = s3c_ts_resume, .driver = { .owner = THIS_MODULE, .name = "s3c-ts", }, }; 当驱动加载的时候会调用探测函数 s3c_ts_probe(),具体代码如下:

static int __init s3c_ts_probe(struct platform_device *pdev){ …… ret = request_irq(ts_irq->start, stylus_action, IRQF_SAMPLE_RANDOM, "s3c_action", ts); if (ret != 0) { dev_err(dev, "s3c_ts.c: Could not allocate ts IRQ_ADC !\n"); ret = -EIO; goto err_irq; } printk(KERN_INFO "%s got loaded successfully : %d bits\n", s3c_ts_name, s3c_ts_cfg->resol_bit); //注册触摸屏设备到 input 系统中 ret = input_register_device(ts->dev); if(ret) { dev_err(dev, "s3c_ts.c: Could not register input device(touchscreen)!\n"); ret = -EIO; goto fail; } …… }

s3c_ts_probe()主要通过 input_register_device()完成触摸屏设备的初始化,将自己的 device 结构 添加到 Linux 设备模型当中,将 input_dev 添加到 input_dev_list 链表中,然后寻找合适的 handler 与 input_handler 配对,配对成功后会针对特定的 input 驱动类型(在 Android 系统中是 Event 驱动) 调用 input_register_handle 和 input_register_handler 进行注册。

7.3.2 Input 子系统的加载过程(子系统核心层) 当 Input 系统模块加载的时候,会首先进行初始化:

192 第 7 章 输入系统驱动

static int __init input_init(void) { int err; //内核注册一个类,用于 Linux 设备模型,注册后会在/sys/class 下面出现 input 目录 err = class_register(&input_class); if (err) { printk(KERN_ERR "input: unable to register input_dev class\n"); return err; } //proc 文件系统的初始化 err = input_proc_init(); if (err) goto fail1; //input 设备的注册 err = register_chrdev(INPUT_MAJOR, "input", &input_fops); if (err) { printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err; }

input_init()中会将 input 的设备操作函数 input_fops 注册到 Linux 内核中,并初始化一些设备的 接口,比如 open,input_fops 结构体的定义如下:

static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, };

当用户空间进行 open 操作时,则会调用 input_open_file(),代码如下:

static int input_open_file(struct inode *inode, struct file *file) { struct input_handler *handler; const struct file_operations *old_fops, *new_fops = NULL; int err; lock_kernel(); //得到对应的 input handler handler = input_table[iminor(inode) >> 5]; if (!handler || !(new_fops = fops_get(handler->fops))) { err = -ENODEV; goto out; } if (!new_fops->open) { fops_put(new_fops); err = -ENODEV; goto out;

193 Android 驱动开发与移植实战详解

} //重定向打开函数 old_fops = file->f_op; file->f_op = new_fops; err = new_fops->open(inode, file); if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops); out: unlock_kernel(); return err; }

其中将 file_operations 设备操作接口给重定向了,使其指向了具体的输入设备,例如触摸屏、 键盘,不同的输入设备的操作方法是不同的。

7.3.3 Input 子系统的事件处理(事件处理层) 事件处理层与用户程序和输入子系统核心打交道,是它们两层的桥梁。一般内核有好几个事件 处理器,像 evdev mousedev jotdev。evdev 事件处理器可以处理所有的事件(Android 上面采用的 evdev),触摸屏驱动就是用的此事件。接下来分析这个事件处理器的实现,其实它也是作为模块注 册到内核中的。在文件 Evdev.c 中,以模块加载的方式注册其模块注册 evdev_handler。

static int __init evdev_init(void) { return input_register_handler(&evdev_handler); }

将 evdev_handler 对应的接口注册到系统中,下面是定义结构体 evdev_handler 的代码。

static struct input_handler evdev_handler = { .event = evdev_event, .connect = evdev_connect, .disconnect = evdev_disconnect, .fops = &evdev_fops,//设备文件的操作函数 .minor = EVDEV_MINOR_BASE, .name = "evdev", .id_table = evdev_ids, };

在文件 Evdev.c 中通过如下代码定义 evdev 结构体。

struct evdev { int exist; int open; int minor; char name[16]; struct input_handle handle;

194 第 7 章 输入系统驱动

wait_queue_head_t wait; struct evdev_client *grab; struct list_head client_list; spinlock_t client_lock;//保护客户端链表 struct mutex mutex; struct device dev; };

结构体 evdev 在配对成功的时候生成,由 handler->connect 生成,对应设备文件为 “/class/input/event(n)”,如触摸屏驱动的 event0,这个设备是用户空间要访问的设备,可以理解它是 一个虚拟设备,因为没有对应的硬件,但是通过 handle->dev 就可以找到 input_dev 结构,而它对 应着触摸屏,设备文件为/class/input/input0。这个设备结构生成之后保存在 evdev_table 中,索引值 是 minor。 当用户空间打开 event 设备的时候,会调用到其中的 evdev_open 函数。

static int evdev_open(struct inode *inode, struct file *file) { struct evdev *evdev; struct evdev_client *client; int i = iminor(inode) - EVDEV_MINOR_BASE; int error; if (i >= EVDEV_MINORS) return -ENODEV; error = mutex_lock_interruptible(&evdev_table_mutex); if (error) return error; //得到 evdev 设备结构,每次调用 evdev_connect 配对成功后都会把分配的 evdev 结构以 minor 为索引,保存在 evdev_table 数组中 evdev = evdev_table[i]; if (evdev) get_device(&evdev->dev); mutex_unlock(&evdev_table_mutex); if (!evdev) return -ENODEV; client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL); if (!client) { error = -ENOMEM; goto err_put_evdev; } spin_lock_init(&client->buffer_lock); wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, "evdev"); client->evdev = evdev;//使客户端与 evdev 设备联系起来 //把 client 连接到 evdev 的 client 链表中 evdev_attach_client(evdev, client); //打开设备 error = evdev_open_device(evdev); if (error) goto err_free_client; file->private_data = client;

195 Android 驱动开发与移植实战详解

return 0; err_free_client: evdev_detach_client(evdev, client); kfree(client); err_put_evdev: put_device(&evdev->dev); return error; }

其中会创建 evdev_client 结构体并初始化,evdev_client 功能是维护用户产生时间的 buffer,以 及链表的索引等。在函数 evdev_open 中会调用到文件 input.c 中的 input_open_device 函数。 以触摸屏为例来介绍 input 事件的传递过程,当用户点击触摸屏时会产生中断,在中断处理的 程序中会调用如下函数来上报点击的坐标。 input_report_abs(dev, ABS_X, xp)。 input_report_abs(dev, ABS_Y, yp)。 接着会调用到 input.c 中的函数 input_event()。

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { unsigned long flags; //判断是否支持这种类型的事件 if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags); add_input_randomness(type, code, value); //对上报的事件进行处理 input_handle_event(dev, type, code, value); spin_unlock_irqrestore(&dev->event_lock, flags); } }

然后调用 input_handle_event 函数。

static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { int disposition = INPUT_IGNORE_EVENT; switch (type) { case EV_SYN: switch (code) { case SYN_CONFIG: disposition = INPUT_PASS_TO_ALL; break; case SYN_REPORT: if (!dev->sync) { dev->sync = 1; disposition = INPUT_PASS_TO_HANDLERS; } break;

196 第 7 章 输入系统驱动

} break; …… if (disposition & INPUT_PASS_TO_HANDLERS) input_pass_event(dev, type, code, value); }

input_handle_event 函数会根据事件类型是否为触摸屏或者键盘等事件类型,进行相应处理, 调用到 input_pass_event 函数。

static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_handle *handle; rcu_read_lock(); //判断是否是绑定的 handle handle = rcu_dereference(dev->grab); if (handle) handle->handler->event(handle, type, code, value); else //没有则遍历 dev_list 列表寻找 handle list_for_each_entry_rcu(handle, &dev->h_list, d_node) //如果打开则将消息传到 input 子系统中去 if (handle->open) handle->handler->event(handle, type, code, value); rcu_read_unlock(); }

这个函数里面会将产生的数据传给之前初始化的 evdev_handler 中的 evdev_event 函数,进行下 一步处理。

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { struct evdev *evdev = handle->private; struct evdev_client *client; struct input_event event; struct timespec ts; ktime_get_ts(&ts); event.time.tv_sec = ts.tv_sec; event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC; event.type = type; event.code = code; event.value = value; rcu_read_lock(); client = rcu_dereference(evdev->grab); //如果 evdev 绑定了这个客户端,则进行处理 if (client) evdev_pass_event(client, &event);

197 Android 驱动开发与移植实战详解

else list_for_each_entry_rcu(client, &evdev->client_list, node) evdev_pass_event(client, &event); rcu_read_unlock(); wake_up_interruptible(&evdev->wait); }

这个函数会将消息传入绑定了 evdev 的客户端去,并对消息进行下一步处理,调用 evdev_pass_event 函数。

static void evdev_pass_event(struct evdev_client *client, struct input_event *event) { spin_lock(&client->buffer_lock); wake_lock_timeout(&client->wake_lock, 5 * HZ); client->buffer[client->head++] = *event;//传递消息 client->head &= EVDEV_BUFFER_SIZE - 1; spin_unlock(&client->buffer_lock); kill_fasync(&client->fasync, SIGIO, POLL_IN); }

可见,evdev_pass_event 函数最终将事件传递给了用户端的 evdev_client 结构中的 input_event 数组,之后将这个数组传递给用户空间,就可以接收到用户的按键信息了。

7.4 模拟器的输入驱动

GoldFish 虚拟处理器使用 event 驱动程序作为键盘输入功能的驱动程序,其驱动程序的相关文 件是“drivers/input/keyboard/goldfish_events.c”。此驱动程序是一个标准的 event 驱动程序,在用户 空间的设备节点为“/dev/event/event0”目录。核心代码如下所示。

static irqreturn_t events_interrupt(int irq, void *dev_id) { struct event_dev *edev = dev_id; unsigned type, code, value; type = __raw_readl(edev->addr + REG_READ); // 类型 code = __raw_readl(edev->addr + REG_READ); // 码 value = __raw_readl(edev->addr + REG_READ); // 数值 input_event(edev->input, type, code, value); return IRQ_HANDLED; }

函数 events_interrupt()是按键事件的中断处理函数,当中断发生后会读取虚拟寄存器的内容, 并将信息上报。模拟器根据主机环境键盘按下的情况可以得到虚拟寄存器中的内容。 在模拟器环境中,使用默认的所有的 KL 和 KCM 文件,由于模拟器环境支持全键盘,因此基 本上包含了大部分的功能。在模拟器环境中,实际上按键的扫描码对应的是桌面电脑的键盘(效果 和鼠标点击模拟器的控制面板类似)。当按下键盘的某些按键后会转化为驱动程序中的扫描码,然

198 第 7 章 输入系统驱动

后再由上层的用户空间处理。上述过程和实际系统中是类似的。通过更改默认 KL 文件的方式,又 可以更改实际按键的映射关系。

7.5 高通平台的输入驱动实现

MSM 的 mahimahi 平台具有触摸屏、轨迹球和简单按键功能。这些功能是在 Android 系统中驱 动程序实现的,并且需要用户空间的内容来协助实现。在 Mahimahi 平台中,输入系统设备包括下 面的 Event 设备。 /dev/input/event4:几个按键的设备。 /dev/input/event2:触摸屏设备。 /dev/input/event5:轨迹球设备。

7.5.1 触摸屏驱动 高通 mahimahi 平台的触摸屏驱动程序的实现文件是“drivers/input/touchscreen/synaptics _i2c_rmi.c”,此文件的核心是函数 synaptics_ts_probe(),在该函数中需要初始化触摸屏的工作模式。 作为输入设备的触摸屏驱动都是在 Linux 平台下的设备名中注册的,同时初始化触摸事件触发时引 起的中断操作。此函数的实现代码如下所示。

static int synaptics_ts_probe( struct i2c_client *client, const struct i2c_device_id *id) { struct synaptics_ts_data *ts; uint8_t buf0[4]; uint8_t buf1[8]; struct i2c_msg msg[2]; int ret = 0; uint16_t max_x, max_y; int fuzz_x, fuzz_y, fuzz_p, fuzz_w; struct synaptics_i2c_rmi_platform_data *pdata; int inactive_area_left; int inactive_area_right; int inactive_area_top; int inactive_area_bottom; int snap_left_on; int snap_left_off; int snap_right_on; int snap_right_off; int snap_top_on; int snap_top_off; int snap_bottom_on; int snap_bottom_off; uint32_t panel_version;

if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {

199 Android 驱动开发与移植实战详解

printk(KERN_ERR "synaptics_ts_probe: need I2C_FUNC_I2C\n"); ret = -ENODEV; goto err_check_functionality_failed; }

ts = kzalloc(sizeof(*ts), GFP_KERNEL); if (ts == NULL) { ret = -ENOMEM; goto err_alloc_data_failed; } INIT_WORK(&ts->work, synaptics_ts_work_func); ts->client = client; i2c_set_clientdata(client, ts); pdata = client->dev.platform_data; if (pdata) ts->power = pdata->power; if (ts->power) { ret = ts->power(1); if (ret < 0) { printk(KERN_ERR "synaptics_ts_probe power on failed\n"); goto err_power_failed; } }

ret = i2c_smbus_write_byte_data(ts->client, 0xf4, 0x01); if (ret < 0) { printk(KERN_ERR "i2c_smbus_write_byte_data failed\n"); /* 失败? */ } { int retry = 10; while (retry-- > 0) { ret = i2c_smbus_read_byte_data(ts->client, 0xe4); if (ret >= 0) break; msleep(100); } } if (ret < 0) { printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); goto err_detect_failed; } printk(KERN_INFO "synaptics_ts_probe: Product Major Version %x\n", ret); panel_version = ret << 8; ret = i2c_smbus_read_byte_data(ts->client, 0xe5); if (ret < 0) { printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); goto err_detect_failed; } printk(KERN_INFO "synaptics_ts_probe: Product Minor Version %x\n", ret);

200 第 7 章 输入系统驱动

panel_version |= ret;

ret = i2c_smbus_read_byte_data(ts->client, 0xe3); if (ret < 0) { printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); goto err_detect_failed; } printk(KERN_INFO "synaptics_ts_probe: product property %x\n", ret);

if (pdata) { while (pdata->version > panel_version) pdata++; ts->flags = pdata->flags; inactive_area_left = pdata->inactive_left; inactive_area_right = pdata->inactive_right; inactive_area_top = pdata->inactive_top; inactive_area_bottom = pdata->inactive_bottom; snap_left_on = pdata->snap_left_on; snap_left_off = pdata->snap_left_off; snap_right_on = pdata->snap_right_on; snap_right_off = pdata->snap_right_off; snap_top_on = pdata->snap_top_on; snap_top_off = pdata->snap_top_off; snap_bottom_on = pdata->snap_bottom_on; snap_bottom_off = pdata->snap_bottom_off; fuzz_x = pdata->fuzz_x; fuzz_y = pdata->fuzz_y; fuzz_p = pdata->fuzz_p; fuzz_w = pdata->fuzz_w; } else { inactive_area_left = 0; inactive_area_right = 0; inactive_area_top = 0; inactive_area_bottom = 0; snap_left_on = 0; snap_left_off = 0; snap_right_on = 0; snap_right_off = 0; snap_top_on = 0; snap_top_off = 0; snap_bottom_on = 0; snap_bottom_off = 0; fuzz_x = 0; fuzz_y = 0; fuzz_p = 0; fuzz_w = 0; }

ret = i2c_smbus_read_byte_data(ts->client, 0xf0); if (ret < 0) {

201 Android 驱动开发与移植实战详解

printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); goto err_detect_failed; } printk(KERN_INFO "synaptics_ts_probe: device control %x\n", ret);

ret = i2c_smbus_read_byte_data(ts->client, 0xf1); if (ret < 0) { printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); goto err_detect_failed; } printk(KERN_INFO "synaptics_ts_probe: interrupt enable %x\n", ret);

ret = i2c_smbus_write_byte_data(ts->client, 0xf1, 0); /* disable interrupt */ if (ret < 0) { printk(KERN_ERR "i2c_smbus_write_byte_data failed\n"); goto err_detect_failed; }

msg[0].addr = ts->client->addr; msg[0].flags = 0; msg[0].len = 1; msg[0].buf = buf0; buf0[0] = 0xe0; msg[1].addr = ts->client->addr; msg[1].flags = I2C_M_RD; msg[1].len = 8; msg[1].buf = buf1; ret = i2c_transfer(ts->client->adapter, msg, 2); if (ret < 0) { printk(KERN_ERR "i2c_transfer failed\n"); goto err_detect_failed; } printk(KERN_INFO "synaptics_ts_probe: 0xe0: %x %x %x %x %x %x %x %x\n", buf1[0], buf1[1], buf1[2], buf1[3], buf1[4], buf1[5], buf1[6], buf1[7]);

ret = i2c_smbus_write_byte_data(ts->client, 0xff, 0x10); /* page select = 0x10 */ if (ret < 0) { printk(KERN_ERR "i2c_smbus_write_byte_data failed for page select\n"); goto err_detect_failed; } ret = i2c_smbus_read_word_data(ts->client, 0x04); if (ret < 0) { printk(KERN_ERR "i2c_smbus_read_word_data failed\n"); goto err_detect_failed; } ts->max[0] = max_x = (ret >> 8 & 0xff) | ((ret & 0x1f) << 8); ret = i2c_smbus_read_word_data(ts->client, 0x06); if (ret < 0) { printk(KERN_ERR "i2c_smbus_read_word_data failed\n");

202 第 7 章 输入系统驱动

goto err_detect_failed; } ts->max[1] = max_y = (ret >> 8 & 0xff) | ((ret & 0x1f) << 8); if (ts->flags & SYNAPTICS_SWAP_XY) swap(max_x, max_y);

ret = synaptics_init_panel(ts); /* will also switch back to page 0x04 */ if (ret < 0) { printk(KERN_ERR "synaptics_init_panel failed\n"); goto err_detect_failed; }

ts->input_dev = input_allocate_device();//创建设备 if (ts->input_dev == NULL) { ret = -ENOMEM; printk(KERN_ERR "synaptics_ts_probe: Failed to allocate input device\n"); goto err_input_dev_alloc_failed; } ts->input_dev->name = "synaptics-rmi-touchscreen"; //声明输入设备 set_bit(EV_SYN, ts->input_dev->evbit); set_bit(EV_KEY, ts->input_dev->evbit); set_bit(BTN_TOUCH, ts->input_dev->keybit); set_bit(BTN_2, ts->input_dev->keybit); set_bit(EV_ABS, ts->input_dev->evbit); inactive_area_left = inactive_area_left * max_x / 0x10000; inactive_area_right = inactive_area_right * max_x / 0x10000; inactive_area_top = inactive_area_top * max_y / 0x10000; inactive_area_bottom = inactive_area_bottom * max_y / 0x10000; snap_left_on = snap_left_on * max_x / 0x10000; snap_left_off = snap_left_off * max_x / 0x10000; snap_right_on = snap_right_on * max_x / 0x10000; snap_right_off = snap_right_off * max_x / 0x10000; snap_top_on = snap_top_on * max_y / 0x10000; snap_top_off = snap_top_off * max_y / 0x10000; snap_bottom_on = snap_bottom_on * max_y / 0x10000; snap_bottom_off = snap_bottom_off * max_y / 0x10000; fuzz_x = fuzz_x * max_x / 0x10000; fuzz_y = fuzz_y * max_y / 0x10000; ts->snap_down[!!(ts->flags & SYNAPTICS_SWAP_XY)] = -inactive_area_left; ts->snap_up[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x + inactive_area_right; ts->snap_down[!(ts->flags & SYNAPTICS_SWAP_XY)] = -inactive_area_top; ts->snap_up[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y + inactive_area_bottom; ts->snap_down_on[!!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_left_on; ts->snap_down_off[!!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_left_off; ts->snap_up_on[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x - snap_right_on; ts->snap_up_off[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x - snap_right_off; ts->snap_down_on[!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_top_on; ts->snap_down_off[!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_top_off; ts->snap_up_on[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y - snap_bottom_on;

203 Android 驱动开发与移植实战详解

ts->snap_up_off[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y - snap_bottom_off; printk(KERN_INFO "synaptics_ts_probe: max_x %d, max_y %d\n", max_x, max_y); printk(KERN_INFO "synaptics_ts_probe: inactive_x %d %d, inactive_y %d %d\n", inactive_area_left, inactive_area_right, inactive_area_top, inactive_area_bottom); printk(KERN_INFO "synaptics_ts_probe: snap_x %d-%d %d-%d, snap_y %d-%d %d-%d\n", snap_left_on, snap_left_off, snap_right_on, snap_right_off, snap_top_on, snap_top_off, snap_bottom_on, snap_bottom_off); //配置具体事件 input_set_abs_params(ts->input_dev, ABS_X, -inactive_area_left, max_x + inactive_ area_right, fuzz_x, 0); input_set_abs_params(ts->input_dev, ABS_Y, -inactive_area_top, max_y + inactive_ area_bottom, fuzz_y, 0); input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 255, fuzz_p, 0); input_set_abs_params(ts->input_dev, ABS_TOOL_WIDTH, 0, 15, fuzz_w, 0); input_set_abs_params(ts->input_dev, ABS_HAT0X, -inactive_area_left, max_x + inactive_area_right, fuzz_x, 0); input_set_abs_params(ts->input_dev, ABS_HAT0Y, -inactive_area_top, max_y + inactive_area_bottom, fuzz_y, 0); /* ts->input_dev->name = ts->keypad_info->name; */ ret = input_register_device(ts->input_dev); if (ret) { printk(KERN_ERR "synaptics_ts_probe: Unable to register %s input device\n", ts->input_dev->name); goto err_input_register_device_failed; } if (client->irq) { ret = request_irq(client->irq, synaptics_ts_irq_handler, 0, client->name, ts); if (ret == 0) { ret = i2c_smbus_write_byte_data(ts->client, 0xf1, 0x01); if (ret) free_irq(client->irq, ts); } if (ret == 0) ts->use_irq = 1; else dev_err(&client->dev, "request_irq failed\n"); } if (!ts->use_irq) { hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ts->timer.function = synaptics_ts_timer_func; hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); } #ifdef CONFIG_HAS_EARLYSUSPEND ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; ts->early_suspend.suspend = synaptics_ts_early_suspend; ts->early_suspend.resume = synaptics_ts_late_resume; register_early_suspend(&ts->early_suspend); #endif

204 第 7 章 输入系统驱动

printk(KERN_INFO "synaptics_ts_probe: Start touchscreen %s in %s mode\n", ts->input_dev->name, ts->use_irq ? "interrupt" : "polling");

return 0;

err_input_register_device_failed: input_free_device(ts->input_dev);

err_input_dev_alloc_failed: err_detect_failed: err_power_failed: kfree(ts); err_alloc_data_failed: err_check_functionality_failed: return ret; }

上述代码通过函数 i2c_smbus_read_byte_data()读取了其寄存器信息,这样获取了其事件信息。 另外也可以通过 i2c_transfer 批量读取其寄存器的信息。

7.5.2 按键和轨迹球驱动 MSM 同时具备按键和轨迹球的功能,对应的驱动程序保存在文件“arch/arm/mach-msm/board- mahimahi-keypad.c”中,文件 board-mahimahi-keypad.c 的实现流程如下所示。 (1)下面是定义全局数组和指针的代码。

static struct gpio_event_info *mahimahi_input_info[] = { &mahimahi_keypad_matrix_info.info, // 键盘矩阵 &mahimahi_keypad_key_info.info, // 键盘信息 &jogball_x_axis.info.info, // 轨迹球 X 方向信息 &jogball_y_axis.info.info, // 轨迹球 Y 方向信息 }; static struct gpio_event_platform_data mahimahi_input_data = { .names = { "mahimahi-keypad", // 按键设备 "mahimahi-nav", // 轨迹球设备 NULL, }, .info = mahimahi_input_info, .info_count = ARRAY_SIZE(mahimahi_input_info), .power = jogball_power, }; static struct platform_device mahimahi_input_device = { .name = GPIO_EVENT_DEV_NAME, .id = 0, .dev = { .platform_data = &mahimahi_input_data, }, };

205 Android 驱动开发与移植实战详解

因为按键和轨迹球是通过 GPIO 系统来实现的,所以在上面定义了一个 gpio_event_info 类型的 数组。“mahimahi-keypad”和“mahimahi-nav”分别表示两个设备的名称。在 gpio_event_info 指针 数组 mahimahi_input_info 中包括 4 个成员,分别是 mahimahi_keypad_matrix_info.info 、 mahimahi_keypad_key_ info.info、jogball_x_axis.info.info 和 jogball_y_axis.info.info。 (2)使 用 gpio_event_matrix_info 矩阵定义按键驱动,按键驱动是利用 GPIO 矩阵实现的,在定 义时需要包含按键的 GPIO 矩阵和 input 设备的信息,具体代码如下所示。

static unsigned int mahimahi_col_gpios[] = { 33, 32, 31 }; static unsigned int mahimahi_row_gpios[] = { 42, 41, 40 }; #define KEYMAP_INDEX(col, row) ((col)*ARRAY_ SIZE(mahimahi_row_gpios) + (row)) #define KEYMAP_SIZE (ARRAY_SIZE(mahimahi_col_gpios) * \ ARRAY_SIZE(mahimahi_row_gpios)) static const unsigned short mahimahi_keymap [KEYMAP_SIZE] = { // 按键映射关系 [KEYMAP_INDEX(0, 0)] = KEY_VOLUMEUP, /* 115 */ [KEYMAP_INDEX(0, 1)] = KEY_VOLUMEDOWN, /* 114 */ [KEYMAP_INDEX(1, 1)] = MATRIX_KEY(1, BTN_MOUSE), }; static struct gpio_event_matrix_info mahimahi _keypad_matrix_info = { .info.func = gpio_event_matrix_func, // 关键函数实现 .keymap = mahimahi_keymap, .output_gpios = mahimahi_col_gpios, .input_gpios = mahimahi_row_gpios, .noutputs = ARRAY_SIZE(mahimahi_col_gpios), .ninputs = ARRAY_SIZE(mahimahi_row_gpios), .settle_time.tv.nsec = 40 * NSEC_PER_USEC, .poll_time.tv.nsec = 20 * NSEC_PER_MSEC, .flags = (GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_REMOVE_PHANTOM_KEYS | GPIOKPF_PRINT_UNMAPPED_KEYS), }; static struct gpio_event_direct_entry mahimahi_ keypad_key_map[] = { // Power 按键 { .gpio = MAHIMAHI_GPIO_POWER_KEY, .code = KEY_POWER, }, }; static struct gpio_event_input_info mahimahi_ keypad_key_info = { .info.func = gpio_event_input_func, // 关键函数实现 .info.no_suspend = true, .flags = 0, .type = EV_KEY,

206 第 7 章 输入系统驱动

.keymap = mahimahi_keypad_key_map, .keymap_size = ARRAY_SIZE(mahimahi_keypad_key_map) };

在上述代码中,mahimahi_keypad_key_matrix_info 和 mahimahi_keypad_info 是 gpio_event_ matrix_info 类型的结构体,功能是分别实现两个按键和一个按键的处理功能。 (3)使用 GPIO 驱动实现轨迹球部分驱动,在具体实现时分别由 X 方向和 Y 方向两部分组成。 具体代码如下所示。

static uint32_t jogball_x_gpios[] = { MAHIMAHI_GPIO_BALL_LEFT, MAHIMAHI_GPIO_BALL_RIGHT, }; static uint32_t jogball_y_gpios[] = { MAHIMAHI_GPIO_BALL_UP, MAHIMAHI_GPIO_BALL_DOWN, }; static struct jog_axis_info jogball_x_axis = { // X 轴的内容 .info = { .info.func = gpio_event_axis_func, // 关键函数实现 .count = ARRAY_SIZE(jogball_x_gpios), .dev = 1, .type = EV_REL, .code = REL_X, .decoded_size = 1U << ARRAY_SIZE(jogball_x_gpios), .map = jogball_axis_map, .gpio = jogball_x_gpios, .flags = GPIOEAF_PRINT_UNKNOWN_DIRECTION, } }; static struct jog_axis_info jogball_y_axis = { // Y 轴的内容 .info = { .info.func = gpio_event_axis_func, // 关键函数实现 .count = ARRAY_SIZE(jogball_y_gpios) .dev = 1, .type = EV_REL, .code = REL_Y, .decoded_size = 1U << ARRAY_SIZE(jogball_y_gpios), .map = jogball_axis_map, .gpio = jogball_y_gpios, .flags = GPIOEAF_PRINT_UNKNOWN_DIRECTION, } };

在上述代码中,使用 jog_axis_info 类型的结构体定义了轨迹球,这种设备的类型(type)是相 对设备 EV_REL。

207 Android 驱动开发与移植实战详解

7.6 OMAP 处理器中的输入驱动实现

在本节将简要介绍 OMAP 处理器平台中输入驱动的基本知识,为读者步入本书后面知识的学 习打下基础。

7.6.1 触摸屏驱动程序 OMAP 的 Zoom 平台的输入设备包括触摸屏和键盘(Qwerty 全键盘)两种,其中触摸屏驱动 程序保存在文件“drivers/input/touchscreen/synaptics_ i2c_rmi.c”中,这是一个 i2c 的触摸屏的驱动 程序,因为和 MSM 的完全相同,所以在此将不再进行具体讲解。

7.6.2 键盘驱动程序 Zoom 平台的键盘驱动程序保存在文件“drivers/input/keyboard/twl4030_keypad.c”中,此驱动 使用了 i2c 的接口,驱动本身经过了一次封装处理。文件 twl4030_keypad.c 中的核心内容是中断处 理相关的内容,其中函数 do_kp_irq()是标准 Linux 系统的中断的处理函数,定义此函数的实现代码 如下所示。

static irqreturn_t do_kp_irq(int irq, void *_kp) { struct twl4030_keypad *kp = _kp; u8 reg; int ret; ret = twl4030_kpread(kp, ®, KEYP_ISR1, 1); // 调用 twl4030_i2c_read if ((ret >= 0) && (reg & KEYP_IMR1_KP)) twl4030_kp_scan(kp, 0); // 非释放所有的处理 else twl4030_kp_scan(kp, 1); // 释放所有的处理 return IRQ_HANDLED; }

函数 twl4030_kp_scan()的功能是实现核心处理功能,先找到按键的行列,然后调用函数 input_report_key()汇报结果。函数 twl4030_kp_scan()的主要的实现代码如下所示。

static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all) { u16 new_state[MAX_ROWS]; int col, row; // ...... 省略部分内容 for (row = 0; row < kp->n_rows; row++) { int changed = new_state[row] ^ kp->kp_state[row]; // ...... 省略部分内容 for (col = 0; col < kp->n_cols; col++) {

208 第 7 章 输入系统驱动

int key; key = twl4030_find_key(kp, col, row); // ...... 省略部分内容 input_report_key(kp->input, key, // 上报按键消息 new_state[row] & (1 << col)); } kp->kp_state[row] = new_state[row]; } input_sync(kp->input); }

函数 twl4030_find_key()的功能是根据行列来扫描键盘信息,下面是此函数的实现代码。

static int twl4030_find_key(struct twl4030_keypad *kp, int col, int row) { int i, rc; rc = KEY(col, row, 0); for (i = 0; i < kp->keymapsize; i++) if ((kp->keymap[i] & ROWCOL_MASK) == rc) return kp->keymap[i] & (KEYNUM_MASK | KEY_PERSISTENT); return -EINVAL; }

在上述代码中,使用数组 kp->keymap 定义了按键的映射关系。数组 kp->keymap 是在文件 “arch/arm/mach-omap2/board-zoom2.c”中定义的,并且和数组 zoom2_twl4030_keymap 相对应。下 面是数组 zoom2_twl4030_keymap 的定义代码。

static int zoom2_twl4030_keymap[] = { KEY(0, 0, KEY_E), KEY(1, 0, KEY_R), KEY(2, 0, KEY_T), KEY(3, 0, KEY_HOME), KEY(6, 0, KEY_I), KEY(7, 0, KEY_LEFTSHIFT), …… KEY(7, 7, KEY_DOWN), KEY(0, 7, KEY_PROG1), KEY(1, 7, KEY_PROG2), KEY(2, 7, KEY_PROG3), KEY(3, 7, KEY_PROG4), 0 };

在 OMAP 的 Zoom 平台中,因为键盘基本上是全键盘,并且其数字键和字母键是共用的,所 以使用全键盘的配置文件基本上可以满足需求。

209

第 8 章 电话系统驱动

作为一款手机来说,最重要的功能是拨打/接听电话和收发短信。在 Android 底层架构中,电 话和短信功能是通过电话系统实现的,本章将详细讲解Android电话系统的基本知识和移植的知识。

8.1 电话系统基础

Android 系统作为一款智能手机平台,电话(Telephony)功能十分重要。电话系统的主要功能 是呼叫(Call)、收发短信(SMS)、数据连接(DataConnection)、SIM 卡和电话本等。

8.1.1 Android 电话系统简介 Android 的电话系统是由 RadioInterfaceLayer(RIL)层实现的,此层提供了电话服务和 Radio 硬件之间的抽象层。RadioInterfaceLayerRIL(RadioInterfaceLayer)负责实现可靠的数据传输、发 送 AT 命令以及 response 解析。应用处理器通过 AT 命令集与带 GPRS 功能的无线通信模块通信。 ATcommand 由 Hayes 公司发明,是一个调制解调器制造商采用的调制解调器命令语言,每条命令 以字母“AT”开头。 RIL 模块大致由 rild、libril.so、librefrence.so 三部分组成。 (1)rild 模块。 rild 模块被编译成一个可执行文件,实现一个 main 函数作为整个 ril 模块入口点,负责串联整 个电话系统,完成初始化,这个可执行文件是带命令行参数,可以在 init.rc 文件中配置,如果没有 则会读取/system/build.prop,加载命令行参数,这里我们是采用读 build.prop 文件的方式配置串口 设备的。

rild.libargs=-d /dev/s3c2410_serial1

这里的 Modem 设备是通过串口设备与 CPU 连接的,也就是这里的 s3c2410_serial1 串口,将通 过这个串口设备向 Modem 发送 AT 指令。 (2)libril.so。 这个库文件和 rild 结合得比较紧密,是其共享库,在编译的时候就确定了这种关系,主要文件 为 ril.cpp 、 ril_event.cpp 、 libril.so ,主要负责同上层的通信工作,接收 ril 请求并传递给 librefrence_ril.so,同时将 librefrence_ril.so 返回的消息送给调用进程。

第 8 章 电话系统驱动

(3)librefrence_ril.so。 这个库文件通过 dlopen 方式加载,与 rild 结合不是很紧密,主要负责跟 Modem 硬件通信,它 转换来自 libril.so 的请求为 AT 命令,同时监听 Modem 的反馈信息,并传回 libril.so,这种运行时 加载的方式可以很方便地修改参数来适配不同的 Modem。

8.1.2 深入分析电话系统的实现文件 (1)目录“hardware/ril/include/telephony/”中的文件 ril.h 是 ril 部分的基础头文件,其中定义 结构体 RIL_RadioFunctions 的代码如下所示。

typedefstruct{ intversion; RIL_RequestFunconRequest; RIL_RadioStateRequestonStateRequest; RIL_Supportssupports; RIL_CancelonCancel; RIL_GetVersiongetVersion; }RIL_RadioFunctions;

在结构体 RIL_RadioFunctions 中包含了几个函数指针的结构体,这实际上是一个移植层的接 口。在实现下层库后,通过 rild 守护进程得到这些函数指针,并执行对应的函数。 其中几个重要的函数指针的原型如下所示。

typedefvoid(*RIL_RequestFunc)(intrequest,void*data,size_tdatalen,RIL_Tokent); typedefRIL_RadioState(*RIL_RadioStateRequest)(); typedefint(*RIL_Supports)(intrequestCode); typedefvoid(*RIL_Cancel)(RIL_Tokent); typedefconstchar*(*RIL_GetVersion)(void);

(2)守护进程 rild。 rild 守护进程的文件保存在目录“hardware/ril/rild”中,核心文件是 rild.c 和 radiooptions.c,此目 录中的文件经过编译后会生成一个可执行程序,这个程序在安装系统的“/system/bin/rild”路径中。 文件 rild.c 是这个守护进程的入口,它具有一个主函数的入口 main(),在执行时将请求转换成 AT 命令的字符串给下层的硬件执行。在运行过程中,使用 dlopen 打开“/system/lib/”目录中的动 态库 libreference-ril.so,然后从中取出 RIL_Init 符号来运行。 RIL_Init 符号是一个函数指针,执行此函数后返回一个 RIL_RadioFunctions 类型的指针。得到 此指针后调用函数 RIL_register(),将这个指针注册到 libril 库之中,然后进入循环。其实此守护进 程提供了一个申请处理的框架,具体功能都是在 libril.so 和 libreference-ril.so 中完成的。 (3)动态库 libreference-ril.so。 动态库 libreference-ril.so 的路径如下所示。

hardware/ril/reference-ril

其中 Android 电话功能主要的文件是 reference-ril.c 和 atchannel.c。这个库必须实现的是一个名 称为 RIL_Init 的函数,这个函数执行的结果是返回一个 RIL_RadioFunctions 结构体的指针,指针指

211 Android 驱动开发与移植实战详解

向函数指针。这个库在执行的过程中需要创建一个线程来执行实际的功能。在执行的过程中,这个 库将打开一个“/dev/ttySXXX”的终端(终端的名字是从上层传入的),然后利用这个终端控制硬 件执行。 (4)libril.so 动态库。 libril.so 库的目录如下所示。

hardware/ril/libril

其中主要文件是 ril.cpp,此库需要实现以下几个接口。

RIL_startEventLoop(void); voidRIL_setcallbacks(constRIL_RadioFunctions*callbacks); RIL_register(constRIL_RadioFunctions*callbacks); RIL_onRequestComplete(RIL_Tokent,RIL_Errnoe,void*response,size_tresponselen); voidRIL_onUnsolicitedResponse(intunsolResponse,void*data, size_tdatalen); RIL_requestTimedCallback(RIL_TimedCallbackcallback,void*param, conststructtimeval*relativeTime);

这些函数也是被 rild 守护进程调用的,不同的 vendor 可以通过自己的方式实现这几个接口, 这样可以保证 RIL 在不同系统的移植。其中函数 RIL_register()把外部的 RIL_RadioFunctions 结构体 注册到这个库之中,在恰当的时候调用相应的函数。在 Android 电话功能执行的过程中,这个库处 理了一些将请求转换成字符串的功能。

8.1.3 电话系统结构 Android 电话系统主要分为 4 层结构,分别是 Modem 驱动、RIL(RadioInterfaceLayer)、电话 服务框架和应用层。具体结构如图 8-1 所示。

短信、MMS和电话本等电话应用 平台API

本地框架 android.telephony包 com.android.internal.telephony包

数据连接控制管理 Android系统

本地框架 rild 守护进程 ril实现库

有关的Modem设备 硬件和驱动

▲图 8-1 电话系统的层次结构

212 第 8 章 电话系统驱动

Android 电话系统从上到下主要包括 Java 应用、Java 框架、本地 RIL 层和 Modem 驱动,这几 部分的具体说明如下所示。 (1)Modem 驱动。 实现电话功能的主要硬件是通信模块(Modem),Modem 通过与通信网络进行沟通传输语音及 数据,完成呼叫、短信等相关电话功能。对于大部分目前的独立通信模块而言,无论是 2G 还是 3G 都已经非常成熟,模块化相当完善,硬件接口非常简单,也有着相对统一的软件接口。一般的 Modem 模块装上 SIM 卡,直接上电即可工作,自动完成初始的找网、网络注册等工作,完成之后即可打 电话、发短信等。但独立模块因为体积问题,在手机设计中较少使用,而是使用 chip-on-board 的 方式。另外也有不少 Modem 基带与应用处理器共存。 (2)RIL 硬件抽象层。 RIL 负责数据的可靠传输、AT 命令的发送以及 Response 解析。应用处理器通过 AT 命令集与 带 GPRS 功能的无线通信模块通信。ATcommand 由 Hayes 公司发明,是一个调制解调器制造商采 用的调制解调器命令语言,每条命令以字母 AT 开头。 RIL 支持的本地代码包括 ril 库和守护进程,主要代码路径如下所示。

hardware/ril/include hardware/ril/libril hardware/ril/rild hardware/ril/reference-ril

编译后结果如下所示。 /system/bin/rild:守护进程。 /system/lib/libril.so:生成 RIL 库。 /system/lib/libreference-ril.so:生成 RIL 参考库。 在 Android 电话系统中没有 JNI 部分,RIL 守护进程通过名为 rild 的 Socker 和 Java 框架层进行通信。 (3)Java 框架。 此部分的代码路径是“frameworks/base/telephony/java/”,在此路径中有如下 Java 类。 android/telephony :实现了 Java 类 android.telephony 、 android.telephony.gsm 以及 android.telephony.cdma。 com/android/internal/telephony:实现了内部的类 com.android.internal.telephony、com.android. internal.telephony.gsm 以及 com.android.internal.telephony.cdma,带 GSM 的是 GSM 的专用协议,而 不带的是通用部分。 (4)应用层。 在电话系统中,通过 Service 实现 Phone 应用,并同时实现 Phone 的 UI 界面逻辑。而短信和网 络选择分别在 MMS 和 Settings 应用中实现。

8.2 移植 Modem 驱动和 RIL 硬件抽象层

在移植 Android 的电话系统时,需要移植 Modem 驱动和 RIL 硬件抽象层。当前大多数 3GModem

213 Android 驱动开发与移植实战详解

是通过 USB 设备实现连接的,也就是说使用 USB 转 Serial 接口。当使用 USB 转 Serial 接口模式时, 一般使用标准驱动来实现。对于一些特定型号的 Modem 设备来说,需要不同的电源/重启操作,需 要自行开发 USB/Serial 的驱动程序。 根据各个系统的硬件差别,在移植的过程中的主要工作是实现一个类似 libreference-ril.so 库的应 用。当宏 RIL_SHLIB 被定义的时候,将使用库的形式;当没有被定义的时候,将使用守护进程的方 式(在这种情况下不需要 rild)。Java 框架及应用电话部分 RIL 移植主要需要考虑的问题如下所示。 RIL 设备所使用的不同端。 在 RIL_RadioFunctions 的 onRequest 函数中需要处理的不同命令。

8.3 移植和调试

经过本章前面内容的讲解,已经了解了 Android 电话系统的基本结构和我们需要移植的任务。 在本节的内容中,将详细讲解在 Android 平台中移植和调试电话系统的方法,为读者步入本书后面 知识的学习打下基础。

8.3.1 驱动程序 在介绍驱动移植之前,需要先了解 rild 和 libril.so 以及 librefrence_ril.so 的关系。 (1)rild。 实现 main()函数作为整个 ril 层的入口点,负责完成初始化工作。 (2)libril.so。 与 rild 紧密结合,在编译时已经建立了这一关系,是由文件 ril.cpp 和ril_event.cpp 组成的。libril.so 驻留在守护进程 rild 中,功能是完成同上层通信的工作,接受 ril 请求传递给 librefrence_ril.so,同 时把来自 librefrence_ril.so 的反馈回传给调用进程。 (3)librefrence_ril.so。 rild 通过手动的 dlopen 方式加载,因为 librefrence.so 主要负责跟 Modem 硬件通信的缘故,所 以结合得稍微松散,这样做更方便替换或修改以适配更多的 Modem 种类。librefrence_ril.so 转换来 自 libril.so 的请求为 AT 命令,同时监控 Modem 的反馈信息,并传递回 libril.so。在初始化时,rild 通过符号 RIL_Init 获取一组函数指针并以此与之建立联系。 (4)radiooptions。 radiooptions 通过获取启动参数,利用 socket 与 rild 进行通信,可供调试时配置 Modem 参数。 经过前面内容的介绍,我们知道 Modem 驱动的主要工作是 USB 转 Serial 接口,以及实现特殊 USB/Serial。上述驱动保存在如下目录中。

drivers/usb/seral/ drivers/seral/

在 Android 电话系统的 Modem 驱动中,通常使用“USB 转 Serial 标准”实现 AT 和数据通道 的接口,此功能是通过文件“drivers/usb/serial/option.c”实现的。在实现时首先定义下面的

214 第 8 章 电话系统驱动 option_1port_device 设备。

staticstructusb_serial_driveroption_1port_device={ .driver={ .owner=THIS_MODULE, .name="option1", }, .description="GSMmodem(1-port)", .usb_driver=&option_driver, .id_table=option_ids, .num_ports=1, .probe=option_probe, .open=usb_wwan_open, .close=usb_wwan_close, .dtr_rts=usb_wwan_dtr_rts, .write=usb_wwan_write, .write_room=usb_wwan_write_room, .chars_in_buffer=usb_wwan_chars_in_buffer, .set_termios=usb_wwan_set_termios, .tiocmget=usb_wwan_tiocmget, .tiocmset=usb_wwan_tiocmset, .ioctl=usb_wwan_ioctl, .attach=usb_wwan_startup, .disconnect=usb_wwan_disconnect, .release=usb_wwan_release, .read_int_callback=option_instat_callback, #ifdefCONFIG_PM .suspend=usb_wwan_suspend, .resume=usb_wwan_resume, #endif };

上述驱动会通过 option_init 注册成为 usb_serial_driver。 然后通过对 usb_driver 的注册来响应 USB 设备枚举,对应的 USBDriver 如下所示。

staticstructusb_driveroption_driver={ .name="option", .probe=usb_serial_probe, .disconnect=usb_serial_disconnect, #ifdefCONFIG_PM .suspend=usb_serial_suspend, .resume=usb_serial_resume, .supports_autosuspend=1, #endif .id_table=option_ids, .no_dynamic_id=1, };

为了匹配枚举中定义的设备,需要定义 WENDORID 和 PRODUCTID。在文件 option.c 中已经 定义了很多可以支持的设备,我们只需在数据里添加设备 IDS 即可。定义数组 option_ids[]的代码

215 Android 驱动开发与移植实战详解

如下所示。

staticconststructusb_device_idoption_ids[]={ {USB_DEVICE(OPTION_VENDOR_ID,OPTION_PRODUCT_COLT)}, {USB_DEVICE(OPTION_VENDOR_ID,OPTION_PRODUCT_RICOLA)}, {USB_DEVICE(OPTION_VENDOR_ID,OPTION_PRODUCT_RICOLA_LIGHT)}, {USB_DEVICE(OPTION_VENDOR_ID,OPTION_PRODUCT_RICOLA_QUAD)}, {USB_DEVICE(OPTION_VENDOR_ID,OPTION_PRODUCT_RICOLA_QUAD_LIGHT)}, {USB_DEVICE(OPTION_VENDOR_ID,OPTION_PRODUCT_RICOLA_NDIS)}, ………………………………

我们可以定义两个需要的 ID,可以按照下面的格式将其添加到上述 option_ids[]数组中。

{USB_DEVICE(XXX_VENDOR_ID,XXX_PRODUCT_RICOLA_LIGHT)},

上述代码中的加粗部分是我们定义的 ID 内容。 此时开启 Modem 电源后,会出现两个名分别为“/dev/ttyusb0”和“/dev/ttyusb1”的端口。

8.3.2 RIL 接口

1.分析 RIL 目录

(1)目录“hardware/ril/libril”。 此目录下代码负责与上层客户进程进行交互。在接收到客户进程命令后,调用相应函数进行处 理,然后将命令响应结果传回客户进程。在收到来自网络端的事件后,也传给客户进程。 文件 ril_commands.h:列出了 telephony 可以接收的命令,每个命令对应的处理函数,命令 响应的处理函数。例如下面的代码。

{RIL_REQUEST_GET_SIM_STATUS,dispatchVoid,responseSimStatus}, {RIL_REQUEST_ENTER_SIM_PIN,dispatchStrings,responseInts}, {RIL_REQUEST_ENTER_SIM_PUK,dispatchStrings,responseInts}, {RIL_REQUEST_ENTER_SIM_PIN2,dispatchStrings,responseInts}, {RIL_REQUEST_ENTER_SIM_PUK2,dispatchStrings,responseInts}, {RIL_REQUEST_CHANGE_SIM_PIN,dispatchStrings,responseInts}, ......

文件 ril_unsol_commands.h:列出了 telephony 可以接收的事件类型和对每个事件的处理函 数,例如下面的代码。

{RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED,responseVoid,WAKE_PARTIAL}, {RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,responseVoid,WAKE_PARTIAL}, {RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED,responseVoid,WAKE_PARTIAL}, {RIL_UNSOL_RESPONSE_NEW_SMS,responseString,WAKE_PARTIAL}, {RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT,responseString,WAKE_PARTIAL}, {RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM,responseInts,WAKE_PARTIAL}, ......

文件 ril_event.h/cpp:实现了处理与事件源(端口,modem 等)相关的功能。ril_event_loop

216 第 8 章 电话系统驱动

监视所有注册的事件源,当某事件源有数据到来时,相应事件源的回调函数被触发 (firePending->ev->func())。 文件 ril.cpp 功能比较复杂,具体说明如下所示。 ¾ 函数 RIL_register() :打开监听端口,接收来自客户进程的命令请求 (s_fdListen=android_get_control_socket(SOCKET_NAME_RIL);),当与某客户进程连接 建立时调用 listenCallback()函数,创建一单独线程监视并处理所有事件源(通过 ril_event_loop)。 ¾ 函数 listenCallback() :当与客户进程连接建立时调用此函数。此函数接着调用 processCommandsCallback 处理来自客户进程的命令请求。 ¾ 函数 processCommandsCallback():具体处理来自客户进程的命令请求。对每一个命令, ril_commands.h 中都规定了对应的命令处理函数(dispatchXXX ), processCommandsCallback 会调用这个命令处理函数进行处理。

¾ dispatch 系列函数:此函数接收来自客户进程的命令与相应参数,并调用 onRequest 进行 处理。 ¾ 函数 RIL_onUnsolicitedResponse():将来自网络端的事件封装(通过调用 responseXXX) 后传给客户进程。 ¾ 函数 RIL_onRequestComplete():通过调用 responseXXX 封装命令的最终响应结构,然后 传给客户进程。

¾ response 系列函数:对每一个命令,都规定了一个对应的 response 函数来处理命令的最 终响应;对每一个网络端的事件,也规定了一个对应的 response 函数来处理此事件。 response 函数可被 onUnsolicitedResponse 或者 onRequestComplete 调用。 (2)目录“hardware/ril/reference-ril”。 此目录下的代码主要负责与 Modem 进行交互。 文件 reference-ril.c:此文件的核心是函数 onRequest()和函数 onUnsolicited()。 ¾ 函数 onRequest():在这个函数里,对每一个 RIL_REQUEST_XXX 请求,都转化成相应 的 ATcommand,发送给 modem,然后睡眠等待。当收到此 ATcommand 的最终响应后, 线程被唤醒,将响应传给客户进程(RIL_onRequestComplete->sendResponse)。 ¾ 函数 onUnsolicited():这个函数处理 modem 从网络端收到的各种事件,如网络信号变化, 拨入的电话,收到短信等。然后将时间传给客户进程(RIL_onUnsolicitedResponse-> sendResponse)。 ¾ 文件 atchannel.c:负 责 向 modem 读写数据。其中,写数据(主要是 ATcommand)功能运行 在主线程中,读数据功能运行在一个单独的读线程中。此文件的核心功能是函数 at_send_command_full_nolock,此函数运行在主线程里面,用于将一个 ATcommand 命 令写入 modem 后进入睡眠状态(使用 pthread_cond_wait 或类似函数),直到 modem 读 线程将其唤醒。唤醒后此函数获得了 ATcommand 的最终响应并返回。函数 readerLoop 运行在一个单独的读线程里面,负责从 modem 中读取数据。读到的数据可分为三种类 型:网络端传入的事件;modem 对当前 ATcommand 的部分响应;modem 对当前

217 Android 驱动开发与移植实战详解

ATcommand 的全部响应。对第三种类型的数据(ATcommand 的全部响应),读线程唤 醒(pthread_cond_signal)睡眠状态的主线程。 文件 at_tok.c:提供 AT 响应的解析函数。 文件 misc.c:只提供一个字符串匹配函数。 (3)目录“hardware/ril/rild”。 此目录下代码的功能是生成 rild 和 radiooptions 的可执行文件。 文件 radiooptions.c:生成 radiooptions 的可执行文件,radiooptions 程序把命令行参数传递 给 socket{rild-debug}去处理,从而达到与 rild 通信的目的。 文件 rild.c:生成 rild 的可执行文件。

2.RIL 接口移植

实现 RIL 接口的过程比较复杂,复杂程度主要体现在维护工作、需要处理的命令和较多结构 体上。在文件“hardware/ril/include/telephony/ril.h”中定义了 RIL 接口,并且有同一个目录下的 ril_cdma_sms.h 作为对 CDMA 协议的有利补充。 在文件 ril.h 中首先定义核心结构 RIL_Env,具体代码如下所示。

structRIL_Env{ //请求完成 void(*OnRequestComplete)(RIL_Tokent,RIL_Errnoe, void*response,size_tresponselen); //上报消息响应 void(*OnUnsolicitedResponse)(intunsolResponse,constvoid*data, size_tdatalen); //请求中进行周期处理 void(*RequestTimedCallback)(RIL_TimedCallbackcallback, void*param,conststructtimeval*relativeTime); };

上述结构体是由 libril.so 库作为标准实现的,可以为硬件抽象层的实现库调用。能够在发生请 求时针对不同的情况而作出具体响应,例如有请求完成函数响应、上报信息函数响应和请求中周期 处理函数三个响应。 在结构体 RIL_RadioFunctions 定义需要的函数指针。

typedefvoid(*RIL_RequestFunc)(intrequest,void*data, size_tdatalen,RIL_Tokent); typedefRIL_RadioState(*RIL_RadioStateRequest)(); typedefint(*RIL_Supports)(intrequestCode); typedefvoid(*RIL_Cancel)(RIL_Tokent); typedefvoid(*RIL_TimedCallback)(void*param); typedefconstchar*(*RIL_GetVersion)(void);

typedefstruct{ intversion;/ RIL_RequestFunconRequest;

218 第 8 章 电话系统驱动

RIL_RadioStateRequestonStateRequest; RIL_Supportssupports; RIL_CancelonCancel; RIL_GetVersiongetVersion; }RIL_RadioFunctions;

RIL 实现库需要实现结构体 RIL_RadioFunctions 中的内容,当通过 RIL_Init 返回 rild 后,rild 会调用下面的函数完成注册。

voidRIL_register(constRIL_RadioFunctions*callbacks);

此时成功搭建了 rild 到 RIL 的实现库的请求发送路径和响应路径。

8.4 实现电话系统驱动

电话系统的驱动部分主要是通过建立一个循环消息队列的机制来管理上层发送的命令和给硬 件 Modem 发 AT 指令,从而达到消息的传输,驱动框架图如图 8-2 所示。

▲图 8-2 RIL 驱动框架

可执行文件 rild 是整个 RIL 模块的入口点,文件路径是 hardware/ril/rild/Rild.c。

8.4.1 RIL 中消息队列的建立 在 main()函数中,调用 libril.so 的 RIL_startEventLoop()建立消息循环队列,通过 dlopen()方式 加载 librefrence_ril.so,初始化回调函数并将返回的值注册进 RIL 系统中。

//定义加载的库文件路径 #define REFERENCE_RIL_PATH "/system/lib/libreference-ril.so" …… int main(int argc, char **argv) { …… switchUser();

219 Android 驱动开发与移植实战详解

//打开库文件 dlHandle = dlopen(rilLibPath, RTLD_NOW); if (dlHandle == NULL) { fprintf(stderr, "dlopen failed: %s\n", dlerror()); exit(-1); } //建立消息 ril 的消息循环 RIL_startEventLoop(); //加载其中的初始化函数 rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init"); if (rilInit == NULL) { fprintf(stderr, "RIL_Init not defined or exported in %s\n", rilLibPath); exit(-1); } if (hasLibArgs) { rilArgv = argv + i - 1; argc = argc -i + 1; } else { static char * newArgv[MAX_LIB_ARGS]; static char args[PROPERTY_VALUE_MAX]; rilArgv = newArgv; property_get(LIB_ARGS_PROPERTY, args, ""); argc = make_argv(args, rilArgv); } rilArgv[0] = argv[0]; //通过之前加载的函数来初始化 librefrence.so 中的接口 funcs = rilInit(&s_rilEnv, argc, rilArgv); //注册回调函数 RIL_register(funcs); done: while(1) { sleep(0x00ffffff); } }

通过调用 RIL_startEventLoop()函数可以建立多路 I/O 驱动机制的消息队列,用来接收上层发出 的命令以及往 Modem 发 AT 指令的工作,是整个 RIL 系统的核心部分,RIL_startEventLoop()定义 在文件 Ril.cpp 中,其路径如下所示。

"hardware/ril/libril/Ril.cpp"

函数 RIL_startEventLoop()创建了 eventLoop 线程来加载消息队列。

extern "C" void RIL_startEventLoop(void) { int ret; pthread_attr_t attr; //标志位,用来等待直到 eventLoop 线程启动

220 第 8 章 电话系统驱动

s_started = 0; pthread_mutex_lock(&s_startupMutex); pthread_attr_init (&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //创建 eventLoop 线程 ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL); while (s_started == 0) { //线程等待 pthread_cond_wait(&s_startupCond, &s_startupMutex); } pthread_mutex_unlock(&s_startupMutex); if (ret < 0) { LOGE("Failed to create dispatch thread errno:%d", errno); return; } }

在 eventLoop()线程里会初始化消息队列,并建立消息队列机制。

static void *eventLoop(void *param) { int ret; int filedes[2]; //初始化消息队列 ril_event_init(); pthread_mutex_lock(&s_startupMutex); s_started = 1; pthread_cond_broadcast(&s_startupCond); pthread_mutex_unlock(&s_startupMutex); ret = pipe(filedes); if (ret < 0) { LOGE("Error in pipe() errno:%d", errno); return NULL; } s_fdWakeupRead = filedes[0]; s_fdWakeupWrite = filedes[1]; fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK); //设置 wakeup 消息 ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true, processWakeupCallback, NULL); //将 wakeup 消息加入到消息队列中 rilEventAddWakeup (&s_wakeupfd_event); //开始消息循环 ril_event_loop(); LOGE ("error in event_loop_base errno:%d", errno);

return NULL; }

真正的等待设备上消息的读写是在 ril_event_loop()中完成的,函数定义在 Ril_event.cpp 中,这 个文件主要是提供处理消息的接口函数,文件路径如下所示。

221 Android 驱动开发与移植实战详解

"hardware/ril/libril/Ril_event.cpp"

函数 ril_event_loop()会一直等待直到设备上有数据读写,然后会判断是 I/O 消息还是超时消息, 将消息加入到 pending_list 的结构体队列中,最后由 firePending()来处理这个结构体队列中的消息, 代码如下所示。

void ril_event_loop() { int n; fd_set rfds; struct timeval tv; struct timeval * ptv; //循环等待 for (;;) { //复制一份设备描述符到本地 memcpy(&rfds, &readFds, sizeof(fd_set)); if (-1 == calcNextTimeout(&tv)) { // no pending timers; block indefinitely ptv = NULL; } else { dlog("~~~~ blocking for %ds + %dus ~~~~", (int)tv.tv_sec, (int)tv.tv_usec); ptv = &tv; } printReadies(&rfds); //用 select 函数来监听设备上的数据 n = select(nfds, &rfds, NULL, NULL, ptv); printReadies(&rfds); dlog("~~~~ %d events fired ~~~~", n); if (n < 0) { if (errno == EINTR) continue; LOGE("ril_event: select error (%d)", errno); // bail? return; } //处理超时消息 processTimeouts(); //处理 I/O 数据消息 processReadReadies(&rfds, n); //处理队列中的消息 firePending(); } }

在 Ril_event.cpp 文件中同时也定义了一些处理消息的重要的接口函数,例如函数 ril_event_init() 会对 timer_list、pending_list 以及 watch_table 进行初始化。

void ril_event_init() { MUTEX_INIT(); FD_ZERO(&readFds);

222 第 8 章 电话系统驱动

init_list(&timer_list); init_list(&pending_list); memset(watch_table, 0, sizeof(watch_table)); }

函数 ril_event_set()会初始化 ril_event 结构体,也就是初始化消息对象。 void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param) { dlog("~~~~ ril_event_set %x ~~~~", (unsigned int)ev); memset(ev, 0, sizeof(struct ril_event)); ev->fd = fd; ev->index = -1; ev->persist = persist; ev->func = func; ev->param = param; fcntl(fd, F_SETFL, O_NONBLOCK); }

函数 ril_event_add()的功能是将 event 添加到监听队列 watch_table 中。 void ril_event_add(struct ril_event * ev) { dlog("~~~~ +ril_event_add ~~~~"); MUTEX_ACQUIRE(); for (int i = 0; i < MAX_FD_EVENTS; i++) { if (watch_table[i] == NULL) { watch_table[i] = ev; ev->index = i; dlog("~~~~ added at %d ~~~~", i); dump_event(ev); FD_SET(ev->fd, &readFds); if (ev->fd >= nfds) nfds = ev->fd+1; dlog("~~~~ nfds = %d ~~~~", nfds); break; } } MUTEX_RELEASE(); dlog("~~~~ -ril_event_add ~~~~"); }

函数 ril_timer_add()可以添加一个 timer event。 void ril_timer_add(struct ril_event * ev, struct timeval * tv) { dlog("~~~~ +ril_timer_add ~~~~"); MUTEX_ACQUIRE(); struct ril_event * list; if (tv != NULL) { //加入到 timer_list 链表

223 Android 驱动开发与移植实战详解

list = timer_list.next; ev->fd = -1; // make sure fd is invalid struct timeval now; getNow(&now); timeradd(&now, tv, &ev->timeout); //排序 while (timercmp(&list->timeout, &ev->timeout, < ) && (list != &timer_list)) { list = list->next; } addToList(ev, list); } MUTEX_RELEASE(); dlog("~~~~ -ril_timer_add ~~~~"); }

ril_event_del()从 watch table 或者 timer_list 队列中删除消息。

void ril_event_del(struct ril_event * ev) { dlog("~~~~ +ril_event_del ~~~~"); MUTEX_ACQUIRE(); if (ev->index < 0 || ev->index >= MAX_FD_EVENTS) { MUTEX_RELEASE(); return; } //从 watch table 中删除消息 removeWatch(ev, ev->index); MUTEX_RELEASE(); dlog("~~~~ -ril_event_del ~~~~"); }

这样就成功地建立了 RIL 系统中的消息队列,可以接收上层发来的消息了。

8.4.2 与底层 Modem 通信 与底层 Modem 通信的主要工作是在文件 librefrence_ril.so 中完成的,librefrence_ril.so 的主要代 码为 Reference-ril.c,文件路径如下所示。

"hardware/ril/reference-ril/Reference-ril.c"

Rild 中通过运行时加载 librefrence_ril.so 调用 RIL_Init()进行初始化,代码如下所示。

const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv) { int ret; int fd = -1; int opt; pthread_attr_t attr; s_rilenv = env; while ( -1 != (opt = getopt(argc, argv, "p:d:s:"))) {

224 第 8 章 电话系统驱动

switch (opt) { case 'p': s_port = atoi(optarg); if (s_port == 0) { usage(argv[0]); return NULL; } LOGI("Opening loopback port %d\n", s_port); break; case 'd': //根据命令行参数来赋值 s_device_path = optarg; LOGI("Opening tty device %s\n", s_device_path); break; case 's': s_device_path = optarg; s_device_socket = 1; LOGI("Opening socket %s\n", s_device_path); break; default: usage(argv[0]); return NULL; } } if (s_port < 0 && s_device_path == NULL) { usage(argv[0]); return NULL; } pthread_attr_init (&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //创建一个 mainLoop 线程 ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL); //将 callbacks 函数返回到 Rild 中 return &s_callbacks; }

在新开的线程mainLoop()中会继续初始化,并打开串口设备,通过调用Atchannel.c中的at_open() 方法创建一个线程 readerLoop(),建立与 Modem 的通信机制。 文件 Atchannel.c 的路径如下所示。

"hardware/ril/reference-ril/Atchannel.c"

函数 mainLoop()的代码如下所示。

static void *mainLoop(void *param) { int fd; int ret; AT_DUMP("== ", "entering mainLoop()", -1 ); at_set_on_reader_closed(onATReaderClosed);

225 Android 驱动开发与移植实战详解

at_set_on_timeout(onATTimeout); //循环等待 Modem 数据的上报或响应 for (;;) { fd = -1; while (fd < 0) { if (s_port > 0) { fd = socket_loopback_client(s_port, SOCK_STREAM); } else if (s_device_socket) { if (!strcmp(s_device_path, "/dev/socket/qemud")) { //针对模拟器的 socket fd = socket_local_client( "qemud", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM ); if (fd >= 0 ) { char answer[2]; if ( write(fd, "gsm", 3) != 3 || read(fd, answer, 2) != 2 || memcmp(answer, "OK", 2) != 0) { close(fd); fd = -1; } } } else fd = socket_local_client( s_device_path, ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM ); } else if (s_device_path != NULL) { //打开串口设备,这里的串口设备为/dev/s3c2410_serial1 fd = open (s_device_path, O_RDWR); if ( fd >= 0 && !memcmp( s_device_path, "/dev/ttyS", 9 ) ) { struct termios ios; tcgetattr( fd, &ios ); ios.c_lflag = 0; /* disable ECHO, ICANON, etc... */ tcsetattr( fd, TCSANOW, &ios ); } } if (fd < 0) { perror ("opening AT interface. retrying..."); sleep(10); } } s_closed = 0; //打开 AT 通道,来接收来自硬件的消息上报 ret = at_open(fd, onUnsolicited); if (ret < 0) { LOGE ("AT error %d on at_open\n", ret); return 0; }

226 第 8 章 电话系统驱动

RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0); sleep(1); waitForClose(); } }

函数 at_open()的代码如下所示。 int at_open(int fd, ATUnsolHandler h) { int ret; pthread_t tid; pthread_attr_t attr; s_fd = fd; s_unsolHandler = h; s_readerClosed = 0; s_responsePrefix = NULL; s_smsPDU = NULL; sp_response = NULL; #ifdef HAVE_ANDROID_OS //没进入这个分支 #ifdef OMAP_CSMI_POWER_CONTROL ret = ioctl(fd, OMAP_CSMI_TTY_ENABLE_ACK); if(ret == 0) { int ack_count; int read_count; int old_flags; char sync_buf[256]; old_flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, old_flags | O_NONBLOCK); do { ioctl(fd, OMAP_CSMI_TTY_READ_UNACKED, &ack_count); read_count = 0; do { ret = read(fd, sync_buf, sizeof(sync_buf)); if(ret > 0) read_count += ret; } while(ret > 0 || (ret < 0 && errno == EINTR)); ioctl(fd, OMAP_CSMI_TTY_ACK, &ack_count); } while(ack_count > 0 || read_count > 0); fcntl(fd, F_SETFL, old_flags); s_readCount = 0; s_ackPowerIoctl = 1; } else s_ackPowerIoctl = 0; #else // OMAP_CSMI_POWER_CONTROL s_ackPowerIoctl = 0; #endif // OMAP_CSMI_POWER_CONTROL #endif /*HAVE_ANDROID_OS*/ pthread_attr_init (&attr);

227 Android 驱动开发与移植实战详解

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //创建 readerLoop 线程来继续初始化 ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr); if (ret < 0) { perror ("pthread_create"); return -1; } return 0; }

在新开的线程 readerLoop()中负责与 Modem 通信,由于 AT 命令都是以\r\n 或\n\r 的换行符来 作为分隔符,所以 readerLoop()中会以行来驱动监听硬件的响应,除非超时或者报错,否则 readerLoop()会挂起等待硬件,直到读到一行完整的数据的时候才会上报,这样 AT 的响应机制已经 成功建立,代码如下。

static void *readerLoop(void *arg) { //循环等待 for (;;) { const char * line; //Line 驱动方式,整行地读数据 line = readline(); if (line == NULL) { break; } if(isSMSUnsolicited(line)) { char *line1; const char *line2; line1 = strdup(line); line2 = readline(); if (line2 == NULL) { break; } if (s_unsolHandler != NULL) { s_unsolHandler (line1, line2); } free(line1); } else { //对数据进行处理 processLine(line); } #ifdef HAVE_ANDROID_OS if (s_ackPowerIoctl > 0) { /* acknowledge that bytes have been read and processed */ ioctl(s_fd, OMAP_CSMI_TTY_ACK, &s_readCount); s_readCount = 0; } #endif /*HAVE_ANDROID_OS*/ } onReaderClosed();

228 第 8 章 电话系统驱动

return NULL; }

这样就有了响应上层消息的消息队列机制,通过 AT 响应的机制来响应硬件上报的消息,从而 实现了电话系统的驱动程序。

8.5 深入分析实现 Android 电话系统的流程

通过本章前面内容的学习,了解了 Android 电话系统的基本结构和移植方法。在本节将简要介 绍 Android 电话系统的实现流程,为读者步入本书后面知识的学习打下基础。

8.5.1 初始启动流程 主入口是文件 rild.c 中的 main()函数,需要完成如下三个任务。 (1)开 启 libril.so 中的 event 机制,在函数 RIL_startEventLoop()中实现,是最核心的由多路 I/O 驱动的消息循环。 此任务的核心内容是通过 RIL_startEventLoop()函数实现的,此函数在文件 ril.cpp 中实现,其 主要目的是通过 pthread_create(&s_tid_dispatch,&attr,eventLoop,NULL)建立一个 dispatch 线程,入口 点在 eventLoop。而在 eventLoop 中会调用 ril_event.cpp 中的 ril_event_loop()函数,以建立消息(event) 队列机制。接下来看上述消息队列的机制,实现代码都在文件 ril_event.cpp 中。主要代码如下所示。

voidril_event_init(); voidril_event_set(structril_event*ev,intfd,boolpersist,ril_event_cbfunc,void*param); voidril_event_add(structril_event*ev); voidril_timer_add(structril_event*ev,structtimeval*tv); voidril_event_del(structril_event*ev); voidril_event_loop(); structril_event{ structril_event*next; structril_event*prev; intfd; intindex; boolpersist; structtimevaltimeout; ril_event_cbfunc; void*param; };

每个 ril_event 结构都与一个 fd 句柄绑定(可以是文件、socket、管道等),并且带一个 func 指 针去执行指定的操作。具体流程是:当完成 ril_event_init 后,通过 ril_event_set 配置一个新 ril_event, 并通过 ril_event_add 加入队列之中(通常用 rilEventAddWakeup 来添加),add 会把队列里所有 ril_event 的 fd,放入一个 fd 集合 readFds 中。这样 ril_event_loop 能通过一个多路复用 I/O 的机制 (select)来等待这些 fd,如果任何一个 fd 有数据写入,则进入分析流程 processTimeouts(), processReadReadies(&rfds,n),firePending()。后文会详细分析这些流程。

229 Android 驱动开发与移植实战详解

在进入 ril_event_loop 之前,通过 pipe 机制实现挂入了一个 s_wakeupfd_event,这 个 event 的目 的是在某些情况下能内部唤醒 ril_event_loop 的多路复用阻塞,比如一些带 timeout 的命令 timeout 到期的时候。 到此为止,第一个任务分析完毕,这样便建立起了基于 event 队列的消息循环,稍后便可以接 受上层发来的请求了(上层请求的 event 对象建立,在第三个任务中)。 (2)初始化 librefrence_ril.so,也就是跟硬件或模拟硬件 modem 通信的部分(后面统一称硬件), 通过 RIL_Init 函数完成。 此任务的入口是 RIL_Init,RIL_Init 首先通过参数获取硬件接口的设备文件或模拟硬件接口的 socket,然后新开一个线程继续初始化处理,即 mainLoop 循环处理。 mainLoop 的主要任务是建立起与硬件的通信,然后通过 read 方法阻塞等待硬件的主动上报或 响应。在注册一些基础回调(timeout,readerclose)后,mainLoop 将首先打开硬件设备文件,建立 起与硬件的通信,s_device_path 和 s_port 是前面获取的设备路径参数,在此将其打开。两者可以同 时打开并拥有各自的 reader,由此可见很容易添加双卡双待等支持。 接下来通过 at_open()函数建立起这一设备文件上的 reader 等待循环,此功能是通过新建一个线 程的方式完成的,新建线程的代码如下所示。

ret=pthread_create(&s_tid_reader,&attr,readerLoop,&attr)

此线程的入口点是 readerLoop。 因为 AT 命令都是以 rn 或 nr 的换行符来作为分隔符的,所以 readerLoop 是 line 驱动的,除非 发生出错或超时,否则会读到一行完整的响应或主动上报时才会返回。这个循环跑起来以后,就建 立了基本的 AT 响应机制。 有了响应的机制后,就可以通过如下代码在 initializeCallback 中执行一些 Modem 的初始化命 令,主要都是 AT 命令的方式。

RIL_requestTimedCallback(initializeCallback,NULL,&TIMEVAL_0)

(3)通过 RIL_Init 获取一组函数指针 RIL_RadioFunctions,并通过 RIL_register 完成注册,并 打开接受上层命令的 socket 通道。 此任务是由 RIL_Init 的返回值开始的,这是一个 RIL_RadioFunctions 结构的指针。此指针的定 义代码如下所示。

typedefstruct{ intversion; RIL_RequestFunconRequest; RIL_RadioStateRequestonStateRequest; RIL_Supportssupports; RIL_CancelonCancel; RIL_GetVersiongetVersion; }RIL_RadioFunctions;

上述指针中最重要的是 onRequest 域,上层来的请求都由这个函数进行映射后转换成对应的 AT 命令发给硬件。rild 通过 RIL_register 注册这一指针。

230 第 8 章 电话系统驱动

在 RIL_register 中还需要完成另外一个任务,就是打开前面提到的跟上层通信的 socket 接口 (s_fdListen 是主接口,s_fdDebug 供调试时使用)。然后将这两个 socket 接口使用任务一中实现的 机制进行注册(仅列出 s_fdListen),对应代码如下所示。

ril_event_set(&s_listen_event,s_fdListen,false, listenCallback,NULL); rilEventAddWakeup(&s_listen_event);

这样将两个 socket 加到任务一中建立起来多路复用 I/O 的检查句柄集合中,一旦有上层来的(调 试)请求,event 机制便能响应处理了。到此为止,启动流程全部介绍完毕。

8.5.2 接收信息流程 获取信息即 request 接收信息,具体流程如下所示。 (1)多路复用 I/O 机制的运转。 request 接收是通过 ril_event_loop 中的多路复用 I/O 实现的,其中使用 ril_event_set 来配置一个 event,主要有如下两种 event。 ril_event_add:添加使用多路 I/O 的 event,负责将其挂到队列,同时将 event 的通道句柄 fd 加入到 watch_table,然后通过 select 进行等待。 ril_timer_add:添加 timerevent,它将其挂在队列,同时重新计算最短超时时间。 无论是哪一种 add,最终都会调用 triggerEvLoop 来刷新队列,并更新超时值或等待对象。在刷 新之后,ril_event_loop 从阻塞的位置使用 select 返回。此时只有两种可能:一种是超时,另一种是 等待到了某 I/O 操作。 超时处理:在 processTimeouts 中完成,需要摘下超时的 event 并将其加入 pending_list。 检查有 I/O 操作的通道的处理:在 processReadReadies 中完成,需要将超时的 event 加入 pending_list。 在 firePending 中检索 pending_list 的 event 并依次执行 event->func。当完成上述操作之后,计 算新的超时时间,并重新 select 阻塞于多路 I/O。 我们知道在初始化完成以后,会在队列上挂了如下 3 个 event 对。 s_listen_event:名为 rild 的 socket,主要使用 requeset&response 通道实现。 s_debug_event:名为 rild-debug 的 socket,调试用 requeset&response 通道,其流程与 s_listen_event 基本相同。 s_wakeupfd_event:是一个无名管道,用于队列主动唤醒。 (2)request 的传入和 dispatch。 上层部分的核心代码保存在如下文件中。

frameworks/base/telephony/java/com/android/internal/telephony/gsm/RIL.java

此文件是 AndroidJava 框架处理 radio(gsm)的核心组件,首先看里面的函数 dial(),代码如下 所示。

publicvoid

231 Android 驱动开发与移植实战详解

dial(Stringaddress,intclirMode,Messageresult) { RILRequestrr=RILRequest.obtain(RIL_REQUEST_DIAL,result); rr.mp.writeString(address); rr.mp.writeInt(clirMode); if(RILJ_LOGD)riljLog(rr.serialString()+">"+requestToString(rr.mRequest)); send(rr); }

在上述代码中,rr 是以 RIL_REQUEST_DIAL 为 request 号而申请的一个 RILRequest 对象,此 request 号在 Java 框架和 rild 库中共享。在 RILRequest 初始化的时候,会连接名为 rild 的 socket(也 就是 rild 中 s_listen_event 绑定的 socket)。 rr.mp 是 Parcel 对象,Parcel 是一套简单的序列化协议,用于将对象或对象的成员序列化成字 节流,以供传递参数之用。在此可以看到 Stringaddress 和 intclirMode 都是将依次序列化的成员。 在之前 rr 初始化的时候,request 号跟 request 的序列号已经成为头两个将被序列化的成员,这为后 面的 request 解析打下了基础。 send 到 handleMessage 的流程比较简单,send 会将 rr 直接传递给另一个线程的 handleMessage, handleMessage 的目的是执行“data=rr.mp.marshall()”序列化操作,并将 data 字节流写入到 rildsocket。 如果此时返回 rild,select 会发现 rildsocket 有了请求链接的信号,这会导致 s_listen_event 被挂 入 pending_list,从而执行 event->func,即执行下面的代码。

staticvoidlistenCallback(intfd,shortflags,void*param);

接下来运行下面的代码获取传入的 socket 描述符。

s_fdCommand=accept(s_fdListen,(sockaddr*)&peeraddr,&socklen)

然后通过 record_stream_new 建立起一个 record_stream 将其与 s_fdCommand 绑定,在此无须关 注 record_stream 的具体流程,只需关注 commandevent 回调和 processCommandsCallback()函数即可。 从前面的 event 机制分析可以得出,一旦 s_fdCommand 上有数据,此回调函数就会被调用。 processCommandsCallback 通过 record_stream_get_next 阻塞读取 s_fdCommand 上发来的数据, 一直到收到一个完整的 request(request 包的完整性由 record_stream 的机制保证)为止,然后将其送 达 processCommandBuffer。 进入 processCommandBuffer 以后就说明正式进入了命令的解析部分,每个命令将以 RequestInfo 的形式存在。对应代码如下所示。

typedefstructRequestInfo{ int32_ttoken; CommandInfo*pCI; structRequestInfo*p_next; charcancelled; charlocal; }RequestInfo;

此处的 pRI 是一个 RequestInfo 结构指针,在上层和 rild 之间的 request 号是统一的,在文件 ril.cpp

232 第 8 章 电话系统驱动

中定义了这个号。对应代码如下所示。

staticCommandInfos_commands[]={ #include"ril_commands.h" };

在定义时包含了一个 ril_commands.h 的枚举。pRI 直接访问数组 s_commands[]以获取自己的 pCI,这是一个 CommandInfo 结构。

typedefstruct{ intrequestNumber; void(*dispatchFunction)(Parcel&p,structRequestInfo*pRI); int(*responseFunction)(Parcel&p,void*response,size_tresponselen); }CommandInfo;

(3)request 的详细解析。 对于 dial 来说,初始化 CommandInfo 结构的代码如下。

{RIL_REQUEST_DIAL,dispatchDial,responseVoid},

在此执行了 dispatchFunction,也就是 dispatchDial()函数。我们可以看到其实有很多种类的 dispatchfunction,比如 dispatchVoid、dispatchStrings、dispatchSIM_IO 等。这些函数的区别在于 Parcel 传入的参数形式,其中 Vo id 就是不带参数的,Strings 是以 string[]做参数。 在拥有 request 号和参数后可以调用具体的 request()函数,调用过程是通过如下代码实现的。

s_callbacks.onRequest(pRI->pCI->requestNumber,xxx,len,pRI)

s_callbacks 是获取自 libreference-ril 的 RIL_RadioFunctions 结构指针,request 请求在这里转入 底层的 libreference-ril 处理,handler 是 reference-ril.c 中的 onRequest。 onRequest 进行一个简单的 switch 分发,RIL_REQUEST_DIAL 的基本流程如下所示。

onRequest-->requestDial-->at_send_command-->at_send_command_full-->at_send_command_fu ll_nolock-->writeline

在 requestDial 中将命令和参数转换成对应的 AT 命令,然后调用公共 sendcommand 接口 at_send_command。除了这个接口之外,还有如下常用的接口。

at_send_command_singleline at_send_command_sms at_send_command_multiline

接下来需要执行 at_send_command_full,前面的几个接口都会最终到这里为止,然后通过一个 互斥的 at_send_command_full_nolock 调用来完成最终的写出操作。

8.5.3 等待硬件响应 通过前面的 request 流程,终止在了 at_send_command_full_nolock 里的 writeline 操作,因为这 里完成命令写出到硬件设备的操作,接下来就是等待硬件响应,也就是 response 的过程了。

233 Android 驱动开发与移植实战详解

在实现 response 信息获取时,在 readerLoop 中用 readline()函数以“行”为单位接收上来。 AT 的 response 方式有如下两种。 主动上报:比如网络状态、短信和来电等都不需要经过请求,此方式用 unsolicited 来专门 描述。 真正意义上的 response:即命令的响应。 此时可以看到所有的行都是经过 SMS 自动上报筛选的,因为短信的 AT 处理通常比较麻烦, 无论收发都单独列出。这里是因为要即时处理这条短信消息(两行,标志+pdu),而不能拆开处理。 处理函数是 onUnsolicited()。 除 SMS 特例之外,所有的 line 都要经过 processLine 处理,看下面的处理流程。

processLine |----nocmd--->handleUnsolicited//主动上报 |----isFinalResponseSuccess--->handleFinalResponse//成功,标准响应 |----isFinalResponseError--->handleFinalResponse//失败,标准响应 |----get'>'--->sendsmspdu//收到>符号,发送 sms 数据再继续等待响应 |----switchs_type--->具体响应//命令有具体的响应信息需要对应分析

在此需要重点关注 handleUnsolicited 自动上报和 switchs_type 具体响应信息,另外具体响应需 要 handleFinalResponse 这样的标准响应来最终完成。 (1)用 onUnsolicite 主动上报响应,实现函数如下。

staticvoidonUnsolicited(constchar*s,constchar*sms_pdu);

response 的主要的解析过程是由文件 at_tok.c 中的函数完成的,其实就是字符串按块解析,具 体的解析方式由每条命令或上报信息自行决定。onUnsolicited 只解析出头部(一般是+XXXX 的形式), 然后按类型决定下一步操作,操作方式有 RIL_onUnsolicitedResponse 和 RIL_requestTimedCallback 两种。 RIL_onUnsolicitedResponse。 将 unsolicited 信息直接返回给上层。通过 Parcel 传递,将 RESPONSE_UNSOLICITED, unsolResponse( request 号)写入 Parcel,然后通过 s_unsolResponses 数组,查找到对应的 responseFunction 完成进一步的解析,存入 Parcel 中。最终通过 sendResponse 将其传递回原进程。 具体流程如下所示。

sendResponse-->sendResponseRaw-->blockingWrite-->writetos_fdCommand

RIL_requestTimedCallback。 通过 event 机制实现的 timer 机制,回调对应的内部处理函数。通过 internalRequestTimedCallback 将回调添加到 event 循环,最终完成 callback 上挂的函数的回调。例如 pollSIMState 和 onPDPContextListChanged 等回调不用返回上层,直接在内部处理就可以实现。 (2)switchs_type 命令的具体响应及 handleFinalResponse 标准响应。 在 sendcommand 的时候设置命令类型 s_type,可以供不同的 AT 使用的类型有 NO_RESULT、 NUMERIC、SINGLELINE 和 MULTILINE。解析这几种类型的方式相似,通过比较 AT 头标记等判

234 第 8 章 电话系统驱动

断处理,如果是对应的响应,就通过 addIntermediate 挂到一个临时结果 sp_response->p_intermediates 队列里。如果不是对应响应,那其实应该是穿插其中的自动上报,用 onUnsolicite 来处理。具体响 应只是起了一个获取响应信息到临时的结果,需要等待具体分析的作用。无论有无具体响应,最终 都以标准响应 handleFinalResponse 来完成,也就是一直接受到 OK、ERROR 等标准 response 来结 束,这是大多数 AT 命令的规范。

235

第 9 章 显示系统驱动

显示系统的功能是操作显示设备并获得显示终端。显示系统和硬件层中的 LCD、LCD 控制器 和 VGA 输出设备等显示设备相对应。在本章将详细讲解 Android 显示系统驱动应用和移植的基本 知识,为读者步入后面知识的学习打下基础。

9.1 显示系统基础

Android 系统的显示驱动采用标准的 Linux 的显示驱动 FrameBuffer 来完成图像数据显示的, FrameBuffer 通常作为其 LCD 控制器或者其他显示设备的驱动,FrameBuffer 驱动是一个字符设备, 这个驱动在文件系统中的设备节点通常是:/dev/fbX 主设备号是 29,次设备号递增生成。用户可 以把 FrameBuffer 看成一块内存,既可以向这块内存中写入数据,也可以从这块内存中读取数据。 在应用程序中,一般通过将 FrameBuffer 设备映射到进程地址空间的方式使用。 由此可见,Android 系统的显示系统驱动和 Surface 库有着紧密的联系。在显示系统的低层实 现了对基本显示输出的封装,在 Surface 中有一部分库提供了对多个图层的支持。

9.1.1 Android 的版本 在 Linux 系统中,FrameBuffer 驱动是标准的显示设备的驱动。对于 PC 系统来说,FrameBuffer 驱动是显卡的驱动。对于嵌入式系统 SOC 处理器来说,FrameBuffer 通常作为其 LCD 控制器或者 其他显示设备的驱动。在上面我们说的 Donut 和 Eclair 都是 Android 的名字,每当 Android 发布一 个新版本的时候,都会以一种 Google 员工们喜爱的食品(尤其是甜点)命名,例如 1.5 版 Cupcake (纸杯蛋糕),1.6 版 Donut(甜甜圈)和 2.0 版 Eclair(法式奶油夹心甜点)。随着新版本的推出, 虽然名字不相同,但是显示系统的结构差不多。主要区别在 Donut 和 Éclair 这两个版本上,具体说 明如下。 在 Donut 及其以前的版本中,使用 libui 直接调用 FrambeBuffer 驱动程序来实现显示部分的 接口。 在 Éclair 及其后面的版本中增加了 Gralloc 模块,这是一个位于显示设备和 libui 库中间的 硬件模块。

第 9 章 显示系统驱动

9.1.2 不同的显示系统

1.Donut 及其以前版本 Donut 及其以前版本的系统头文件保存在 ui 库的目录“frameworks/base/include/ui/” 中,在此目录中主要包含了如下代码文件。 EGLNativeSurface.h:定义了类 EGLNativeSurface,此类继承了类 egl_native_window_t。 EGLDisplayInfoSurface.h :定义了类 EGLDisplayInfoSurface ,此类继承了类 EGLNativeSurface。 EGLNativeWindowsSurface.h:定义了类 EGLNativeWindowsSurface,是 EGLNativeSurface 的另一个实现者。 此版本的显示系统代码被保存在“frameworks/base/libs/ui/ ”目录下,其中文件 EGLDisplaySurface.h 是系统和 FrameBuffer 设备的接口。 2.Eclair 及其后面版本 在 Android 的 Eclair 及其后面版本中,显示系统的本地部分包含硬件抽象层和 ui 库中的部分, 和以前版本的区别是在实现层的上面增加了一个名为 Gralloc(图形分配)的硬件设备,此硬件位 于 libui 和显示设备的驱动程序之间,被作为显示系统的硬件抽象层来使用。 Gralloc 模块的实现文件被保存在“system/lib/hw”目录的动态库中,在运行过程中使用 dlopen 和 dlsym 的方式动态打开并取出符号来使用,而在系统的其他部分没有连接此动态库。 Gralloc 模块比较灵活,是可以被移植的模块,是系统和显示之间的接口,是以硬件的形式存 在的。在 Android 系统中,既可以使用 FrameBuffer 作为 Gralloc 模块的驱动程序,也可以在此模块 中不使用 FrameBuffer 设备。

9.1.3 FrameBuffer 驱动的使用基础 Linux 中的 PCI 设备可以将其控制寄存器映射到物理内存空间,而后,对这些控制寄存器的访 问,变成了对物理内存的访问。因此,这些寄存器又被称为“memio”。一旦被映射到物理内存, Linux 的普通进程就可以通过 mmap 将这些内存 I/O 映射到进程地址空间,这样就可以直接访问这 些寄存器了。 FrameBuffer 设备属于字符设备,采用了“文件层-驱动层”的接口方式。Linux 为帧缓冲设备 定义的驱动层接口为 struct fb_info 结构。在文件层次上,用户调用 struct file_operations 的函数操作, 其中间接调用 struct fb_ops 的函数来操作硬件。当向内核注册 FB 设备的时候,也注册了 struct fb_ops 的指针。当打开 fb 设备时,先调用 fb_drivers[]的 xxxfb_init()来初始化设备。 当“/dev”目录下有多个 FB 设备的时候,系统会根据每个设备注册的先后顺序来分配次设备 号,第一个被注册的 framebuffer 的 minor 等于 0;第二个被注册的 framebuffer 的 minor 等于 1,依 此类推,例如/dev/fb0、/dev/fb1。 在 FrameBuffer 中有很多相关命令,例如下面是清空屏幕命令:

#dd if=/dev/zero of=/dev/fb

237 Android 驱动开发与移植实战详解

下面的命令可以保存 Framebuffer 中的内容:

#dd if=/dev/fb of=fbfile

下面的命令可以将内容重新写回屏幕:

#dd if=fbfile of=/dev/fb

9.2 移植 Android 显示系统

在 Linux 系统中,标准的设备显示驱动是 FrameBuffer。FrameBuffer 驱动程序是 PC 系统中的 显卡驱动程序,是嵌入式系统中的 SOC 处理器,通常将 FrameBuffer 作为其 LCD 控制器或者其他 显示设备的驱动。在本节的内容中,将详细讲解移植 Android 显示系统的核心知识。

9.2.1 FrameBuffer 驱动程序 FrameBuffer 驱动是一个字符设备,通常此驱动在文件系统中的设备节点中位置是 “/dev/fbX”。FrameBuffer 驱动的主设备号是 29,此设备号用递增数字生成。每个系统可以有多 个显示设备,分别使用“/dev/fb0”、“/dev/fb1”和“/dev/fb2”等来表示。 在用户空间中,通常驱动 FrameBuffer 使用 ioctl 和 mmap 等文件系统接口进行操作,其中 ioctl 用于获得和设置信息,mmap 可以将 FrameBuffer 的内存映射到用户空间。FrameBuffer 驱动可以直 接支持 Write 操作,可以直接用写的方式输出显示内容。 Android 显示驱动 FrameBuffer 的架构图如图 9-1 所示。

▲图 9-1 显示驱动 FrameBuffer 的架构图

238 第 9 章 显示系统驱动

FrameBuffer 对应的源文件的路径如下所示。

"linux/include/linux/fb.h"//定义了一些重要的数据结构 "drivers/video/fbmem.c"//framebuffe 设备的入口和初始化 "drivers/video/samsung/S3fb2.c"//添加自己的 framebuffer 驱动程序

其中文件 fb.h 是 FrameBuffer 中最重要的头文件,定义了 FrameBuffer 开发中的很多重要的全 局变量和数据结构,很多数据结构都是直接暴露给用户空间供开发者直接使用的,例如 fb_var_screeninfo 描述了可修改的屏幕参数信息,比如分辨率、每个像素的 bit 数等。

struct fb_var_screeninfo { __u32 xres; //行可以见像素 __u32 yres; //列可见像素 __u32 xres_virtual; //虚拟像素 __u32 yres_virtual; //虚拟像素 __u32 xoffset; //水平偏移量 __u32 yoffset; //竖直偏移量 __u32 bits_per_pixel; //像素所占的 bit 数 __u32 grayscale; struct fb_bitfield red; struct fb_bitfield green; struct fb_bitfield blue; struct fb_bitfield transp; __u32 nonstd; __u32 activate; __u32 height; //内存中的图像高度 __u32 width; //内存中的图像宽度 __u32 accel_flags; //加速标志 __u32 pixclock; __u32 left_margin; __u32 right_margin; __u32 upper_margin; __u32 lower_margin; __u32 hsync_len; __u32 vsync_len; __u32 sync; __u32 vmode; __u32 rotate; __u32 reserved[5]; //保留数据 };

fb_var_screeninfo 这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时 不能被修改,比如 FrameBuffer 内存的起始地址。它依赖于被设定的模式,当一个模式被设定后, 内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。

struct fb_fix_screeninfo { char id[16]; unsigned long smem_start; //内存起始地址(物理地址) __u32 smem_len; //内存长度

239 Android 驱动开发与移植实战详解

__u32 type; __u32 type_aux; __u32 visual; __u16 xpanstep; //没有硬件设备就为零 __u16 ypanstep; __u16 ywrapstep; __u32 line_length; //一行数据的字节长度 unsigned long mmio_start; //内存映射的 I/O 起始地址(物理地址) __u32 mmio_len; __u32 accel; //可用的加速类型 __u16 reserved[3]; };

文件 fb.h 定义了 Framebuffer 驱动的核心数据接口是 fb_info,下面是在文件 fb.h 中的对应代码。

struct fb_info { atomic_t count; int node; int flags; struct mutex lock; /* 锁定 打开、释放接口 */ struct mutex mm_lock; /* fb_mmap 和 smem_*锁定*/ struct fb_var_screeninfo var; /* 显示屏的信息 */ struct fb_fix_screeninfo fix; /* 显示屏的固定信息 */ struct fb_monspecs monspecs; /* 当前检测规范 */ struct work_struct queue; /* 帧事件队列*/ struct fb_pixmap pixmap; /* 图像硬件映射*/ struct fb_pixmap sprite; /* 光标硬件映射*/ struct fb_cmap cmap; struct list_head modelist; /* 模式列表 */ struct fb_videomode *mode; /* 当前模式 */

在上述数据接口 fb_info 中,包含了 FrameBuffer 驱动的主要信息。 struct fb_var_screeninfo 和 struct fb_fix_screeninfo:是两个相关的数据结构,分别对应 FBIOGET_VSCREENINFO 和 FBIOGET_FSCREENINFO 这两个 ioctl,用于从用户空间获得显示 信息。 fb_ops:表示 FrameBuffer 驱动的操作。是描述 FrameBuffer 设备的操作接口的结构体,表 示这些定义的接口可以直接从用户空间进行调用。下面是其定义代码。

struct fb_ops { struct module *owner; //Open 方法 int (*fb_open)(struct fb_info *info, int user); int (*fb_release)(struct fb_info *info, int user); //从 FrameBuffer 中读数据 ssize_t (*fb_read)(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos); //写数据 ssize_t (*fb_write)(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos);

240 第 9 章 显示系统驱动

//检查参数是否正确 int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info); int (*fb_set_par)(struct fb_info *info); //设置颜色寄存器 int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info); int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info); //清空屏幕 int (*fb_blank)(int blank, struct fb_info *info); //双 Buffer 机制 int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info); //翻转角度 void (*fb_rotate)(struct fb_info *info, int angle); //Buffer 的同步 int (*fb_sync)(struct fb_info *info); //FrameBuffer 的设备命令字 int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, unsigned long arg); //物理内存到虚拟内存的映射 int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma); //保存当前的状态 void (*fb_save_state)(struct fb_info *info); //重新载入并保存状态 void (*fb_restore_state)(struct fb_info *info); void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps, struct fb_var_screeninfo *var); };

fb_cmap:描述设备无关的颜色映射信息。可以通过 FBIOGETCMAP 和 FBIOPUTCMAP 对 应的 ioctl 操作设定或获取颜色映射信息。

struct fb_cmap { __u32 start; //第一个入口 __u32 len; //入口的数字 __u16 *red; __u16 *green; __u16 *blue; __u16 *transp; //透明度,可以为 0 };

Android 设备的显示同样跟 LCD 控制器的特性密不可分,在 6410 中 LCD 控制器被集成在芯片 的内部作为一个相对独立的单元,看做是一个平台设备,代码路径“arch/arm/plat-s3c64xx/dev.c” 描述了 LCD 控制器的资源信息。

static struct resource s3c_lcd_resource[] = { [0] = { .start = S3C64XX_PA_LCD,//控制器端口开始地址 .end = S3C64XX_PA_LCD + SZ_1M - 1,//控制器端口结束地址 .flags = IORESOURCE_MEM, },

241 Android 驱动开发与移植实战详解

[1] = { .start = IRQ_LCD_VSYNC,//LCD 中断 .end = IRQ_LCD_SYSTEM, .flags = IORESOURCE_IRQ,//表示为 LCD 中断 } };

struct platform_device s3c_device_lcd = { .name = "s3c-lcd",//平台设备 LCD 设备名 .id = -1, .num_resources = ARRAY_SIZE(s3c_lcd_resource),//资源数量 .resource = s3c_lcd_resource,//引用上面定义的资源 .dev = { .dma_mask = &s3c_device_lcd_dmamask, .coherent_dma_mask = 0xffffffffUL } };

在实现 FrameBuffer 驱动时,通常使用如下两个函数分别实现注册和注销功能。

extern int register_framebuffer(struct fb_info *fb_info); extern int unregister_framebuffer(struct fb_info *fb_info);

另外,上述文件 fb.h 中还定义了 FrameBuffer 驱动的 ioctl 命令,对应代码如下。

#define FBIOGET_VSCREENINFO 0x4600/*获得变化屏幕信息*/ #define FBIOPUT_VSCREENINFO 0x4601/*设置变化屏幕信息*/ #define FBIOGET_FSCREENINFO 0x4602/*获得固定屏幕信息*/ #define FBIOGETCMAP 0x4604/*设置固定屏幕信息*/ #define FBIOPUTCMAP 0x4605/*设置映射内容*/ #define FBIOPAN_DISPLAY 0x4606/*调整显示区域*/

在实现 FrameBuffer 驱动的过程中,需要定义一个实现 fb_info 结构和实现 fb_ops 中的各个函 数的指针。从驱动程序的用户空间实现 ioctl 调用时,会转换成调用其中的函数。当注册具体的 Framebuffer 驱动后,将会自动递增一个次设备号。在配置 Linux 系统时,FrameBuffer 驱动的配置 选项如下所示。

Device Drivers>Graphics support

对应的配置文件如下所示。

drivers/video/Kconfig

在配置文件里面包含了对文本模式、控制台和启动图标等子选项的支持。

9.2.2 硬件抽象层

1.Donut 及其以前版本

在 Donut 及其以前版本中,因为显示系统的硬件抽象层位于 libui 标准库,所以不需要移植这部

242 第 9 章 显示系统驱动

分内容,整个移植的过程只需要移植 FrameBuffer 驱动程序即可。又因为 libui 对于 FrameBuffer 驱动 程序使用了标准的规则,所以只要在当前系统中实现 Linux 中的 FrameBuffer 驱动程序,就可以在 Donut 及其以前版本的 Android 系统中实现显示,仅有的一点区别是驱动程序的设备节点的路径。 在Donut及其以前版本中的硬件抽象层的核心文件是“frameworks/base/libs/ui/EGLDisplaySurface. cpp”,此文件的核心功能是通过函数 mapFrameBuffer()实现的,通过此函数实现了对驱动程序的操作。 函数 mapFrameBuffer()的实现代码如下所示。

status_t EGLDisplaySurface::mapFrameBuffer() { char const * const device_template[] = { "/dev/graphics/fb%u", "/dev/fb%u", 0 }; int fd = -1; int i=0; char name[64]; while ((fd==-1) && device_template[i]) { snprintf(name, 64, device_template[i], 0); fd = open(name, O_RDWR, 0); i++; } if (fd < 0) return -errno;

struct fb_fix_screeninfo finfo; if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) return -errno;

struct fb_var_screeninfo info; if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1) return -errno;

info.reserved[0] = 0; info.reserved[1] = 0; info.reserved[2] = 0; info.xoffset = 0; info.yoffset = 0; info.yres_virtual = info.yres * 2; info.bits_per_pixel = 16; /*明确请求 5/6/5 */ info.red.offset = 11; info.red.length = 5; info.green.offset = 5; info.green.length = 6; info.blue.offset = 0; info.blue.length = 5; info.transp.offset = 0; info.transp.length = 0;

243 Android 驱动开发与移植实战详解

info.activate = FB_ACTIVATE_NOW;

uint32_t flags = PAGE_FLIP; if (ioctl(fd, FBIOPUT_VSCREENINFO, &info) == -1) { info.yres_virtual = info.yres; flags &= ~PAGE_FLIP; LOGW("FBIOPUT_VSCREENINFO failed, page flipping not supported"); }

if (info.yres_virtual < info.yres * 2) { info.yres_virtual = info.yres; flags &= ~PAGE_FLIP; LOGW("page flipping not supported (yres_virtual=%d, requested=%d)", info.yres_virtual, info.yres*2); }

if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1) return -errno;

int refreshRate = 1000000000000000LLU / ( uint64_t( info.upper_margin + info.lower_margin + info.yres ) * ( info.left_margin + info.right_margin + info.xres ) * info.pixclock ); if (refreshRate == 0) { refreshRate = 60*1000; // 60 Hz } if (int(info.width) <= 0 || int(info.height) <= 0) { // 驱动不会返回信息 // 默认 160 dpi info.width = 51; info.height = 38; } float xdpi = (info.xres * 25.4f) / info.width; float ydpi = (info.yres * 25.4f) / info.height; float fps = refreshRate / 1000.0f; LOGI( "using (fd=%d)\n" "id = %s\n" "xres = %d px\n" "yres = %d px\n" "xres_virtual = %d px\n" "yres_virtual = %d px\n" "bpp = %d\n" "r = %2u:%u\n" "g = %2u:%u\n" "b = %2u:%u\n", fd, finfo.id, info.xres,

244 第 9 章 显示系统驱动

info.yres, info.xres_virtual, info.yres_virtual, info.bits_per_pixel, info.red.offset, info.red.length, info.green.offset, info.green.length, info.blue.offset, info.blue.length );

LOGI( "width = %d mm (%f dpi)\n" "height = %d mm (%f dpi)\n" "refresh rate = %.2f Hz\n", info.width, xdpi, info.height, ydpi, fps ); if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) return -errno; if (finfo.smem_len <= 0) return -errno; /* *打开并且映射显示 */ void* buffer = (uint16_t*) mmap( 0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (buffer == MAP_FAILED) return -errno; //至少暂时总是清除 fb memset(buffer, 0, finfo.smem_len); uint8_t* offscreen[2]; offscreen[0] = (uint8_t*)buffer; if (flags & PAGE_FLIP) { offscreen[1] = (uint8_t*)buffer + finfo.line_length*info.yres; } else { offscreen[1] = (uint8_t*)malloc(finfo.smem_len); if (offscreen[1] == 0) { munmap(buffer, finfo.smem_len); return NO_MEMORY; } } mFlags = flags; mInfo = info; mFinfo = finfo; mSize = finfo.smem_len; mIndex = 0; for (int i=0 ; i<2 ; i++) { mFb[i].version = sizeof(GGLSurface);

245 Android 驱动开发与移植实战详解

mFb[i].width = info.xres; mFb[i].height = info.yres; mFb[i].stride = finfo.line_length / (info.bits_per_pixel >> 3); mFb[i].data = (GGLubyte*)(offscreen[i]); mFb[i].format = GGL_PIXEL_FORMAT_RGB_565; } return fd; }

上述代码的实现流程如图 9-2 所示。

ioctl接口 函数mmap()

将显示驱动内存 打开设备节点 获取驱动信息 映射到用户空间

fb0

fb1

fbx

▲图 9-2 函数 mapFrameBuffer()的实现流程图

2.Eclair 及其后面版本

在 Eclair 及其后面版本中,显示部分的硬件抽象层是 Gralloc 模块。因为 Gralloc 模块是灵活多 变的,所以移植方式也多种多样,具体来说分为如下两种情况。 如果继续使用 Android 中已经实现的 Gralloc 模块,此时可以继续使用标准的 FrameBuffer 驱动程序,只需要移植 FrameBuffer 的内容即可。 如果想自己实现特定的 Gralloc 模块,则此模块就是当前系统的显示设备和 Android 接口, 此时显示设备可以是各种类型的驱动程序。 (1)头文件。 Gralloc 模块的头文件在文件“hardware/libhardware/include/hardware/gralloc.h”中定义,此文件 的实现流程如下所示。 定义子设备和模块的名称,对应代码如下所示。

#define GRALLOC_HARDWARE_MODULE_ID "gralloc"

246 第 9 章 显示系统驱动

#define GRALLOC_HARDWARE_FB0 "fb0" #define GRALLOC_HARDWARE_GPU0 "gpu0"

其中 gralloc 是硬件模块的名称,fb0 是 FrameBuffer 设备,gpu0 是图形处理单元设备。 ¾ 通过扩展定义 gralloc_module_t 实现 Gralloc 硬件模块,对应代码如下所示。 typedef struct gralloc_module_t { struct hw_module_t common; int (*registerBuffer)(struct gralloc_module_t const* module, buffer_handle_t handle); int (*unregisterBuffer)(struct gralloc_module_t const* module, buffer_handle_t handle); int (*lock)(struct gralloc_module_t const* module, buffer_handle_t handle, int usage, int l, int t, int w, int h, void** vaddr); int (*unlock)(struct gralloc_module_t const* module, buffer_handle_t handle); int (*perform)(struct gralloc_module_t const* module, int operation, ... ); void* reserved_proc[7]; } gralloc_module_t;

上述 gralloc_module_t 是此头文件的核心,各个函数指针的具体说明如下所示。 registerBuffer:在 alloc_device_t::alloc 前调用。 lock:用于访问特定的缓冲区,在调用此接口时硬件设备需要结束渲染或完成同步处理。 unlock:在所用 buffer 改变之后被调用; perform:用于未来某个用途。 ¾ 定义函数 gralloc_open()打开 gralloc 的接口,此函数的实现代码如下所示。 static inline int gralloc_open(const struct hw_module_t* module, struct alloc_device_t** device) { return module->methods->open(module, GRALLOC_HARDWARE_GPU0, (struct hw_device_t**)device); }

¾ 定义函数 framebuffer_open()打开 FrameBuffer 的接口,此函数的实现代码如下所示。 static inline int framebuffer_open(const struct hw_module_t* module, struct framebuffer_device_t** device) { return module->methods->open(module, GRALLOC_HARDWARE_FB0, (struct hw_device_t**)device); }

¾ 定义函数 framebuffer_open()关闭 FrameBuffer 的接口,此函数的实现代码如下所示。 static inline int framebuffer_close(struct framebuffer_device_t* device) { return device->common.close(&device->common); }

247 Android 驱动开发与移植实战详解

¾ 定义函数 gralloc_close()关闭 gralloc 的接口,此函数的实现代码如下所示。

static inline int gralloc_close(struct alloc_device_t* device) { return device->common.close(&device->common); }

设备 GRALLOC_HARDWARE_GPU0 对应的设备是结构体 alloc_device_t,设备 GRALLOC_ HARDWARE_FB0 对应的设备是结构体 framebuffer_device_t,这两个结构体的具体定义代码如下 所示。

typedef struct alloc_device_t { struct hw_device_t common; int (*alloc)(struct alloc_device_t* dev,//以宽、高、颜色格式为参数来分配 int w, int h, int format, int usage, buffer_handle_t* handle, int* stride); int (*free)(struct alloc_device_t* dev, buffer_handle_t handle);

} alloc_device_t;

typedef struct framebuffer_device_t { struct hw_device_t common; const uint32_t flags; const uint32_t width;//宽 const uint32_t height; //高 const int stride; //每行内容 const int format; //颜色格式 const float xdpi; //X 方向像素密度 const float ydpi; //Y 方向像素密度 const float fps; //频率 const int minSwapInterval; const int maxSwapInterval; int reserved[8]; int (*setSwapInterval)(struct framebuffer_device_t* window, int interval); int (*setUpdateRect)(struct framebuffer_device_t* window, int left, int top, int width, int height);

int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer); int (*compositionComplete)(struct framebuffer_device_t* dev); void* reserved_proc[8]; } framebuffer_device_t;

(2)文件 FramebufferNativeWindow.cpp。 Gralloc 模块是由如下这三个结构体来描述的。 gralloc_module_t 模块。 alloc_device_t 设备。 framebuffer_device_t 设备。

248 第 9 章 显示系统驱动

上述结构体中的函数指针起了非常重要的作用,Gralloc 模块是由 ui 库中的如下文件调用的。

frameworks/base/include/ui/FramebufferNativeWindow.cpp

在文件 FramebufferNativeWindow.cpp 中定义了类 FramebufferNativeWindow,此类继承了 android_native_buffer_t,这是对上层的接口,表示这是一个本地窗口。在此文件中定义了构造函数 FramebufferNativeWindow(),实现代码如下所示。

FramebufferNativeWindow::FramebufferNativeWindow():BASE(),fbDev(0), grDev(0), mUpdateOnDemand(false){ hw_module_t const* module; if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) == 0) { int stride; int err; err = framebuffer_open(module, &fbDev); //打开 Framebuffer 设备 LOGE_IF(err, "couldn't open framebuffer HAL (%s)", strerror(-err)); err = gralloc_open(module, &grDev); //打开 gralloc 设备 LOGE_IF(err, "couldn't open gralloc HAL (%s)", strerror(-err)); // bail out if we can't initialize the modules if (!fbDev || !grDev) return; mUpdateOnDemand = (fbDev->setUpdateRect != 0); // 初始化 buffer FIFO mNumBuffers = 2; mNumFreeBuffers = 2; mBufferHead = mNumBuffers-1; //初始化两个缓冲区 buffers[0] = new NativeBuffer( fbDev->width, fbDev->height, fbDev->format, GRALLOC_USAGE_HW_FB); buffers[1] = new NativeBuffer( fbDev->width, fbDev->height, fbDev->format, GRALLOC_USAGE_HW_FB); err = grDev->alloc(grDev, fbDev->width, fbDev->height, fbDev->format, GRALLOC_USAGE_HW_FB, &buffers[0]->handle, &buffers[0]->stride); LOGE_IF(err, "fb buffer 0 allocation failed w=%d, h=%d, err=%s", fbDev->width, fbDev->height, strerror(-err)); //从 gralloc 设备中分配内存 err = grDev->alloc(grDev, fbDev->width, fbDev->height, fbDev->format, GRALLOC_USAGE_HW_FB, &buffers[1]->handle, &buffers[1]->stride); LOGE_IF(err, "fb buffer 1 allocation failed w=%d, h=%d, err=%s", fbDev->width, fbDev->height, strerror(-err)); //从 Framebuffer 设备中获得常量 const_cast(android_native_window_t::flags) = fbDev->flags; const_cast(android_native_window_t::xdpi) = fbDev->xdpi; const_cast(android_native_window_t::ydpi) = fbDev->ydpi; const_cast(android_native_window_t::minSwapInterval) = fbDev->minSwapInterval; const_cast(android_native_window_t::maxSwapInterval) = fbDev->maxSwapInterval;

249 Android 驱动开发与移植实战详解

} else { LOGE("Couldn't get gralloc module"); } //赋值各个处理函数的指针 android_native_window_t::setSwapInterval = setSwapInterval; android_native_window_t::dequeueBuffer = dequeueBuffer; android_native_window_t::lockBuffer = lockBuffer; android_native_window_t::queueBuffer = queueBuffer; android_native_window_t::query = query; android_native_window_t::perform = perform; }

在函数 FramebufferNativeWindow()中使用了双显示区缓冲方式,初始化过程如图 9-3 所示。

获取显示区的宽 构建NativeBuffer 打开Gralloc模块 打开设备 、高、颜色格式 结构

分配内存

获取设备信息

给指针赋值

▲图 9-3 初始化流程图

图 9-3 所示流程的具体描述如下所示。 打开 Gralloc 模块,然后打开设备 framebuffer_device_t 和 alloc_device_t。 从设备设备 framebuffer_device_t 中获取显示区的宽、高、颜色格式,构建 NativeBuffer 结构。 从设备 alloc_device_t 中分配内存到 NativeBuffer 句柄。 获取 framebuffer_device_t 设备中的其他信息。 分别给指针 dequeueBuffer、lockBuffer、queueBuffer、query 和 perform 赋值。 (3)文件 GraphicBufferAllocator.cpp。 文件 frameworks/base/libs/ui/GraphicBufferAllocator.cpp 通过调用 Gralloc 模块和 gralloc_module_t 模块来显示缓冲区的分配,此文件的核心代码如下所示。

status_t GraphicBufferAllocator::alloc(uint32_t w, uint32_t h, PixelFormat format, int usage, buffer_handle_t* handle, int32_t* stride) { //保证没分配 0 x 0 缓冲 w = clamp(w); h = clamp(h);

250 第 9 章 显示系统驱动

status_t err;

if (usage & GRALLOC_USAGE_HW_MASK) { err = mAllocDev->alloc(mAllocDev, w, h, format, usage, handle, stride); } else { err = sw_gralloc_handle_t::alloc(w, h, format, usage, handle, stride); } LOGW_IF(err, "alloc(%u, %u, %d, %08x, ...) failed %d (%s)", w, h, format, usage, err, strerror(-err)); if (err == NO_ERROR) { Mutex::Autolock _l(sLock); KeyedVector& list(sAllocList); alloc_rec_t rec; rec.w = w; rec.h = h; rec.format = format; rec.usage = usage; rec.vaddr = 0; rec.size = h * stride[0] * bytesPerPixel(format); list.add(*handle, rec); } else { String8 s; dump(s); LOGD("%s", s.string()); } return err; }

在上述代码中,mAllocDev 是一个 alloc_device_t 设备类型,当调用者具有 GRALLOC_ USAGE_HW_MASK 标志时,alloc_device_t 会调用分配一个内存,否则将从软件中分配一个内存。 (4)文件 GraphicBufferMapper.cpp。 文件 frameworks/base/libs/ui/GraphicBufferMapper.cpp 通过调用 Gralloc 模块显示缓冲的映射, 并在里面注册了显示的缓冲内容。并且使用完毕后可以注销显示的缓冲内容。此文件的核心代码如 下所示。

//注册显示的缓冲内容 status_t GraphicBufferMapper::registerBuffer(buffer_handle_t handle) { status_t err; if (sw_gralloc_handle_t::validate(handle) < 0) { err = mAllocMod->registerBuffer(mAllocMod, handle); } else { err = sw_gralloc_handle_t::registerBuffer((sw_gralloc_handle_t*)handle); } LOGW_IF(err, "registerBuffer(%p) failed %d (%s)", handle, err, strerror(-err)); return err; }

251 Android 驱动开发与移植实战详解

//注销显示的缓冲内容 status_t GraphicBufferMapper::unregisterBuffer(buffer_handle_t handle) { status_t err; if (sw_gralloc_handle_t::validate(handle) < 0) { err = mAllocMod->unregisterBuffer(mAllocMod, handle); } else { err = sw_gralloc_handle_t::unregisterBuffer((sw_gralloc_handle_t*)handle); } LOGW_IF(err, "unregisterBuffer(%p) failed %d (%s)", handle, err, strerror(-err)); return err; } //锁定 status_t GraphicBufferMapper::lock(buffer_handle_t handle, int usage, const Rect& bounds, void** vaddr) { status_t err; if (sw_gralloc_handle_t::validate(handle) < 0) { err = mAllocMod->lock(mAllocMod, handle, usage, bounds.left, bounds.top, bounds.width(), bounds.height(), vaddr); } else { err = sw_gralloc_handle_t::lock((sw_gralloc_handle_t*)handle, usage, bounds.left, bounds.top, bounds.width(), bounds.height(), vaddr); } LOGW_IF(err, "lock(...) failed %d (%s)", err, strerror(-err)); return err; } //解锁 status_t GraphicBufferMapper::unlock(buffer_handle_t handle) { status_t err; if (sw_gralloc_handle_t::validate(handle) < 0) { err = mAllocMod->unlock(mAllocMod, handle); } else { err = sw_gralloc_handle_t::unlock((sw_gralloc_handle_t*)handle); } LOGW_IF(err, "unlock(...) failed %d (%s)", err, strerror(-err)); return err; }

在上述代码中,mAllocDev 是一个 alloc_device_t 设备类型,根据句柄的范围可以从 Gralloc 模 块中注册 Buffer,也可以从软件中注册 Buffer。 (5)文件 LayerBuffer.cpp。 在管理库 SurfaceFlinger 中也调用了 Gralloc 模块,调用路径是“frameworks/base/libs/surfaceflinger/ LayerBuffer.cpp”。

252 第 9 章 显示系统驱动

SurfaceFinger 按英文翻译过来就是 Surface 投递者。SufaceFlinger 的构成并不是太复杂,复杂 的是它的客户端建构。SufaceFlinger 的主要功能如下所示。 将 Layers(Surfaces)的内容刷新到屏幕上。 维持 Layer 的 Zorder 序列,并对 Layer 最终输出作出裁剪计算。 响应 Client 要求,创建 Layer,与客户端的 Surface 建立连接。 接收 Client 要求,修改输出大小、Alpha 等 Layer 属性。 作为投递者的实际意义,我们首先需要知道的是如何投递、投掷物、投递路线和投递目的地。 SurfaceFlinger 的基本组成框架如图 9-4 所示。

Main Surface in OpenGL ES

▲图 9-4 SurfaceFlinger 的基本组成框架

SurfaceFlinger 的管理对象如下所示。 mClientsMap:管理客户端与服务端的连接。 ISurface,IsurfaceComposer:AIDL 调用接口实例。 mLayerMap:服务端的 Surface 的管理对象。 mCurrentState.layersSortedByZ :以 Surface 的 Z-order 序列排列的 Layer 数组。 graphicPlane:缓冲区输出管理。 OpenGL ES:图形计算,图像合成等图形库。 gralloc.xxx.so:这是个跟平台相关的图形缓冲区管理器。 pmem Device:提供共享内存,在这里只是在 gralloc.xxx.so 可见,在上层被 gralloc.xxx.so 抽象了。 在文件 LayerBuffer.cpp 中定义 Buffer 类,然后为其定义构造函数。其中构造函数的实现代码 如下所示。

LayerBuffer::Buffer::Buffer(const ISurface::BufferHeap& buffers, ssize_t offset) : mBufferHeap(buffers) {

253 Android 驱动开发与移植实战详解

NativeBuffer& src(mNativeBuffer); src.crop.l = 0; src.crop.t = 0; src.crop.r = buffers.w; src.crop.b = buffers.h; src.img.w = buffers.hor_stride ?: buffers.w; src.img.h = buffers.ver_stride ?: buffers.h; src.img.format = buffers.format; src.img.offset = offset; src.img.base = buffers.heap->base(); src.img.fd = buffers.heap->heapID(); }

在上述代码中,调用了 gralloc_module_t 的可选实现的函数指针 perform,如果在当前使用的 Gralloc 模块中实现了这个函数指针时则在此调用函数。

9.3 实现显示系统的驱动程序

在模拟器中使用的驱动程序是 Goldfish 和 FrameBuffer 驱动程序,使用的硬件抽象层是 Gralloc 模块。Gralloc 模块既可以被模拟器使用,也可以给实际硬件系统使用。在本节将详细讲解实现 Android 显示驱动程序的基本知识,为读者步入本书后面知识的学习打下基础。

9.3.1 Goldfish 中的 FrameBuffer 驱动程序 Goldfish 中的 FrameBuffer 驱动程序保存在文件 drivers/video/goldfishfb.c 中,此文件的主要代 码如下所示。

//验证函数 static int goldfish_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { if((var->rotate & 1) != (info->var.rotate & 1)) { if((var->xres != info->var.yres) || (var->yres != info->var.xres) || (var->xres_virtual != info->var.yres) || (var->yres_virtual > info->var.xres * 2) || (var->yres_virtual < info->var.xres )) { return -EINVAL; } } else { if((var->xres != info->var.xres) || (var->yres != info->var.yres) || (var->xres_virtual != info->var.xres) || (var->yres_virtual > info->var.yres * 2) || (var->yres_virtual < info->var.yres )) { return -EINVAL; } }

254 第 9 章 显示系统驱动

if((var->xoffset != info->var.xoffset) || (var->bits_per_pixel != info->var.bits_per_pixel) || (var->grayscale != info->var.grayscale)) { return -EINVAL; } return 0; } //驱动程序的初始化函数 static int goldfish_fb_probe(struct platform_device *pdev) { int ret; struct resource *r; struct goldfish_fb *fb; size_t framesize; uint32_t width, height; dma_addr_t fbpaddr; fb = kzalloc(sizeof(*fb), GFP_KERNEL); if(fb == NULL) { ret = -ENOMEM; goto err_fb_alloc_failed; } spin_lock_init(&fb->lock); init_waitqueue_head(&fb->wait); platform_set_drvdata(pdev, fb); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if(r == NULL) { ret = -ENODEV; goto err_no_io_base; } fb->reg_base = IO_ADDRESS(r->start - IO_START); fb->irq = platform_get_irq(pdev, 0); if(fb->irq < 0) { ret = -ENODEV; goto err_no_irq; } width = readl(fb->reg_base + FB_GET_WIDTH); height = readl(fb->reg_base + FB_GET_HEIGHT); fb->fb.fbops = &goldfish_fb_ops; fb->fb.flags = FBINFO_FLAG_DEFAULT; fb->fb.pseudo_palette = fb->cmap; //strncpy(fb->fb.fix.id, clcd_name, sizeof(fb->fb.fix.id)); fb->fb.fix.type = FB_TYPE_PACKED_PIXELS; fb->fb.fix.visual = FB_VISUAL_TRUECOLOR; fb->fb.fix.line_length = width * 2; //RGB565 每个像素占用 16 位,两个字节 fb->fb.fix.accel = FB_ACCEL_NONE; fb->fb.fix.ypanstep = 1; fb->fb.var.xres = width;//实际显示区域 fb->fb.var.yres = height; fb->fb.var.xres_virtual = width; //虚拟显示区域 fb->fb.var.yres_virtual = height * 2;

255 Android 驱动开发与移植实战详解

fb->fb.var.bits_per_pixel = 16; fb->fb.var.activate = FB_ACTIVATE_NOW; fb->fb.var.height = readl(fb->reg_base + FB_GET_PHYS_HEIGHT); fb->fb.var.width = readl(fb->reg_base + FB_GET_PHYS_WIDTH); fb->fb.var.red.offset = 11; fb->fb.var.red.length = 5; fb->fb.var.green.offset = 5; fb->fb.var.green.length = 6; fb->fb.var.blue.offset = 0; fb->fb.var.blue.length = 5; //显示缓冲区大小 framesize = width * height * 2 * 2; //进行内存映射 fb->fb.screen_base = dma_alloc_writecombine(&pdev->dev, framesize, &fbpaddr, GFP_KERNEL); printk("allocating frame buffer %d * %d, got %p\n", width, height, fb->fb.screen_base); if(fb->fb.screen_base == 0) { ret = -ENOMEM; goto err_alloc_screen_base_failed; } fb->fb.fix.smem_start = fbpaddr; fb->fb.fix.smem_len = framesize; ret = fb_set_var(&fb->fb, &fb->fb.var); if(ret) goto err_fb_set_var_failed; ret = request_irq(fb->irq, goldfish_fb_interrupt, IRQF_SHARED, pdev->name, fb); if(ret) goto err_request_irq_failed; writel(FB_INT_BASE_UPDATE_DONE, fb->reg_base + FB_INT_ENABLE); goldfish_fb_pan_display(&fb->fb.var, &fb->fb); // updates base ret = register_framebuffer(&fb->fb); if(ret) goto err_register_framebuffer_failed; #ifdef CONFIG_ANDROID_POWER fb->early_suspend.suspend = goldfish_fb_early_suspend; fb->early_suspend.resume = goldfish_fb_late_resume; android_register_early_suspend(&fb->early_suspend); #endif return 0; //错误处理 err_register_framebuffer_failed: free_irq(fb->irq, fb); err_request_irq_failed: err_fb_set_var_failed: dma_free_writecombine(&pdev->dev, framesize, fb->fb.screen_base, fb->fb.fix.smem_start); err_alloc_screen_base_failed: err_no_irq: err_no_io_base: kfree(fb); err_fb_alloc_failed:

256 第 9 章 显示系统驱动

return ret; }

在上述代码中,通过 FrameBuffer 驱动程序实现了对 RGB565 颜色空间的支持,其中虚拟显示 的 y 值是实际显示的 2 倍,这样就实现了双缓存功能。

9.3.2 使用 Gralloc 模块的驱动程序 不同的硬件有不同的硬件图形加速设备和缓冲内存实现方法。Android Gralloc 动态库抽象的任 务是消除不同的设备之间的差别,在上层看来都是同样的方法和对象。在 Moudle 层隐藏缓冲区操 作细节。Android 使用了动态链接库 gralloc.xxx.so 来封装底层细节。 默认 Gralloc 模块的实现源码保存在“hardware/libhardware/modules/gralloc/”目录中,Android 的 Gralloc 模块主要由如下三个实现文件实现。 gralloc.cpp:在里面实现了 gralloc_module_t 模块和 alloc_device_t 设备。 mapper.cpp:在里面实现了工具函数。 framebuffer.cpp:在里面实现了 alloc_device_t 设备。 (1)文件 gralloc.cpp。 第一步:定义函数 gralloc_device_open(),此函数是一个模块打开函数,实现代码如下所示。

int gralloc_device_open(const hw_module_t* module, const char* name, hw_device_t** device) { int status = -EINVAL; if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) { gralloc_context_t *dev; dev = (gralloc_context_t*)malloc(sizeof(*dev)); /* initialize our state here */ memset(dev, 0, sizeof(*dev)); /* initialize the procs */ dev->device.common.tag = HARDWARE_DEVICE_TAG; dev->device.common.version = 0; dev->device.common.module = const_cast(module); dev->device.common.close = gralloc_close; dev->device.alloc = gralloc_alloc; dev->device.free = gralloc_free; *device = &dev->device.common; status = 0; } else { //打开 framebuffer_device_t 设备 status = fb_device_open(module, name, device); } return status; } 第二步:通过函数 gralloc_alloc_framebuffer_locked()调用 gralloc_alloc_framebuffer_locked(), 主要代码如下所示。

static int gralloc_alloc_framebuffer_locked(alloc_device_t* dev,

257 Android 驱动开发与移植实战详解

size_t size, int usage, buffer_handle_t* pHandle) { private_module_t* m = reinterpret_cast( dev->common.module); // allocate the framebuffer if (m->framebuffer == NULL) { // initialize the framebuffer, the framebuffer is mapped once // and forever. int err = mapFrameBufferLocked(m); if (err < 0) { return err; } }

第三步:定义函数 gralloc_alloc_framebuffer_locked() ,此函数的功能是如果当前没有 Framebuffer-->mapFrameBufferLocked(m) ,如果不支持 PAGE_FLIP ,则通过软件方法分配 gralloc_alloc_buffer,并且决定使用双 Framebuffer 中的哪个作为缓冲地址。此函数的实现代码如下 所示。

static int gralloc_alloc_framebuffer_locked(alloc_device_t* dev, size_t size, int usage, buffer_handle_t* pHandle) { private_module_t* m = reinterpret_cast( dev->common.module); //分配帧 if (m->framebuffer == NULL) { //初始化帧 int err = mapFrameBufferLocked(m); if (err < 0) { return err; } }

第四步:定义函数 gralloc_alloc_buffer(),主要代码如下所示。

static int gralloc_alloc_buffer(alloc_device_t* dev, size_t size, int usage, buffer_handle_t* pHandle) { int err = 0; int flags = 0; int fd = -1; void* base = 0; int offset = 0; int lockState = 0; size = roundUpToPageSize(size); #if HAVE_ANDROID_OS //应该在 have_pmem 某处定义 if (usage & GRALLOC_USAGE_HW_TEXTURE) { //使用 pmem 时可以回退到 copybit 模块 flags |= private_handle_t::PRIV_FLAGS_USES_PMEM; }

258 第 9 章 显示系统驱动

if (usage & GRALLOC_USAGE_HW_2D) { flags |= private_handle_t::PRIV_FLAGS_USES_PMEM; } if ((flags & private_handle_t::PRIV_FLAGS_USES_PMEM) == 0) { try_ashmem: fd = ashmem_create_region("gralloc-buffer", size); if (fd < 0) { LOGE("couldn't create ashmem (%s)", strerror(-errno)); err = -errno; } } else { private_module_t* m = reinterpret_cast( dev->common.module); err = init_pmem_area(m); if (err == 0) { // pmem 缓冲区始终是 mmapped 状态 base = m->pmem_master_base; lockState |= private_handle_t::LOCK_STATE_MAPPED; offset = sAllocator.allocate(size); if (offset < 0) { //没有更多的 pmem 记忆 err = -ENOMEM; } else { struct pmem_region sub = { offset, size };

//开始新建"sub-heap" fd = open("/dev/pmem", O_RDWR, 0); err = fd < 0 ? fd : 0;

//连接它 if (err == 0) err = ioctl(fd, PMEM_CONNECT, m->pmem_master); //提供给用户可用进程 if (err == 0) err = ioctl(fd, PMEM_MAP, &sub); if (err < 0) { err = -errno; close(fd); sAllocator.deallocate(offset); fd = -1; } //LOGD_IF(!err, "allocating pmem size=%d, offset=%d", size, offset); memset((char*)base + offset, 0, size); } } else { if ((usage & GRALLOC_USAGE_HW_2D) == 0) { //如果来电者没有要求 pmem,则可以尝试别的方法 flags &= ~private_handle_t::PRIV_FLAGS_USES_PMEM; err = 0; goto try_ashmem;

259 Android 驱动开发与移植实战详解

} else { LOGE("couldn't open pmem (%s)", strerror(-errno)); } } } #else // HAVE_ANDROID_OS

fd = ashmem_create_region("Buffer", size); if (fd < 0) { LOGE("couldn't create ashmem (%s)", strerror(-errno)); err = -errno; } #endif // HAVE_ANDROID_OS if (err == 0) { private_handle_t* hnd = new private_handle_t(fd, size, flags); hnd->offset = offset; hnd->base = int(base)+offset; hnd->lockState = lockState; *pHandle = hnd; } LOGE_IF(err, "gralloc failed err=%s", strerror(-err));

return err; }

上述代码的运作流程如下所示。 如果系统没用 PMEM,则直接赋值 fd = ashmem_create_region("gralloc-buffer", size)。 如果有 PMEM,则 init_pmem_area(m)。 获取本次需要的 size:offset = sAllocator.allocate(size)。 建立 PMEM region:struct pmem_region sub = { offset, size }。 重新打开:fd = open("/dev/pmem", O_RDWR, 0)。 链接空间:err = ioctl(fd, PMEM_CONNECT, m->pmem_master)。 获取句柄:private_handle_t* hnd = new private_handle_t(fd, size, flags)。 在文件 gralloc.cpp 中,通过结构体类型 private_module_t 扩展了 gralloc_module_t 结构体,此结 构体在文件 gralloc_priv.h 中定义。定义结构体 private_module_t 的代码如下所示。

struct private_module_t { gralloc_module_t base; private_handle_t* framebuffer; uint32_t flags; uint32_t numBuffers; uint32_t bufferMask; pthread_mutex_t lock; buffer_handle_t currentBuffer; int pmem_master; void* pmem_master_base; struct fb_var_screeninfo info;

260 第 9 章 显示系统驱动

struct fb_fix_screeninfo finfo; float xdpi; float ydpi; float fps; enum { //此标志证明已经被送回到缓冲区 PRIV_USAGE_LOCKED_FOR_POST = 0x80000000 };

(2)文件 mapper.cpp。 在此文件 mapper.cpp 中定义的函数是结构体 gralloc_module_t 的具体实现,此文件的具体实现 流程如下所示。 第一步:定义函数 gralloc_register_buffer()建立一个新的 private_handle_t 对象,如果不是本进 程对象则赋初值,其实现代码如下所示。

int gralloc_register_buffer(gralloc_module_t const* module, buffer_handle_t handle) { if (private_handle_t::validate(handle) < 0) return -EINVAL; private_handle_t* hnd = (private_handle_t*)handle; if (hnd->pid != getpid()) { hnd->base = 0; hnd->lockState = 0; hnd->writeOwner = 0; } return 0; }

第二步:定义函数 gralloc_unregister_buffer(),通过 validate 判断 handle 是否合法。对应代码如 下所示。

int gralloc_unregister_buffer(gralloc_module_t const* module, buffer_handle_t handle) { if (private_handle_t::validate(handle) < 0) return -EINVAL; private_handle_t* hnd = (private_handle_t*)handle;

LOGE_IF(hnd->lockState & private_handle_t::LOCK_STATE_READ_MASK, "[unregister] handle %p still locked (state=%08x)", hnd, hnd->lockState); //从未在这个过程被创造的 unmap 缓冲 if (hnd->pid != getpid()) { if (hnd->lockState & private_handle_t::LOCK_STATE_MAPPED) { gralloc_unmap(module, handle); } hnd->base = 0; hnd->lockState = 0;

261 Android 驱动开发与移植实战详解

hnd->writeOwner = 0; } return 0; }

(3)文件 framebuffer.cpp。 文件 framebuffer.cpp 用于实现设备 framebuffer_device_t,其核心代码和 Donut 之前版本的 EGLDisplaySurface.cpp文件的实现类似,不同的是在文件 framebuffer.cpp 中使用双缓冲的实现方式。 文件 framebuffer.cpp 的具体实现流程如下所示。 第一步:定义函数 fb_device_open()初始化设备 framebuffer_device_t,实现代码如下所示。

int fb_device_open(hw_module_t const* module, const char* name, hw_device_t** device) { int status = -EINVAL; if (!strcmp(name, GRALLOC_HARDWARE_FB0)) { alloc_device_t* gralloc_device; status = gralloc_open(module, &gralloc_device); if (status < 0) return status; /*在此初始化我们的状态*/ fb_context_t *dev = (fb_context_t*)malloc(sizeof(*dev)); memset(dev, 0, sizeof(*dev));

/* 初始化进程 */ dev->device.common.tag = HARDWARE_DEVICE_TAG; dev->device.common.version = 0; dev->device.common.module = const_cast(module); dev->device.common.close = fb_close; dev->device.setSwapInterval = fb_setSwapInterval; dev->device.post = fb_post; dev->device.setUpdateRect = 0; private_module_t* m = (private_module_t*)module; status = mapFrameBuffer(m);//映射 FrameBuffer 设备 if (status >= 0) {//填充设备 framebuffer_device_t 的各个内容 int stride = m->finfo.line_length / (m->info.bits_per_pixel >> 3); const_cast(dev->device.flags) = 0; const_cast(dev->device.width) = m->info.xres; const_cast(dev->device.height) = m->info.yres; const_cast(dev->device.stride) = stride; const_cast(dev->device.format) = HAL_PIXEL_FORMAT_RGB_565; const_cast(dev->device.xdpi) = m->xdpi; const_cast(dev->device.ydpi) = m->ydpi; const_cast(dev->device.fps) = m->fps; const_cast(dev->device.minSwapInterval) = 1; const_cast(dev->device.maxSwapInterval) = 1; *device = &dev->device.common; } }

262 第 9 章 显示系统驱动

return status; }

第二步:定义函数 mapFrameBufferLocked()打开 FrameBuffer 设备的真正功能,此函数需要完 成下面的工作。 打开 framebuffer 设备。 判断是否支持 PAGE_FLIP。 计算刷新率。 打印 gralloc 信息。 填充 private_module_t。 函数 mapFrameBufferLocked()的实现代码如下所示。

int mapFrameBufferLocked(struct private_module_t* module) { // 已经初始化... if (module->framebuffer) { return 0; }

char const * const device_template[] = { "/dev/graphics/fb%u", "/dev/fb%u", 0 }; int fd = -1; int i=0; char name[64]; while ((fd==-1) && device_template[i]) { snprintf(name, 64, device_template[i], 0); fd = open(name, O_RDWR, 0); i++; } if (fd < 0) return -errno; struct fb_fix_screeninfo finfo; if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) return -errno; struct fb_var_screeninfo info; if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1) return -errno; info.reserved[0] = 0; info.reserved[1] = 0; info.reserved[2] = 0; info.xoffset = 0; info.yoffset = 0; info.activate = FB_ACTIVATE_NOW; info.bits_per_pixel = 16; info.red.offset = 11; info.red.length = 5;

263 Android 驱动开发与移植实战详解

info.green.offset = 5; info.green.length = 6; info.blue.offset = 0; info.blue.length = 5; info.transp.offset = 0; info.transp.length = 0; info.yres_virtual = info.yres * NUM_BUFFERS; uint32_t flags = PAGE_FLIP; if (ioctl(fd, FBIOPUT_VSCREENINFO, &info) == -1) { info.yres_virtual = info.yres; flags &= ~PAGE_FLIP; LOGW("FBIOPUT_VSCREENINFO failed, page flipping not supported"); } if (info.yres_virtual < info.yres * 2) { //需要至少 2 页翻转的 info.yres_virtual = info.yres; flags &= ~PAGE_FLIP; LOGW("page flipping not supported (yres_virtual=%d, requested=%d)", info.yres_virtual, info.yres*2); } if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1) return -errno; int refreshRate = 1000000000000000LLU / ( uint64_t( info.upper_margin + info.lower_margin + info.yres ) * ( info.left_margin + info.right_margin + info.xres ) * info.pixclock ); if (refreshRate == 0) { refreshRate = 60*1000; // 60 Hz } if (int(info.width) <= 0 || int(info.height) <= 0) { //驱动没有返回信息 // 默认 160 dpi info.width = ((info.xres * 25.4f)/160.0f + 0.5f); info.height = ((info.yres * 25.4f)/160.0f + 0.5f); } float xdpi = (info.xres * 25.4f) / info.width; float ydpi = (info.yres * 25.4f) / info.height; float fps = refreshRate / 1000.0f; LOGI( "using (fd=%d)\n" "id = %s\n" "xres = %d px\n" "yres = %d px\n" "xres_virtual = %d px\n" "yres_virtual = %d px\n" "bpp = %d\n" "r = %2u:%u\n" "g = %2u:%u\n" "b = %2u:%u\n",

264 第 9 章 显示系统驱动

fd, finfo.id, info.xres, info.yres, info.xres_virtual, info.yres_virtual, info.bits_per_pixel, info.red.offset, info.red.length, info.green.offset, info.green.length, info.blue.offset, info.blue.length ); LOGI( "width = %d mm (%f dpi)\n" "height = %d mm (%f dpi)\n" "refresh rate = %.2f Hz\n", info.width, xdpi, info.height, ydpi, fps ); if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1) return -errno; if (finfo.smem_len <= 0) return -errno; module->flags = flags; module->info = info; module->finfo = finfo; module->xdpi = xdpi; module->ydpi = ydpi; module->fps = fps; int err; size_t fbSize = roundUpToPageSize(finfo.line_length * info.yres_virtual); module->framebuffer = new private_handle_t(dup(fd), fbSize, private_handle_t::PRIV_FLAGS_USES_PMEM); module->numBuffers = info.yres_virtual / info.yres; module->bufferMask = 0; void* vaddr = mmap(0, fbSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (vaddr == MAP_FAILED) { LOGE("Error mapping the framebuffer (%s)", strerror(errno)); return -errno; } module->framebuffer->base = intptr_t(vaddr); memset(vaddr, 0, fbSize); return 0; }

第三步:定义函数 fb_post()将某个缓冲区显示在屏幕上,具体代码如下所示。 static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer) { if (private_handle_t::validate(buffer) < 0) return -EINVAL; fb_context_t* ctx = (fb_context_t*)dev;

265 Android 驱动开发与移植实战详解

private_handle_t const* hnd = reinterpret_cast(buffer); private_module_t* m = reinterpret_cast( dev->common.module);

if (m->currentBuffer) { m->base.unlock(&m->base, m->currentBuffer); m->currentBuffer = 0; } if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) { m->base.lock(&m->base, buffer, private_module_t::PRIV_USAGE_LOCKED_FOR_POST, 0, 0, m->info.xres, m->info.yres, NULL);

const size_t offset = hnd->base - m->framebuffer->base; m->info.activate = FB_ACTIVATE_VBL; m->info.yoffset = offset / m->finfo.line_length; if (ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info) == -1) { LOGE("FBIOPUT_VSCREENINFO failed"); m->base.unlock(&m->base, buffer); return -errno; } m->currentBuffer = buffer; } else { //如果不能做 page_flip,则复制缓冲对前面 void* fb_vaddr; void* buffer_vaddr;

m->base.lock(&m->base, m->framebuffer, GRALLOC_USAGE_SW_WRITE_RARELY, 0, 0, m->info.xres, m->info.yres, &fb_vaddr); m->base.lock(&m->base, buffer, GRALLOC_USAGE_SW_READ_RARELY, 0, 0, m->info.xres, m->info.yres, &buffer_vaddr); memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres);

m->base.unlock(&m->base, buffer); m->base.unlock(&m->base, m->framebuffer); } return 0; }

在上述代码中检查了 buffer 是否合法,并实现了相应的类型转换处理。如果 currentbuffer 非空 则 unlock。

9.4 MSM 中显示驱动的实现

经过本章前面内容的学习,了解了在模拟器中 Goldfish 和 FrameBuffer 驱动程序的基本知识。

266 第 9 章 显示系统驱动

在本节将详细剖析当前主流处理器平台显示系统驱动的实现过程,为读者步入本书后面知识的学习 打下基础。

9.4.1 MSM 中的 FrameBuffer 驱动程序 MSM 处理器的入口源文件是“drivers/staging/msm/msm_fb.c”,从具体实现代码可以看出这是 一个标准的 FrameBuffer 驱动程序,此驱动使用了 RGB565 颜色空间,使用 2 倍实际显示区的内存 作为虚拟显示区。文件 msm_fb.c 的具体实现流程如下所示。 (1)定义接口 fb_ops msm_fb_ops,此接口是高通 msm fb 设备的文件操作函数接口,具体代码 如下所示。

static struct fb_ops msm_fb_ops = { .owner = THIS_MODULE, .fb_open = msm_fb_open, .fb_release = msm_fb_release, .fb_read = NULL, .fb_write = NULL, .fb_cursor = NULL, .fb_check_var = msm_fb_check_var, /* 参数检查 */ .fb_set_par = msm_fb_set_par, /* 设置显示相关参数 */ .fb_setcolreg = NULL, /*设置颜色寄存器*/ .fb_blank = NULL, /*空白显示*/ .fb_pan_display = msm_fb_pan_display, /* 显示 */ .fb_fillrect = msm_fb_fillrect, /* 绘制矩形*/ .fb_copyarea = msm_fb_copyarea, /*从另一个地区复制数据*/ .fb_imageblit = msm_fb_imageblit, /*绘制一个图像的显示*/ .fb_cursor = NULL, .fb_rotate = NULL, .fb_sync = NULL, /* 等待闲置,可选的 */ .fb_ioctl = msm_fb_ioctl, /*执行全具体事项,可选的 */ .fb_mmap = NULL, };

(2)定义接口 platform_driver msm_fb_driver,此接口是高通 msm fb 的 driver 接口。具体代码 如下所示。

static struct platform_driver msm_fb_driver = { .probe = msm_fb_probe,//驱动探测函数 .remove = msm_fb_remove, #ifndef CONFIG_ANDROID_POWER .suspend = msm_fb_suspend, .suspend_late = NULL, .resume_early = NULL, .resume = msm_fb_resume, #endif .shutdown = NULL, .driver = { /*驱动必须匹配装置名称,将其添加在平台上. */

267 Android 驱动开发与移植实战详解

.name = "msm_fb", }, };

(3)和标准的 FrameBuffer 驱动程序相比,MSM 在驱动中增加了特殊的 iocttl,对应代码如下 所示。

void msm_fb_add_device(struct platform_device *pdev) { struct msm_fb_panel_data *pdata; struct platform_device *this_dev = NULL; struct fb_info *fbi; struct msm_fb_data_type *mfd = NULL; u32 type, id, fb_num; if (!pdev) return; id = pdev->id; pdata = pdev->dev.platform_data; if (!pdata) return; type = pdata->panel_info.type; fb_num = pdata->panel_info.fb_num; if (fb_num <= 0) return; if (fbi_list_index >= MAX_FBI_LIST) { printk(KERN_ERR "msm_fb: no more framebuffer info list!\n"); return; } /** * alloc 盘区设备数据 */ this_dev = msm_fb_device_alloc(pdata, type, id);

if (!this_dev) { printk(KERN_ERR "%s: msm_fb_device_alloc failed!\n", __func__); return; } fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL); if (fbi == NULL) { platform_device_put(this_dev); printk(KERN_ERR "msm_fb: can't alloca framebuffer info data!\n"); return; } mfd = (struct msm_fb_data_type *)fbi->par; mfd->key = MFD_KEY; mfd->fbi = fbi; mfd->panel.type = type; mfd->panel.id = id; mfd->fb_page = fb_num;

268 第 9 章 显示系统驱动

mfd->index = fbi_list_index; mfd->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE; mfd->pdev = this_dev; mfd_list[mfd_list_index++] = mfd; fbi_list[fbi_list_index++] = fbi; platform_set_drvdata(this_dev, mfd); if (platform_device_add(this_dev)) { printk(KERN_ERR "msm_fb: platform_device_add failed!\n"); platform_device_put(this_dev); framebuffer_release(fbi); fbi_list_index--; return; } }

9.4.2 MSM 中的 Gralloc 驱动程序 MSM 处理器重新实现了 Gralloc 模块的架构,此 Gralloc 模块是基于 FrameBuffer 和 Pmem 驱 动实现的。在 MSM 平台中,和 Gralloc 模块相关的实现文件如下所示。 hardware/msm7k/libgralloc:MSM7 系列的实现文件。 hardware/msm7k/libgralloc-qsd8k:QSD8K 系列的实现文件。 在 MSM 中实现 Gralloc 模块的方法和在 Android 系统中的实现方法类似,区别是使用了 Pmem 的部分。 (1)文件 gralloc.cpp。 在文件 gralloc.cpp 中,使用 Gralloc 中的结构体 private_module_t 扩展了里面的 private_module_t 结构体。结构体 private_module_t 是在文件 gralloc_priv.h 中实现的,在此文件中包含了和上下文有 关的信息。文件 gralloc.cpp 的具体实现流程如下所示。 首先需要定义结构体 HAL_MODULE_INFO_SYM,具体代码如下所示。

struct private_module_t HAL_MODULE_INFO_SYM = { base: { common: { tag: HARDWARE_MODULE_TAG, version_major: 1, version_minor: 0, id: GRALLOC_HARDWARE_MODULE_ID, name: "Graphics Memory Allocator Module", author: "The Android Open Source Project", methods: &gralloc_module_methods }, registerBuffer: gralloc_register_buffer, unregisterBuffer: gralloc_unregister_buffer, lock: gralloc_lock, unlock: gralloc_unlock, perform: gralloc_perform, }, framebuffer: 0,

269 Android 驱动开发与移植实战详解

fbFormat: 0, flags: 0, numBuffers: 0, bufferMask: 0, lock: PTHREAD_MUTEX_INITIALIZER, currentBuffer: 0, pmem_master: -1, pmem_master_base: 0, };

上述代码和 Gralloc 中的代码基本一致,只是增加了结构体 private_module_t 的 perform()函数 指针来实现 gralloc_perform()。函 数 gralloc_perform()是在文件 mapper.cpp 中定义,实现代码如下 所示。

int gralloc_perform(struct gralloc_module_t const* module, int operation, ... ) { int res = -EINVAL; va_list args; va_start(args, operation);

switch (operation) { case GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER: { int fd = va_arg(args, int); size_t size = va_arg(args, size_t); size_t offset = va_arg(args, size_t); void* base = va_arg(args, void*); native_handle_t** handle = va_arg(args, native_handle_t**); private_handle_t* hnd = (private_handle_t*)native_handle_create( private_handle_t::sNumFds, private_handle_t::sNumInts); hnd->magic = private_handle_t::sMagic; hnd->fd = fd; hnd->flags = private_handle_t::PRIV_FLAGS_USES_PMEM; hnd->size = size; hnd->offset = offset; hnd->base = intptr_t(base) + offset; hnd->lockState = private_handle_t::LOCK_STATE_MAPPED; *handle = (native_handle_t *)hnd; res = 0; break; } }

va_end(args); return res; }

在上述代码中,通过 case 语句只实现了 GRALLOC_MODULE_PERFORM_CREATE_HANDLE_ FROM_BUFFER,此命令是在 SurfaceFlinger 中被调用的内容,这是一个可选的功能,通过调用 Pmem

270 第 9 章 显示系统驱动

获取了内存的大小。 (2)文件 framebuffer.cpp。 在文件 hardware/msm7k/libgralloc-qsd8k/framebuffer.cpp 中实现了 QSD8K 的 framebuffer_ device_t 设备驱动,具体实现源码和标准的程序类似,区别是增加了更多颜色格式的支持,并且用 RGBA8888 作为默认的颜色格式。 其中在 post 中的区别是不支持双缓冲需要的内存复制功能时,不会再调用 memcopy 来实现, 而是调用 msm_copy_buffer 来实现。函数 fb_post()的实现代码如下所示。

static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer){ if (private_handle_t::validate(buffer) < 0) return -EINVAL; int nxtIdx; bool reuse; struct qbuf_t qb; fb_context_t* ctx = (fb_context_t*)dev; private_handle_t const* hnd = reinterpret_cast(buffer); private_module_t* m = reinterpret_cast( dev->common.module); if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) { reuse = false; nxtIdx = (m->currentIdx + 1) % NUM_BUFFERS; if (m->swapInterval == 0) { if (pthread_mutex_trylock(&(m->avail[nxtIdx].lock))) { reuse = true; } else { if (! m->avail[nxtIdx].is_avail) reuse = true; pthread_mutex_unlock(&(m->avail[nxtIdx].lock)); } } else { if ((m->mddi_panel) && (m->currentIdx >= 0)) { pthread_mutex_lock(&(m->avail[m->currentIdx].lock)); if (! m->avail[m->currentIdx].is_avail) { pthread_cond_wait(&(m->avail[m->currentIdx].cond), &(m->avail[m->currentIdx].lock)); m->avail[m->currentIdx].is_avail = true; } pthread_mutex_unlock(&(m->avail[m->currentIdx].lock)); } } if(!reuse){ if (m->currentBuffer && m->mddi_panel) { m->base.unlock(&m->base, m->currentBuffer); } m->base.lock(&m->base, buffer, private_module_t::PRIV_USAGE_LOCKED_FOR_POST, 0,0, m->info.xres, m->info.yres, NULL); pthread_mutex_lock(&(m->avail[nxtIdx].lock));

271 Android 驱动开发与移植实战详解

m->avail[nxtIdx].is_avail = false; pthread_mutex_unlock(&(m->avail[nxtIdx].lock)); qb.idx = nxtIdx; qb.buf = buffer; pthread_mutex_lock(&(m->qlock)); m->disp.push(qb); pthread_cond_signal(&(m->qpost)); pthread_mutex_unlock(&(m->qlock)); if (!m->mddi_panel && m->currentBuffer) { if (m->swapInterval != 0) { pthread_mutex_lock(&(m->avail[m->currentIdx].lock)); if (! m->avail[m->currentIdx].is_avail) { pthread_cond_wait(&(m->avail[m->currentIdx].cond), &(m->avail[m->currentIdx].lock)); m->avail[m->currentIdx].is_avail = true; } pthread_mutex_unlock(&(m->avail[m->currentIdx].lock)); } m->base.unlock(&m->base, m->currentBuffer); } m->currentBuffer = buffer; m->currentIdx = nxtIdx; } else { if (m->currentBuffer) m->base.unlock(&m->base, m->currentBuffer); m->base.lock(&m->base, buffer, private_module_t::PRIV_USAGE_LOCKED_FOR_POST, 0,0, m->info.xres, m->info.yres, NULL); m->currentBuffer = buffer; } } else { void* fb_vaddr; void* buffer_vaddr; m->base.lock(&m->base, m->framebuffer, GRALLOC_USAGE_SW_WRITE_RARELY, 0, 0, m->info.xres, m->info.yres, &fb_vaddr); m->base.lock(&m->base, buffer, GRALLOC_USAGE_SW_READ_RARELY, 0, 0, m->info.xres, m->info.yres, &buffer_vaddr); //memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres); msm_copy_buffer( m->framebuffer, m->framebuffer->fd, m->info.xres, m->info.yres, m->fbFormat, m->info.xoffset, m->info.yoffset, m->info.width, m->info.height); m->base.unlock(&m->base, buffer); m->base.unlock(&m->base, m->framebuffer); }

272 第 9 章 显示系统驱动

return 0; }

(3)文件 gralloc.cpp。 在文件 libgralloc-qsd8k/gralloc.cpp 中分别定义了函数 alloc()、free()、close(),这些函数是 MSM 的 Galloc 模块默认的实现函数。函数 gralloc_alloc 是 MSM 中 gralloc_device_t 设备的分配函数,如 果其参数不具有 GRALLOC_USAGE_HW_FB 宏,则会调用 gralloc_alloc_buffer 来分配内存。由此 可见,这部分的实现和默认 Galloc 模块中的是不同的。函数 Gallocgralloc_alloc_buffer()的实现代码 如下所示。

static int gralloc_alloc_buffer(alloc_device_t* dev, size_t size, int usage, buffer_handle_t* pHandle) { int err = 0; int flags = 0; int fd = -1; void* base = 0; int offset = 0; int lockState = 0; size = roundUpToPageSize(size); if (usage & GRALLOC_USAGE_HW_TEXTURE) { flags |= private_handle_t::PRIV_FLAGS_USES_PMEM; } if (usage & GRALLOC_USAGE_HW_2D) { flags |= private_handle_t::PRIV_FLAGS_USES_PMEM; } if ((flags & private_handle_t::PRIV_FLAGS_USES_PMEM) == 0) { try_ashmem: fd = ashmem_create_region("gralloc-buffer", size); if (fd < 0) { LOGE("couldn't create ashmem (%s)", strerror(errno)); err = -errno; } } else { private_module_t* m = reinterpret_cast( dev->common.module); err = init_pmem_area(m); if (err == 0) { base = m->pmem_master_base; lockState |= private_handle_t::LOCK_STATE_MAPPED; offset = sAllocator.allocate(size); if (offset < 0) { err = -ENOMEM; } else { struct pmem_region sub = { offset, size }; int openFlags = O_RDWR | O_SYNC; uint32_t uread = usage & GRALLOC_USAGE_SW_READ_MASK; uint32_t uwrite = usage & GRALLOC_USAGE_SW_WRITE_MASK; if (uread == GRALLOC_USAGE_SW_READ_OFTEN ||

273 Android 驱动开发与移植实战详解

uwrite == GRALLOC_USAGE_SW_WRITE_OFTEN) { openFlags &= ~O_SYNC; } fd = open("/dev/pmem", openFlags, 0); err = fd < 0 ? fd : 0; if (err == 0) err = ioctl(fd, PMEM_CONNECT, m->pmem_master); //给用户提供进程 if (err == 0) err = ioctl(fd, PMEM_MAP, &sub); if (err < 0) { err = -errno; close(fd); sAllocator.deallocate(offset); fd = -1; } else { memset((char*)base + offset, 0, size); cacheflush(intptr_t(base) + offset, size, 0); } } } else { if ((usage & GRALLOC_USAGE_HW_2D) == 0) { flags &= ~private_handle_t::PRIV_FLAGS_USES_PMEM; err = 0; goto try_ashmem; } else { LOGE("couldn't open pmem (%s)", strerror(errno)); } } } if (err == 0) { private_handle_t* hnd = new private_handle_t(fd, size, flags); hnd->offset = offset; hnd->base = int(base)+offset; hnd->lockState = lockState; *pHandle = hnd; } LOGE_IF(err, "gralloc failed err=%s", strerror(-err)); return err; }

在文件 gralloc.cpp 中,函数 init_pmem_area_locked()能够从默认的内存中实现内存映射,并且 此函数是通过文件描述符实现的,这和函数 mapBuffer()有很大的不同。函数 init_pmem_area_locked() 的实现代码如下所示。

static int init_pmem_area_locked(private_module_t* m) { int err = 0; int master_fd = open("/dev/pmem", O_RDWR, 0); if (master_fd >= 0) {

274 第 9 章 显示系统驱动

size_t size; pmem_region region; if (ioctl(master_fd, PMEM_GET_TOTAL_SIZE, ®ion) < 0) { LOGE("PMEM_GET_TOTAL_SIZE failed, limp mode"); size = 8<<20; // 8 MiB } else { size = region.len; } sAllocator.setSize(size);

void* base = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, master_fd, 0); if (base == MAP_FAILED) { err = -errno; base = 0; close(master_fd); master_fd = -1; } m->pmem_master = master_fd; m->pmem_master_base = base; } else { err = -errno; } return err; }

上述函数的运行流程如下所示。 通过 open("/dev/pmem", O_RDWR, 0)打开 PMEM。 通过 ioctl(master_fd, PMEM_GET_TOTAL_SIZE, ®ion)获取所有空间。 分配“sAllocator.setSize(size)”指定数目的空间。 完成映射功能。 函数 gralloc_free()的功能是实现 alloc_device_t 中的释放功能,和默认的 Gralloc 相比,区别是 在此不使用 PRIV_FLAGS_FRAMEBUFFER 标志实现。函数 gralloc_free()的实现代码如下所示。

static int gralloc_free(alloc_device_t* dev, buffer_handle_t handle) { if (private_handle_t::validate(handle) < 0) return -EINVAL; private_handle_t const* hnd = reinterpret_cast(handle); if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) { private_module_t* m = reinterpret_cast( dev->common.module); const size_t bufferSize = m->finfo.line_length * m->info.yres; int index = (hnd->base - m->framebuffer->base) / bufferSize; m->bufferMask &= ~(1<flags & private_handle_t::PRIV_FLAGS_USES_PMEM) {

275 Android 驱动开发与移植实战详解

if (hnd->fd >= 0) { struct pmem_region sub = { hnd->offset, hnd->size }; int err = ioctl(hnd->fd, PMEM_UNMAP, &sub); LOGE_IF(err<0, "PMEM_UNMAP failed (%s), " "fd=%d, sub.offset=%lu, sub.size=%lu", strerror(errno), hnd->fd, hnd->offset, hnd->size); if (err == 0) { sAllocator.deallocate(hnd->offset); } } } gralloc_module_t* module = reinterpret_cast( dev->common.module); terminateBuffer(module, const_cast(hnd)); } close(hnd->fd); delete hnd; return 0; }

MSM 平台提高了 alloc_device_t 设备的性能,通过使用 Pmem 驱动程序作为内存映射工具,将 原来通过 ashmem 分配和管理的内存部分转移到了 Pmem 上面。

9.5 OMAP 中显示驱动的实现

在本节将详细剖析 OMAP 处理器平台显示系统驱动的实现过程。OMAP 平台的驱动程序由 FrameBuffer 驱动和 Gralloc 模块构成,其中里面的 FrameBuffer 是标准驱动,而 Gralloc 模块既可以 使用默认的,也可以使用自己自定义的。

9.5.1 文件 omapfb-main.c OMAP 处理器中 FrameBuffer 驱动的主要实现文件是“drivers/video/omap2/omapfb/ omapfb-main.c”,在此文件中通过函数 omapfb_create_framebuffers()来注册 FrameBuffer 驱动程序, 此函数的实现代码如下所示。

static int omapfb_create_framebuffers(struct omapfb2_device *fbdev) { int r, i; fbdev->num_fbs = 0; DBG("create %d framebuffers\n", CONFIG_FB_OMAP2_NUM_FBS); for (i = 0; i < CONFIG_FB_OMAP2_NUM_FBS; i++) { struct fb_info *fbi; struct omapfb_info *ofbi; fbi = framebuffer_alloc(sizeof(struct omapfb_info), fbdev->dev); if (fbi == NULL) { dev_err(fbdev->dev,

276 第 9 章 显示系统驱动

"unable to allocate memory for plane info\n"); return -ENOMEM; } clear_fb_info(fbi); fbdev->fbs[i] = fbi; ofbi = FB2OFB(fbi); ofbi->fbdev = fbdev; ofbi->id = i; ofbi->region = &fbdev->regions[i]; ofbi->region->id = i; init_rwsem(&ofbi->region->lock); ofbi->rotation_type = def_vrfb ? OMAP_DSS_ROT_VRFB : OMAP_DSS_ROT_DMA; ofbi->mirror = def_mirror; fbdev->num_fbs++; } DBG("fb_infos allocated\n"); for (i = 0; i < min(fbdev->num_fbs, fbdev->num_overlays); i++) { struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); ofbi->overlays[0] = fbdev->overlays[i]; ofbi->num_overlays = 1; } r = omapfb_allocate_all_fbs(fbdev); if (r) { dev_err(fbdev->dev, "failed to allocate fbmem\n"); return r; } DBG("fbmems allocated\n"); for (i = 0; i < fbdev->num_fbs; i++) { struct fb_info *fbi = fbdev->fbs[i]; struct omapfb_info *ofbi = FB2OFB(fbi); omapfb_get_mem_region(ofbi->region); r = omapfb_fb_init(fbdev, fbi); omapfb_put_mem_region(ofbi->region); if (r) { dev_err(fbdev->dev, "failed to setup fb_info\n"); return r; } } DBG("fb_infos initialized\n"); for (i = 0; i < fbdev->num_fbs; i++) { r = register_framebuffer(fbdev->fbs[i]); if (r != 0) { dev_err(fbdev->dev, "registering framebuffer %d failed\n", i); return r; } } DBG("framebuffers registered\n"); for (i = 0; i < fbdev->num_fbs; i++) {

277 Android 驱动开发与移植实战详解

struct fb_info *fbi = fbdev->fbs[i]; struct omapfb_info *ofbi = FB2OFB(fbi); omapfb_get_mem_region(ofbi->region); r = omapfb_apply_changes(fbi, 1); omapfb_put_mem_region(ofbi->region); if (r) { dev_err(fbdev->dev, "failed to change mode\n"); return r; } } if (fbdev->num_fbs > 0) { struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[0]); if (ofbi->num_overlays > 0) { struct omap_overlay *ovl = ofbi->overlays[0]; r = omapfb_overlay_enable(ovl, 1); if (r) { dev_err(fbdev->dev, "failed to enable overlay\n"); return r; } } } DBG("create_framebuffers done\n"); return 0; }

9.5.2 文件 omapfb.h 在文件 include/linux/omapfb.h 中定义了额外的 ioctl 命令号,具体代码如下所示。

#define OMAP_IOW(num, dtype) _IOW('O', num, dtype) #define OMAP_IOR(num, dtype) _IOR('O', num, dtype) #define OMAP_IOWR(num, dtype) _IOWR('O', num, dtype) #define OMAP_IO(num) _IO('O', num) #define OMAPFB_MIRROR OMAP_IOW(31, int) #define OMAPFB_SYNC_GFX OMAP_IO(37) #define OMAPFB_VSYNC OMAP_IO(38) #define OMAPFB_SET_UPDATE_MODE OMAP_IOW(40, int) #define OMAPFB_GET_CAPS OMAP_IOR(42, struct omapfb_caps) #define OMAPFB_GET_UPDATE_MODE OMAP_IOW(43, int) #define OMAPFB_LCD_TEST OMAP_IOW(45, int) #define OMAPFB_CTRL_TEST OMAP_IOW(46, int) #define OMAPFB_UPDATE_WINDOW_OLD OMAP_IOW(47, struct omapfb_update_window_old) #define OMAPFB_SET_COLOR_KEY OMAP_IOW(50, struct omapfb_color_key) #define OMAPFB_GET_COLOR_KEY OMAP_IOW(51, struct omapfb_color_key) #define OMAPFB_SETUP_PLANE OMAP_IOW(52, struct omapfb_plane_info) #define OMAPFB_QUERY_PLANE OMAP_IOW(53, struct omapfb_plane_info) #define OMAPFB_UPDATE_WINDOW OMAP_IOW(54, struct omapfb_update_window) #define OMAPFB_SETUP_MEM OMAP_IOW(55, struct omapfb_mem_info) #define OMAPFB_QUERY_MEM OMAP_IOW(56, struct omapfb_mem_info) #define OMAPFB_WAITFORVSYNC OMAP_IO(57)

278 第 9 章 显示系统驱动

#define OMAPFB_MEMORY_READ OMAP_IOR(58, struct omapfb_memory_read) #define OMAPFB_GET_OVERLAY_COLORMODE OMAP_IOR(59, struct omapfb_ovl_colormode) #define OMAPFB_WAITFORGO OMAP_IO(60) #define OMAPFB_GET_VRAM_INFO OMAP_IOR(61, struct omapfb_vram_info) #define OMAPFB_SET_TEARSYNC OMAP_IOW(62, struct omapfb_tearsync_info) #define OMAPFB_GET_DISPLAY_INFO OMAP_IOR(63, struct omapfb_display_info)

9.6 6416 中 FrameBuffer 的工作原理

在 FrameBuffer 驱动的开发过程中,文件 fbmem.c 是一个通用的驱动核心,它为上层应用程序 提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系 统内核注册它们自己。fbmem.c 为所有支持 FrameBuffer 的设备驱动提供了通用的接口,避免重复 工作。 文件 fbmem.c 中 fb 设备也是以模块的形式加载到系统中去的,在函数 module_init()中完成了 FrameBuffer 设备在平台的注册,文件中还定义了多个 FrameBuffer 设备在系统中的注册过程。 下面是 FrameBuffer 设备在平台上的初始化和注册代码。

static int __init fbmem_init(void) { //构建 FrameBuffer 的接口函数 proc_create("fb", 0, NULL, &fb_proc_fops); //以 FrameBuffer 的主设备号为条件进行注册 if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) printk("unable to get major %d for fb devs\n", FB_MAJOR); fb_class = class_create(THIS_MODULE, "graphics"); if (IS_ERR(fb_class)) { printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); fb_class = NULL; } return 0; }

在 fbmem_init()中注册了字符设备的文件操作函数 fb_fops,fb_fops 的定义代码如下。

static const struct file_operations fb_fops = { .owner = THIS_MODULE, .read = fb_read, .write = fb_write, .unlocked_ioctl = fb_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = fb_compat_ioctl, #endif .mmap = fb_mmap, .open = fb_open, .release = fb_release,

279 Android 驱动开发与移植实战详解

#ifdef HAVE_ARCH_FB_UNMAPPED_AREA .get_unmapped_area = get_fb_unmapped_area, #endif #ifdef CONFIG_FB_DEFERRED_IO .fsync = fb_deferred_io_fsync, #endif };

多个 FrameBuffer 需要在系统中注册时,有如下两个重要的全局变量。 struct fb_info *registered_fb[FB_MAX]。 int num_registered_fb。 上述两变量记录了所有 fb_info 结构的实例,fb_info 能够结构描述显卡的当前状态,所有设备 对应的 fb_info 结构都保存在这个数组中,当一个 FrameBuffer 设备驱动向系统注册自己时,其对 应的 fb_info 结构就会添加到这个结构中,同时 num_registered_fb 会自动加 1。 在文件 Fbmem.c 中,register_framebuffer 是提供给下层 FrameBuffer 设备驱动的接口,设备驱 动通过这两函数向系统注册自己。底层设备驱动所要做的所有事情就是填充 fb_info 结构,然后向 系统注册,具体代码如下:

intregister_framebuffer(struct fb_info *fb_info) { int i; struct fb_event event; struct fb_videomode mode; //如果超过了所能注册设备的最大值,返回错误 if (num_registered_fb == FB_MAX) return -ENXIO; if (fb_check_foreignness(fb_info)) return -ENOSYS; num_registered_fb++; //判断该设备是否已经注册过 for (i = 0 ; i < FB_MAX; i++) if (!registered_fb[i]) break; fb_info->node = i; mutex_init(&fb_info->lock); //创建设备 fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), NULL, "fb%d", i); if (IS_ERR(fb_info->dev)) { printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev)); fb_info->dev = NULL; } else fb_init_device(fb_info); //分配像素 if (fb_info->pixmap.addr == NULL) { fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); if (fb_info->pixmap.addr) {

280 第 9 章 显示系统驱动

fb_info->pixmap.size = FBPIXMAPSIZE; fb_info->pixmap.buf_align = 1; fb_info->pixmap.scan_align = 1; fb_info->pixmap.access_align = 32; fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; } } fb_info->pixmap.offset = 0; if (!fb_info->pixmap.blit_x) fb_info->pixmap.blit_x = ~(u32)0; if (!fb_info->pixmap.blit_y) fb_info->pixmap.blit_y = ~(u32)0; if (!fb_info->modelist.prev || !fb_info->modelist.next) INIT_LIST_HEAD(&fb_info->modelist); fb_var_to_videomode(&mode, &fb_info->var); fb_add_videomode(&mode, &fb_info->modelist); registered_fb[i] = fb_info; event.info = fb_info; fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); return 0; }

register_framebuffer 实际上是有具体的 FrameBuffer 设备加载的时候回调用进行注册的,同时 在文件 fbmem.c 中定义了文件操作的接口 fb_proc_fops,实现了常用的文件接口,例如 open、read、 write 等方法。不过最终的实现还要对应到具体的硬件实现上,去分析 6410 的 FrameBuffer 设备的 加载过程。文件路径是“drivers/video/samsung/S3cfb2.c”。 在 module_init 中会将 s3fb_pci_driver 注册到系统中,s3fb_pci_driver 定义了这个 pci 设备的具 体实现。

static struct platform_driver s3cfb_driver = { .probe = s3cfb_probe, .remove = s3cfb_remove, .suspend = s3cfb_suspend, .resume = s3cfb_resume, .driver = { .name = S3CFB_NAME, .owner = THIS_MODULE, },

当 FrameBuffer 被总线探测到后会自动调用函数 s3cfb_probe(),在其中会调用文件 fbmem.c 中 的 register_framebuffer()函数来注册自己。

static int s3cfb_probe(struct platform_device *pdev) { struct s3c_platform_fb *pdata; struct resource *res; int ret = 0; //初始化全局的结构体 ctrl = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL);

281 Android 驱动开发与移植实战详解

if (!ctrl) { err("failed to allocate for global fb structure\n"); goto err_global; } ctrl->dev = &pdev->dev; //设置 LCD 信息 s3cfb_set_lcd_info(ctrl); pdata = to_fb_plat(&pdev->dev); if (pdata->cfg_gpio) pdata->cfg_gpio(pdev); //得到 framebuffer 的时钟 ctrl->clock = clk_get(&pdev->dev, pdata->clk_name); if (IS_ERR(ctrl->clock)) { err("failed to get fimd clock source\n"); ret = -EINVAL; goto err_clk; } //使能时钟 clk_enable(ctrl->clock); //获得 CPU 的 I/O 资源 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { err("failed to get io memory region\n"); ret = -EINVAL; goto err_io; } //申请内存区域 res = request_mem_region(res->start, res->end - res->start + 1, pdev->name); if (!res) { err("failed to request io memory region\n"); ret = -EINVAL; goto err_io; } ctrl->regs = ioremap(res->start, res->end - res->start + 1); if (!ctrl->regs) { err("failed to remap io region\n"); ret = -EINVAL; goto err_io; } //得到平台的中断 ctrl->irq = platform_get_irq(pdev, 0); if (request_irq(ctrl->irq, s3cfb_irq_frame, IRQF_DISABLED, \ pdev->name, ctrl)) { err("request_irq failed\n"); ret = -EINVAL; goto err_irq; } #ifdef CONFIG_FB_S3C_V2_TRACE_UNDERRUN if (request_irq(platform_get_irq(pdev, 1), s3cfb_irq_fifo, \

282 第 9 章 显示系统驱动

IRQF_DISABLED, pdev->name, ctrl)) { err("request_irq failed\n"); ret = -EINVAL; goto err_irq; } s3cfb_set_fifo_interrupt(ctrl, 1); info("fifo underrun trace\n"); #endif //初始化 s3cfb 的全局变量 s3cfb_init_global(); s3cfb_display_on(ctrl); //显示面板控制 if (pdata->backlight_on) pdata->backlight_on(pdev); if (pdata->reset_lcd) //重置 LCD pdata->reset_lcd(pdev); if (ctrl->lcd->init_ldi) ctrl->lcd->init_ldi(); //分配内存 if (s3cfb_alloc_framebuffer()) goto err_alloc; //注册 s3cframebuffer if (s3cfb_register_framebuffer()) goto err_alloc; s3cfb_set_clock(ctrl); s3cfb_enable_window(pdata->default_win); ret = device_create_file(&(pdev->dev), &dev_attr_win_power); if (ret < 0) err("failed to add sysfs entries\n"); return 0; err_alloc: free_irq(ctrl->irq, ctrl); err_irq: iounmap(ctrl->regs); err_io: clk_disable(ctrl->clock); err_clk: clk_put(ctrl->clock); err_global: return ret; }

在 s3cfb_alloc_framebuffer()函数中会注册 s3cframebuffer 的设备操作函数 s3fb_ops,下面是定 义结构体的代码。

struct fb_ops s3cfb_ops = { .owner = THIS_MODULE, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea,

283 Android 驱动开发与移植实战详解

.fb_imageblit = cfb_imageblit, .fb_check_var = s3cfb_check_var, .fb_set_par = s3cfb_set_par, .fb_blank = s3cfb_blank, .fb_pan_display = s3cfb_pan_display, .fb_setcolreg = s3cfb_setcolreg, .fb_cursor = s3cfb_cursor, .fb_ioctl = s3cfb_ioctl, .fb_open = s3cfb_open, .fb_release = s3cfb_release, };

当上层应用打开 framebuffer 设备时,会首先调用到 Fbmem.c 这个通用 framebuffer 驱动中的 fb_open(),然后在 fb_open 中会重定向到具体的 framebuffer 设备(),即会调用到 s3fb_open(),具体 代码如下:

static int s3cfb_open(struct fb_info *fb, int user) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct s3cfb_window *win = fb->par; int ret = 0; mutex_lock(&ctrl->lock); if (atomic_read(&win->in_use)) { if (win->id == pdata->default_win) { dev_dbg(ctrl->dev, \ "multiple open for default window\n"); ret = 0; } else { dev_dbg(ctrl->dev, \ "do not allow multiple open " \ "for non-default window\n"); ret = -EBUSY; } } else atomic_inc(&win->in_use); mutex_unlock(&ctrl->lock); return ret; }

如果上层应用需要使用双 buffer 机制,则会对 framebuffer 设备调用 ioctl 并设置 FBIOPAN_DISPLAY 命令。在文件 Fbmem.c 中,下面是接口 ioctl 对 FBIOPAN_DISPLAY 命令的处 理代码。

long do_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg){ struct fb_ops *fb; struct fb_var_screeninfo var; struct fb_fix_screeninfo fix; struct fb_con2fbmap con2fb;

284 第 9 章 显示系统驱动

struct fb_cmap_user cmap; struct fb_event event; void __user *argp = (void __user *)arg; long ret = 0; fb = info->fbops; if (!fb) return -ENODEV; switch (cmd) { …… case FBIOPAN_DISPLAY: if (copy_from_user(&var, argp, sizeof(var))) { ret = -EFAULT; break; } acquire_console_sem(); //调用具体设备的接口 ret = fb_pan_display(info, &var); release_console_sem(); if (ret == 0 && copy_to_user(argp, &var, sizeof(var))) ret = -EFAULT; break; …… } }

真正实现双 buffer 切换的是函数 fb_pan_display(),定义在文件 S3cfb2.c 中,下面是定义代码。 static int s3cfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *fb) { struct s3cfb_window *win = fb->par; if (var->yoffset + var->yres > var->yres_virtual) { err("invalid yoffset value\n"); return -EINVAL; } //设置 y 轴偏移量,达到双 buffe 切换的效果 fb->var.yoffset = var->yoffset; dev_dbg(ctrl->dev, "[fb%d] yoffset for pan display: %d\n", win->id, \ var->yoffset); s3cfb_set_buffer_address(ctrl, win->id); return 0; }

285

第 10 章 音频系统驱动

如今 Android 设备的音频系统的驱动基本都是由 ALSA 架构来实现的,在 Linux 内核中为声卡 提供的驱动组件,代替了原来的 OSS 系统,成为目前主流的音频系统。在本章将详细讲解 Android 音频系统驱动的实现和移植内容,为读者进入本书后面知识的学习打下基础。

10.1 音频系统结构

Android 音频系统对应的硬件设备有音频输入和音频输出两部分,手机中的输入设备通常是话 筒和摄像头,输出设备通常是耳机和扬声器。Android 音频系统的核心是 Audio 系统,它在 Android 中负责音频方面的数据流传输和控制功能,也负责音频设备的管理。Audio 部分作为 Android 的 Audio 系统的输入/输出层次,一般负责播放 PCM 声音输出和从外部获取 PCM 声音,并管理声音 设备和设置。 Audio 系统主要分成如下几个层次。 (1)Media 库提供的 Audio 系统本地部分接口。 (2)AudioFlinger 作为 Audio 系统的中间层。 (3)Audio 的硬件抽象层提供底层支持。 (4)Audio 接口通过 JNI 和 Java 框架提供给上层。 Android 音频系统的基本层次结构如图 10-1 所示。 图 10-1 中各个构成部分的具体说明如下所示。 (1)Audio 的 Java 部分。 Java 部分的代码路径是“frameworks/base/media/java/android/media”。 与 Audio 相关的 Java 包是“android.media”,在里面主要包含了和 AudioManager、Audio 等系 统相关的类。 (2)Audio 的 JNI 部分。 JNI 部分的代码路径是“frameworks/base/core/jni”。 生成库是“libandroid_runtime.so”,Audio 的 JNI 是其中的一个部分。 (3)Audio 的框架部分。 框架部分的头文件路径是“frameworks/base/include/media/”。 源代码路径是“frameworks/base/media/libmedia/”。

第 10 章 音频系统驱动

Audio 本地框架是 Media 库的一部分,本部分内容被编译成库 libmedia.so,提供 Audio 部分的 接口(包括基于 Binder 的 IPC 机制)。

Java Audio Class Java框架

Audio JNI Audio本地API

Audio Flinger(libaudioflingerso )

AudioHardwareInterface libmedia Audio Audio Audio Recorder System Track Audio HAL Audio A2dp (libaudioso) Generic C框架

内核空间 Audio Driver /dev/eac

▲图 10-1 Android 音频系统的框架结构

(4)Audio Flinger。 Flinger 部分的代码路径是“frameworks/base/libs/audioflinger ”,此部分内容被编译成库 libaudioflinger.so,这是 Audio 系统的本地服务部分。 (5)Audio 的硬件抽象层接口。 硬件抽象层接口的头文件路径是“hardware/libhardware_legacy/include/hardware/”。 Audio 硬件抽象层的实现在各个系统中可能是不同的,需要使用代码去继承相应的类并实现它 们,作为 Android 系统本地框架层和驱动程序接口。

10.2 音频系统的层次

Audio 系统从上到下分别是由 Java 的 Audio 类、Audio 本地框架类、AudioFlinger 和 Audio 的 硬件抽象层几个部分组成。在本节将简要介绍这几个层次的基本知识,为读者步入本书后面知识的 学习打下基础。

10.2.1 层次说明 (1)Audio 本地框架类:是 libmedia.so 的一个部分,这些 Audio 接口对上层提供接口,由下层 的本地代码去实现。 (2)AudioFlinger:继承了 libmeida 里面的接口,提供实现库 libaudiofilnger.so。这部分内容没

287 Android 驱动开发与移植实战详解

有自己的对外头文件,上层调用的只是 libmedia 本部分的接口,但实际调用的内容是 libaudioflinger.so。 (3)JNI:在 Audio 系统中,使用 JNI 和 Java 对上层提供接口,JNI 部分通过调用 libmedia 库 提供的接口来实现。 (4)Audio 硬件抽象层:提供到硬件的接口,供 AudioFlinger 调用。Audio 的硬件抽象层实际 上是各个平台开发过程中需要主要关注和独立完成的部分。 因为 Android 中的 Audio 系统不涉及编解码环节,只负责上层系统和底层 Audio 硬件的交互, 所以通常以 PCM 作为输入/输出格式。 在 Android 的 Audio 系统中,无论上层还是下层,都使用一个管理类和输出输入两个类来表示 整个 Audio 系统,输出输入两个类负责数据通道。在各个层次之间具有对应关系,如表 10-1 所示。

表 10-1 Android 各个层次的对应关系 层次说明 Audio 管理环节 Audio 输出 Audio 输入 android.media android.media android.media Java 层 AudioSystem AudioTrack AudioRecorder 本地框架层 AudioSystem AudioTrack AudioRecorder AudioFlinger IAudioFlinger IAudioTrack IAudioRecorder 硬件抽象层 AudioHardwareInterface AudioStreamOut AudioStreamIn

10.2.2 Media 库中的 Audio 框架 在 Media 库中提供了 Android 的 Audio 系统的核心框架,在库中实现了 AudioSystem、AudioTrack 和 AudioRecorder 三个类。另外还提供了 IAudioFlinger 类接口,通过此类可以获得 IAudioTrack 和 IAudioRecorder 两个接口,分别用于声音的播放和录制。AudioTrack 和 AudioRecorder 分别通过调 用 IAudioTrack 和 IAudioRecorder 来实现。 Audio 系统的头文件被保存在“frameworks/base/include/media/”目录中,其中包含了如下头 文件。 (1)AudioSystem.h:media 库的 Audio 部分对上层的总管接口。 (2)IAudioFlinger.h:需要下层实现的总管接口。 (3)AudioTrack.h:放音部分对上接口。 (4)IAudioTrack.h:放音部分需要下层实现的接口。 (5)AudioRecorder.h:录音部分对上接口。 (6)IAudioRecorder.h:录音部分需要下层实现的接口。 其中文件 IAudioFlinger.h、IAudioTrack.h 和 IAudioRecorder.h 的接口是通过下层的继承来实现 的。文件 AudioFlinger.h、AudioTrack.h 和 AudioRecorder.h 是对上层提供的接口,它们既供本地程 序调用(例如声音的播放器、录制器等),也可以通过 JNI 向 Java 层提供接口。 具体从功能上看,AudioSystem 用于综合管理 Audio 系统,而 AudioTrack 和 AudioRecorder 分 别负责输出和输入音频数据,即分别实现播放和录制功能。

288 第 10 章 音频系统驱动

在文件 AudioSystem.h 中定义了需要的枚举值和 set/get 等接口,主要代码如下所示。 class AudioSystem { public: enum stream_type { // Audio 流的类型 SYSTEM = 1, RING = 2, MUSIC = 3, ALARM = 4, NOTIFICATION = 5, BLUETOOTH_SCO = 6, ENFORCED_AUDIBLE = 7, NUM_STREAM_TYPES }; enum audio_output_type { // Audio 数据输出类型 AUDIO_OUTPUT_DEFAULT =-1, AUDIO_OUTPUT_HARDWARE = 0, AUDIO_OUTPUT_A2DP = 1, NUM_AUDIO_OUTPUT_TYPES }; enum audio_format { // Audio 数据格式 FORMAT_DEFAULT = 0, PCM_16_BIT, PCM_8_BIT, INVALID_FORMAT }; enum audio_mode { // Audio 模式 MODE_INVALID = -2, MODE_CURRENT = -1, MODE_NORMAL = 0, MODE_RINGTONE, MODE_IN_CALL, NUM_MODES //不是一个有效的输入,意味着结束清单 }; enum audio_routes { // Audio 路径类型 ROUTE_EARPIECE = (1 << 0), ROUTE_SPEAKER = (1 << 1), ROUTE_BLUETOOTH_SCO = (1 << 2), ROUTE_HEADSET = (1 << 3), ROUTE_BLUETOOTH_A2DP = (1 << 4), ROUTE_ALL = -1UL, }; enum audio_in_acoustics { AGC_ENABLE = 0x0001, AGC_DISABLE = 0, NS_ENABLE = 0x0002, NS_DISABLE = 0, TX_IIR_ENABLE = 0x0004, TX_DISABLE = 0

289 Android 驱动开发与移植实战详解

}; static status_t speakerphone(bool state); static status_t isSpeakerphoneOn(bool* state); static status_t bluetoothSco(bool state); static status_t isBluetoothScoOn(bool* state); static status_t muteMicrophone(bool state); static status_t isMicrophoneMuted(bool *state); static status_t setMasterVolume(float value); static status_t setMasterMute(bool mute); static status_t getMasterVolume(float* volume); static status_t getMasterMute(bool* mute); static status_t setStreamVolume(int stream, float value); static status_t setStreamMute(int stream, bool mute); static status_t getStreamVolume(int stream, float* volume); static status_t getStreamMute(int stream, bool* mute); static status_t setMode(int mode); static status_t getMode(int* mode); static status_t setRouting(int mode, uint32_t routes, uint32_t mask); static status_t getRouting(int mode, uint32_t* routes); static status_t isMusicActive(bool *state); static status_t setParameter(const char* key, const char* value); static void setErrorCallback(audio_error_callback cb); static const sp& get_audio_flinger(); static float linearToLog(int volume); static int logToLinear(float volume); static status_t getOutputSamplingRate(int* samplingRate, int stream = DEFAULT); static status_t getOutputFrameCount(int* frameCount, int stream = DEFAULT); static status_t getOutputLatency(uint32_t* latency, int stream = DEFAULT); static bool routedToA2dpOutput(int streamType); static status_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount, size_t* buffSize); }; 因为上述枚举值是通过单独的位来表示 audio_routes 的,而不是用顺序的枚举值来表示,所以 在使用这个值的过程中可以使用“或”的方式。这说明声音既可以从耳机(EARPIECE)输出,也 可以从扬声器(SPEAKER)输出。此功能是否能够实现,是由下层提供支持的。在这个类中,set/get 等接口控制了 Audio 声音大小、Audio 模式和路径等内容。 AudioTrack 是 Audio 输出环节的类,在里面包含了最重要的 write()接口,主要代码如下所示。

class AudioTrack { typedef void (*callback_t)(int event, void* user, void *info); AudioTrack( int streamType, uint32_t sampleRate = 0, // 音频的采样律 int format = 0, //音频的格式(例如 8 位或者 16 位的 PCM) int channelCount = 0, // 音频的通道数 int frameCount = 0, // 音频的帧数 uint32_t flags = 0, callback_t cbf = 0,

290 第 10 章 音频系统驱动

void* user = 0, int notificationFrames = 0); void start(); void stop(); void flush(); void pause(); void mute(bool); ssize_t write(const void* buffer, size_t size); ……………………………………………………………

类 AudioRecord 的功能是实现和 Audio 输入相关的功能,其核心功能是通过接口函数 read()实 现的,主要代码如下所示。

class AudioRecord { public:  AudioRecord(int streamType,  uint32_t sampleRate = 0, // 音频的采样律  int format = 0, // 音频的格式(例如 8 位或者 16 位的 PCM)  int channelCount = 0, // 音频的通道数  int frameCount = 0, // 音频的帧数  uint32_t flags = 0,  callback_t cbf = 0,  void* user = 0,  int notificationFrames = 0);  status_t start();  status_t stop();  ssize_t read(void* buffer, size_t size);

在类 AudioTrack 和 AudioRecord 中,函数 read()和 write()的参数都是内存的指针及其大小,内 存中的内容一般表示的是 Audio 的原始数据(PCM 数据)。这两个类还涉及 Auido 数据格式、通道 数、帧数目等参数,不但可以在建立时指定,也可以在建立之后使用 set()函数进行设置。 另外,在 libmedia 库中提供的只是一个 Audio 系统框架,其中类 AudioSystem、AudioTrack 和 AudioRecord 分别调用下层的接口 IAudioFlinger、IAudioTrack 和 IAudioRecord 来实现。另外的一 个接口是 IAudioFlingerClient,它作为向 IAudioFlinger 中注册的监听器,相当于使用回调函数获取 IAudioFlinger 运行时信息。

10.2.3 本地代码 在 Android 系统中,AudioFlinger 是 Audio 音频系统的中间层,能够作为 libmedia 提供的 Audio 部分接口的实现。这部分本地代码的路径是“frameworks/base/libs/audioflinger”。 文件 AudioFlinger.h 和 AudioFlinger.cpp 是实现 AudioFlinger 的核心文件,在里面提供了 IAudioFlinger 实现类 AudioFlinger,其接口代码如下所示。

class AudioFlinger : public BnAudioFlinger, public IBinder::DeathRecipient {

291 Android 驱动开发与移植实战详解

public: static void instantiate(); virtual status_t dump(int fd, const Vector& args); virtual sp createTrack( // 获得音频输出接口(Track) pid_t pid, int streamType, uint32_t sampleRate, int format, int channelCount, int frameCount, uint32_t flags, const sp& sharedBuffer, status_t *status); virtual uint32_t sampleRate(int output) const; virtual int channelCount(int output) const; virtual int format(int output) const; virtual size_t frameCount(int output) const; virtual uint32_t latency(int output) const; virtual status_t setMasterVolume(float value); virtual status_t setMasterMute(bool muted); virtual status_t setStreamVolume(int stream, float value); virtual status_t setStreamMute(int stream, bool muted); virtual status_t setRouting(int mode, uint32_t routes, uint32_t mask); virtual uint32_t getRouting(int mode) const; virtual status_t setMode(int mode); virtual int getMode() const; virtual sp openRecord( // 获得音频输出接口(Record) pid_t pid, int streamType, uint32_t sampleRate, int format, int channelCount, int frameCount, uint32_t flags, status_t *status);

由上述代码可以看出,AudioFlinger 使用函数 createTrack()创建音频的输出设备 IAudioTrack, 使用函数 openRecord()创建音频的输入设备 IaudioRecord,并且还使用接口 get/set 实现控制功能。 定义构造函数 AudioFlinger(),在初始化 AudioFlinger 之后会首先获得放音设备,然后为混音 器(Mixer)建立线程并建立放音设备线程,最后在线程中获得放音设备。构造函数 AudioFlinger() 的实现代码如下所示。

AudioFlinger::AudioFlinger() { mHardwareStatus = AUDIO_HW_IDLE; mAudioHardware = AudioHardwareInterface::create(); mHardwareStatus = AUDIO_HW_INIT;

292 第 10 章 音频系统驱动

if (mAudioHardware->initCheck() == NO_ERROR) { mHardwareStatus = AUDIO_HW_OUTPUT_OPEN; status_t status; AudioStreamOut *hwOutput = mAudioHardware->openOutputStream (AudioSystem::PCM_16_BIT, 0, 0, &status); mHardwareStatus = AUDIO_HW_IDLE; if (hwOutput) { mHardwareMixerThread = new MixerThread(this, hwOutput, AudioSystem::AUDIO_OUTPUT_HARDWARE); } else { LOGE("Failed to initialize hardware output stream, status: %d", status); } #ifdef WITH_A2DP mA2dpAudioInterface = new A2dpAudioInterface(); AudioStreamOut *a2dpOutput = mA2dpAudioInterface->openOutputStream (AudioSystem::PCM_16_BIT, 0, 0, &status); if (a2dpOutput) { mA2dpMixerThread = new MixerThread(this, a2dpOutput, AudioSystem::AUDIO_ OUTPUT_A2DP); if (hwOutput) { uint32_t frameCount = ((a2dpOutput->bufferSize()/a2dpOutput->frameSize()) * hwOutput->sampleRate()) / a2dpOutput->sampleRate(); MixerThread::OutputTrack *a2dpOutTrack = new MixerThread::OutputTrack (mA2dpMixerThread, hwOutput->sampleRate(), AudioSystem::PCM_16_BIT, hwOutput->channelCount(), frameCount); mHardwareMixerThread->setOuputTrack(a2dpOutTrack); } } else { LOGE("Failed to initialize A2DP output stream, status: %d", status); } #endif setRouting(AudioSystem::MODE_NORMAL, AudioSystem::ROUTE_SPEAKER, AudioSystem:: ROUTE_ALL); setRouting(AudioSystem::MODE_RINGTONE, AudioSystem::ROUTE_SPEAKER, AudioSystem:: ROUTE_ALL); setRouting(AudioSystem::MODE_IN_CALL, AudioSystem::ROUTE_EARPIECE, AudioSystem:: ROUTE_ALL); setMode(AudioSystem::MODE_NORMAL); setMasterVolume(1.0f); setMasterMute(false); mAudioRecordThread = new AudioRecordThread(mAudioHardware, this); if (mAudioRecordThread != 0) { mAudioRecordThread->run("AudioRecordThread", PRIORITY_URGENT_AUDIO); } } else { LOGE("Couldn't even initialize the stubbed audio hardware!"); }

293 Android 驱动开发与移植实战详解

}

在文件 AudioResampler.h 中定义音频重取样器的工具类 AudioResampler,定义此类的代码如下 所示。

class AudioResampler { public: enum src_quality { DEFAULT=0, LOW_QUALITY=1, // 线性差值算法 MED_QUALITY=2, // 立方差值算法 HIGH_QUALITY=3 // fixed multi-tap FIR 算法 }; static AudioResampler* create(int bitDepth, int inChannelCount, // 静态地创建函数 int32_t sampleRate, int quality=DEFAULT); virtual ~AudioResampler(); virtual void init() = 0; virtual void setSampleRate(int32_t inSampleRate); // 设置重采样率 virtual void setVolume(int16_t left, int16_t right); // 设置音量 virtual void resample(int32_t* out, size_t outFrameCount, AudioBufferProvider* provider) = 0; };

在上述音频重取样工具类中包含如下 3 种质量。 (1)低等质量(LOW_QUALITY):使用线性差值算法实现。 (2)中等质量(MED_QUALITY):使用立方差值算法实现。 (3)高等质量(HIGH_ QUALITY):使用 FIR(有限阶滤波器)实现。 在 AudioResampler 中,AudioResamplerOrder1 是线性实现,AudioResamplerCubic.*文件提供立 方实现方式,AudioResamplerSinc.*提供 FIR 实现。 通过文件 AudioMixer.h 和 AudioMixer.cpp 实现了一个 Audio 系统混音器,它被 AudioFlinger 调用,一般用于在声音输出之前的处理,提供多通道处理、声音缩放、重取样。AudioMixer 调用 了 AudioResampler。

10.2.4 JNI 代码 在 Audio 系统中通过 JNI 向 Java 层提供功能强大的接口,这样就可以在 Java 层通过 JNI 接口 完成 Audio 系统的大部分操作。 Audio JNI 的实现代码保存在“frameworks/base/core/jni”目录下,在此目录中主要包含了如下 3 个核心文件,这三个核心文件分别对应了 Android Java 框架中的 3 个类的支持。 (1)android.media.AudioSystem:负责 Audio 系统的总体控制。 (2)android.media.AudioTrack:负责 Audio 系统的输出环节。 (3)android.media.AudioRecorder:负责 Audio 系统的输入环节。

294 第 10 章 音频系统驱动

在 Java 层中可以对 Audio 系统进行控制和数据流操作,其中控制操作和底层的处理基本一致。 对于数据流操作来说,因为 Java 不支持指针,所以接口被封装成了另外的形式。例如在音频输出 功能中,通过文件 android_media_AudioTrack.cpp 提供了写字节和写短整型的接口类型。对应代码 如下所示。

static jint android_media_AudioTrack_native_ write(JNIEnv *env, jobject thiz, jbyteArray javaAudioData, jint offsetInBytes, jint sizeInBytes, jint javaAudioFormat) { jbyte* cAudioData = NULL; AudioTrack *lpTrack = NULL; lpTrack = (AudioTrack *)env->GetIntField( thiz, javaAudioTrackFields. Native TrackInJavaObj); ssize_t written = 0; if (lpTrack->sharedBuffer() == 0) { //进行写操作 written = lpTrack->write(cAudioData + offsetInBytes, sizeInBytes); } else { if (javaAudioFormat == javaAudioTrackFields.PCM16) { memcpy(lpTrack->sharedBuffer()->pointer(), cAudioData+offsetInBytes, sizeInBytes); written = sizeInBytes; } else if (javaAudioFormat == javaAudioTrackFields.PCM8) { int count = sizeInBytes; int16_t *dst = (int16_t *)lpTrack->sharedBuffer()->pointer(); const int8_t *src = (const int8_t *) (cAudioData + offsetInBytes); while(count--) { *dst++ = (int16_t)(*src++^0x80) << 8; } written = sizeInBytes; } } env->ReleasePrimitiveArrayCritical(javaAudioData, cAudioData, 0); return (int)written; }

10.2.5 Java 代码 在 Android 的 Audio 系统中,和 Java 相关的类定义在 android.media 包中,Java 部分的代码保 存在“frameworks/base/media/java/android/media”目录中,在里面主要实现了下面的类。 (1)android.media.AudioSystem。 (2)android.media. Audio Track。 (3)android.media.AudioRecorder。 (4)android.media.AudioFormat。

295 Android 驱动开发与移植实战详解

其中前 3 个类和本地代码是对应的,在 AudioFormat 中提供了一些和 Audio 相关的枚举值。在 此需要注意的是,在 Audio 系统的 Java 代码中,虽然可以通过 AudioTrack 和 AudioRecorder 的 write() 和 read()接口在 Java 层对 Audio 的数据流进行操作,但是,更多的时候并不需要这样做,而是在本 地代码中直接调用接口进行数据流的输入/输出,而 Java 层只进行控制类操作,不处理数据流。

10.3 移植工作

Audio 的标准化部分是硬件抽象层的接口,所以针对不同的特定平台,需要移植 Audio 驱动程 序和 Audio 硬件抽象层。

10.3.1 两个任务 (1)移植 Audio 驱动程序:此部分需要在 Linux 内核中实现,虽然有很多实现方式,但是大多 数 Audio 驱动程序都需要提供用于音量控制等功能的控制类接口,通过这些接口实现 PCM 输入、 输出的数据类接口。 (2)移植 Audio 硬件抽象层:这是 Audio 驱动程序和 Audio 本地框架类 AudioFlinger 的接口。 根据 Android 系统对接口的定义,Audio 硬件抽象层是 C++类的接口,需要在继承接口中定义三个 类来实现 Audio 硬件抽象层,这三个类分别实现总控、输入和输出功能。 要想实现一个 Android 的硬件抽象层,则需要实现 AudioHardwareInterface、AudioStream Out 和 AudioStreamIn 这三个类,并将代码编译成动态库 libauido.so。AudioFlinger 会连接这个动态库, 并调用其中的 createAudioHardware()函数来获取接口。 在文件 AudioHardwareBase.h 中定义了类 AudioHardwareBase ,此类继承了 Audio HardwareInterface,通过继承此接口也可以实现 Audio 的硬件抽象层。 Android 系统的 Audio 硬件抽象层可以通过继承类 AudioHardwareInterface 来实现,其中分为控 制部分和输入/输出处理部分。

10.3.2 Audio 的硬件抽象层 Audio 系统的硬件抽象层是 AudioFlinger 和 Audio 硬件之间的接口,在不同系统的移植过程中 可以有不同的实现方式。其中 Audio 硬件抽象层的接口路径是“hardware/libhardware_legacy/ include/hardware/”,此路径的核心文件是 AudioHardwareBase.h 和 AudioHardwareInterface.h。 作为 Android 系统的 Audio 硬件抽象层,既可以基于 Linux 标准的 ALSA 或 OSS 音频驱动来 实现,也可以基于私有的 Audio 驱动接口来实现。 在文件 AudioHardwareInterface.h 中定义了三个类,分别是 AudioStreamOut、AudioStreamIn 和 AudioHardwareInterface。类 AudioStreamOut 和 AudioStreamIn 分别描述了音频输出设备和音频输入 设备,其中负责数据流的接口分别是函数 write()和 read(),参数是一块内存的指针和长度,另外还 有一些设置和获取接口。类 AudioStreamOut 和 AudioStreamIn 的实现代码如下所示。

class AudioStreamOut { public:

296 第 10 章 音频系统驱动

virtual ~AudioStreamOut() = 0; virtual status_t setVolume(float volume) = 0; virtual ssize_t write(const void* buffer, size_t bytes) = 0; virtual int channelCount() const = 0; virtual int format() const = 0; virtual status_t setVolume(float volume) = 0; virtual ssize_t write(const void* buffer, size_t bytes) = 0; virtual status_t dump(int fd, const Vector& args) = 0; }; class AudioStreamIn { public: virtual ~AudioStreamIn() = 0; virtual status_t setGain(float gain) = 0; virtual ssize_t read(void* buffer, ssize_t bytes) = 0; virtual int channelCount() const = 0; virtual int format() const = 0; virtual status_t setGain(float gain) = 0; virtual ssize_t read(void* buffer, ssize_t bytes) = 0; virtual status_t dump(int fd, const Vector& args) = 0; };

在上述代码中,类 AudioStreamOut 和 AudioStreamIn 是两个对应的接口类,分别实现了输出和 输入环节。类 AudioStreamOut 和 AudioStreamIn 都需要通过 Audio 硬件抽象层的核心 AudioHardwareInterface 接口类来获取。实现接口类 AudioHardwareInterface 的代码如下所示。

class AudioHardwareInterface { public: AudioHardwareInterface(); virtual ~AudioHardwareInterface() { } virtual status_t initCheck() = 0; virtual status_t standby() = 0; virtual status_t setVoiceVolume(float volume) = 0; virtual status_t setMasterVolume(float volume) = 0; virtual status_t setRouting(int mode, uint32_t routes); virtual status_t getRouting(int mode, uint32_t* routes); virtual status_t setMode(int mode); virtual status_t getMode(int* mode); virtual status_t setMicMute(bool state) = 0; virtual status_t getMicMute(bool* state) = 0; virtual status_t setParameter(const char* key, const char* value); virtual AudioStreamOut* openOutputStream( // 打开输出流 int format=0, int channelCount=0, uint32_t sampleRate=0) = 0; virtual AudioStreamIn* openInputStream( // 打开输入流 int format, int channelCount, uint32_t sampleRate) = 0; virtual status_t dumpState(int fd, const Vector& args); static AudioHardwareInterface* create();

297 Android 驱动开发与移植实战详解

在上述 AudioHardwareInterface 接口中,分别使用函数 openOutputStream()和 openInputStream() 获取 AudioStreamOut 和 AudioStreamIn 这两个类,将它们分别作为音频输入设备和输出设备来使用。 在文件 AudioHardwareInterface.h 中还定义了 C 语言的接口来获取一个 AudioHardware Interface 类型的指针,定义代码如下所示。

extern "C" AudioHardwareInterface* createAudioHardware(void);

10.3.3 实现 AudioFlinger 中的 Audio 硬件抽象层 在 Android 的 AudioFlinger 中,可以通过编译宏的方式来选择到底用哪一个 Audio 硬件抽象层。 可选择的 Audio 硬件抽象层既可以作为参考设计,也可以在没有实际的 Audio 硬件抽象层时使用, 目的是保证系统的正常运行。

1.编译文件

文件 Android.mk 是 AudioFlinger 的编译文件,定义代码如下所示。

ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true) LOCAL_STATIC_LIBRARIES += libaudiointerface else LOCAL_SHARED_LIBRARIES += libaudio endif LOCAL_MODULE:= libaudioflinger include $(BUILD_SHARED_LIBRARY)

在上述代码中,当 BOARD_USES_GENERIC_AUDIO 为 True 时连接 libaudiointerface.a 静态库, 当 BOARD_USES_GENERIC_AUDIO 为 False 时连接 libaudiointerface.so 动态库,在大多数的情况 下使用后者。 另外,在 Android.mk 中也生成了 libaudiointerface.a,具体代码如下所示。

include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ AudioHardwareGeneric.cpp \ AudioHardwareStub.cpp \ AudioDumpInterface.cpp \ AudioHardwareInterface.cpp LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libmedia \ libhardware_legacy ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true) LOCAL_CFLAGS += -DGENERIC_AUDIO endif LOCAL_MODULE:= libaudiointerface include $(BUILD_STATIC_LIBRARY)

在上述代码中,分别编译如下 4 个源文件来生成 libaudiointerface.a 静态库。

298 第 10 章 音频系统驱动

(1)AudioHardwareInterface.cpp:用于实现基础类和管理。 (2)AudioHardwareGeneric.cpp:实现基于特定驱动的通用 Audio 硬件抽象层。 (3)AudioHardwareStub.cpp:实现 Audio 硬件抽象层的一个桩。 (4)AudioDumpInterface.cpp:实现输出到文件的 Audio 硬件抽象层。 在文件 AudioHardwareInterface.cpp 中定义了 AudioHardwareInterface::create(),此函数是 Audio 硬件抽象层的创建函数,主要代码如下所示。

AudioHardwareInterface* AudioHardwareInterface::create() { AudioHardwareInterface* hw = 0; char value[PROPERTY_VALUE_MAX]; #ifdef GENERIC_AUDIO hw = new AudioHardwareGeneric(); // 此处用通用的 Audio 硬件抽象层 #else if (property_get("ro.kernel.qemu", value, 0)) { LOGD("Running in emulation - using generic audio driver"); hw = new AudioHardwareGeneric(); } else { LOGV("Creating Vendor Specific AudioHardware"); hw = createAudioHardware(); // 此处用实际的 Audio 硬件抽象层 } #endif if (hw->initCheck() != NO_ERROR) { LOGW("Using stubbed audio hardware.No sound will be produced."); delete hw; hw = new AudioHardwareStub(); // 此处用实际的 Audio 硬件抽象层的桩实现 } #ifdef DUMP_FLINGER_OUT hw = new AudioDumpInterface(hw); // 此处用实际的 Audio 的 Dump 接口实现 #endif return hw; } 2.桩方式实现 在文件 AudioHardwareStub.h 和 AudioHardwareStub.cpp 中,通过桩方式实现了 Android 硬件抽 象层。桩方式不操作实际的硬件和文件,只进行了空操作。当在系统没有实际的 Audio 设备时才使 用桩方式实现,目的是保证系统的正常工作。如果使用这个硬件抽象层,实际上 Audio 系统的输入 和输出都将为空。 在文件 AudioHardwareStub.h 中定义类 AudioStreamOutStub 和 AudioStreamInStub,功能是分 别实现输入和输出功能。主要代码如下所示。

class AudioStreamOutStub : public AudioStreamOut {

299 Android 驱动开发与移植实战详解

public: virtual status_t set(int format, int channelCount, uint32_t sampleRate); virtual uint32_t sampleRate() const { return 44100; } virtual size_t bufferSize() const { return 4096; } virtual int channelCount() const { return 2; } virtual int format() const { return AudioSystem::PCM_16_BIT; } virtual uint32_t latency() const { return 0; } virtual status_t setVolume(float volume) { return NO_ERROR; } virtual ssize_t write(const void* buffer, size_t bytes); virtual status_t standby(); virtual status_t dump(int fd, const Vector& args); }; class AudioStreamInStub : public AudioStreamIn { public: virtual status_t set(int format, int channelCount, uint32_t sampleRate, AudioSystem:: audio_in_acoustics acoustics); virtual uint32_t sampleRate() const { return 8000; } virtual size_t bufferSize() const { return 320; } virtual int channelCount() const { return 1; } virtual int format() const { return AudioSystem::PCM_16_BIT; } virtual status_t setGain(float gain) { return NO_ERROR; } virtual ssize_t read(void* buffer, ssize_t bytes); virtual status_t dump(int fd, const Vector& args); virtual status_t standby() { return NO_ERROR; } };

在上述代码中,只用缓冲区大小、采样率和通道数这三个固定的参数将一些函数直接无错误返回。 使用 AudioHardwareStub 类来继承 AudioHardwareBase,也就是继承 AudioHardwareInterface。 主要代码如下所示。

 class AudioHardwareStub : public AudioHardwareBase  {  public:  AudioHardwareStub();  virtual ~AudioHardwareStub();  virtual status_t initCheck();  virtual status_t setVoiceVolume(float volume);  virtual status_t setMasterVolume(float volume);  virtual status_t setMicMute(bool state) { mMicMute = state; return NO_ERROR; }  virtual status_t getMicMute(bool* state) { *state = mMicMute ; return NO_ERROR; }  virtual status_t setParameter(const char* key, const char* value)  { return NO_ERROR; }  virtual AudioStreamOut* openOutputStream( //打开输出流

300 第 10 章 音频系统驱动

 int format=0,  int channelCount=0,  uint32_t sampleRate=0,  status_t *status=0);   virtual AudioStreamIn* openInputStream( //打开输入流  int format,  int channelCount,  uint32_t sampleRate,  status_t *status,  AudioSystem::audio_in_acoustics acoustics); ……………………………………………

为了保证可以输入和输出声音,桩实现的主要内容是实现 AudioStreamOutStub 和 AudioStreamInStub 类的读/写函数。实现代码如下所示。

ssize_t AudioStreamOutStub::write(const void* buffer, size_t bytes) { usleep(bytes * 1000000 / sizeof(int16_t) / channelCount() / sampleRate()); return bytes; } ssize_t AudioStreamInStub::read(void* buffer, ssize_t bytes) { usleep(bytes * 1000000 / sizeof(int16_t) / channelCount() / sampleRate()); memset(buffer, 0, bytes); return bytes; }

当使用这个接口来输入和输出音频时,和真实的设备并没有任何关系,输出和输入都使用延时 来完成。在输出时不会播出声音,但是返回值表示全部内容已经输出完成;在输入时会返回全部为 0 的数据。

3.通用 Audio 硬件抽象层

在 Android 系统中,文件 AudioHardwareGeneric.h 和 AudioHardwareGeneric.cpp 实现了通用的 Audio 硬件抽象层。与前面介绍的桩实现方式不同,这是一个真正能够使用的 Audio 硬件抽象层, 但是它需要 Android 的一种特殊的声音驱动程序的支持。 在通用硬件抽象层中,类 AudioStreamOutGeneric、AudioStreamInGeneric 和 AudioHardwareGeneric 分别继承 Audio 硬件抽象层的 3 个接口。对应代码如下所示。

class AudioStreamOutGeneric : public AudioStreamOut { // ...... 通用 Audio 输出类的接口 }; class AudioStreamInGeneric : public AudioStreamIn { // ...... 通用 Audio 输入类的接口 };

301 Android 驱动开发与移植实战详解

class AudioHardwareGeneric : public AudioHardwareBase { // ...... 通用 Audio 控制类的接口 };

在文件 AudioHardwareGeneric.cpp 中使用的驱动程序是“/dev/eac”,这是一个非标准程序,定 义设备路径的代码如下所示。

static char const * const kAudioDeviceName = "/dev/eac";

eac 是 Linux 中的一个 misc 驱动程序,作为 Android 的通用音频驱动,写设备表 注意 示放音,读设备表示录音。

在 Linux 操作系统中,“/dev/eac”驱动程序在文件系统中的节点主设备号为 10,是次设备号自 动生成的。通过构造函数 AudioHardwareGeneric()可以打开这个驱动程序的设备节点。对应代码如 下所示。

AudioHardwareGeneric::AudioHardwareGeneric() : mOutput(0), mInput(0), mFd(-1), mMicMute(false) { mFd = ::open(kAudioDeviceName, O_RDWR); //打开通用音频设备的节点 }

此音频设备是一个比较简单的驱动程序,在里面并没有很多设置接口,只是用写设备来表示录 音,用读设备来表示放音。放音和录音支持的都是 16 位的 PCM。对应代码如下所示。

ssize_t AudioStreamOutGeneric::write(const void* buffer, size_t bytes) { Mutex::Autolock _l(mLock); return ssize_t(::write(mFd, buffer, bytes)); //写入硬件设备 } ssize_t AudioStreamInGeneric::read(void* buffer, ssize_t bytes) { AutoMutex lock(mLock); if (mFd < 0) { return NO_INIT; } return ::read(mFd, buffer, bytes); // 读取硬件设备 }

尽管 AudioHardwareGeneric 是一个可以真正工作的 Audio 硬件抽象层,但是这种实现方式非 常简单,不支持各种设置,参数也只能使用默认的。而且,这种驱动程序需要在 Linux 核心加入 eac 驱动程序的支持。

4.具备 Dump 功能的 Audio 硬件抽象层

在文件 AudioDumpInterface.h 和 AudioDumpInterface.cpp 中提供了 Dump 功能的 Audio 硬件抽 象层,功能是将输出的 Audio 数据写入到文件中。其实 AudioDumpInterface 本身支持 Audio 输出功

302 第 10 章 音频系统驱动

能,但是不支持输入功能。在文件 AudioDumpInterface.h 中定义类的代码如下所示。

class AudioStreamOutDump : public AudioStreamOut { public: AudioStreamOutDump( AudioStreamOut* FinalStream); ~AudioStreamOutDump(); virtual ssize_t write(const void* buffer, size_t bytes); virtual uint32_t sampleRate() const { return mFinalStream->sampleRate(); } virtual size_t bufferSize() const { return mFinalStream->bufferSize(); } virtual int channelCount() const { return mFinalStream->channelCount(); } virtual int format() const { return mFinalStream->format(); } virtual uint32_t latency() const { return mFinalStream->latency(); } virtual status_t setVolume(float volume) { return mFinalStream->setVolume(volume); } virtual status_t standby(); }; class AudioDumpInterface : public AudioHardwareBase { virtual AudioStreamOut* openOutputStream( int format=0, int channelCount=0, uint32_t sampleRate=0, status_t *status=0); }

上述代码中只是实现了 AudioStreamOut 输出,并没有实现 AudioStreamIn 输入。此 Audio 硬件 抽象层只支持输出功能,不支持输入功能。其中定义输出文件名称的格式如下所示。

#define FLINGER_DUMP_NAME "/data/FlingerOut.pcm"

在文件 AudioDumpInterface.cpp 中通过函数 AudioStreamOut()实现写操作,写入的对象就是这 个文件。对应代码如下所示。

ssize_t AudioStreamOutDump::write(const void* buffer, size_t bytes) { ssize_t ret; ret = mFinalStream->write(buffer, bytes); if(!mOutFile && gFirst) { gFirst = false; mOutFile = fopen(FLINGER_DUMP_NAME, "r"); if(mOutFile) { fclose(mOutFile); mOutFile = fopen(FLINGER_DUMP_NAME, "ab"); // 打开输出文件 } } if (mOutFile) { fwrite(buffer, bytes, 1, mOutFile); // 写文件输出内容

303 Android 驱动开发与移植实战详解

} return ret; }

如果文件是打开的,则可以使用追加方式写入。所以当使用这个 Audio 硬件抽象层时,播放的 内容(PCM)将全部被写入文件。而且这个类支持各种格式的输出,这取决于调用者的设置。 使用 AudioDumpInterface 的目的并不是为了实际的应用,而是为了调试我们使用的类。当使用 播放器调试音频时,有时无法确认是解码器的问题还是 Audio 输出单元的问题,这时就可以用这个 类来替换实际的 Audio 硬件抽象层,将解码器输出的 Audio 的 PCM 数据写入文件中,由此可以判 断解码器的输出是否正确。

10.3.4 真正实现 Audio 硬件抽象层 想要实现一个真正的 Audio 硬件抽象层,需要完成和前面实现硬件抽象层类似的工作。例如可 以基于 Linux 标准的音频驱动 OSS(Open Sound System)或 ALSA(Advanced Linux Sound Architecture)驱动程序来实现。 (1)基于 OSS 驱动程序实现。 对于 OSS 驱动程序来说,实现方式和前面的 AudioHardwareGeneric 方式类似,数据流的读/写 操作通过对/dev/dsp 设备的读/写来完成;区别在于 OSS 支持了更多的 ioctl 来进行设置,还涉及通 过/dev/mixer 设备进行控制,并支持更多不同的参数。 (2)ALSA 驱动程序。 对于 ALSA 驱动程序来说,实现方式一般不是直接调用驱动程序的设备节点,而是先实现用 户空间的 alsa-lib,然后 Audio 硬件抽象层通过调用 alsa-lib 来实现。 在实现 Audio 硬件抽象层时,如果系统中有多个 Audio 设备,此时可由硬件抽象层自行处理 setRouting()函数设定。例如可以选择支持多个设备的同时输出,或者有优先级输出。对于这种情况, 数据流一般来自函数 AudioStreamOut::write(),可由硬件抽象层确定输出方法。对于某种特殊的情 况,也有可能采用硬件直接连接的方式,此时数据流可能并不来自上面的 write(),这样就没有数据 通道,只有控制接口。Audio 硬件抽象层也是可以处理这种情况的。

10.4 MSM 平台实现 Audio 驱动系统

了解了 Audio 驱动程序的基本知识后,在本节内容中将讲解 MSM 平台中 Audio 系统的实现方 法,为读者步入本书后面知识的学习打下基础。

10.4.1 实现 Audio 驱动程序 在 MSM 平台中,Audio 驱动程序被保存在“arch/arm/mach-msm/qdspX”目录中。如果是版本 为 5 的 DSP 处理器,其驱动目录为“qdsp5”;如果是版本为 6 的 DSP 处理器,其驱动目录为“qdsp6”。 Audio 驱动程序的头文件通常是“/include/linux/msm_mdp.h”,而“qdsp6”的特定头文件是 “arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h”。在 Audio 系统中涉及的头文件如下所示。

304 第 10 章 音频系统驱动

(1)audio_ctl.c:音频控制文件,生成设备的节点是“dev/msm_audio_ctl”。 (2)routing.c:控制音频路径,生成设备的节点是“dev/msm_audio_route”。 (3)pcm_in.c:PCM 输入通道,生成设备的节点是“dev/msm_pcm_out”。 (4)mp3.c:MP3 码流直接输出通道,生成设备的节点是“dev/msm_mp3”。 在 MSM 平台中,Audio 驱动程序并不是主流的标准驱动程序,在用户空间中包括了两个控制 节点和三个数据节点。其中两个控制节点用于控制 Audio 的基本内容和路径,而三个数据节点包括 PCM 输出、PCM 输入和 MP3 码流输出。 在文件“include/linux/msm_audio.h”中定义了 Audio 系统的 ioctl 控制命令,具体代码如下所示。

#define AUDIO_IOCTL_MAGIC 'a' #define AUDIO_START _IOW(AUDIO_IOCTL_MA #define AUDIO_STOP _IOW(AUDIO_IOCTL_MA #define AUDIO_FLUSH _IOW(AUDIO_IOCTL_MA #define AUDIO_GET_CONFIG _IOR(AUDIO_IOCTL_MA #define AUDIO_SET_CONFIG _IOW(AUDIO_IOCTL_MA #define AUDIO_GET_STATS _IOR(AUDIO_IOCTL_MA #define AUDIO_ENABLE_AUDPP _IOW(AUDIO_IOCTL_MA #define AUDIO_SET_ADRC _IOW(AUDIO_IOCTL_MA #define AUDIO_SET_EQ _IOW(AUDIO_IOCTL_MA #define AUDIO_SET_RX_IIR _IOW(AUDIO_IOCTL_MA #define AUDIO_SET_VOLUME _IOW(AUDIO_IOCTL_MA #define AUDIO_ENABLE_AUDPRE _IOW(AUDIO_IOCTL_ #define AUDIO_SET_AGC _IOW(AUDIO_IOCTL_ #define AUDIO_SET_NS _IOW(AUDIO_IOCTL_ #define AUDIO_SET_TX_IIR _IOW(AUDIO_IOCTL_ #define AUDIO_PAUSE _IOW(AUDIO_IOCTL_ #define AUDIO_GET_PCM_CONFIG _IOR(AUDIO_IOCTL_ #define AUDIO_SET_PCM_CONFIG _IOW(AUDIO_IOCTL_ #define AUDIO_SWITCH_DEVICE _IOW(AUDIO_IOCTL_ #define AUDIO_MAX_COMMON_IOCTL_NUM 100 #define AUDIO_MAX_COMMON_IOCTL_NUM 100

10.4.2 实现硬件抽象层 MSM 的硬件抽象层已经包含在 Android 的开放源码中,这些都是通用的代码。这些代码被保 存在 “hardware/msm7k” 目录中,使用 MSM7K 处理器实现 libaudio,通过 QSD8K 处理器实现 libaudio-qsd8k。其中在“libaudio-qsd8k”目录中主要包含如下文件。 (1)AudioHardware.cpp:实现了 Audio 硬件抽象层。 (2)AudioHardware.h:定义了 Audio 硬件抽象层的类。 (3)AudioPolicyManager.cpp:实现了 Audio 策略管理。 (4)AudioPolicyManager.h:定义了 Audio 策略管理类。 (5)msm_audio.h:实现了和内核相同的 ioctl 命令和数据结构的定义。 在文件 AudioHardware.h 中定义了三个类,分别是 AudioHardware、class AudioStreamOutMSM72xx 和 AudioStreamInMSM72xx,这三个类都继承自其他类,分别实现 Audio 系统的总控、输出和输入

305 Android 驱动开发与移植实战详解

环节的功能。具体代码如下所示。

class AudioHardware : public AudioHardwareBase { class AudioStreamOutMSM72xx; class AudioStreamInMSM72xx; public: AudioHardware(); virtual ~AudioHardware(); virtual status_t initCheck(); class AudioStreamOutMSM72xx : public AudioStreamOut { public: AudioStreamOutMSM72xx(); virtual ~AudioStreamOutMSM72xx(); status_t set(AudioHardware* mHardware, uint32_t devices, int *pFormat, uint32_t *pChannels, uint32_t *pRate); ………………………………… private: AudioHardware* mHardware; int mFd; int mStartCount; int mRetryCount; bool mStandby; uint32_t mDevices; }; class AudioStreamInMSM72xx : public AudioStreamIn { public: enum input_state { AUDIO_INPUT_CLOSED, AUDIO_INPUT_OPENED, AUDIO_INPUT_STARTED }; …………………………………

类 AudioStreamOutMSM72xx 的核心功能是通过函数 write()实现的,此函数先打开 PCM 输出 设备,然后设置配置,并通过 ioctl 命令开始 Audio 处理流,并读、写操作文件。函数 write()的实 现代码如下所示。

ssize_t AudioHardware::AudioStreamOutMSM72xx::write(const void* buffer, size_t bytes) { status_t status = NO_INIT; size_t count = bytes; const uint8_t* p = static_cast(buffer); if (mStandby) { LOGV("open pcm_out driver"); status = ::open("/dev/msm_pcm_out", O_RDWR);//打开驱动程序 if (status < 0) {

306 第 10 章 音频系统驱动

if (errCount++ < 10) { LOGE("Cannot open /dev/msm_pcm_out errno: %d", errno); } goto Error; } mFd = status; LOGV("get config"); struct msm_audio_config config; status = ioctl(mFd, AUDIO_GET_CONFIG, &config);//获取配置 if (status < 0) { LOGE("Cannot read pcm_out config"); goto Error; } LOGV("set pcm_out config"); config.channel_count = AudioSystem::popCount(channels()); config.sample_rate = sampleRate(); config.buffer_size = bufferSize(); config.buffer_count = AUDIO_HW_NUM_OUT_BUF; config.codec_type = CODEC_TYPE_PCM; status = ioctl(mFd, AUDIO_SET_CONFIG, &config);//开始进行配置 if (status < 0) { LOGE("Cannot set config"); goto Error; } LOGV("buffer_size: %u", config.buffer_size); LOGV("buffer_count: %u", config.buffer_count); LOGV("channel_count: %u", config.channel_count); LOGV("sample_rate: %u", config.sample_rate); uint32_t acdb_id = mHardware->getACDB(MOD_PLAY, mHardware->get_snd_dev()); status = ioctl(mFd, AUDIO_START, &acdb_id);//开始 Audio if (status < 0) { LOGE("Cannot start pcm playback"); goto Error; } status = ioctl(mFd, AUDIO_SET_VOLUME, &stream_volume);//设置音量 if (status < 0) { LOGE("Cannot start pcm playback"); goto Error; } LOGV("acquire wakelock"); acquire_wake_lock(PARTIAL_WAKE_LOCK, kOutputWakelockStr); mStandby = false; } while (count) { ssize_t written = ::write(mFd, p, count);//写操作 PCM 输出 if (written >= 0) {//计算剩余数据 count -= written; p += written; } else { if (errno != EAGAIN) return written;

307 Android 驱动开发与移植实战详解

mRetryCount++; LOGW("EAGAIN - retry"); } } return bytes; Error: if (mFd >= 0) { ::close(mFd); mFd = -1; } usleep(bytes * 1000000 / frameSize() / sampleRate()); return status; }

类 AudioStreamInMSM72xx 的核心功能是通过函数 read()实现的,此函数的实现代码如下所示。

ssize_t AudioHardware::AudioStreamInMSM72xx::read( void* buffer, ssize_t bytes) { LOGV("AudioStreamInMSM72xx::read(%p, %ld)", buffer, bytes); if (!mHardware) return -1; size_t count = bytes; uint8_t* p = static_cast(buffer); if (mState < AUDIO_INPUT_OPENED) { Mutex::Autolock lock(mHardware->mLock); if (set(mHardware, mDevices, &mFormat, &mChannels, &mSampleRate, mAcoustics) != NO_ERROR) { return -1; } } if (mState < AUDIO_INPUT_STARTED) { mHardware->set_mRecordState(1); if (support_a1026 == 1) { mHardware->doAudience_A1026_Control(mHardware->get_mMode(), 1, mHardware-> get_snd_dev()); } uint32_t acdb_id = mHardware->getACDB(MOD_REC, mHardware->get_snd_dev()); if (ioctl(mFd, AUDIO_START, &acdb_id)) { //开始 Audio 数据流 LOGE("Error starting record"); return -1; } LOGI("AUDIO_START: start kernel pcm_in driver."); mState = AUDIO_INPUT_STARTED; } while (count) { ssize_t bytesRead = ::read(mFd, buffer, count); //读操作 if (bytesRead >= 0) { //计算出还需要读取的数据量 count -= bytesRead; p += bytesRead; } else { if (errno != EAGAIN) return bytesRead; mRetryCount++;

308 第 10 章 音频系统驱动

LOGW("EAGAIN - retrying"); } } return bytes; }

在上述代码中,通过 mFd 打开 Audio 输入设备的描述,输入设备是“dev/msm/_pcm_in”。其实 在函数 set()中,已经对设备“dev/msm/_pcm_in”进行了基本配置。函数 set()的实现代码如下所示。

status_t AudioHardware::AudioStreamOutMSM72xx::set( AudioHardware* hw, uint32_t devices, int *pFormat, uint32_t *pChannels, uint32_t *pRate) { int lFormat = pFormat ? *pFormat : 0; uint32_t lChannels = pChannels ? *pChannels : 0; uint32_t lRate = pRate ? *pRate : 0; mHardware = hw; if (lFormat == 0) lFormat = format(); if (lChannels == 0) lChannels = channels(); if (lRate == 0) lRate = sampleRate(); if ((lFormat != format()) || (lChannels != channels()) || (lRate != sampleRate())) { if (pFormat) *pFormat = format(); if (pChannels) *pChannels = channels(); if (pRate) *pRate = sampleRate(); return BAD_VALUE; } if (pFormat) *pFormat = lFormat; if (pChannels) *pChannels = lChannels; if (pRate) *pRate = lRate; mDevices = devices; return NO_ERROR; }

10.5 OSS 平台实现 Audio 驱动系统

OSS(Open Sound System,开放声音系统)是 Unix 平台上一个统一的音频接口。在本节将简 要介绍在 OSS 上实现 Audio 系统的基本知识,为读者步入本书后面知识的学习打下基础。

10.5.1 OSS 驱动程序介绍 OSS 驱动是字符型设备,因为在 Unix 系统中所有的设备都被统一成文件,通过对文件的访问 方式(首先 open,然后 read/write,同时可以使用 ioctl 读取/设置参数,最后 close)来访问设备。 所以在 OSS 中主要包含以下的几种设备文件。 (1)“/dev/mixer”:访问声卡中内置的 mixer,调整音量大小,选择音源。 (2)“ /dev/sndstat”:测试声卡,执行 cat /dev/sndstat 会显示声卡驱动的信息。

309 Android 驱动开发与移植实战详解

(3)“/dev/dsp”、“/dev/dspW”和“/dev/audio”:读这个设备就相当于录音,写这个设备就相当 于放音。“/dev/dsp”与“ /dev/audio”之间的区别在于采样的编码不同,“/dev/audio”使用μ律编码, “/dev/dsp”使用 8-bit(无符号)线性编码,“/dev/dspW”使用 16-bit(有符号)线形编码。“/dev/audio” 主要是为了与 SunOS 兼容,所以尽量不要使用。 (4)“/dev/sequencer”:访问声卡内置的,或者连接在 MIDI 接口的 synthesizer。 在 Linux 系统中有如下三个和 OSS 相关的文件。

include/linux/sound.h sound/sound_core.c include/linux/soundcard.h

Linux 音频的输入/输出功能是通过“/dev/dsp”设备实现的,但对于这些声音信号的处理则是 通过“/dev/mixer”设备来完成的。查看文件 linux/soundcard.h 可以获取对 Mixer 文件操作所需要的 变量,在此文件中列出了如下的常用的变量。

SOUND_MIXER_WRITE_VOLUME = 0xc0044d00 SOUND_MIXER_WRITE_BASS = 0xc0044d01 SOUND_MIXER_WRITE_PCM = 0xc0044d04 SOUND_MIXER_WRITE_LINE = 0xc0044d06 SOUND_MIXER_WRITE_MIC = 0xc0044d07 SOUND_MIXER_WRITE_RECSRC = 0xc0044dff SOUND_MIXER_LINE = 7 SOUND_MASK_LINE = 64

上述变量名都是在 soundcard.h 中可以查到的,通过名称即可看出其用途,后面的赋值在该头 文件中则并不是这样定义的,而是通过调用一些函数返回出来的,应该是声卡上对应的地址。在应 用程序中可通过 ioctl(fd,cmd,arg)来对这些变量进行赋值。其中 fd 即为一个打开“/dev/mixer”的文 件指针,cmd 为上面所列的这些变量,arg 则是对这些变量进行操作所需赋给的结构体或变量。

10.5.2 mixer 在 OSS 中,mixer()是核心。Audio Mixer 函数缩进一组控制音频线到目标设备的函数,并可以 控制音量和其他效果。在这组 API 中,尽管只有十个函数和两个消息,但使用起来还是比较难。 在本节中,通过应用这些函数编写成 2 个应用程序,来展示它们的使用方法。而且,尽可能采用实 际应用中的用户界面,这样,更有可能被读者直接利用。 程序 1 光盘:\daima\10\MIXER_MUTE。 此程序运行后能够等效于 Windows Volume Control 的 Mute all 核选框,核心功能是通过文件 MUTEDLG.CPP 实现的,主要代码如下所示。

LONG CMuteDlg::OnMixerCtrlChange(UINT wParam, LONG lParam) { if ((HMIXER)wParam == m_hMixer && (DWORD)lParam == m_dwMuteControlID) { // The state of the master mute control has changed. Refresh it.

310 第 10 章 音频系统驱动

LONG lVal; if (this->amdGetMasterMuteValue(lVal)) m_bMute = lVal;

this->UpdateData(FALSE); }

return 0L; }

BOOL CMuteDlg::amdInitialize() { // get the number of mixer devices present in the system m_nNumMixers = ::mixerGetNumDevs();

m_hMixer = NULL; ::ZeroMemory(&m_mxcaps, sizeof(MIXERCAPS));

//打开第一台 mixer //音频搅拌器设备当前不存在 if (m_nNumMixers != 0) { if (::mixerOpen(&m_hMixer, 0, (DWORD)this->GetSafeHwnd(), NULL, MIXER_OBJECTF_MIXER | CALLBACK_WINDOW) != MMSYSERR_NOERROR) return FALSE;

if (::mixerGetDevCaps((UINT)m_hMixer, &m_mxcaps, sizeof(MIXERCAPS)) != MMSYSERR_NOERROR) return FALSE; }

return TRUE; }

BOOL CMuteDlg::amdUninitialize() { BOOL bSucc = TRUE;

if (m_hMixer != NULL) { bSucc = ::mixerClose(m_hMixer) == MMSYSERR_NOERROR; m_hMixer = NULL; }

return bSucc; }

311 Android 驱动开发与移植实战详解

BOOL CMuteDlg::amdGetMasterMuteControl() { m_strDstLineName.Empty(); m_strMuteControlName.Empty();

if (m_hMixer == NULL) return FALSE;

// 获取 dwLineID MIXERLINE mxl; mxl.cbStruct = sizeof(MIXERLINE); mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; if (::mixerGetLineInfo((HMIXEROBJ)m_hMixer, &mxl, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE) != MMSYSERR_NOERROR) return FALSE;

// 获取 dwControlID MIXERCONTROL mxc; MIXERLINECONTROLS mxlc; mxlc.cbStruct = sizeof(MIXERLINECONTROLS); mxlc.dwLineID = mxl.dwLineID; mxlc.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; mxlc.cControls = 1; mxlc.cbmxctrl = sizeof(MIXERCONTROL); mxlc.pamxctrl = &mxc; if (::mixerGetLineControls((HMIXEROBJ)m_hMixer, &mxlc, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR) return FALSE;

// 记录 dwControlID m_strDstLineName = mxl.szName; m_strMuteControlName = mxc.szName; m_dwMuteControlID = mxc.dwControlID;

return TRUE; }

BOOL CMuteDlg::amdGetMasterMuteValue(LONG &lVal) const { if (m_hMixer == NULL || m_strDstLineName.IsEmpty() || m_strMuteControlName.IsEmpty()) return FALSE;

312 第 10 章 音频系统驱动

MIXERCONTROLDETAILS_BOOLEAN mxcdMute; MIXERCONTROLDETAILS mxcd; mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); mxcd.dwControlID = m_dwMuteControlID; mxcd.cChannels = 1; mxcd.cMultipleItems = 0; mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); mxcd.paDetails = &mxcdMute; if (::mixerGetControlDetails((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) return FALSE;

lVal = mxcdMute.fValue;

return TRUE; }

BOOL CMuteDlg::amdSetMasterMuteValue(LONG lVal) const { if (m_hMixer == NULL || m_strDstLineName.IsEmpty() || m_strMuteControlName.IsEmpty()) return FALSE;

MIXERCONTROLDETAILS_BOOLEAN mxcdMute = { lVal }; MIXERCONTROLDETAILS mxcd; mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); mxcd.dwControlID = m_dwMuteControlID; mxcd.cChannels = 1; mxcd.cMultipleItems = 0; mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); mxcd.paDetails = &mxcdMute; if (::mixerSetControlDetails((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) return FALSE;

return TRUE; }

执行效果如图 10-2 所示。 程序 2 光盘:\daima\13\MIXER_VOLUME。 此程序运行后能够等效于 Windows Volume Control 的进度条,核心功能是通过文件 VOLUMEDLG.CPP 实现 ▲图 10-2 执行效果 1

313 Android 驱动开发与移植实战详解

的,主要代码如下所示。

LONG CVolumeDlg::OnMixerCtrlChange(UINT wParam, LONG lParam) { if ((HMIXER)wParam == m_hMixer && (DWORD)lParam == m_dwVolumeControlID) { // The state of the master volume control has changed. Refresh it. DWORD dwVal; if (this->amdGetMasterVolumeValue(dwVal)) m_ctrlSlider.SetPos(dwVal); }

return 0L; }

BOOL CVolumeDlg::amdInitialize() { //在当前系统得到搅拌器设备的数量 m_nNumMixers = ::mixerGetNumDevs();

m_hMixer = NULL; ::ZeroMemory(&m_mxcaps, sizeof(MIXERCAPS));

//打开第一台 mixer // A "mapper" for audio mixer devices does not currently exist. if (m_nNumMixers != 0) { if (::mixerOpen(&m_hMixer, 0, (DWORD)this->GetSafeHwnd(), NULL, MIXER_OBJECTF_MIXER | CALLBACK_WINDOW) != MMSYSERR_NOERROR) return FALSE; if (::mixerGetDevCaps((UINT)m_hMixer, &m_mxcaps, sizeof(MIXERCAPS)) != MMSYSERR_NOERROR) return FALSE; } return TRUE; }

BOOL CVolumeDlg::amdUninitialize() { BOOL bSucc = TRUE; if (m_hMixer != NULL) { bSucc = ::mixerClose(m_hMixer) == MMSYSERR_NOERROR; m_hMixer = NULL; }

314 第 10 章 音频系统驱动

return bSucc; }

BOOL CVolumeDlg::amdGetMasterVolumeControl() { m_strDstLineName.Empty(); m_strVolumeControlName.Empty();

if (m_hMixer == NULL) return FALSE;

// 获取 dwLineID MIXERLINE mxl; mxl.cbStruct = sizeof(MIXERLINE); mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; if (::mixerGetLineInfo((HMIXEROBJ)m_hMixer, &mxl, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE) != MMSYSERR_NOERROR) return FALSE;

// 获取 dwControlID MIXERCONTROL mxc; MIXERLINECONTROLS mxlc; mxlc.cbStruct = sizeof(MIXERLINECONTROLS); mxlc.dwLineID = mxl.dwLineID; mxlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; mxlc.cControls = 1; mxlc.cbmxctrl = sizeof(MIXERCONTROL); mxlc.pamxctrl = &mxc; if (::mixerGetLineControls((HMIXEROBJ)m_hMixer, &mxlc, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR) return FALSE;

//记录 dwControlID m_strDstLineName = mxl.szName; m_strVolumeControlName = mxc.szName; m_dwMinimum = mxc.Bounds.dwMinimum; m_dwMaximum = mxc.Bounds.dwMaximum; m_dwVolumeControlID = mxc.dwControlID;

return TRUE; }

BOOL CVolumeDlg::amdGetMasterVolumeValue(DWORD &dwVal) const {

315 Android 驱动开发与移植实战详解

if (m_hMixer == NULL || m_strDstLineName.IsEmpty() || m_strVolumeControlName.IsEmpty()) return FALSE;

MIXERCONTROLDETAILS_UNSIGNED mxcdVolume; MIXERCONTROLDETAILS mxcd; mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); mxcd.dwControlID = m_dwVolumeControlID; mxcd.cChannels = 1; mxcd.cMultipleItems = 0; mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); mxcd.paDetails = &mxcdVolume; if (::mixerGetControlDetails((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) return FALSE;

dwVal = mxcdVolume.dwValue;

return TRUE; }

BOOL CVolumeDlg::amdSetMasterVolumeValue(DWORD dwVal) const { if (m_hMixer == NULL || m_strDstLineName.IsEmpty() || m_strVolumeControlName.IsEmpty()) return FALSE; MIXERCONTROLDETAILS_UNSIGNED mxcdVolume = { dwVal }; MIXERCONTROLDETAILS mxcd; mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); mxcd.dwControlID = m_dwVolumeControlID; mxcd.cChannels = 1; mxcd.cMultipleItems = 0; mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); mxcd.paDetails = &mxcdVolume; if (::mixerSetControlDetails((HMIXEROBJ)m_hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR) return FALSE;

return TRUE; }

执行效果如图 10-3 所示。

316 第 10 章 音频系统驱动

▲图 10-3 执行效果 2

10.6 ALSA 平台实现 Audio 系统

ALSA 是 Advanced Linux Sound Architecture 的缩写,是高级 Linux 声音架构的简称,它在 Linux 操作系统上提供了音频和 MIDI(Musical Instrument Digital Interface,音乐设备数字化接口)的支 持。在 2.6 系列内核中,ALSA 已经成为默认的声音子系统,用来替换 2.4 系列内核中的 OSS。在 本节的内容中,简要介绍在 ALSA 上实现 Audio 系统的基本知识。

10.6.1 ALSA 基础 ALSA 是一个完全开放源代码的音频驱动程序集,除了像 OSS 那样提供了一组内核驱动程序 模块之外,ALSA 还专门为简化应用程序的编写提供了相应的函数库,利用该函数库,开发人员可 以方便快捷地开发出自己的应用程序,细节则留给函数库内部处理。 ALSA 的体系结构大体分为两部分,一部分是 ALSA 的内核,也就是驱动程序,实现音频设备的 正常工作,另一部分是库文件,也就是 ALSA lib,为应用程序开发者提供丰富的接口调用,而不用关 心具体的硬件实现,这两部分在 Android 系统中都有具体的实现。ALSA 的体系结构如图 10-4 所示。

Linux 核心 ALSA 核心驱动库 声卡、音频设备 ALSA 核心应用程序接口

ALSA 库

访问硬件 插件

ALSA 库应用程序接口

应用程序

▲图 10-4 ALSA 的体系结构

317 Android 驱动开发与移植实战详解

10.6.2 注册音频设备和音频驱动

1.注册音频设备

设备本身非常简单,复杂的是这个设备的 drvdata。drvdata 里面包含了三部分,分别是关于 machine 的、关于 Platform 的和关于 codec 的。从大体上说 machine 主要是关于 CPU 的,也可以说 是关于 SSP 本身设置的,而 Platform 是关于平台级别的,就是说这个平台本身实现相关的,而 codec 就是与我们所用的音频 codec 相关的。基本上这里就可以看出整个音频驱动的架构特点,就是从 ALSA 层进入内核 ALSA 层接口和 core 层,这里再调用上面说的三个方面的函数来处理,先是 CPU 级别的,再是 Platform 的,再是 codec 级别的,这几层做完了,工作也就做得差不多了,后面会详 细讲,当然这个执行顺序不是固定的(不知道是不是 marvel 写代码不专业导致的),但多半都包括 了这三部分的工作。

2.注册音频驱动

前面讲了设备的注册,里面的设备的名字就是“soc-audio”,而这里的 driver 的注册时名字也 是“soc-audio”,对于 Platform 的设备匹配的原则是根据名字的,所以将会匹配成功,成功后就会 执行 Audio 驱动提供的函数 soc_probe()。 函数 soc_probe()本身架构很简单,和前面说的逻辑一样,先调用了 CPU 级别的 Probe,再是 codec 级别的,最后是 Platform 的(这里三个的顺序就不一样),但是因为 CPU 级别的和 Platform 级别的都为空,最后都调用了 codec 级别的 Probe 函数,也就是 micco_soc_probe,这个函数基本上 就完成了所有应该完成的音频驱动的初始化了;简单地划分,分成两部分,对上和对下:对上主要 是注册设备节点,以及这些设备节点对应的流的创建,对下主要是读写函数的设置,codec 本身的 dai 设置,初始化寄存器的设置,最重要的就是后面的 control 的创建和门的创建了。

10.6.3 ALSA 的底层接口 ALSA 的底层接口很大一部分都是 ALSA lib 中的接口,将 ALSA lib 移植到 Android 系统中就 必须适配 Android 中音频系统的接口,在 Android 中的音频接口主要定义封装在一个抽象类中,定 义在 AudioHardwareInterface.h 中,类中定义的都是纯虚函数,需要具体的音频设备类来继承实现。 其中头文件路径如下。

"hardware/libhardware_legacy/include/hardware_legacy/AudioHardwareInterface.h"

在上述头文件中定义了三个抽象类,AudioHardwareInterface 、 AudioStreamIn 和 AudioStreamOut。AudioHardwareInterface 负责打开关闭以及管理音频设备,AudioStreamIn 负责管 理音频输入流,AudioStreamOut 负责管理音频输出流。 定义 AudioHardwareInterface 主要接口的代码如下所示。

class AudioHardwareInterface { public:

318 第 10 章 音频系统驱动

virtual ~AudioHardwareInterface() {} virtual status_t initCheck() = 0; virtual status_t setVoiceVolume(float volume) = 0; virtual status_t setMode(int mode) = 0; //打开输入流 virtual AudioStreamIn* openInputStream( uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustics) = 0; //打开输出流 virtual AudioStreamOut* openOutputStream( uint32_t devices, int *format=0, uint32_t *channels=0, uint32_t *sampleRate=0, status_t *status=0) = 0; …… }

AudioStreamIn 主要接口的定义代码如下:

class AudioStreamIn { public: virtual ~AudioStreamIn() = 0; virtual uint32_t sampleRate() const = 0; virtual size_t bufferSize() const = 0; virtual uint32_t channels() const = 0; //从音频设备读数据 virtual ssize_t read(void* buffer, ssize_t bytes) = 0; …… }

定义 AudioStreamOut 的代码如下:

class AudioStreamOut { public: virtual ~AudioStreamOut() = 0; virtual uint32_t sampleRate() const = 0; virtual size_t bufferSize() const = 0; virtual uint32_t channels() const = 0; //向音频设备写数据 virtual ssize_t write(const void* buffer, size_t bytes) = 0; …… }

Android 中又设计了一个 AudioHardwareBase 类来继承 AudioHardwareInterface 实现某些细节, 但是主要的接口还是得在具体的音频设备中去实现。

319 Android 驱动开发与移植实战详解

头文件路径如下:

"hardware/libhardware_legacy/include/hardware_legacy/AudioHardwareBase.h"

当需要放音或者录音时,AudioFlinger 会打开一个具体的音频设备,创建一个具体的音频对象, 这个对象已经不是通用的对象了,而是指向某个具体的音频设备,这里是 ALSA 系统,代码路径 如下:

"hardware/libaudio/AudioHardware.cpp"

创建函数如下:

extern "C" AudioHardwareInterface* createAudioHardware(void) { //返回具体的音频设备对象 return new AudioHardware(); }

当音频对象创建后,则可以用这个对象来调用具体的接口函数,实现放音或者录音。

10.6.4 放音流程 如果需要放音,则要向设备写数据,此时需要首先用下面的代码打开音频输出流。

AudioStreamOut* AudioHardware::openOutputStream( uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) { sp out; status_t rc; { Mutex::Autolock lock(mLock); //只能单路输出 if (mOutput != 0) { if (status) { *status = INVALID_OPERATION; } return NULL; } //创建一个输出流对象 out = new AudioStreamOutALSA(); //初始化参数 rc = out->set(this, devices, format, channels, sampleRate); if (rc == NO_ERROR) { mOutput = out; } } if (rc != NO_ERROR) { if (out != 0) { out.clear(); }

320 第 10 章 音频系统驱动

} if (status) { *status = rc; } return out.get(); }

调用 AudioStreamOutALSA 中的 set()函数可以对采样率、声道等参数进行设置,需要注意的是 目前只支持单通道放音。然后需要通过 write()函数将音频数据写入到 PCM 设备中去,代码如下:

ssize_t AudioHardware::AudioStreamOutALSA::write(const void* buffer, size_t bytes) { status_t status = NO_INIT; const uint8_t* p = static_cast(buffer); int ret; if (mHardware == NULL) return NO_INIT; { AutoMutex lock(mLock); if (mStandby) { //如果状态是 standby 状态,则等待 AutoMutex hwLock(mHardware->lock()); acquire_wake_lock (PARTIAL_WAKE_LOCK, "AudioOutLock"); //得到一个可用的 Input 对象 sp spIn = mHardware->getActiveInput_l(); while (spIn != 0) { int cnt = spIn->standbyCnt(); mHardware->lock().unlock(); spIn->lock(); mHardware->lock().lock(); //确保不会影响输入流状态 if ((spIn == mHardware->getActiveInput_l()) && (cnt == spIn->standbyCnt())) { LOGV("AudioStreamOutALSA::write() force input standby"); spIn->close_l(); break; } spIn->unlock(); spIn = mHardware->getActiveInput_l(); } //打开输出设备,保证在输入设备之前打开 open_l(); if (spIn != 0) { if (spIn->open_l() != NO_ERROR) { spIn->doStandby_l(); } spIn->unlock(); } if (mPcm == NULL) { release_wake_lock("AudioOutLock"); goto Error;

321 Android 驱动开发与移植实战详解

} mStandby = false; } TRACE_DRIVER_IN(DRV_PCM_WRITE) //向设备上写数据 ret = pcm_write(mPcm,(void*) p, bytes); TRACE_DRIVER_OUT if (ret == 0) { return bytes; } status = -errno; } Error: standby(); usleep((((bytes * 1000) / frameSize()) * 1000) / sampleRate()); return status; }

在 write()函数中,会首先打开 PCM 输出设备,然后再打开 mixer 设备(混音设备),最后将数 据写入到设备文件中,最终的打开设备的操作 pcm_open()函数定义在 Alsa_pcm.c 中。 路径为

"hardware/libhardware/Alsa_pcm.c"

打开设备的主要代码为

struct pcm *pcm_open(unsigned flags) { const char *dname; struct pcm *pcm; struct snd_pcm_info info; struct snd_pcm_hw_params params; struct snd_pcm_sw_params sparams; unsigned period_sz; unsigned period_cnt; pcm = calloc(1, sizeof(struct pcm)); if (!pcm) return &bad_pcm; //根据 flags 值判断是打开输入设备还是输出设备 if (flags & PCM_IN) { //输入设备路径 dname = "/dev/snd/pcmC0D0c"; } else { //输出设备路径 dname = "/dev/snd/pcmC0D0p"; } period_sz = 128 * (((flags & PCM_PERIOD_SZ_MASK) >> PCM_PERIOD_SZ_SHIFT) + 1); period_cnt = ((flags & PCM_PERIOD_CNT_MASK) >> PCM_PERIOD_CNT_SHIFT) + PCM_PERIOD_CNT_MIN;

322 第 10 章 音频系统驱动

pcm->flags = flags; //打开设备 pcm->fd = open(dname, O_RDWR); if (pcm->fd < 0) { oops(pcm, errno, "cannot open device '%s'"); return pcm; } //通过 ioctl 接口访问 ALSA 驱动 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { oops(pcm, errno, "cannot get info - %s"); goto fail; } info_dump(&info); …… if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { oops(pcm, errno, "cannot set hw params"); goto fail; } …… if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { oops(pcm, errno, "cannot set sw params"); goto fail; }

pcm->buffer_size = period_cnt * period_sz; pcm->underruns = 0; return pcm; fail: close(pcm->fd); pcm->fd = -1; return pcm; }

pcm_open()函数主要是打开了 output 的设备,通过 ioctl 接口得到一些 ALSA 驱动的参数信息, 并初始化 ALSA 驱动,Alsa_pcm.c 中定义的接口主要实现对 PCM 设备和 PCM 数据的一些操作, 对于音频输入的部分也通过这些接口实现。 mixer 设备的打开操作通过 Alsa_mixer.c 中的 mixer_open()函数完成,主要代码如下:

struct mixer *mixer_open(void) { struct snd_ctl_elem_list elist; struct snd_ctl_elem_info tmp; struct snd_ctl_elem_id *eid = NULL; struct mixer *mixer = NULL; unsigned n, m; int fd; //打开混音设备 fd = open("/dev/snd/controlC0", O_RDWR); if (fd < 0) return 0;

323 Android 驱动开发与移植实战详解

memset(&elist, 0, sizeof(elist)); //通过 ioctl 接口调用驱动中的函数 if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) goto fail; …… if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) goto fail; for (n = 0; n < mixer->count; n++) { struct snd_ctl_elem_info *ei = mixer->info + n; ei->id.numid = eid[n].numid; if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) goto fail; mixer->ctl[n].info = ei; mixer->ctl[n].mixer = mixer; …… } free(eid); return mixer; fail: if (eid) free(eid); if (mixer) mixer_close(mixer); else if (fd >= 0) close(fd); return 0; }

在打开 mixer 设备的函数中,首先打开了 mixer 的设备,然后从驱动中读取到一些设备信息, 并初始化,放音和录音都是需要 mixer 设备的,其功能是对声音进行混合管理,是音频系统中不可 缺少的环节。当打开 PCM 输出设备和 mixer 设备后,接着需要往设备上写数据,此功能通过 Alsa_pcm.c 中的 pcm_write()函数实现,代码如下:

int pcm_write(struct pcm *pcm, void *data, unsigned count) { struct snd_xferi x; if (pcm->flags & PCM_IN) return -EINVAL; x.buf = data; x.frames = (pcm->flags & PCM_MONO) ? (count / 2) : (count / 4); //循环向设备中写数据 for (;;) { if (!pcm->running) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)) return oops(pcm, errno, "cannot prepare channel"); if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) return oops(pcm, errno, "cannot write initial data"); pcm->running = 1; return 0; }

324 第 10 章 音频系统驱动

if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) { pcm->running = 0; if (errno == EPIPE) { pcm->underruns++; continue; } return oops(pcm, errno, "cannot write stream data"); } return 0; } }

pcm_write()函数会循环向设备中写数据,通过 ioctl 接口将数据写到驱动中去,从而实现放音 功能。

10.6.5 录音流程 ALSA 中的录音跟放音流程大致一样,也是先创建一个 AudioStreamInALSA 对象,通过这个 对象来设置录音参数,代码如下:

AudioStreamIn* AudioHardware::openInputStream( uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustic_flags) { if (!AudioSystem::isInputDevice((AudioSystem::audio_devices)devices)) { if (status) { *status = BAD_VALUE; } return NULL; } status_t rc = NO_ERROR; sp in; { Mutex::Autolock lock(mLock); //创建 AudioStreamInALSA 对象 in = new AudioStreamInALSA(); //设置输入参数 rc = in->set(this, devices, format, channels, sampleRate, acoustic_flags); if (rc == NO_ERROR) { mInputs.add(in); } } if (rc != NO_ERROR) { if (in != 0) { in.clear(); } } if (status) { *status = rc;

325 Android 驱动开发与移植实战详解

} return in.get(); }

然后需要通过 read()函数来读取设备上的音频数据,跟放音的 write()是逆过程,代码如下:

ssize_t AudioHardware::AudioStreamInALSA::read(void* buffer, ssize_t bytes) { status_t status = NO_INIT; int ret; if (mHardware == NULL) return NO_INIT; { AutoMutex lock(mLock); //如果设备是 standby 状态,则等待 if (mStandby) { AutoMutex hwLock(mHardware->lock()); acquire_wake_lock (PARTIAL_WAKE_LOCK, "AudioInLock"); sp spOut = mHardware->output(); while (spOut != 0) { if (!spOut->checkStandby()) { int cnt = spOut->standbyCnt(); mHardware->lock().unlock(); mLock.unlock(); spOut->lock(); mLock.lock(); mHardware->lock().lock(); if ((spOut == mHardware->output()) && (cnt == spOut->standbyCnt())) { spOut->close_l(); break; } spOut->unlock(); spOut = mHardware->output(); } else { spOut.clear(); } } //如果 output 不为 0,则表示 output 设备处于活动状态,可先将 output 设备打开 if (spOut != 0) { if (spOut->open_l() != NO_ERROR) { spOut->doStandby_l(); } spOut->unlock(); } //打开 input 设备 open_l(); if (mPcm == NULL) { release_wake_lock("AudioInLock"); goto Error; } mStandby = false; }

326 第 10 章 音频系统驱动

if (mDownSampler != NULL) { size_t frames = bytes / frameSize(); size_t framesIn = 0; mReadStatus = 0; do { size_t outframes = frames - framesIn; mDownSampler->resample( (int16_t *)buffer + (framesIn * mChannelCount), &outframes); framesIn += outframes; } while ((framesIn < frames) && mReadStatus == 0); ret = mReadStatus; //计算需要读取的数据的大小 bytes = framesIn * frameSize(); } else { TRACE_DRIVER_IN(DRV_PCM_READ) //读 PCM 数据 ret = pcm_read(mPcm, buffer, bytes); TRACE_DRIVER_OUT } if (ret == 0) { return bytes; } LOGW("read error: %d", ret); status = ret; } Error: standby(); usleep((((bytes * 1000) / frameSize()) * 1000) / sampleRate()); return status; }

在 read()函数中,必须打开 input 设备和 mixer 设备,同时需注意,output 设备如果是活动状态, 则必须先打开 output 设备并设置成 standby 状态,打开 input 设备和 mixer 设备的方式同放音是一样 的,只不过 input 设备文件有所不同,input 设备文件路径为“/dev/snd/pcmC0D0c”。 最后从驱动中读数据的方法为 pcm_read(),代码如下:

int pcm_read(struct pcm *pcm, void *data, unsigned count) { struct snd_xferi x; if (!(pcm->flags & PCM_IN)) return -EINVAL; x.buf = data; x.frames = (pcm->flags & PCM_MONO) ? (count / 2) : (count / 4); //循环读取数据 for (;;) { if (!pcm->running) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)) return oops(pcm, errno, "cannot prepare channel"); //调用驱动中的接口

327 Android 驱动开发与移植实战详解

if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) return oops(pcm, errno, "cannot start channel"); pcm->running = 1; } if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) { pcm->running = 0; if (errno == EPIPE) { pcm->underruns++; continue; } return oops(pcm, errno, "cannot read stream data"); } return 0; } }

pcm_read()会循环从 PCM 设备中读取数,主要的实现方法是通过 ioctl 接口从驱动中得到 PMC 数据的,系统中的多媒体库会将得到的 PCM 数据进行编码,封装成音频文件。

10.6.6 在 Android 中使用 ALSA 声卡

1.使用过程

在 Android 系统中使用 ALSA 声卡的具体流程如下所示。 (1)使用下面的 CD 命令来到 Android 源码的根目录,在此以 Android 2.0 为例。

cd /home/figo/android/Android-2.0

(2)从 Android 主页下载 ALSA 声卡关源码,下载命令如下所示。

git clone git://android.git.kernel.org/platform/external/alsa-lib.git git clone git://android.git.kernel.org/platform/external/alsa-utils.git git clone git://android.git.kernel.org/platform/hardware/alsa_sound.git

(3)下载完成后修改板子的 BoardConfig.mk 文件,确保板子可以使用 ALSA 声卡,修改代码 如下所示。

#HAVE_HTC_AUDIO_DRIVER := true #BOARD_USES_GENERIC_AUDIO := true BOARD_USES_ALSA_AUDIO := true BUILD_WITH_ALSA_UTILS := true

在 Android-2.0 版本中,文件 BoardConfig.mk 位于如下目录中。

Android-2.0/build/target/board/generic

(4)重新编译一遍 Android,编译完成后在根文件“/system/etc/asound.conf”添加配置声卡的 工作参数脚本,具体代码如下所示。

# #

328 第 10 章 音频系统驱动

# # Mixer devices # # ctl.AndroidPlayback { type hw card 0 # Can replace with drivers name from /proc/asound/cards } ctl.AndroidRecord { type hw card 0 }

# # # # Playback devices # # pcm.AndroidPlayback { type hw card 0 device 0 } pcm.AndroidPlayback_Speaker { type hw card 0 device 0 } pcm.AndroidPlayback_Speaker_normal { type hw card 0 device 0 } pcm.AndroidPlayback_Speaker_ringtone { type hw card 0 device 0 } pcm.AndroidPlayback_Speaker_incall { type hw card 0 device 0 } pcm.AndroidPlayback_Earpiece { type hw card 0 device 0 }

329 Android 驱动开发与移植实战详解

pcm.AndroidPlayback_Earpiece_normal { type hw card 0 device 0 }

pcm.AndroidPlayback_Earpiece_ringtone { type hw card 0 device 0 }

pcm.AndroidPlayback_Earpiece_incall { type hw card 0 device 0 }

pcm.AndroidPlayback_Bluetooth { type hw card 0 device 0 }

pcm.AndroidPlayback_Bluetooth_normal { type hw card 0 device 0 }

pcm.AndroidPlayback_Bluetooth_ringtone { type hw card 0 device 0 }

pcm.AndroidPlayback_Bluetooth_incall { type hw card 0 device 0 }

pcm.AndroidPlayback_Headset { type hw card 0 device 0 }

pcm.AndroidPlayback_Headset_normal { type hw

330 第 10 章 音频系统驱动 card 0 device 0 } pcm.AndroidPlayback_Headset_ringtone { type hw card 0 device 0 } pcm.AndroidPlayback_Headset_incall { type hw card 0 device 0 } pcm.AndroidPlayback_Bluetooth-A2DP { type hw card 0 device 0 } pcm.AndroidPlayback_Bluetooth-A2DP_normal { type hw card 0 device 0 } pcm.AndroidPlayback_Bluetooth-A2DP_ringtone { type hw card 0 device 0 } pcm.AndroidPlayback_Bluetooth-A2DP_incall { type hw card 0 device 0 } pcm.AndroidRecord { type hw card 0 device 0 } pcm.AndroidRecord_Microphone { type hw card 0 device 0 }

331 Android 驱动开发与移植实战详解

2.几个需要注意的问题

(1)ALSA 音频路径。 我们知道在“sound/soc/codecs”目录中有很多音频的 Codec 驱动,例如笔者使用的是 wm9713, AP 是 s3c6410,在此驱动文件中定义了很多 widget 和 control,ALSA 在 PlayBack(回放)或 Record (录制)的时候,文件“sound/soc/soc-dapm.c”中的函数 dapm_power_widgets()会根据“配置情况” 来打开相应的 Widget,并搭建一个完整的音频路径。只要搭建该音频路径成功,就可以正常工作。 sound/soc/codecs/wm9710.c 中的 audio_map[]就是一个 wm9713 的路由表,根据 wm9713 手册 中的 Audio Paths Overview 可以选择自己需要的音频路径,在 audio_map[]中测试一下,看 audio_map 中是否支持这种路径。 (2)配置 ALSA。 在 ALSA 中最主要的是配置 ALSA 音频调试,当 ALSA 使用 amixer 命令打开 audio_map[]中 的开关(control/switch)和 其 他 control(控制),并设置这些 control 后,在使用 aplay(播放)/arecord (录音)的时候即可搭建正确的路径,实现播放和录音功能。 例如在调试的时候,在不使用 amixer 控制的时候(这是默认状态),arecord 可以正确录音,使 用文件 sound/soc/soc-dapm.c 中的函数 dump_dapm()来 Dump 出的路径是正确的;当 aplay 的时候, dump_dapm 出来的路径是错误的,原因是默认设置里没有打开 playback 的开关(switch)。当遇到 上述问题的时候,运行如下命令即可正确地 playback。

alsa_amixer cset numid=4,iface=MIXER,name='Headphone Playback Switch' 1 alsa_amixer cset numid=93,iface=MIXER,name='Left Headphone Out Mux' 2 alsa_amixer cset numid=34,iface=MIXER,name='Out3 Playback Switch'1 alsa_amixer cset numid=95,iface=MIXER,name='Left Speaker Out Mux' 4 alsa_amixer cset numid=94,iface=MIXER,name='Right Speaker Out Mux' 2 alsa_amixer cset numid=91,iface=MIXER,name='Out 3 Mux' 2 alsa_amixer cset numid=81,iface=MIXER,name='Left HP Mixer PCM Playback Swit' 1 alsa_amixer cset numid=75,iface=MIXER,name='Right HP Mixer PCM Playback Swi' 1 alsa_amixer cset numid=3,iface=MIXER,name='Headphone Playback Volume' 26 alsa_amixer cset numid=36,iface=MIXER,name='Out3 Playback Volume' 48

由此可见,在打开 playback 路径时需要一个开关,dapm_power_widgets 会自动把这些开关连 接的 widget 连接起来而构成一个完整的播放路径。 (3)在 Android 中配置 ALSA。 在 Andriod 中使用 alsa-lib 时也需要配置音频路径,具体来说有如下两个配置方法。 在文件 AudioHardwareALSA.cpp 中,使用函数 system()调用 amixer 来完成配置。具体代码如 下所示。

system("alsa_amixer cset numid=4,iface=MIXER,name='Headphone Playback Switch' 1");

编写 asound.conf 文件,在 AudioHardwareALSA.cpp 中的 ALSAMixer::ALSAMixer 对象初始化 时,会通过 alsa-lib 的 conf.c 文件中的函数来读取文件“/etc/asound.conf”以获取配置信息,并对 codec 进行配置。

332 第 10 章 音频系统驱动

3.ASLA 中的重要命令

(1)alsa_amixer 命令。 命令 alsa_amixer 用于配置音频 codec 的 mixer 开关、mux 对路选择、volume 值等,例如下面 的代码。

alsa_amixer --help alsa_amixer contents alsa_amixer contents numid=30,iface=MIXER,name='Headphone Playback ZC Switch' ; type=BOOLEAN,access=rw------,values=2 : values=off,off numid=4,iface=MIXER,name='Headphone Playback Switch' ; type=BOOLEAN,access=rw------,values=2 : values=off,off numid=3,iface=MIXER,name='Headphone Playback Volume' ; type=INTEGER,access=rw------,values=2,min=0,max=31,step=0 : values=31,31 numid=6,iface=MIXER,name='PCM Playback Volume' ; type=INTEGER,access=rw------,values=2,min=0,max=31,step=0 : values=23,23 numid=5,iface=MIXER,name='Line In Volume' ; type=INTEGER,access=rw------,values=2,min=0,max=31,step=0 : values=23,23 numid=7,iface=MIXER,name='Mic 1 Volume' ; type=INTEGER,access=rw------,values=1,min=0,max=31,step=0 : values=23 numid=8,iface=MIXER,name='Mic 2 Volume' ; type=INTEGER,access=rw------,values=1,min=0,max=31,step=0 : values=23 numid=85,iface=MIXER,name='Mic A Source' ; type=ENUMERATED,access=rw------,values=1,items=3 ; Item #0 'Mic 1' ; Item #1 'Mic 2 A' ; Item #2 'Mic 2 B' : values=0

alsa_amixer controls numid=30,iface=MIXER,name='Headphone Playback ZC Switch' numid=4,iface=MIXER,name='Headphone Playback Switch' numid=3,iface=MIXER,name='Headphone Playback Volume' numid=6,iface=MIXER,name='PCM Playback Volume' numid=5,iface=MIXER,name='Line In Volume' numid=7,iface=MIXER,name='Mic 1 Volume' numid=8,iface=MIXER,name='Mic 2 Volume' numid=85,iface=MIXER,name='Mic A Source' numid=84,iface=MIXER,name='Mic B Source' numid=9,iface=MIXER,name='Mic Boost (+20dB) Switch' numid=10,iface=MIXER,name='Mic Headphone Mixer Volume'

333 Android 驱动开发与移植实战详解

numid=47,iface=MIXER,name='Aux Playback Headphone Volume' numid=48,iface=MIXER,name='Aux Playback Master Volume' numid=49,iface=MIXER,name='Aux Playback Mono Volume' numid=67,iface=MIXER,name='Mono Mixer Aux Playback Switch' numid=69,iface=MIXER,name='Mono Mixer Bypass Playback Swit' numid=70,iface=MIXER,name='Mono Mixer Mic 1 Sidetone Switc' numid=71,iface=MIXER,name='Mono Mixer Mic 2 Sidetone Switc' numid=65,iface=MIXER,name='Mono Mixer PC Beep Playback Swi' numid=68,iface=MIXER,name='Mono Mixer PCM Playback Switch' numid=66,iface=MIXER,name='Mono Mixer Voice Playback Switc'

(2)alsa_alsactl store 命令。 此命令用于生成文件“/etc/asound.state”,在显示当前 codec 的状态时可以根据该文件检查 codec 的状态是否正确。例如下面的代码。

# cat /etc/asound.state state.SMDK6400 { control.1 { comment.access 'read write' comment.type INTEGER comment.count 2 comment.range '0 - 31' iface MIXER name 'Speaker Playback Volume' value.0 31 value.1 31 } control.2 { comment.access 'read write' comment.type BOOLEAN comment.count 2 iface MIXER name 'Speaker Playback Switch' value.0 false value.1 false } control.3 { comment.access 'read write' comment.type INTEGER comment.count 2 comment.range '0 - 31' iface MIXER name 'Headphone Playback Volume' value.0 26 value.1 26 } control.4 { comment.access 'read write' comment.type BOOLEAN comment.count 2

334 第 10 章 音频系统驱动

iface MIXER name 'Headphone Playback Switch' value.0 true value.1 true } control.5 { comment.access 'read write' comment.type INTEGER comment.count 2 comment.range '0 - 31' iface MIXER name 'Line In Volume' value.0 23 value.1 23 }

amixer 命令的用法可以参照 alsa_amixer contents 中的内容;编写 asound.conf 文件的方法可以 参照 alsa_alsactl 生成“/etc/asound.state”的过程。下面的代码是笔者本人编写的 asound.conf。

## ## Mixer Devices ## ctl.AndroidPlayback { type hw card 0 # Can replace with driver"s name from /proc/asound/cards } ctl.AndroidRecord { type hw card 0 # Can replace with driver"s name from /proc/asound/cards } ## ## Playback Devices ## pcm.AndroidPlayback { type hooks slave.pcm { type hw card 0 device 0 # Must be of type "digital audio playback" } hooks.0 { type ctl_elems hook_args [ { name 'Master Playback Switch' value true } { name 'Master Playback Volume' value.0 51

335 Android 驱动开发与移植实战详解

value.1 51 } { name 'Phone Playback Switch' value false } { name 'Phone Playback Volume' value.0 0 value.1 0 } { name 'Mic Playback Switch' value false } { name 'Mic Playback Volume' value.0 0 value.1 0 } { name 'Mic Boost (+20dB)' value false } { name 'Line Playback Switch' value false } { name 'Line Playback Volume' value.0 0 value.1 0 } { name 'PCM Playback Switch' value true } { name 'PCM Playback Volume' value.0 51 value.1 51 } { name 'Capture Source' value.0 Mic value.1 Mic } { name 'Capture Switch' value true

336 第 10 章 音频系统驱动

} { name 'Capture Volume' value.0 0 value.1 0 } ] } }

上述代码只是 asound.conf 文件的一部分,其他 pcm.AndroidPlayback_xxx 的写法都类似,唯一 的区别是 hook_argsp[]中的内容需要根据自己的需要来设置。

10.6.7 在 OMAP 平台移植 Android 的 ALSA 声卡驱动 在接下来的内容中,讲解在 OMAP3530 平台上移植 Android 的 ALSA 声卡驱动的方法,我们 以最难以移植操作的 Android 1.5 为例进行讲解。 (1)使用 GIT 下载移植代码,读者需要注意的是,不同人在网上下载的移植代码可能会有区 别。我们需要明确在 AudioSystem 类中是否定义了 DEVICE_OUT_EARPIECE,我们需要选择使用 没有定义的。 先运行如下命令:

git clone git://gitorious.org/android-on-freerunner/platform_external_alsa-lib.git

将下载的内容复制到“external”目录下,并重命名为“alsa-lib”。 然后运行如下命令:

git clone git://gitorious.org/android-on-freerunner/platform_hardware_alsa_sound.git

将下载的内容复制到“hardware”目录下,并重命名为“libaudio-alsa”。 再运行如下命令:

git clone git://gitorious.org/android-on-freerunner/platform_external_alsa-utils.git

将下载的内容复制到“external”目录下,并重命名为“alsa-utils”。 然后通过以下命令下载 DEVICE_OUT_EARPIECE 的代码。

git clone git://android.git.kernel.org/platform/external/alsa-lib.git git clone git://android.git.kernel.org/platform/external/alsa-utils.git git clone git://android.git.kernel.org/platform/hardware/alsa_sound.git

复制和重命名 DEVICE_OUT_EARPIECE 的方法和前面的方法相同。 (2)修改文件 system/core/init/device.c,在里面加上如下代码以创建“/dev/snd”。

} else if(!strncmp(uevent->subsystem, "mtd", 3)) { base = "/dev/mtd/"; mkdir(base, 0755); } else if(!strncmp(uevent->subsystem, "sound", 5)) {

337 Android 驱动开发与移植实战详解

base = "/dev/snd/"; mkdir(base, 0755);

(3)修改文件“system/core/init/devices.c”,目的是增加设备节点及权限。

static struct perms_ devperms[] = { ... { "/dev/snd/", 0664, AID_SYSTEM, AID_AUDIO, 1 }, ...

(4)修改文件 build/target/board/generic/BoardConfig.mk。

1 # config.mk 2 # 3 # Product-specific compile-time definitions. 4 # 5 6 # The generic product target doesn't have any hardware-specific pieces. 7 TARGET_NO_BOOTLOADER := true 8 TARGET_NO_KERNEL := true 9 TARGET_NO_RADIOIMAGE := true 10 #HAVE_HTC_AUDIO_DRIVER := true 11 BOARD_USES_ALSA_AUDIO := true 12 BUILD_WITH_ALSA_UTILS := true 13 #BOARD_USES_GENERIC_AUDIO := true 14 BOARD_USES_GENERIC_AUDIO := false

(5)修改文件“hardware/alsa_sound/Android.mk”,此步骤很重要,否则编译不会通过。

1 # hardware/libaudio-alsa/Android.mk 2 # 3 # Copyright 2008 Wind River Systems 4 # 5 6 ifeq ($(strip $(BOARD_USES_ALSA_AUDIO)),true) 7 8 LOCAL_PATH := $(call my-dir) 9 10 include $(CLEAR_VARS) 11 12 LOCAL_ARM_MODE := arm 13 LOCAL_CFLAGS := -D_POSIX_SOURCE 14 # LOCAL_WHOLE_STATIC_LIBRARIES := libasound 15 16 LOCAL_C_INCLUDES += external/alsa-lib/include 17 18 LOCAL_SRC_FILES := AudioHardwareALSA.cpp 19 20 LOCAL_MODULE := libaudio 22 LOCAL_STATIC_LIBRARIES += libaudiointerface \ 23 # libasound

338 第 10 章 音频系统驱动

24 25 LOCAL_SHARED_LIBRARIES := \ 26 libcutils \ 27 libutils \ 28 libmedia \ 29 libhardware_legacy \ 30 libdl \ 31 libc \ 32 libasound 33 34 include $(BUILD_SHARED_LIBRARY) 35 36 endif

(6)重建如下编译选项。 build/envsetup.sh :不同下载的脚本名字可能有不同。 choosecombo。 make clean :必须经过此过程,否则不能在 Android 系统中发声。 (7)编译 make -j4 //core dual。 (8)制作文件系统。 在文件“/system/etc/asound.conf()”需要注意如下几个特别的配置: view plaincopy to clipboardprint? ctl.AndroidOut { type hw card 0 } ctl.AndroidIn { type hw card 0 } pcm.AndroidPlayback { type hw card 0 device 0 } pcm.AndroidRecord { type hw card 0 device 0 ctl.AndroidOut { type hw card 0 } ctl.AndroidIn { type hw card 0 }

339 Android 驱动开发与移植实战详解

pcm.AndroidPlayback { type hw card 0 device 0 } pcm.AndroidRecord { type hw card 0 device 0 }

(9)在编译后修改文件 init.rc,重新设置 Audio 驱动的设备节点的 owner 和访问属性。

chown root audio /dev/snd/controlC0 chown root audio /dev/snd/pcmC0D0c chown root audio /dev/snd/pcmC0D0p chown root audio /dev/snd/timer chmod 0666 /dev/snd/controlC0 chmod 0666 audio /dev/snd/pcmC0D0c chmod 0666 audio /dev/snd/pcmC0D0p chmod 0666 audio /dev/snd/timer

10.7 6410 中的 ALSA 驱动

本节将以 6410 开发板为例,介绍在 6410 中的 ALSA 驱动的实现过程。

10.7.1 ALSA 的设备文件 ALSA 的驱动程序会在 Android 系统中创建几个设备节点,应用程序通过这几个设备节点来操 作 ALSA,ALSA 的设备节点创建在“/dev/snd”目录下,在这个目录下会产生多个 ALSA 设备, 如图 10-5 所示。

▲图 10-5 多个 ALSA 设备目录

其中主要设备的功能如下。 controlC0 设备:负责声卡的控制,例如通道选择、麦克风的控制等。 pcmC0D0c 设备:负责录音功能。

340 第 10 章 音频系统驱动

pcmC0D0p 设备:负责放音功能。 mixer 设备:负责混音的工作。 timer 设备:为定时器。

10.7.2 创建声卡和 PCM 设备 ALSA 驱动如果要工作起来,必须先创建一个声卡设备,snd_card 结构体用来描述声卡设备, 大部分与声音相关的逻辑设备都在 snd_card 的管理之下,ALSA 驱动的首要任务就是创建 snd_card 结构体,结构体路径如下:

"include/sound/core.h"

这里我们的声卡芯片是三星平台的 wm9713 芯片,接下来介绍声卡的创建流程,在 Smdk64xx_wm9713.c 中的文件路径是“sound/soc/s3c64xx/Smdk64xx_wm9713.c”。 通过模块加载的方式调用 smdk6400_init()加载 wm9713 设备,代码如下:

static int __init smdk6400_init(void) { int ret; smdk6400_snd_ac97_device = platform_device_alloc("soc-audio", -1); if (!smdk6400_snd_ac97_device) return -ENOMEM; platform_set_drvdata(smdk6400_snd_ac97_device, &smdk6400_snd_ac97_devdata); smdk6400_snd_ac97_devdata.dev = &smdk6400_snd_ac97_device->dev; ret = platform_device_add(smdk6400_snd_ac97_device); if (ret) platform_device_put(smdk6400_snd_ac97_device); return ret; }

将 smdk6400_snd_ac97_device 设备加载到系统中,下面是定义 smdk6400_snd_ac97_device 结 构体的代码。

static struct snd_soc_device smdk6400_snd_ac97_devdata = { .machine = &smdk6400, .platform = &s3c24xx_soc_platform, .codec_dev = &soc_codec_dev_wm9713, };

其中 codec_dev 设备赋值为 soc_codec_dev_wm9713,soc_codec_dev_wm9713 是一个具体描述 wm9713codec 设备的结构体,定义在 Wm9713.c 文件中。文件路径是“sound/soc/codecs/Wm9713.c”。 下面是定义 soc_codec_dev_wm9713 结构体的代码。

struct snd_soc_codec_device soc_codec_dev_wm9713 = { .probe = wm9713_soc_probe, .remove = wm9713_soc_remove, .suspend = wm9713_soc_suspend,

341 Android 驱动开发与移植实战详解

.resume = wm9713_soc_resume, };

在 codec 设备加载的时候会调用 wm9713_soc_probe()方法,里面会调用 Soc-core.c 中的 snd_soc_new_pcms()函数创建声卡和 PCM 设备。文件路径是“sound/soc/Soc-core.c”。下面是定义 snd_soc_new_pcms()的代码。

int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid) { struct snd_soc_codec *codec = socdev->codec; struct snd_soc_machine *machine = socdev->machine; int ret = 0, i; mutex_lock(&codec->mutex); //创建声卡 codec->card = snd_card_new(idx, xid, codec->owner, 0); if (!codec->card) { printk(KERN_ERR "asoc: can't create sound card for codec %s\n", codec->name); mutex_unlock(&codec->mutex); return -ENODEV; } codec->card->dev = socdev->dev; codec->card->private_data = codec; strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver)); //创建 PCM 设备,可以创建多个 PCM 设备 for (i = 0; i < machine->num_links; i++) { ret = soc_new_pcm(socdev, &machine->dai_link[i], i); if (ret < 0) { printk(KERN_ERR "asoc: can't create pcm %s\n", machine->dai_link[i].stream_name); mutex_unlock(&codec->mutex); return ret; } } mutex_unlock(&codec->mutex); return ret; }

当声卡设备和 PCM 设备被创建之后,会通过 Soc-core 这个核心层将其注册进系统,Soc-core 是 Soc 芯片的核心驱动,主要是对 Soc 声卡芯片进行管理,将驱动函数注册到系统中,从而使上层 应用可以通过调用 open、close、read、write 等函数和一些 ioctl 命令来设置 PCM 或者 mixer 设备, 对 PCM 设备的操作接口都定义在 Pcm_native.c 文件中,文件路径是“sound/core/Pcm_native.c”。 在文件 Pcm_native.c 中定义了两套对 PCM 设备操作的接口,分别实现放音和录音的驱动实现, 代码如下:

const struct file_operations snd_pcm_f_ops[2] = { { //放音部分

342 第 10 章 音频系统驱动

.owner = THIS_MODULE, .write = snd_pcm_write, .aio_write = snd_pcm_aio_write, .open = snd_pcm_playback_open, .release = snd_pcm_release, .poll = snd_pcm_playback_poll, .unlocked_ioctl = snd_pcm_playback_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, .get_unmapped_area = dummy_get_unmapped_area, }, { //录音部分 .owner = THIS_MODULE, .read = snd_pcm_read, .aio_read = snd_pcm_aio_read, .open = snd_pcm_capture_open, .release = snd_pcm_release, .poll = snd_pcm_capture_poll, .unlocked_ioctl = snd_pcm_capture_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, .get_unmapped_area = dummy_get_unmapped_area, } }; 则 ALSA lib 中调用的 read、write 和 ioctl 命令最终会在这里实现建立完成,从而从 Android 的 SurfaceFlinger.cpp、ALSA lib 到 ALSA 驱动的层次,通过上述驱动运行后可以在 6410 中播放音乐, 如图 10-6 所示。

▲图 10-6 播放音乐

343

第 11 章 视频输出系统驱动

在移动智能手机系统中,视频应用也比较重要。例如在日常生活中,我们经常用手机播放电影, 也通常用手机在线观看欧美大片。在本章将详细讲解Android 视频输出系统驱动的实现和移植内容, 为读者步入本书后面知识的学习打下基础。

11.1 视频输出系统结构

在 Android 系统中,视频输出系统对应的是 Overlay 子系统,此系统是 Android 的一个可选系 统,用于加速显示输出视频数据。视频输出系统的硬件通常叠加在主显示区之上的额外的叠加显示 区。这个额外的叠加显示区和主显示区使用独立的显示内存。在通常情况下,主显示区用于输出图 形系统,通常是 RGB 颜色空间。额外显示区用于输出视频,通常是 YUV 颜色空间。主显示区和 叠加显示区通过 Blending(硬件混淆)自动显示在屏幕上。在软件部分我们无须关心叠加的实现过 程,但是可以控制叠加的层次顺序和叠加层的大小等内容。 Overlay 系统的基本层次结构如图 11-1 所示。

视频播放输出 Overlay本地API

Java框架 UI库、SurfaceFlinger和Overlay硬件抽象层

Android系统

视频输出设备 硬件和驱动

▲图 11-1 Overlay 的基本层次结构

Android 中的 Overlay 系统没有 Java 部分,在里面只包含了视频输出的驱动程序、硬件抽象层

第 11 章 视频输出系统驱动

和本地框架等。Overlay 系统的结构如图 11-2 所示。

Overlay API

libui.so Overlay SurfaceFlinger

Overlay的硬件抽象层

Video HAL实现 C框架

内核空间 移植部分 视频输出驱动

▲图 11-2 Overlay 系统结构

在图 11-2 结构中的各个构成部分的具体说明如下所示。 (1)Overlay 驱动程序:通常是基于 FrameBuffer 或 V4L2 的驱动程序。在此文件中主要定义了 两个 struct,分别是 data device 和 control device,这两个结构体分别针对 data device 和 control device 的函数 open()和函数 close()。这两个函数是注册到 device_module 里面的函数。 (2)Overlay 硬件抽象层:代码路径如下所示。

hardware/libhardware/include/hardware/overlay.h

Overlay 硬件抽象层是一个 Android 中标准的硬件模块,其接口只有一个头文件。 (3)Overlay 服务部分:代码路径如下所示。

framework/base/libs/surfaceflinger/

由此可见,Overlay 系统的服务部分包含在 SurfaceFlinger 中,此层次的内容比较简单,主要功 能是通过类 LayerBuffer 实现的。首先要明确的是 SurfaceFlinger 只是负责控制 merge Surface,比 如 说计算出两个 Surface 重叠的区域,至于 Surface 需要显示的内容,则通过 Skia、Opengl 和 Pixflinger 来计算。所以我们在介绍 SurfaceFlinger 之前先忽略里面存储的内容究竟是什么,先弄清楚它对 merge 的一系列控制的过程,然后再结合 2D、3D 引擎来看它的处理过程。 (4)本地框架代码。本地框架的头文件路径如下所示。

framework/base/include/ui

源代码路径如下所示。

framework/base/libs/include/ui

Overlay 系统只是整个框架的一部分,主要功能是通过类 Ioverlay 和 Overlay 实现的,源代码被 编译成 libui.so,它提供的 API 主要在视频输出和照相机取景模块中使用。

345 Android 驱动开发与移植实战详解

11.2 移植的内容

Overlay 系统的底层和系统框架接口是硬件抽象层,在实现 Overlay 系统移植时需要移植硬件 抽象层和下面对应的驱动程序。Overlay 系统的硬件抽象层使用了 Android 标准硬件模块的接口, 此接口是标准 C 语言接口,是通过函数和指针来实现具体功能的。在接口里面包含了数据流接口 和控制接口,我们需要根据硬件平台的具体情况来实现。 Overlay 系统的驱动程序通常是视频输出驱动程序,可以通过标准的 FrameBuffer 驱动程序或 Video for Linux 2 视频输出驱动程序来实现。因为系统的不同,即使使用同一种驱动程序,也有不 同的实现方式。 (1)FrameBuffer 驱动程序方式。 FrameBuffer 驱动程序方式是最直接的方式,实现视频输出,从驱动程序的角度和一般 FrameBuffer 驱动程序类似。区别是视频输出通过 YUV 格式颜色空间,而用于图形界面的 FrameBuffer 使用 RGB 颜色和空间。 (2)Video for Linux 2 方式。 Video for Linux 2 是 Linux 视频系统的一个标准框架,在其第一个版本 Video for Linux 2 中提供 了摄像头视频输入框架,从 Video for Linux 2 版本开始提供视频输出接口。使用此视频输出接口, 可以根据系统的性能来调整队列的数目。

11.3 分析硬件抽象层

Overlay 系统的硬件抽象层是一个硬件模块,在本节将简要介绍 Overlay 系统的硬件抽象层的 基本知识,为读者步入本书后面知识的学习打下基础。

11.3.1 Overlay 系统硬件抽象层的接口 在如下文件中定义 Overlay 系统硬件抽象层的接口。

hardware/libhardware/include/hardware/overlay.h

在文件 overlay.h 中,主要定义了 data device 和 control device 两个 struct。并提供针对 data device 和 control device 的函数 open()和函数 close()。文件 overlay.h 的代码结构如图 11-3 所示。 (1)定义 Overlay 控制设备和 Overlay 数据设备,名称被定义为如下两个字符串。

#define OVERLAY_HARDWARE_CONTROL "control" #define OVERLAY_HARDWARE_DATA "data" (2)定义一个枚举 enum,定义了所有支援的 Format,FrameBuffer 会根据 Format 和 width、 height 来决定 Buffer(FrameBuffer 里面用来显示的 Buffer) 的大小。定义 enum 的代码如下所示。

enum { OVERLAY_FORMAT_RGBA_8888 = HAL_PIXEL_FORMAT_RGBA_8888,

346 第 11 章 视频输出系统驱动

OVERLAY_FORMAT_RGB_565 = HAL_PIXEL_FORMAT_RGB_565, OVERLAY_FORMAT_BGRA_8888 = HAL_PIXEL_FORMAT_BGRA_8888, OVERLAY_FORMAT_YCbCr_422_SP = HAL_PIXEL_FORMAT_YCbCr_422_SP, OVERLAY_FORMAT_YCbCr_420_SP = HAL_PIXEL_FORMAT_YCbCr_420_SP, OVERLAY_FORMAT_YCrCb_420_SP = HAL_PIXEL_FORMAT_YCrCb_420_SP, OVERLAY_FORMAT_YCbYCr_422_I = HAL_PIXEL_FORMAT_YCbCr_422_I, OVERLAY_FORMAT_YCbYCr_420_I = HAL_PIXEL_FORMAT_YCbCr_420_I, OVERLAY_FORMAT_CbYCrY_422_I = HAL_PIXEL_FORMAT_CbYCrY_422_I, OVERLAY_FORMAT_CbYCrY_420_I = HAL_PIXEL_FORMAT_CbYCrY_420_I, OVERLAY_FORMAT_DEFAULT = 99 // The actual color format is determined // by the overlay };

▲图 11-3 文件 overlay.h 结构

(3)定义和 Overlay 系统相关结构体。 在文件 overlay.h 中,和 Overlay 系统相关结构体是 overlay_t 和 overlay_handle_t,结构体 overlay_handle_t 是在内部使用的结构体,用于保存 Overlay 硬件设备的句柄。在使用的过程中,需 要从 overlay_t 获取 overlay_handle_t。其中上一层的使用只实现结构体 overlay_handle_t 指针的传 递,具体的操作是在 Overlay 的硬件抽象层中完成的。 定义结构体 overlay_t 的代码如下所示。

typedef struct overlay_t { uint32_t w; //宽 uint32_t h; //高 int32_t format; //颜色格式 uint32_t w_stride; //一行的内容 uint32_t h_stride; //一列的内容 uint32_t reserved[3]; /* returns a reference to this overlay's handle (the caller doesn't * take ownership) */ overlay_handle_t (*getHandleRef)(struct overlay_t* overlay); uint32_t reserved_procs[7];

347 Android 驱动开发与移植实战详解

} overlay_t;

(4)定义结构体 overlay_control_device_t,此结构体定义了一个 control device,里面的成员 除了 common 都是函数,这些函数就是我们需要去实现的,在实现的时候我们会基于这个结构 体扩展出一个关于 control device 的 context 的结构体,context 结构体内部会扩充一些信息并且 包含 control device。Common 每一个 device 都必须有,而且必须放到第一位,目的只是为了实现 overlay_control_device_t 和 hw_device_t 的匹配。定义结构体 overlay_control_device_t 的代码如下 所示。

struct overlay_control_device_t { struct hw_device_t common; int (*get)(struct overlay_control_device_t *dev, int name); //建立设备 overlay_t* (*createOverlay)(struct overlay_control_device_t *dev, uint32_t w, uint32_t h, int32_t format); //释放资源,分配的 handle, 和 control device 的内存 void (*destroyOverlay)(struct overlay_control_device_t *dev, overlay_t* overlay); //设置 overlay 的显示范围。(如果是 camera 的 preview, 那么 h, w 要和 preview h, w 一致) int (*setPosition)(struct overlay_control_device_t *dev, overlay_t* overlay, int x, int y, uint32_t w, uint32_t h); //获取 overlay 的显示范围 int (*getPosition)(struct overlay_control_device_t *dev, overlay_t* overlay, int* x, int* y, uint32_t* w, uint32_t* h); int (*setParameter)(struct overlay_control_device_t *dev, overlay_t* overlay, int param, int value); int (*stage)(struct overlay_control_device_t *dev, overlay_t* overlay); int (*commit)(struct overlay_control_device_t *dev, overlay_t* overlay); };

(5)定义结构 overlay_data_device_t,此结构和 overlay_control_device_t 类似。其中 overlay_ control_device_t 负责实现初始化、销毁和控制类的操作,overlay_data_device_t 的功能是显示内存 输出的数据操作。定义结构体 overlay_data_device_t 的代码如下所示。

struct overlay_data_device_t { struct hw_device_t common; //通过参数 handle 来初始化 data device int (*initialize)(struct overlay_data_device_t *dev, overlay_handle_t handle); //重新配置显示参数 w, h。 这两个参数生效这里需要 close 然后重新 open int (*resizeInput)(struct overlay_data_device_t *dev, uint32_t w, uint32_t h); //下面两个分别设置显示的区域,和获取显示的区域,当播放的时候,需要坐标和宽高来定义如何显示这些数据 int (*setCrop)(struct overlay_data_device_t *dev, uint32_t x, uint32_t y, uint32_t w, uint32_t h) ; int (*getCrop)(struct overlay_data_device_t *dev,

348 第 11 章 视频输出系统驱动

uint32_t* x, uint32_t* y, uint32_t* w, uint32_t* h) ; int (*setParameter)(struct overlay_data_device_t *dev, int param, int value); int (*dequeueBuffer)(struct overlay_data_device_t *dev, overlay_buffer_t *buf); int (*queueBuffer)(struct overlay_data_device_t *dev, overlay_buffer_t buffer); void* (*getBufferAddress)(struct overlay_data_device_t *dev, overlay_buffer_t buffer); int (*getBufferCount)(struct overlay_data_device_t *dev); int (*setFd)(struct overlay_data_device_t *dev, int fd); };

11.3.2 实现硬件抽象层 在实现 Overlay 系统的硬件抽象层时,具体实现方法取决于硬件和驱动程序,根据设备需要来 进行处理。 (1)FrameBuffer 驱动程序方式。 此方式需要先实现函数 getBufferAddress(),返回通过 mmap 获得 FrameBuffer 的指针即可。如 果没有双缓冲问题则不需要真正实现函数 dequeueBuffer()和 queueBuffer(),这两个函数是在文件 overlay.cpp 中定义的,此文件被保存在“hardware/libhardware/modules/overlay/overlay.cpp”目 录 中 。 函数 getBufferAddress()的功能是返回 FrameBuffer 内部显示的内存,通过 mmap 获取内存地址。 实现 getBufferAddress()函数的代码如下所示。

void* Overlay::getBufferAddress(overlay_buffer_t buffer) { if (mStatus != NO_ERROR) return NULL; return mOverlayData->getBufferAddress(mOverlayData, buffer); }

实现函数 dequeueBuffer()和 queueBuffer()的代码如下所示。

status_t Overlay::dequeueBuffer(overlay_buffer_t* buffer) { if (mStatus != NO_ERROR) return mStatus; return mOverlayData->dequeueBuffer(mOverlayData, buffer); }

status_t Overlay::queueBuffer(overlay_buffer_t buffer) { if (mStatus != NO_ERROR) return mStatus; return mOverlayData->queueBuffer(mOverlayData, buffer); }

(2)Video for Linux 2 方式。 如果使用 Video for Linux 2 的输出驱动,函数 dequeueBuffer()和函数 queueBuffer()在调用驱动 时,主要 ioctl 是一致的,即分别调用 VIDIOC_QBUF 和 VIDIOC_DQBUF 即可直接实现,其他的

349 Android 驱动开发与移植实战详解

初始化工作已在 initialize 中进行处理。因为存在视频数据队列,虽然在此处理的内容比一般的帧缓 冲区要复杂,但是可以实现更高的性能。 由此可以得出,在某一个硬件系统中,Overlay 的硬件层和 Overlay 系统的调用者都是特定实 现的,所以只需匹配上下层代码即可实现,并不需要满足每一个要求,各个接口可以根据具体情况 来灵活使用。

11.3.3 实现接口 Overlay 系统提供了接口 overlay,此接口用于叠加在主显示层上面的另外一个显示层。此叠加 的显示层经常作为视频的输出或相机取景器的预览界面来使用。文件 Overlay.h 的主要内部实现类 是 Overlay 和 overlayRef。OverlayRef 需要和 surface 配合来使用,通过 Isurface 可以创建出 OverlayRef。实现 RefBase 的主要代码如下所示。

class Overlay : public virtual RefBase { public: Overlay(const sp& overlayRef); void destroy(); //获取 overlay handle,可以根据自己的需要扩展,扩展之后就有很多数据了 overlay_handle_t getHandleRef() const; //获取 framebuffer 用于显示的内存地址 status_t dequeueBuffer(overlay_buffer_t* buffer); status_t queueBuffer(overlay_buffer_t buffer); status_t resizeInput(uint32_t width, uint32_t height); status_t setCrop(uint32_t x, uint32_t y, uint32_t w, uint32_t h) ; status_t getCrop(uint32_t* x, uint32_t* y, uint32_t* w, uint32_t* h) ; status_t setParameter(int param, int value); void* getBufferAddress(overlay_buffer_t buffer);

/*获取属性的信息*/ uint32_t getWidth() const; uint32_t getHeight() const; int32_t getFormat() const; int32_t getWidthStride() const; int32_t getHeightStride() const; int32_t getBufferCount() const; status_t getStatus() const;

private: virtual ~Overlay();

sp mOverlayRef; overlay_data_device_t *mOverlayData; status_t mStatus; };

Overlay(const sp& overlayRef);

350 第 11 章 视频输出系统驱动

在上述代码中,通过 surface 来控制 Overlay,也可以在不使用 Overlay 的情况下统一进行管理。 此处是通过 overlayRef 来创建 Overlay,一旦获取了 Overlay 就可以通过这个 Overlay 来获取到用 来显示的 Address 地址,向 Address 中写入数据后就可以显示我们的图像了。

11.4 实现 Overlay 硬件抽象层

在前面的内容中,介绍了 Overlay 系统的基本知识和硬件抽象层的原理。在接下来的内容中, 将详细讲解实现 Overlay 硬件抽象层的框架的基本知识,为步入本书后面知识的学习打下基础。 在 Android 系统中,提供了一个 Overlay 硬件抽象层的框架实现,在里面有完整的实现代码, 我们可以将其作为使用 Overlay 硬件抽象层的方法。但是在里面没有使用具体硬件,所以不会有实 际的现实效果。上述框架实现的源码目录如下所示。

hardware/libhardware/modules/overlay/

在上述目录中,主要包含了文件 Android.mk 和 overlay.cpp,其中文件 Android.mk 的主要代码 如下所示。

LOCAL_PATH := $(call my-dir)

# HAL module implemenation, not prelinked and stored in # hw/..so include $(CLEAR_VARS) LOCAL_PRELINK_MODULE := false LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw LOCAL_SHARED_LIBRARIES := liblog LOCAL_SRC_FILES := overlay.cpp LOCAL_MODULE := overlay.trout include $(BUILD_SHARED_LIBRARY)

Overlay 库是一个 C 语言库,没有被其他库所链接,在使用时是被动打开的。所以它必须被放 置在目标文件系统的“system/lib/hw”目录中。 文件 overlay.cpp 的主要代码如下所示。

//此结构体用于扩充 overlay_control_device_t 结构体 struct overlay_control_context_t { struct overlay_control_device_t device; /* our private state goes below here */ }; //此结构体用于扩充 overlay_data_device_t 结构体 struct overlay_data_context_t { struct overlay_data_device_t device; /* our private state goes below here */ };

//定义打开函数 static int overlay_device_open(const struct hw_module_t* module, const char* name,

351 Android 驱动开发与移植实战详解

struct hw_device_t** device);

static struct hw_module_methods_t overlay_module_methods = { open: overlay_device_open }; struct overlay_module_t HAL_MODULE_INFO_SYM = { common: { tag: HARDWARE_MODULE_TAG, version_major: 1, version_minor: 0, id: OVERLAY_HARDWARE_MODULE_ID, name: "Sample Overlay module", author: "The Android Open Source Project", methods: &overlay_module_methods, } static int overlay_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) { int status = -EINVAL; if (!strcmp(name, OVERLAY_HARDWARE_CONTROL)) { //Overlay 的控制设备 struct overlay_control_context_t *dev; dev = (overlay_control_context_t*)malloc(sizeof(*dev));

/*在此初始化我们的状态*/ memset(dev, 0, sizeof(*dev)); //初始化结构体

/* initialize the procs */ dev->device.common.tag = HARDWARE_DEVICE_TAG; dev->device.common.version = 0; dev->device.common.module = const_cast(module); dev->device.common.close = overlay_control_close;

dev->device.get = overlay_get; dev->device.createOverlay = overlay_createOverlay; dev->device.destroyOverlay = overlay_destroyOverlay; dev->device.setPosition = overlay_setPosition; dev->device.getPosition = overlay_getPosition; dev->device.setParameter = overlay_setParameter;

*device = &dev->device.common; status = 0; } else if (!strcmp(name, OVERLAY_HARDWARE_DATA)) { //Overlay 的数据设备 struct overlay_data_context_t *dev; dev = (overlay_data_context_t*)malloc(sizeof(*dev));

/* initialize our state here */ memset(dev, 0, sizeof(*dev)); //初始化结构体

/* 进程初始化 */ dev->device.common.tag = HARDWARE_DEVICE_TAG;

352 第 11 章 视频输出系统驱动

dev->device.common.version = 0; dev->device.common.module = const_cast(module); dev->device.common.close = overlay_data_close;

dev->device.initialize = overlay_initialize; dev->device.dequeueBuffer = overlay_dequeueBuffer; dev->device.queueBuffer = overlay_queueBuffer; dev->device.getBufferAddress = overlay_getBufferAddress;

*device = &dev->device.common; status = 0; } return status; }

11.5 在 OMAP 平台实现 Overlay 系统

经过本章前面内容的学习,了解了 Overlay 系统的基本知识,并分析了框架源码和 Android 源 码。在本节将简要介绍在 OMAP 平台中实现 Overlay 系统的基本知识,为读者步入本书后面知识 的学习打下基础。

11.5.1 实现输出视频驱动程序 在 OMAP 平台中,实现视频输出驱动程序的代码保存在“drivers/media/video/omap-vout/”目 录中。在此目录中主要包含如下文件。 文件 omapvout-dss.c 和 omapvout-dss.h:封装了 DSS 系统的功能。 文件 omapvout-mem.c 和 omapvout-mem.h:实现内存映射、释放和分配等功能。 文件 omapvout-vbq.c 和 omapvout-vbq.h:用于操作虚拟内存。 文件 omapvout.c 和 omapvout.h:这是 OMAP 平台的主框架,用于注册 V412 输出驱动程序 的接口。 在文件 omapvout.c 中通过函数 omapvout_probe()建立了多个 Video 输出设备,实现此函数的主 要代码如下所示。

static int __init omapvout_probe(struct platform_device *pdev, enum omap_plane plane, int vid) { struct omapvout_device *vout = NULL; int rc = 0; DBG("omapvout_probe %d %d\n", plane, vid); vout = kzalloc(sizeof(struct omapvout_device), GFP_KERNEL); if (vout == NULL) { rc = -ENOMEM; goto err0; } mutex_init(&vout->mtx);

353 Android 驱动开发与移植实战详解

vout->max_video_width = OMAPVOUT_VIDEO_MAX_WIDTH; vout->max_video_height = OMAPVOUT_VIDEO_MAX_HEIGHT; vout->max_video_bytespp = OMAPVOUT_VIDEO_MAX_BPP; rc = omapvout_dss_init(vout, plane); if (rc != 0) { printk(KERN_INFO "DSS init failed\n"); goto cleanup; } #ifdef CONFIG_VIDEO_OMAP_VIDEOOUT_BUFPOOL vout->bp = dev_get_drvdata(&pdev->dev); omapvout_bp_init(vout); #endif /* 注册 V4L2 接口 */ vout->vdev = omapvout_devdata; video_set_drvdata(&vout->vdev, vout); if (video_register_device(&vout->vdev, VFL_TYPE_GRABBER, vid) < 0) { printk(KERN_ERR MODULE_NAME": could not register with V4L2\n"); rc = -EINVAL; goto cleanup; } vout->id = plane; return 0; cleanup: omapvout_free_resources(vout); err0: dev_err(&pdev->dev, "failed to setup omapvout\n"); return rc; }

在文件 omapvout.c 中定义 ideo_device 类型的结构 omapvout_devdata,此结构体是在函数 omapvout_probe_device 中被注册的 Video 设备。定义结构 omapvout_devdata 的代码如下所示。

static struct video_device omapvout_devdata = { .name = MODULE_NAME, .fops = &omapvout_fops, .ioctl_ops = &omapvout_ioctl_ops, .vfl_type = VID_TYPE_OVERLAY | VID_TYPE_CHROMAKEY, .release = video_device_release, .minor = -1, };

11.5.2 实现 Overlay 硬件抽象层 在 OMAP 平台通过 Android 系统实现了 Overlay 硬件抽象层,此硬件抽象层是基于 v412 视频 驱动程序实现的。OMAP 平台的 Overlay 硬件抽象层在目录“hardware/ti/omap3/liboverlay/”中实现, 此目录下的构成文件如图 11-4 所示。 (1)文件 Android.mk。 文件 Android.mk 主要代码如下所示。

354 第 11 章 视频输出系统驱动

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS) LOCAL_PRELINK_MODULE := false LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw LOCAL_SHARED_LIBRARIES := liblog libcutils LOCAL_SRC_FILES := v4l2_utils.c overlay.cpp LOCAL_MODULE := overlay.omap3//设置库模块的名字是 overlay.omap3 include $(BUILD_SHARED_LIBRARY)

▲图 11-4 构成文件

通过上述代码,生成了名为 overlay.omap3 的动态库,被存放在目标系统的“/system/lib/hw” 中,这是 Android 标准的硬件模块。 (2)文件 overlay.cpp。 在文件 overlay.cpp 中提供了 OMAP 平台的 Overlay 硬件模块框架,其中函数 overlay_create Overlay()是 Overlay 控制设备的 createOverlay()指针的实现。函数 overlay_createOverlay()的实现代 码如下所示。

static overlay_t* overlay_createOverlay(struct overlay_control_device_t *dev, uint32_t w, uint32_t h, int32_t format) { LOGD("overlay_createOverlay:IN w=%d h=%d format=%d\n", w, h, format); LOG_FUNCTION_NAME;

overlay_object *overlay; overlay_control_context_t *ctx = (overlay_control_context_t *)dev; overlay_shared_t *shared;

int ret; uint32_t num = NUM_OVERLAY_BUFFERS_REQUESTED; int fd; int shared_fd;

if (format == OVERLAY_FORMAT_DEFAULT) { format = OVERLAY_FORMAT_YCbYCr_422_I; }

if (ctx->overlay_video1) { LOGE("Error - overlays already in use\n"); return NULL; }

355 Android 驱动开发与移植实战详解

shared_fd = create_shared_data(&shared);//创建内存共享区域 if (shared_fd < 0) { LOGE("Failed to create shared data"); return NULL; } //打开 Overlay 设备 fd = v4l2_overlay_open(V4L2_OVERLAY_PLANE_VIDEO1); if (fd < 0) { LOGE("Failed to open overlay device\n"); goto error; }//初始化 Overlay 设备 if (v4l2_overlay_init(fd, w, h, format)) { LOGE("Failed initializing overlays\n"); goto error1; } //设置剪切区域 if (v4l2_overlay_set_crop(fd, 0, 0, w, h)) { LOGE("Failed defaulting crop window\n"); goto error1; } //设置旋转 if (v4l2_overlay_set_rotation(fd, 0, 0)) { LOGE("Failed defaulting rotation\n"); goto error1; } //申请内存 if (v4l2_overlay_req_buf(fd, &num, 0)) { LOGE("Failed requesting buffers\n"); goto error1; }

overlay = new overlay_object(fd, shared_fd, shared->size, w, h, format, num); if (overlay == NULL) { LOGE("Failed to create overlay object\n"); goto error1; } //处理上下文 ctx->overlay_video1 = overlay;

overlay->setShared(shared);

shared->controlReady = 0; shared->streamEn = 0; shared->streamingReset = 0; shared->dispW = LCD_WIDTH; // 确定此属性 shared->dispH = LCD_HEIGHT;

LOGI("Opened video1/fd=%d/obj=%08lx/shm=%d/size=%d", fd, (unsigned long)overlay, shared_fd, shared->size);

356 第 11 章 视频输出系统驱动

LOGD("overlay_createOverlay: OUT"); return overlay;

error1: close(fd); error: destroy_shared_data(shared_fd, shared, true); return NULL; }

(3)文件 v4l2_utils.c。 在文件 v4l2_utils.c 中,通过函数 v4l2_overlay_open()来打开 Overlay 设备,此函数的实现代码 如下所示。

int v4l2_overlay_open(int id) { LOG_FUNCTION_NAME

if (id == V4L2_OVERLAY_PLANE_VIDEO1) return open("/dev/video1", O_RDWR); //打开第一个设备 else if (id == V4L2_OVERLAY_PLANE_VIDEO2) return open("/dev/video2", O_RDWR); //打开第二个设备 return -EINVAL; }

在上述代码中,参数 id 是 Overlay 设备的编号。从上述代码可以看出,在 Overlay 设备中已经 包含了“dev/video 1”和“dev/video 2”两个设备。在实现硬件抽象层时,先打开第一个来解决问 题,如果有需要再打开第二个。 在文件 v4l2_utils.c 中需要封装 v412 驱动程序,函数 v4l2_overlay_map_buf()在初始化阶段进行 定义,用于从取得设备中得到内存。函数 v4l2_overlay_map_buf()的实现代码如下所示。

int v4l2_overlay_map_buf(int fd, int index, void **start, size_t *len) { LOG_FUNCTION_NAME

struct v4l2_buffer buf; int ret; //查询信息 ret = v4l2_overlay_query_buffer(fd, index, &buf); if (ret) return ret;

if (is_mmaped(&buf)) { LOGE("Trying to mmap buffers that are already mapped!\n"); return -EINVAL; } *len = buf.length;

357 Android 驱动开发与移植实战详解

//映射内存 *start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (*start == MAP_FAILED) { LOGE("map failed, length=%u offset=%u\n", buf.length, buf.m.offset); return -EINVAL; } return 0; }

还需要调用函数 v4l2_overlay_query_buffer()调用 v412 的 ioctl 命令来查询内存,此函数的实现 代码如下所示。

int v4l2_overlay_query_buffer(int fd, int index, struct v4l2_buffer *buf) { LOG_FUNCTION_NAME

memset(buf, 0, sizeof(struct v4l2_buffer));

buf->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; //输出 Buffer 类型 Video buf->memory = V4L2_MEMORY_MMAP; //内存类型是从内核中得到的映射内存 buf->index = index; LOGI("query buffer, mem=%u type=%u index=%u\n", buf->memory, buf->type, buf->index); return v4l2_overlay_ioctl(fd, VIDIOC_QUERYBUF, buf, "querybuf ioctl"); }

还需要定义函数 v4l2_overlay_stream_on()来打开数据流,定义函数 v4l2_overlay_stream_off() 关闭数据流。这两个函数的实现代码如下所示。

int v4l2_overlay_stream_on(int fd) { LOG_FUNCTION_NAME int ret; uint32_t type = V4L2_BUF_TYPE_VIDEO_OUTPUT; ret = v4l2_overlay_set_local_alpha(fd, 1); if (ret) return ret; ret = v4l2_overlay_ioctl(fd, VIDIOC_STREAMON, &type, "stream on"); return ret; }

int v4l2_overlay_stream_off(int fd) { LOG_FUNCTION_NAME int ret; uint32_t type = V4L2_BUF_TYPE_VIDEO_OUTPUT; ret = v4l2_overlay_set_local_alpha(fd, 0); if (ret) return ret;

358 第 11 章 视频输出系统驱动

ret = v4l2_overlay_ioctl(fd, VIDIOC_STREAMOFF, &type, "stream off"); return ret; }

还需要定义函数 v4l2_overlay_q_buf()来对应 Overlay 数据设备的 queueBuffer 接口,定义函数 v4l2_overlay_dq_buf()来对应 Overlay 数据设备的 dequeueBuffer 接口。这两个函数的实现代码如下 所示。

int v4l2_overlay_q_buf(int fd, int index) { struct v4l2_buffer buf; int ret;

ret = v4l2_overlay_query_buffer(fd, buffer_cookie, index, &buf); if (ret) return ret; if (is_queued(buf)) { LOGE("Trying to queue buffer to kernel that is already queued!\n"); return -EINVAL } //输出 Buffer 类型 Video buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; buf.index = index; //内存类型是从内核中得到的映射内存 buf.memory = V4L2_MEMORY_MMAP; buf.field = V4L2_FIELD_NONE; buf.timestamp.tv_sec = 0; buf.timestamp.tv_usec = 0; buf.flags = 0;

return v4l2_overlay_ioctl(fd, VIDIOC_QBUF, &buf, "qbuf"); }

int v4l2_overlay_dq_buf(int fd, int *index) { struct v4l2_buffer buf; int ret;

/* ret = v4l2_overlay_query_buffer(fd, buffer_cookie, index, &buf); if (ret) return ret;

if (is_dequeued(buf)) { LOGE("Trying to dequeue buffer that is not in kernel!\n"); return -EINVAL } //输出 Buffer 类型 Video buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; //内存类型是从内核中得到的映射内存

359 Android 驱动开发与移植实战详解

buf.memory = V4L2_MEMORY_MMAP; ret = v4l2_overlay_ioctl(fd, VIDIOC_DQBUF, &buf, "dqbuf"); if (ret) return ret; *index = buf.index; return 0; }

11.6 系统层调用 Overlay 模块

经过本章前面内容的学习,了解了 Overlay 系统的基本知识。在本节简要介绍系统层调用 Overlay 模块的知识,为读者步入本书后面知识的学习打下基础。

11.6.1 测试文件 在如下文件“frameworks/base/libs/surfaceflinger/tests/overlays/overlays.cpp”中提供了简单调用 Overlay 的方法。上述测试程序在编译时会有如下编译失败提示。

sp surface = client->createSurface(getpid(), 0, 320, 240, PIXEL_FORMAT_UNKNOWN, ISurfaceComposer::ePushBuffers);

其实读者朋友们无须担心,造成上述错误的原因是申请 Surface 接口失败,和 Overlay 系统无关。

int main(int argc, char** argv) { //建立线程池 sp proc(ProcessState::self()); ProcessState::self()->startThreadPool();

//创建一个 SurfaceFlinger client sp client = new SurfaceComposerClient();

//创建一个 surface,最后那个参数是类型? sp surface = client->createSurface(getpid(), 0, 320, 240, PIXEL_FORMAT_UNKNOWN, ISurfaceComposer::ePushBuffers);

//取得 isurface 接口 sp isurface = Test::getISurface(surface); printf("isurface = %p\n", isurface.get());

//创建一个 overlay sp ref = isurface->createOverlay(320, 240, PIXEL_FORMAT_RGB_565); sp overlay = new Overlay(ref); /* *创建好 overlay 后,即可使用 overlay 的 API,这些都对应到 overlay HAL 的具体实现 */ overlay_buffer_t buffer; overlay->dequeueBuffer(&buffer); printf("buffer = %p\n", buffer);

360 第 11 章 视频输出系统驱动

void* address = overlay->getBufferAddress(buffer); printf("address = %p\n", address); overlay->queueBuffer(buffer);//最重要的操作就是通过 queueBuffer 将 buffer 列队 return 0; }

11.6.2 在 Android 系统中创建 Overlay Overlay 系统是一个功能强大的系统,不但实现了视频输出,而且还实现了和摄像头、GPS 等 有关的功能。Overlay 的具体应用如下所示。 (1)摄像头应用文件 CameraService.cpp(frameworks\base\camera\libcameraservice),实现流程 如下所示。

setPreviewDisplay()、startPreviewMode() | setOverlay() | creatOverlay()

相关内容将在本书后面的章节中进行介绍。 (2)界面相关应用文件 ISurface.cpp(frameworks\base\libs\ui) LayerBaseClient::Surface::onTransact() <--该函数位于 LayerBase.cpp,好像是用于 ibind 进程通 信的函数。其中函数 BnSurface::onTransact()有 5 种方式,只有确定有 overlay 硬件支持时才会调用 如下 case CREATE_OVERLAY 语句。

...... switch(code) { case REQUEST_BUFFER: { CHECK_INTERFACE(ISurface, data, reply); int bufferIdx = data.readInt32(); int usage = data.readInt32(); sp buffer(requestBuffer(bufferIdx, usage)); return GraphicBuffer::writeToParcel(reply, buffer.get()); } case REGISTER_BUFFERS: { CHECK_INTERFACE(ISurface, data, reply); BufferHeap buffer; buffer.w = data.readInt32(); buffer.h = data.readInt32(); buffer.hor_stride = data.readInt32(); buffer.ver_stride= data.readInt32(); buffer.format = data.readInt32(); buffer.transform = data.readInt32(); buffer.flags = data.readInt32(); buffer.heap = interface_cast(data.readStrongBinder()); status_t err = registerBuffers(buffer); reply->writeInt32(err); return NO_ERROR;

361 Android 驱动开发与移植实战详解

} break; case UNREGISTER_BUFFERS: { CHECK_INTERFACE(ISurface, data, reply); unregisterBuffers(); return NO_ERROR; } break; case POST_BUFFER: { CHECK_INTERFACE(ISurface, data, reply); ssize_t offset = data.readInt32(); postBuffer(offset); return NO_ERROR; } break; case CREATE_OVERLAY: { CHECK_INTERFACE(ISurface, data, reply); int w = data.readInt32(); int h = data.readInt32(); int f = data.readInt32(); sp o = createOverlay(w, h, f); return OverlayRef::writeToParcel(reply, o); } break; default: return BBinder::onTransact(code, data, reply, flags); ......

(3)文 件 LayerBuffer.cpp(frameworks\base\libs\surfaceflinger),此 文 件 是 createOverlay 的实现, 实现流程如下所示。

sp LayerBuffer::SurfaceLayerBuffer::createOverlay(uint32_t w, uint32_t h, int32_t format) | sp LayerBuffer::createOverlay(uint32_t w, uint32_t h, int32_t f) | sp source = new OverlaySource(*this, &result, w, h, f); //通过 OverlaySource 来创建 overlay

LayerBuffer::OverlaySource::OverlaySource()//此函数调用了 Overlay HAL 的 API createOverlay { overlay_control_device_t* overlay_dev = mLayer.mFlinger->getOverlayEngine();//get HAL overlay_t* overlay = overlay_dev->createOverlay(overlay_dev, w, h, format);//HAL API overlay_dev->setParameter(overlay_dev, overlay, OVERLAY_DITHER, OVERLAY_ENABLE); //设置参数,初始化 OverlayRef 类,OverlayRef 的构造函数在 Overlay.cpp 中 mOverlay = overlay; mWidth = overlay->w; mHeight = overlay->h; mFormat = overlay->format; mWidthStride = overlay->w_stride; mHeightStride = overlay->h_stride; mInitialized = false; ......

362 第 11 章 视频输出系统驱动

*overlayRef = new OverlayRef(mOverlayHandle, channel,mWidth, mHeight, mFormat, mWidthStride, mHeightStride); }

11.6.3 管理 Overlay HAL 模块 文件 Overlay.cpp(frameworks\base\libs\ui)的功能是管理 Overlay HAL 模块并封装 HAL 的 API, 具体实现流程如下所示。 (1)打开 Overlay HAL 模块,实现代码如下所示。

Overlay::Overlay(const sp& overlayRef) : mOverlayRef(overlayRef), mOverlayData(0), mStatus(NO_INIT) { mOverlayData = NULL; hw_module_t const* module; if (overlayRef != 0) { if (hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) { if (overlay_data_open(module, &mOverlayData) == NO_ERROR) { mStatus = mOverlayData->initialize(mOverlayData, overlayRef->mOverlayHandle); } } } }

(2)初始化 Overlay HAL,实现代码如下所示。

overlayRef = new OverlayRef(mOverlayHandle, channel,mWidth, mHeight, mFormat, mWidthStride, mHeightStride);

其构造函数位于文件 Overlay.cpp 中,对应代码如下所示。

OverlayRef::OverlayRef(overlay_handle_t handle, const sp& channel, uint32_t w, uint32_t h, int32_t f, uint32_t ws, uint32_t hs) : mOverlayHandle(handle), mOverlayChannel(channel), mWidth(w), mHeight(h), mFormat(f), mWidthStride(ws), mHeightStride(hs), mOwnHandle(false) { }

(3)需要封装需要的 API,比如 TI 自己编写的函数 opencore()用于负责视频输出。各个 API 的实现和编码请读者参考开源文件,在此将不再一一讲解。 虽然 Overlay 的输出对象有两种,一种是视频(主要是 YUV 格式,调用系统的 V4L2),另一 个是 ISurface 的一些图像数据(RGB 格式,直接写 framebuffer)。但是从代码实现角度看,目前 Android 系统默认并没有使用 Overlay 功能,虽然提供了 Skeleton 的 Overlay HAL,并对其进行封装, 但是上层几乎没有调用到封装的 API。 如果要用好 Overlay HAL,需要大量修改上层框架,这对视屏播放可能比较重要,可参考 TI 编写的实现文件 Android_surface_output_omap34xx.cpp。并且 Surface 实现的 Overlay 功能和 Copybit

363 Android 驱动开发与移植实战详解

的功能有部分重复,从 TI 的代码看主要是实现 V4L2 的 Overlay 功能。

11.7 抽象层实现(V4l2 驱动实现方式)

Overlay 的硬件抽象层是以模块的形式载入 Android 系统中的,主要工作就是实现这些接口, 不同的硬件平台可能会选择不同的驱动来实现。 在 Overlay.cpp 中利用 V4L2 驱动来实现 Overlay 的底层接口,创建 Overlay 对象的时候就要打 开 V4L2 设备并初始化,文件路径是“hardware/s3c6410/liboverlay/Overlay.cpp”,代码如下:

static overlay_t* overlay_createOverlay(struct overlay_control_device_t *dev,uint32_t w, uint32_t h, int32_t format) { LOG_FUNCTION_NAME; overlay_object *overlay; overlay_control_context_t *ctx = (overlay_control_context_t *)dev; overlay_shared_t *shared; int ret; uint32_t num = NUM_OVERLAY_BUFFERS_REQUESTED; int fd; int shared_fd; if (format == OVERLAY_FORMAT_DEFAULT) { format = OVERLAY_FORMAT_YCbYCr_422_I; } if (ctx->overlay_video1) { LOGE("Error - overlays already in use\n"); return NULL; } //创建一块共享内存 shared_fd = create_shared_data(&shared); if (shared_fd < 0) { LOGE("Failed to create shared data"); return NULL; } //打开 V4L2 设备 fd = v4l2_overlay_open(V4L2_OVERLAY_PLANE_VIDEO1); if (fd < 0) { LOGE("Failed to open overlay device\n"); goto error; } //数据大小,格式等进行初始化 if (v4l2_overlay_init(fd, w, h, format)) { LOGE("Failed initializing overlays\n"); goto error1; } //设置剪裁框 if (v4l2_overlay_set_crop(fd, 0, 0, w, h)) { LOGE("Failed defaulting crop window\n");

364 第 11 章 视频输出系统驱动

goto error1; } //得到旋转角度 if (v4l2_overlay_set_rotation(fd, 0, 0)) { LOGE("Failed defaulting rotation\n"); goto error1; } //申请分配一片内存 if (v4l2_overlay_req_buf(fd, &num, 0)) { LOGE("Failed requesting buffers\n"); goto error1; } overlay = new overlay_object(fd, shared_fd, shared->size, w, h, format, num); if (overlay == NULL) { LOGE("Failed to create overlay object\n"); goto error1; } ctx->overlay_video1 = overlay; overlay->setShared(shared); shared->controlReady = 0; shared->streamEn = 0; shared->streamingReset = 0; shared->dispW = LCD_WIDTH; shared->dispH = LCD_HEIGHT; return overlay; error1: close(fd); error: destroy_shared_data(shared_fd, shared, true); return NULL; }

在创建 Overlay 对象的时候,会打开 Video 设备(类似 Camera 系统),并对驱动视频数据的大小、 颜色格式、剪裁框、旋转角度等进行初始化,调用 V4l2_utils.c 中的 v4l2_overlay_req_buf()在内核 空间分配一片物理内存区域。文件路径是“hardware/s3c6410/liboverlay/V4l2_utils.c”。代码如下:

int v4l2_overlay_req_buf(int fd, uint32_t *num_bufs, int cacheable_buffers, int zerocopy) { struct v4l2_requestbuffers reqbuf; int ret, i; //设置 buffer 类型 reqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (zerocopy) reqbuf.memory = V4L2_MEMORY_USERPTR; else reqbuf.memory = V4L2_MEMORY_MMAP; //申请的 buffer 数量 reqbuf.count = *num_bufs; //调用驱动接口申请 ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf);

365 Android 驱动开发与移植实战详解

if (ret < 0) { error(fd, "reqbuf ioctl"); return ret; } if (reqbuf.count > *num_bufs) { error(fd, "Not enough buffer structs passed to get_buffers"); return -ENOMEM; } *num_bufs = reqbuf.count; return 0; }

这里申请的内存用来存储视频采集后的数据,在 Overlay 与驱动中的视频数据打交道的还有两 个重要接口,overlay_dequeueBuffer() 和 overlay_queueBuffer() 。 overlay_dequeueBuffer()会通过 v4l2_overlay_ dq_buf()来调用 V4L2 驱动中的接口,代码如下:

int overlay_dequeueBuffer(struct overlay_data_device_t *dev, overlay_buffer_t *buffer) { struct overlay_data_context_t* ctx = (struct overlay_data_context_t*)dev; int rc; int i = -1; pthread_mutex_lock(&ctx->shared->lock); if ( ctx->shared->streamingReset ) { ctx->shared->streamingReset = 0; pthread_mutex_unlock(&ctx->shared->lock); return ALL_BUFFERS_FLUSHED; } pthread_mutex_unlock(&ctx->shared->lock); if (ctx->shared->streamEn) { //调用 V4L2 工具中的接口 if ((rc = v4l2_overlay_dq_buf( ctx->ctl_fd, &i )) != 0) { LOGE("Failed to DQ/%d\n", rc); } else if (i < 0 || i > ctx->num_buffers) { rc = -EINVAL; } else { *((int *)buffer) = i; ctx->qd_buf_count --; } } else { rc = -1; } return rc; }

v4l2_overlay_dq_buf()会初始化 buffer 类型,从驱动中读上来一帧视频数据,代码如下:

int v4l2_overlay_dq_buf(int fd, int *index, int zerocopy) {

366 第 11 章 视频输出系统驱动

struct v4l2_buffer buf; int ret; //设置 buffer 类型 buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (zerocopy) buf.memory = V4L2_MEMORY_USERPTR; else buf.memory = V4L2_MEMORY_MMAP; //调用 ioctl 接口 ret = v4l2_overlay_ioctl(fd, VIDIOC_DQBUF, &buf, "dqbuf"); if (ret) return ret; //读取视频数据并保存 *index = buf.index; return 0; }

overlay_queueBuffer()通过 v4l2_overlay_q_buf()调用 V4L2 驱动中的接口,代码如下:

int overlay_queueBuffer(struct overlay_data_device_t *dev, overlay_buffer_t buffer) { struct overlay_data_context_t* ctx = (struct overlay_data_context_t*)dev; int cnt = 0; pthread_mutex_lock(&ctx->shared->lock); if ( ctx->shared->streamingReset ) { ctx->shared->streamingReset = 0; pthread_mutex_unlock(&ctx->shared->lock); return ALL_BUFFERS_FLUSHED; } pthread_mutex_unlock(&ctx->shared->lock); if (!ctx->shared->dataReady) { ctx->shared->dataReady = 1; enable_streaming(ctx->shared, ctx->ctl_fd); } if (!ctx->shared->controlReady) return -1; //调用 V4L2 工具中的接口 int rc = v4l2_overlay_q_buf( ctx->ctl_fd, (int)buffer, (int) ctx->zerocopy ); if (rc == 0 && ctx->qd_buf_count < ctx->num_buffers) { ctx->qd_buf_count ++; } return rc; }

v4l2_overlay_q_buf()初始化 buffer,并将其重新放入到缓存队列中,保存下一帧视频数据,代 码如下:

int v4l2_overlay_q_buf(int fd, int buffer, int zerocopy) { struct v4l2_buffer buf; int ret; if (zerocopy) {

367 Android 驱动开发与移植实战详解

uint8_t *pPhyYAddr; uint8_t *pPhyCAddr; struct fimc_buf fimc_src_buf; uint8_t index; memcpy(&pPhyYAddr, (void *) buffer, sizeof(pPhyYAddr)); memcpy(&pPhyCAddr, (void *) (buffer + sizeof(pPhyYAddr)), sizeof(pPhyCAddr)); memcpy(&index, (void *) (buffer + sizeof(pPhyYAddr) + sizeof(pPhyCAddr)), sizeof(index)); fimc_src_buf.base[0] = (dma_addr_t) pPhyYAddr; fimc_src_buf.base[1] = (dma_addr_t) pPhyCAddr; fimc_src_buf.base[2] = (dma_addr_t) (pPhyCAddr + (pPhyCAddr - pPhyYAddr)/4); buf.index = index; buf.memory = V4L2_MEMORY_USERPTR; buf.m.userptr = (unsigned long)&fimc_src_buf; buf.length = 0; } else { buf.index = buffer; buf.memory = V4L2_MEMORY_MMAP; } //初始化 buffer 参数 buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; buf.field = V4L2_FIELD_NONE; buf.timestamp.tv_sec = 0; buf.timestamp.tv_usec = 0; buf.flags = 0; //调用 ioctl 接口 return v4l2_overlay_ioctl(fd, VIDIOC_QBUF, &buf, "qbuf"); }

当 Overlay 的接口的功能全部实现了,则框架层调用 Overlay 时就可以将视频数据直接通过硬 件输出到屏幕上了,而不需要调用 Android 中 SurfaceFlinger.cpp 中的接口来混合并显示数据了。

368

第 12 章 振动器系统驱动

在手机系统中,振动器是比较常见的功能之一,例如可以将来电设置为震动模式,也可以将闹 钟设置为震动模式。在 Android 系统中也有振动系统模块,也能够实现上述来电铃声和闹钟的震动 设置。在本章的内容中,将详细讲解 Android 振动器系统驱动的实现和移植内容。

12.1 振动器系统结构

作为一名 Android 开发人员,应该都知道振动器负责控制引动电话的震动功能,Android 中的 振动器系统就是一个专供这方面功能的小系统,能够根据时间和来电而实现振动功能。 Android 振动系统包括驱动程序、硬件抽象层、JNI 部分、Java 框架类等几个部分,并且向 Java 应用程序层提供了简单的 API 作为平台接口。Android 振动器系统的基本层次结构如图 12-1 所示。

Android应用 API

Java框架

Vibrator震动类和VibratorService震动服务

本地框架

本地VibratorService JNI硬件抽象层

Android系统

具体震动设备 硬件和驱动

▲图 12-1 Android 振动器系统的框架结构

Android 振动器系统自下而上包含了驱动程序、振动器系统硬件抽象层、振动器系统 Java 框架 类、Java 框架中振动器系统使用等几个部分,其结构如图 12-2 所示。

Android 驱动开发与移植实战详解

Java应用

Java框架类

硬件抽象层

驱动程序 需要移植

▲图 12-2 振动器系统结构元素

在图 12-2 中,各个构成元素的具体说明如下所示。 (1)驱动程序:是某特定硬件平台振动器的驱动程序,通常基于 Android 的 Timed Output 驱动 框架来实现。 (2)硬件抽象层。 振动系统硬件抽象层接口路径如下:

hardware/libhardware_legacy/include/hardware_legacy/vibrator.h

振动系统的硬件抽象层在 Android 中的默认代码路径如下:

hardware/libhardware_legacy/vibrator/vibrator.c

因为 Android 振动器的硬件抽象层是 libhardware_legacy.so 的一部分,所以通常并不需要重新 实现。 (3)JNI 框架部分。 此部分的代码路径如下:

frameworks/base/services/jni/com_android_server_VibratorService.cpp

在此文件中定义了振动器的 JNI 部分,通过调用硬件抽象层向上层提供接口。 (4)Java 应用部分。 此部分的代码路径如下:

frameworks/base/services/java/com/android/server/VibratorService.java frameworks/base/core/java/android/os/Vibrator.java

VibratorService.java 通过调用 VibratorService JNI 来实现 com.android.server 包中的 VibratorService 类。VibratorService 类不是平台的 API,只被 Android 系统 Java 框架中的一小部分 调用。 在文件 Vibrator.java 中实现了 android.os 包中的 Vibrator 类,这是向 Java 层提供的 API。

370 第 12 章 振动器系统驱动

12.1.1 硬件抽象层 振动系统硬件抽象层接口的实现文件是 vibrator.h,主要代码如下所示。

#ifndef _HARDWARE_VIBRATOR_H #define _HARDWARE_VIBRATOR_H #if __cplusplus extern "C" { #endif

/** * 开始震动 * * @震动时间,单位毫秒 * * @返回 0 表示成功,返回 1 表示出错 */ int vibrator_on(int timeout_ms);

/** * 关闭 vibrator * * @返回 0 表示成功,返回 1 表示出错 */ int vibrator_off();

#if __cplusplus } // extern "C" #endif #endif // _HARDWARE_VIBRATOR_H

振动系统的硬件抽象层在 Android 中的默认代码路径如下:

hardware/libhardware_legacy/vibrator/vibrator.c

文件 vibrator.c 的主要代码如下所示。

#include #include #include #include

#define THE_DEVICE "/sys/class/timed_output/vibrator/enable"

static int sendit(int timeout_ms) { int nwr, ret, fd; char value[20]; #ifdef QEMU_HARDWARE if (qemu_check()) {

371 Android 驱动开发与移植实战详解

return qemu_control_command( "vibrator:%d", timeout_ms ); } #endif

fd = open(THE_DEVICE, O_RDWR); if(fd < 0) return errno;

nwr = sprintf(value, "%d\n", timeout_ms); ret = write(fd, value, nwr);

close(fd);

return (ret == nwr) ? 0 : -1; }

int vibrator_on(int timeout_ms) { /* 是一个常数,由最大值允许的时间决定*/ return sendit(timeout_ms); } int vibrator_off() { return sendit(0); }

12.1.2 JNI 框架部分 JNI 框架部分的实现文件是 com_android_server_VibratorService.cpp,主要代码如下所示。

#define LOG_TAG "VibratorService" #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include #include #include #include namespace android {

static void vibratorOn(JNIEnv *env, jobject clazz, jlong timeout_ms) { // LOGI("vibratorOn\n"); vibrator_on(timeout_ms); }

static void vibratorOff(JNIEnv *env, jobject clazz) { // LOGI("vibratorOff\n"); vibrator_off();

372 第 12 章 振动器系统驱动

} //定义 JNI 的方法 static JNINativeMethod method_table[] = { { "vibratorOn", "(J)V", (void*)vibratorOn }, //振动器开 { "vibratorOff", "()V", (void*)vibratorOff } //振动器关 };

int register_android_server_VibratorService(JNIEnv *env) { return jniRegisterNativeMethods(env, "com/android/server/VibratorService", method_table, NELEM(method_table)); }

};

在上述代码中,核心功能是通过 JNINativeMethod method_table[]和 register_android_server_ VibratorService()实现的。其中使用如下文件通过调用 VibratorService JNI 来实现 com.android.server 包中的 VibratorService 类。

frameworks/base/services/java/com/android/server/VibratorService.java

并且通过文件“frameworks/base/core/java/android/os/Vibrator.java”实现了 android.os 包中的 Vibrator 类,然后获得名称为 vibrator 的服务,并配合同目录中的 IVibratorService.aidl 文件向应用 程序层提供 Vibrator 的相关 API。

12.2 开始移植

了解了和振动器系统相关构成元素之后,在本节的内容中我们开始讲解具体移植的方法。根据 特定的硬件平台,有如下两种移植振动器系统的方法。 (1)由于已经具有硬件抽象层,振动器系统的移植只需要实现驱动程序即可。这个驱动程序需 要基于 Android 内核中的 Timed Output 驱动框架。 (2)根据自己实现的驱动程序,在 libhardware_legacy.so 库中重新实现振动器的硬件抽象层定 义接口。因为振动器硬件抽象层的接口非常简单,所以此种实现方式非常简单。

12.2.1 移植振动器驱动程序 要想实现 Vibrator 驱动程序,只需要实现振动的接口即可。因为这是一个输出设备,所以需要 接受振动时间作为参数。在 Android 中,可以使用多种方式来实现 Vibrator 驱动程序。在此推荐基 于 Android 内核定义的 Timed Output 驱动程序框架实现 Vibrator 的驱动程序。 Timed Output 有“定时输出”之意,用于定时发出某个输出,此种驱动程序依然是基于 sys 文 件系统来完成的。 在文件 drivers/staging/android/timed_output.h 中定义了一个名为 timed_output_dev 的结构体,其 中包含了 enable 和 get_time 这两个函数指针,当实现结构体后,使用函数 timed_output_dev_register()

373 Android 驱动开发与移植实战详解

实现注册,使用函数 timed_output_dev_unregister()实现注销。 Timed Output 驱动程序框架将为每个设备在/sys/class/timed_output/目录中建立一个子目录,其 中设备子目录中的 enable 文件就是设备的控制文件。当读这个 enable 文件时表示获得剩余时间, 当写这个文件时表示根据时间振动。虽然 Timed Output 类型驱动本身有获得剩余时间的能力(读 enable 文件),但是在 Android Vibrator 硬件抽象层以上的各层接口都没有使用这个功能。 通过 sys 文件系统可以调试 Timed Output 驱动设备,对于 Vibrator 设备来说,其实现的 Timed Output 驱动程序的名称是“vibrator”,所以 Vibrator 设备在 sys 文件系统中的方法如下所示。

# echo "10000" > /sys/class/timed_output/vibrator/enable # cat /sys/class/timed_output/vibrator/enable 3290 # echo "0" > /sys/class/timed_output/vibrator/enable

对于 enable 文件来说,“写”表示使能指定的时间,“读”表示获取剩余时间。

12.2.2 实现硬件抽象层 接下来开始讲解实现硬件抽象层。

1.硬件抽象层的接口

由前面的知识可以了解到,Vibrator 硬件抽象层的接口在如下文件中:

hardware/libhardware_legacy/include/hardware_legacy/vibrator.h

文件 vibrator.h 的核心代码如下所示。

int vibrator_on(int timeout_ms); // 开始振动 int vibrator_off(); // 关闭振动

在文件 vibrator.h 中定义了两个接口,分别表示振动和关闭,振动开始以毫秒(ms)作为时间 单位。

2.实现标准硬件抽象层

Vibrator 硬件抽象层是标准的实现代码,定义在如下文件中。

hardware/libhardware_legacy/vibrator/vibrator.c

文件 vibrator.c 的核心内容是函数 sendit(),此函数的实现代码如下所示。

#define THE_DEVICE "/sys/class/timed_output/vibrator/enable" static int sendit(int timeout_ms) { int nwr, ret, fd; char value[20]; #ifdef QEMU_HARDWARE // 使用 QEMU 的情况 if (qemu_check()) {

374 第 12 章 振动器系统驱动

return qemu_control_command( "vibrator:%d", timeout_ms ); } #endif fd = open(THE_DEVICE, O_RDWR); // 读取 sys 文件系统中的内容 if(fd < 0) return errno; nwr = sprintf(value, "%d\n", timeout_ms); ret = write(fd, value, nwr); close(fd); return (ret == nwr) ? 0 : -1; }

上述 sendit()函数的功能是根据时间进行“振动”,在真实的硬件中是通过 sys 文件系统的文件 进行控制的,如果是模拟器环境则通过 QEMU 发送命令。其中 vibrator_on()调用 sendit()以时间作 为参数,vibrator_on()调用 sendit()以 0 作为参数。

12.3 在 MSM 平台实现振动器驱动

在 MSM 的 Mahimahi 平台中,因为是基于 Timed Output 驱动程序框架的驱动程序来实现 Vibrator 的,所以不需要再实现硬件抽象层。 在 Mahimahi 平台中,Vibrator 驱动程序在如下内核文件中实现。

arch/arm/mach-msm/msm_vibrator.c

文件 msm_vibrator.c 的实现代码如下所示。

#include #include #include #include #include <../../../drivers/staging/android/timed_output.h> #include

#include

#define PM_LIBPROG 0x30000061 #if (CONFIG_MSM_AMSS_VERSION == 6220) || (CONFIG_MSM_AMSS_VERSION == 6225) #define PM_LIBVERS 0xfb837d0b #else #define PM_LIBVERS 0x10001 #endif

#define HTC_PROCEDURE_SET_VIB_ON_OFF 21 #define PMIC_VIBRATOR_LEVEL (3000)

static struct work_struct work_vibrator_on; static struct work_struct work_vibrator_off; static struct hrtimer vibe_timer;

375 Android 驱动开发与移植实战详解

static void set_pmic_vibrator(int on) { static struct msm_rpc_endpoint *vib_endpoint; struct set_vib_on_off_req { /* 定义 RPC 的端点 */ struct rpc_request_hdr hdr; uint32_t data; } req; if (!vib_endpoint) { vib_endpoint = msm_rpc_connect(PM_LIBPROG, PM_LIBVERS, 0); if (IS_ERR(vib_endpoint)) { printk(KERN_ERR "init vib rpc failed!\n"); vib_endpoint = 0; return; } } if (on) req.data = cpu_to_be32(PMIC_VIBRATOR_LEVEL); /* 得到请求时间 */ else req.data = cpu_to_be32(0); msm_rpc_call(vib_endpoint, HTC_PROCEDURE_SET_VIB_ON_OFF, &req, sizeof(req), 5 * HZ); /* 进行 RPC 调用 */ }

static void pmic_vibrator_on(struct work_struct *work) { set_pmic_vibrator(1); }

static void pmic_vibrator_off(struct work_struct *work) { set_pmic_vibrator(0); }

static void timed_vibrator_on(struct timed_output_dev *sdev) { schedule_work(&work_vibrator_on); }

static void timed_vibrator_off(struct timed_output_dev *sdev) { schedule_work(&work_vibrator_off); }

static void vibrator_enable(struct timed_output_dev *dev, int value) { hrtimer_cancel(&vibe_timer);

if (value == 0)

376 第 12 章 振动器系统驱动

timed_vibrator_off(dev); else { value = (value > 15000 ? 15000 : value); timed_vibrator_on(dev); hrtimer_start(&vibe_timer, ktime_set(value / 1000, (value % 1000) * 1000000), HRTIMER_MODE_REL); } }

static int vibrator_get_time(struct timed_output_dev *dev) { if (hrtimer_active(&vibe_timer)) { ktime_t r = hrtimer_get_remaining(&vibe_timer); return r.tv.sec * 1000 + r.tv.nsec / 1000000; } else return 0; }

static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer) { timed_vibrator_off(NULL); return HRTIMER_NORESTART; }

static struct timed_output_dev pmic_vibrator = { .name = "vibrator", .get_time = vibrator_get_time, .enable = vibrator_enable, };

void __init msm_init_pmic_vibrator(void) { INIT_WORK(&work_vibrator_on, pmic_vibrator_on); INIT_WORK(&work_vibrator_off, pmic_vibrator_off); hrtimer_init(&vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); /* 定时器 */ vibe_timer.function = vibrator_timer_func; timed_output_dev_register(&pmic_vibrator); /* 注册 timed_output_dev 设备 */ } MODULE_DESCRIPTION("timed output pmic vibrator device"); MODULE_LICENSE("GPL");

在上述实现代码中,核心功能是通过函数 set_pmic_vibrator()实现的,此函数通过 MSM 系统的 远程过程调用(RPC)实现了具体的功能,调用的指令由 HTC_PROCEDURE_SET_VIB_ON_OFF 指定。 上述 MSM 驱动程序的初始化处理是通过上述代码中的函数 msm_init_pmic_vibrator(void)实现 的,其中 vibrator_work 为 work_struct 类型,在队列的执行函数 update_vibrator 中,调用 set_pmic_vibrator()函数。

377 Android 驱动开发与移植实战详解

pmic_vibrator 是一个 timed_output_dev 类型的设备。其 enable 函数指针的实现 vibrator_enable 根据输入的数值开始定时器,并通过向调度队列进行输出操作。get_time 函数指针的实现 vibrator_get_time 则只是从定时器中获取剩余时间。 此处之所以使用定时器加队列的方式,是因为 enable 的调用将形成一个持续时间的效果,但 是调用本身并不宜阻塞,所以就让函数 vibrator_enable()退出后通过定时器实现效果。

378

第 13 章 Android 多媒体插件框架

前面三章分别讲解了 Android 音频系统驱动、视频系统驱动和 OpenMax 多媒体系统驱动的基 本知识。除了这三章中讲解的知识外,在 Android 多媒体领域中还涉及了其他的高级知识,例如本 章将要讲解的多媒体插件引擎。在本章将详细讲解 OpenCore 引擎和 Stagefright 的基本知识和移植 方法,为读者步入本书后面知识的学习打下基础。

13.1 Android 多媒体插件

在 Android 的多媒体系统中,可以根据需要添加一些第三方插件,这样可以增强多媒体系统的 功能。在 Android 系统的本地多媒体引擎上面,是 Android 的多媒体本地框架,而在多媒体本地框 架上面是多媒体 JNI 和多媒体的 Java 框架部分。和多媒体相关的应用程序通过 Android Java 框架层, 来提供标准的多媒体 API 进行构建。本章将要讲解的 OpenCore 引擎和 Stagefright 引擎是 Android 本地框架中定义接口的实现者,上层调用者不知道 Android 下层使用什么多媒体引擎。 Android 多媒体引擎和插件的基本层次结构如图 13-1 所示。

Android媒体应用 平台API

Java框架 Media的Java类

本地框架 Media JNI和Media本地框架库

IO插件 OpenCore Stagefright 其他引擎 Android系统

Codec驱动 硬件和驱动

▲图 13-1 Android 多媒体引擎和插件的基本层次

Android 驱动开发与移植实战详解

Android 系统的多媒体框架系统如图 13-2 所示。

▲图 13-2 Android 系统的多媒体框架结构

从多媒体应用的实现角度来看,多媒体系统主要包含如下两方面的内容。 (1)输入输出环节:音频、视频纯数据流的输入、输出系统。 (2)中间处理环节:包括文件格式处理和编码/解码环节处理。 假如想要处理一个 MP3 文件,媒体播放器的处理流程是:将一个 MP3 格式的文件作为播放器 的输入,将声音从播放器设备输出。在具体实现上,MP3 播放器经过了 MP3 格式文件解析、MP3 码流解码和 PCM 输出播放的过程。整个过程如图 13-3 所示。

MP3 PCM 编码流 数据

MP3 文件格式 解码 音频 文件 解析单元 单元 输出 设备

▲图 13-3 MP3 播放器结构

13.2 需要移植的内容

在移植多媒体插件时,主要包含了输入/输出环节和编码/解码环节这两方面的工作。其中输入/

380 第 13 章 Android 多媒体插件框架

输出环节主要是基于 Android 硬件抽象层来实现的,而编码/解码环节的工作通常是基于 OpenMax IL 层实现的。 在 Android 系统中,有两个常用的本地多媒体引擎,分别是 OpenCore 引擎和 Stagefright 引擎。 这两个引擎都实现了 Android 本地框架的 Media 部分,这样可以定义媒体播放器和媒体录制器。在 Android 2.2 前的版本中,都是使用 OpenCore 引擎实现多媒体功能。从 Android 2.2 开始,OpenCore 引擎和 Stagefright 引擎同时存在,并且主要通过 Stagefright 引擎实现媒体文件的播放。 OpenCore 引擎和 Stagefright 引擎支持两种插件类型,一种是实现媒体输入/输出环节的插件, 另外一种是实现编码/解码环节的插件。 (1)OpenCore 引擎:使用 MediaIO 形式实现媒体播放器的视频输出功能,使用 OpenMax 实现 编码和解码插件。 (2)Stagefright 引擎:使用 VideoRenderer 形式实现媒体播放器的视频输出功能,使用 Android 封装的 OpenMax 接口实现编码和解码插件。

13.3 OpenCore 引擎详解

在本节将详细讲解 OpenCore 引擎的基本知识,并分别介绍 OpenCore 引擎的具体结构和插件 机制,为读者步入本书后面知识的学习打下基础。

13.3.1 OpenCore 的层次结构 OpenCore 的另外一个常用的称呼是 PacketVideo,它是 Android 的多媒体核心。其实 PacketVideo 是一家公司的名称,而 OpenCore 是这套多媒体框架的软件层的名称。在 Android 开发者的眼中, 二者的含义基本相同。与其他 Android 程序库相比,OpenCore 的代码非常庞大,是基于 C++实现 的,定义了全功能的操作系统移植层,各种基本的功能均被封装成类的形式,各层次之间的接口多 使用继承等方式。OpenCore 是一个多媒体的框架,主要包含了如下两方面的内容。 (1)PVPlayer:提供了媒体播放器的功能,可以完成各种音频(Audio)、视频(Video)流的 回放(Playback)功能。 (2)PVAuthor:提供了记录媒体流的功能,可以完成各种音频(Audio)、视频(Video)流以 及静态图像捕获功能。 PVPlayer 和 PVAuthor 以 SDK 的形式提供给开发者,可以在这个 SDK 之上构建多种应用程序 和服务。在移动终端中常使用的多媒体应用程序,例如媒体播放器、照相机、录像机、录音机等。 OpenCore 的基本结构如图 13-4 所示。 在图 13-4 所示的结构中,主要层次元素的具体说明如下所示。 (1)OSCL:是 Compatibility Library 的缩写,意为操作系统兼容库。为了更 好地在不同操作系统移植,在里面包含了一些操作系统底层的操作,有基本数据类型、配置、字符 串工具、IO、错误处理、线程等内容,类似一个基础的 C++库。 (2)PVMF:是 PacketVideo Multimedia Framework 的缩写,意为 PV 多媒体框架,可以在框架 内实现一个文件解析(parser)和组成(composer)、编解码的 NODE,也可以继承其通用的接口,

381 Android 驱动开发与移植实战详解

在用户层实现一些 NODE。

Android Player Android Author Android HAL NODE NODE Android IO

Video Output Codec NODEs PVPlayer Engine PVAuthor Engine 2way Engine Camera FileFormat NODEs

Sink/Source NODEs

PVME 3rd Codec

OSCL

OS,lib

▲图 13-4 OpenCore 的层次结构

(3)PVPlayer Engine:是 PVPlayer 引擎。 (4)PVAuthor Engine:PVAuthor 引擎。 除了上述 4 个元素外,其实在 OpenCore 中包含的内容还有很多。从播放的角度看,PVPlayer 输入 (Source)的是文件或者网络媒体流,输出(Sink)的是音频视频的输出设备,其基本功能 包含了媒体流控制、文件解析、音频视频流的解码(Decode)等方面的内容。除了从文件中播放媒 体文件之外,还包含了与网络相关的 RTSP 流(Real Time Stream Protocol,实时流协议)。在媒体 流记录的方面,PVAuthor 的输入(Source)是照相机、麦克风等设备,输出(Sink)是各种文件, 包含了流的同步、音频视频流的编码(Encode)以及文件的写入等功能。 在使用 OpenCore SDK 的时候,有可能需要在应用程序层实现一个适配器(Adaptor),然后在 适配器上实现具体的功能,PVMF 的 NODE 也可以基于通用接口在上层实现,并且以插件的形式 使用。

13.3.2 OpenCore 的代码结构 在 Android 系统中,OpenCore 的代码保存在“external/opencore/”目录,此目录是 OpenCore 的根目录,在里面包含如下所示的子目录。

382 第 13 章 Android 多媒体插件框架

(1)android:是一个上层库,基于 PVPlayer 和 PVAuthor 的 SDK 实现了一个为 Android 使用 的 Player 和 Author。 (2)baselibs:在里面包含了数据结构和线程安全等内容的底层库。 (3)codecs_v2:这是一个内容较多的库,主要包含了编解码的实现和 OpenMAX 的实现。 (4)engines:包含 PVPlayer 和 PVAuthor 引擎的实现。 (5)extern_libs_v2:包含了 khronos 的 OpenMAX 的头文件。 (6)fileformats:文件格式的解析(parser)工具。 (7)nodes:在里面提供了 PVMF 的 NODE,主要是编解码和文件解析方面的 NODE。 (8)oscl:是操作系统兼容库。 (9)pvmi:包含了输入输出控制的抽象接口。 (10)protocols:主要包含了和网络相关的 RTSP、RTP、HTTP 等协议的内容。 (11)pvcommon:是 pvcommon 库文件的 Android.mk 文件,没有源文件。 (12)pvplayer:是 pvplayer 库文件的 Android.mk 文件,没有源文件。 (13)pvauthor:pvauthor 库文件的 Android.mk 文件,没有源文件。 (14)tools_v2:包含了编译工具以及一些可注册的模块。 除此之外,在“external/opencore/”目录中还包含了下面的 2 个文件。 Android.mk:全局的编译文件。 pvplayer.conf:配置文件。 在“external/opencore/”的各个子文件夹中还包含了很多个 Android.mk 文件,在这些文件之间 存在着“递归”的关系。例如在根目录下的 Android.mk 中包含了下面的内容片断。 include $(PV_TOP)/pvcommon/Android.mk。 include $(PV_TOP)/pvplayer/Android.mk。 include $(PV_TOP)/pvauthor/Android.mk。 这表示要引用 pvcommon 、 pvplayer 和 pvauthor 等文件夹下面的 Android.mk 文件。 “external/opencore/”目录中各个 Android.mk 文件可以按照排列组合进行使用,将几个 Android.mk 内容合并在一个库里面。

13.3.3 OpenCore 的编译结构 在 Android 开源版本中,下面是通过 OpenCore 编译出来的库。 libopencoreauthor.so:OpenCore 的 Author 库。 libopencorecommon.so:OpenCore 底层的公共库。 libopencoredownloadreg.so:下载注册库。 libopencoredownload.so:下载功能实现库。 libopencoremp4reg.so:MP4 注册库。 libopencoremp4.so:MP4 功能实现库。 libopencorenet_support.so:网络支持库。 libopencoreplayer.so:OpenCore 的 Player 库。

383 Android 驱动开发与移植实战详解

libopencorertspreg.so:RTSP 注册库。 libopencorertsp.so:RTSP 功能实现库。 OpenCore 中的各个库之间的关系如下所示。 libopencorecommon.so:是所有的库的依赖库,提供了公共的功能。 libopencoreplayer.so 和 libopencoreauthor.so:是两个并立的库,分别用于回放和记录,而且 这两个库是 OpenCore 对外的接口库。 libopencorenet_support.so:提供网络支持的功能。 除此之外,还有一些功能以插件(Plug-In)的方式放入 Player 中使用,每个功能使用两个库, 一个实现具体功能,一个用于注册。在接下来的内容中,将简要介绍 OpenCore 中各个库的基本结构。

1.库 libopencorecommon.so 的结构

库 libopencorecommon.so 是整个 OpenCore 的核心库,其编译控制的文件的路径如下所示。

pvcommon/Android.mk

上述文件使用递归的方式寻找子文件,其主要内容如下所示。

include $(BUILD_SHARED_LIBRARY) include $(PV_TOP)//oscl/oscl/osclbase/Android.mk include $(PV_TOP)//oscl/oscl/osclerror/Android.mk include $(PV_TOP)//oscl/oscl/osclmemory/Android.mk include $(PV_TOP)//oscl/oscl/osclutil/Android.mk include $(PV_TOP)//oscl/pvlogger/Android.mk include $(PV_TOP)//oscl/oscl/osclproc/Android.mk include $(PV_TOP)//oscl/oscl/osclio/Android.mk include $(PV_TOP)//oscl/oscl/osclregcli/Android.mk include $(PV_TOP)//oscl/oscl/osclregserv/Android.mk include $(PV_TOP)//oscl/unit_test/Android.mk include $(PV_TOP)//oscl/oscl/oscllib/Android.mk include $(PV_TOP)//pvmi/pvmf/Android.mk include $(PV_TOP)//baselibs/pv_mime_utils/Android.mk include $(PV_TOP)//nodes/pvfileoutputnode/Android.mk include $(PV_TOP)//baselibs/media_data_structures/Android.mk include $(PV_TOP)//baselibs/threadsafe_callback_ao/Android.mk include $(PV_TOP)//codecs_v2/utilities/colorconvert/Android.mk include $(PV_TOP)//codecs_v2/audio/gsm_amr/amr_nb/common/Android.mk include $(PV_TOP)//codecs_v2/video/avc_h264/common/Android.mk

这些被包含的 Android.mk 文件真正指定需要编译的文件,这些文件在 Android.mk 的目录及其 子目录中。事实上,在 libopencorecommon.so 库中包含了以下内容。 OSCL 的所有内容。 Pvmf 框架部分的内容(pvmi/pvmf/Android.mk)。 基础库中的一些内容(baselibs)。 编解码的一些内容。

384 第 13 章 Android 多媒体插件框架

文件输出的 node(nodes/pvfileoutputnode /Android.mk)。 从库 libopencorecommon.so 的结构可以看出,最终生成库的结构与 OpenCore 的层次关系并非 完全重合。在库 libopencorecommon.so 中已经包含了底层的 OSCL 的内容、PVMF 的框架以及 Node 和编解码的工具。

2.库 libopencoreplayer.so 的结构

库 libopencoreplayer.so 是一个用于实现播放功能的库,其编译控制的文件的路径如下所示。

pvplayer/Android.mk

上述文件 Android.mk 的主要代码如下所示。

include $(BUILD_SHARED_LIBRARY) include $(PV_TOP)//engines/player/Android.mk include $(PV_TOP)//codecs_v2/audio/aac/dec/util/getactualaacconfig/Android.mk include $(PV_TOP)//codecs_v2/video/avc_h264/dec/Android.mk include $(PV_TOP)//codecs_v2/audio/aac/dec/Android.mk include $(PV_TOP)//codecs_v2/audio/gsm_amr/amr_nb/dec/Android.mk include $(PV_TOP)//codecs_v2/audio/gsm_amr/amr_wb/dec/Android.mk include $(PV_TOP)//codecs_v2/audio/gsm_amr/common/dec/Android.mk include $(PV_TOP)//codecs_v2/audio/mp3/dec/Android.mk include $(PV_TOP)//codecs_v2/utilities/m4v_config_parser/Android.mk include $(PV_TOP)//codecs_v2/utilities/pv_video_config_parser/Android.mk include $(PV_TOP)//codecs_v2/omx/omx_common/Android.mk include $(PV_TOP)//codecs_v2/omx/omx_queue/Android.mk include $(PV_TOP)//codecs_v2/omx/omx_h264/Android.mk include $(PV_TOP)//codecs_v2/omx/omx_aac/Android.mk include $(PV_TOP)//codecs_v2/omx/omx_amr/Android.mk include $(PV_TOP)//codecs_v2/omx/omx_mp3/Android.mk include $(PV_TOP)//codecs_v2/omx/factories/omx_m4v_factory/Android.mk include $(PV_TOP)//codecs_v2/omx/omx_proxy/Android.mk include $(PV_TOP)//nodes/common/Android.mk include $(PV_TOP)//pvmi/content_policy_manager/Android.mk include $(PV_TOP)//pvmi/content_policy_manager/plugins/oma1/passthru/Android.mk include $(PV_TOP)//pvmi/content_policy_manager/plugins/common/Android.mk include $(PV_TOP)//pvmi/media_io/pvmiofileoutput/Android.mk include $(PV_TOP)//fileformats/common/parser/Android.mk include $(PV_TOP)//fileformats/id3parcom/Android.mk include $(PV_TOP)//fileformats/rawgsmamr/parser/Android.mk include $(PV_TOP)//fileformats/mp3/parser/Android.mk include $(PV_TOP)//fileformats/mp4/parser/Android.mk include $(PV_TOP)//fileformats/rawaac/parser/Android.mk include $(PV_TOP)//fileformats/wav/parser/Android.mk include $(PV_TOP)//nodes/pvaacffparsernode/Android.mk include $(PV_TOP)//nodes/pvmp3ffparsernode/Android.mk include $(PV_TOP)//nodes/pvamrffparsernode/Android.mk include $(PV_TOP)//nodes/pvmediaoutputnode/Android.mk include $(PV_TOP)//nodes/pvomxvideodecnode/Android.mk

385 Android 驱动开发与移植实战详解

include $(PV_TOP)//nodes/pvomxaudiodecnode/Android.mk include $(PV_TOP)//nodes/pvwavffparsernode/Android.mk include $(PV_TOP)//pvmi/recognizer/Android.mk include $(PV_TOP)//pvmi/recognizer/plugins/pvamrffrecognizer/Android.mk include $(PV_TOP)//pvmi/recognizer/plugins/pvmp3ffrecognizer/Android.mk include $(PV_TOP)//pvmi/recognizer/plugins/pvwavffrecognizer/Android.mk include $(PV_TOP)//engines/common/Android.mk include $(PV_TOP)//engines/adapters/player/framemetadatautility/Android.mk include $(PV_TOP)//protocols/rtp_payload_parser/util/Android.mk include $(PV_TOP)//android/Android.mk include $(PV_TOP)//android/drm/oma1/Android.mk include $(PV_TOP)//tools_v2/build/modules/linux_rtsp/core/Android.mk include $(PV_TOP)//tools_v2/build/modules/linux_rtsp/node_registry/Android.mk include $(PV_TOP)//tools_v2/build/modules/linux_net_support/core/Android.mk include $(PV_TOP)//tools_v2/build/modules/linux_download/core/Android.mk include $(PV_TOP)//tools_v2/build/modules/linux_download/node_registry/Android.mk include $(PV_TOP)//tools_v2/build/modules/linux_mp4/core/Android.mk include $(PV_TOP)//tools_v2/build/modules/linux_mp4/node_registry/Android.mk

在库 libopencoreplayer.so 中包含了下面的内容。 解码工具。 文件的解析器(MP4)。 解码工具对应的 Node。 player 的引擎部分(路径是“engines/player/Android.mk”)。 为 Android 使用的 player 适配器(路径是“android/Android.mk”)。 识别工具(路径是“pvmi/recognizer”)。 编解码工具中的 OpenMax 部分(路径是“codecs_v2/omx”)。 对应几个插件 Node 的注册。 库 libopencoreplayer.so 中的内容较多,其中主要为各个文件解析器和解码器,PVPlayer 的核心 功能在文件“engines/player/Android.mk”中,而文件“android/Android.mk”的内容比较特殊,它 是在 PVPlayer 之上构建的一个为 Android 使用的播放器。

3.库 libopencoreauthor.so 的结构

库 libopencoreauthor.so 是实现媒体流记录的功能库,其编译控制的文件的路径如下所示。

pvauthor/Android.mk

上述文件 Android.mk 的主要代码如下所示。

include $(BUILD_SHARED_LIBRARY) include $(PV_TOP)//engines/author/Android.mk include $(PV_TOP)//codecs_v2/video/m4v_h263/enc/Android.mk include $(PV_TOP)//codecs_v2/audio/gsm_amr/amr_nb/enc/Android.mk include $(PV_TOP)//codecs_v2/video/avc_h264/enc/Android.mk include $(PV_TOP)//fileformats/mp4/composer/Android.mk include $(PV_TOP)//nodes/pvamrencnode/Android.mk

386 第 13 章 Android 多媒体插件框架

include $(PV_TOP)//nodes/pvmp4ffcomposernode/Android.mk include $(PV_TOP)//nodes/pvvideoencnode/Android.mk include $(PV_TOP)//nodes/pvavcencnode/Android.mk include $(PV_TOP)//nodes/pvmediainputnode/Android.mk include $(PV_TOP)//android/author/Android.mk

在库 libopencoreauthor.so 中包含了如下内容。 编码工具,例如视频流 H263、H264,音频流 Amr。 文件的组成器,例如 MP4。 编码工具对应的 Node。 用于媒体输入的 Node(目录是“nodes/pvmediainputnode/Android.m”)。 author 引擎(目录是“engines/author /Android.mk”)。 Android 的 author 适配器(目录是“android/author/Android.mk”)。 在库 libopencoreauthor.so 中,其内容主要由各个文件编码器和文件组成器构成,其中 PVAuthor 的核心功能在“engines/author /Android.mk”目录中,而文件“android/author/Android.mk”是在 PVAuthor 之上构建的一个为 Android 使用的媒体记录器。

4.其他库

除了前面介绍的 4 个库之外,在 OpenCore 中还有另外几个库,具体说明如下所示。 网络支持库 libopencorenet_support.so,对应的 Android.mk 文件的路径如下所示。

tools_v2/build/modules/linux_net_support/core/Android.mk

MP4 功能实现库 libopencoremp4.so 和注册库 libopencoremp4reg.so,对应的 Android.mk 文件的 路径如下所示。

tools_v2/build/modules/linux_mp4/core/Android.mk tools_v2/build/modules/linux_mp4/node_registry/Android.mk

RTSP 功能实现库 libopencorertsp.so 和注册库 libopencorertspreg.so,对应的 Android.mk 文件的 路径如下所示。

tools_v2/build/modules/linux_rtsp/core/Android.mk tools_v2/build/modules/linux_rtsp/node_registry/Android.mk

下载功能实现库 libopencoredownload.so 和注册库 libopencoredownloadreg.so ,对应的 Android.mk 文件的路径如下所示。

tools_v2/build/modules/linux_download/core/Android.mk tools_v2/build/modules/linux_download/node_registry/Android.mk

13.3.4 OpenCore OSCL OSCL 是 Operating System Compatibility Library(操作系统兼容库)的缩写,在里面包含了一 些不同操作系统中移植层的功能,其代码结构如下所示。

387 Android 驱动开发与移植实战详解

oscl/oscl |-- config:配置的宏 |-- makefile |-- makefile.pv |-- osclbase:包含基本类型、宏以及一些 STL 容器类似的功能 |-- osclerror:错误处理的功能 |-- osclio:文件 IO 和 Socket 等功能 |-- oscllib:动态库接口等功能 |-- osclmemory:内存管理、自动指针等功能 |-- osclproc:线程、多任务通信等功能 |-- osclregcli:注册客户端的功能 |-- osclregserv:注册服务器的功能 `-- osclutil:字符串等基本功能 在目录“oscl”中,通常用一个目录表示一个模块。OSCL 对应的功能非常详细,几乎封装 C 语言中的每一个细节功能,并且提供了 C++接口提供上层使用。其实在 OperCore 中的 PVMF 和 Engine 都在使用 OSCL,整个 OperCore 的调用者也需要使用 OSCL。 在实现 OSCL 时,简单封装了很多典型的 C 语言函数,例如 osclutil 中与数学相关的功能在 oscl_math.inl 中被定义成为了内嵌(inline)的函数,代码如下所示。

OSCL_COND_EXPORT_REF OSCL_INLINE double oscl_log(double value) { return (double) log(value); } OSCL_COND_EXPORT_REF OSCL_INLINE double oscl_log10(double value) { return (double) log10(value); } OSCL_COND_EXPORT_REF OSCL_INLINE double oscl_sqrt(double value) { return (double) sqrt(value); }

因为文件 oscl_math.inl 被 oscl_math.h 所包含,所以其结果是 oscl_log()等功能的使用等价于原 始的 log()等函数。 其实 OSCL 的实现比较复杂,很多 C 语言标准库的句柄都被定义成为了 C++类的形式,实现 起来会比较繁琐,但是幸运的是复杂性不是很高。以 oscllib 为例,其代码结构如下所示。 oscl/oscl/oscllib/ |-- Android.mk |-- build | `-- make | `-- makefile

388 第 13 章 Android 多媒体插件框架

`-- src |-- oscl_library_common.h |-- oscl_library_list.cpp |-- oscl_library_list.h |-- oscl_shared_lib_interface.h |-- oscl_shared_library.cpp `-- oscl_shared_library.h 其中文件oscl_shared_library.h 是提供给上层使用的动态库的接口功能,定义的接口代码如下所示。

class OsclSharedLibrary { public: OSCL_IMPORT_REF OsclSharedLibrary(); OSCL_IMPORT_REF OsclSharedLibrary(const OSCL_String& aPath); OSCL_IMPORT_REF ~OsclSharedLibrary(); OSCL_IMPORT_REF OsclLibStatus LoadLib(const OSCL_String& aPath); OSCL_IMPORT_REF OsclLibStatus LoadLib(); OSCL_IMPORT_REF void SetLibPath(const OSCL_String& aPath); OSCL_IMPORT_REF OsclLibStatus QueryInterface(const OsclUuid& aInterfaceId, OsclAny*& aInterfacePtr); OSCL_IMPORT_REF OsclLibStatus Close(); OSCL_IMPORT_REF void AddRef(); OSCL_IMPORT_REF void RemoveRef(); }

这些接口都与库的加载有关系,而在文件 oscl_shared_library.cpp 中,其具体的功能通过使用 函数 dlopen()等来实现。

13.3.5 实现 OpenCore 中的 OpenMax 部分 OpenCore 中的 OpenMax 是作为插件来实现的,只要封装了 OpenMax,就可以在 OpenCore 中 使用标准的 OpenMax。

1.OpenMax 结构

在 OpenCore 中,在目录“extern_libs_v2/khronos/openmax/include/”中的头文件中包含标准的 OpenMax。在文件“build_config/opencore_dynamic/Android_omx_aacdec_sharedlibrary.mk”中,声 明了插件 OpenMax 的主要库是 libomx_sharedlibrary.so,主要代码如下所示。

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_WHOLE_STATIC_LIBRARIES := \ libomx_aac_component_lib \ libpv_aac_dec LOCAL_MODULE := libomx_aacdec_sharedlibrary -include $(PV_TOP)/Android_platform_extras.mk -include $(PV_TOP)/Android_system_extras.mk

389 Android 驱动开发与移植实战详解

LOCAL_SHARED_LIBRARIES += libomx_sharedlibrary libopencore_common

include $(BUILD_SHARED_LIBRARY) include $(PV_TOP)/codecs_v2/omx/omx_aac/Android.mk include $(PV_TOP)/codecs_v2/audio/aac/dec/Android.mk

库 libomx_sharedlibrary.so 是为 omx 针对 OpenCore 的接口层库,也就是说在每个模拟器上 libomx_sharedlibrary.so 向外(即 OpenCore)提供的接口是一致的。库 libomx_sharedlibrary.so 可以 动态打开各个 OpenMax 的编码/解码模块,各个编码/解码模块通过调用 codecs_v2 中 audio 和 video 目录中软件的编码/解码库来实现。 在“opencore”的根目录中有一个名为 pvplayer.cfg 的文件,此文件的功能是实现 OpenCore 运 行过程的动态配置,此文件的主要代码如下所示。

(0x1d4769f0,0xca0c,0x11dc,0x95,0xff,0x08,0x00,0x20,0x0c,0x9a,0x66),"libopencore_rtspr eg.so" (0x1d4769f0,0xca0c,0x11dc,0x95,0xff,0x08,0x00,0x20,0x0c,0x9a,0x66),"libopencore_downl oadreg.so" (0x1d4769f0,0xca0c,0x11dc,0x95,0xff,0x08,0x00,0x20,0x0c,0x9a,0x66),"libopencore_mp4lo calreg.so" (0x6d3413a0,0xca0c,0x11dc,0x95,0xff,0x08,0x00,0x20,0x0c,0x9a,0x66),"libopencore_mp4lo calreg.so" (0xa054369c,0x22c5,0x412e,0x19,0x17,0x87,0x4c,0x1a,0x19,0xd4,0x5f),"libomx_sharedlibr ary.so"

2.OpenMax 接口

OpenCore 中的 OpenMax 接口是通过封装标准 OpenMax IL 层来构建的,这些接口的基本内容 相同,但是不同于标准的 OpenMax IL 层的 C 语言接口。在 OpenCore 中有如下三个和 OpenMax 接口相关的头文件。 opencore/codecs_v2/omx/omx_mastercore/include/omx_interface.h:定义插件接口。 opencore/codecs_v2/omx/omx_common/include/pv_omxcore.h:核心定义。 opencore/codecs_v2/omx/omx_baseclass/include/pv_omxcomponent.h:定义 PV 的 OpenMax 组件。 文件 omx_interface.h 定义了 OpenMax 接口的核心功能,在里面包含了各种函数指针的定义类型。

typedef OMX_ERRORTYPE OMX_APIENTRY(*tpOMX_Init)(void); typedef OMX_ERRORTYPE OMX_APIENTRY(*tpOMX_Deinit)(void); typedef OMX_ERRORTYPE OMX_APIENTRY(*tpOMX_ComponentNameEnum)( OMX_OUT OMX_STRING cComponentName, OMX_IN OMX_U32 nNameLength, OMX_IN OMX_U32 nIndex); typedef OMX_ERRORTYPE OMX_APIENTRY(*tpOMX_GetHandle)( OMX_OUT OMX_HANDLETYPE* pHandle, OMX_IN OMX_STRING cComponentName, OMX_IN OMX_PTR pAppData,

390 第 13 章 Android 多媒体插件框架

OMX_IN OMX_CALLBACKTYPE* pCallBacks); typedef OMX_ERRORTYPE OMX_APIENTRY(*tpOMX_FreeHandle)( OMX_IN OMX_HANDLETYPE hComponent); typedef OMX_ERRORTYPE(*tpOMX_GetComponentsOfRole)( OMX_IN OMX_STRING role, OMX_INOUT OMX_U32 *pNumComps, OMX_INOUT OMX_U8 **compNames); typedef OMX_ERRORTYPE(*tpOMX_GetRolesOfComponent)( OMX_IN OMX_STRING compName, OMX_INOUT OMX_U32 *pNumRoles, OMX_OUT OMX_U8 **roles); typedef OMX_ERRORTYPE OMX_APIENTRY(*tpOMX_SetupTunnel)( OMX_IN OMX_HANDLETYPE hOutput, OMX_IN OMX_U32 nPortOutput, OMX_IN OMX_HANDLETYPE hInput, OMX_IN OMX_U32 nPortInput); typedef OMX_ERRORTYPE(*tpOMX_GetContentPipe)( OMX_OUT OMX_HANDLETYPE *hPipe, OMX_IN OMX_STRING szURI);

typedef OMX_BOOL(*tpOMXConfigParser)( OMX_PTR aInputParameters, OMX_PTR aOutputParameters);

上述函数指针是 OpenMax 的核心方法,这些指针类型需要使用继承来设置。 在文件 omx_interface.h 中还定义了类 OMXInterface,在此类中包含了一系列函数,这些函数 返回的都是上面类型的函数指针。类 OMXInterface 是 OpenMax 直接实现 OpenCore 的接口。

3.OpenMax 组织结构

在文件“opencore/codecs_v2/omx/omx_sharedlibrary/interface/src/pv_omx_interface.cpp”中实现 了类 OMXInterface,并且是通过实现类里面的函数指针方式实现的。 在文件 pv_omx_interface.cpp 中,函数 PVGetInterface()和 PVReleaseInterface()是使用 C 语言导 出的函数,这两个函数的实现代码如下所示。

extern "C" { OSCL_EXPORT_REF OsclAny* PVGetInterface() { return PVOMXInterface::Instance(); } OSCL_EXPORT_REF void PVReleaseInterface(void* interface) { PVOMXInterface* pInterface = (PVOMXInterface*)interface; if (pInterface) { OSCL_DELETE(pInterface); } }

391 Android 驱动开发与移植实战详解

}

在文件 pv_omx_interface.cpp 中,类 PVOMXInterface 继承了 OMXInterface,在此类的构造函 数中设置了各个 OMXInterface 中的函数指针。构造函数 PVOMXInterface()的实现代码如下所示。

private: PVOMXInterface() { //设置指针的 OMX 的核心方法 pOMX_Init = OMX_Init; pOMX_Deinit = OMX_Deinit; pOMX_ComponentNameEnum = OMX_ComponentNameEnum; pOMX_GetHandle = OMX_GetHandle; pOMX_FreeHandle = OMX_FreeHandle; pOMX_GetComponentsOfRole = OMX_GetComponentsOfRole; pOMX_GetRolesOfComponent = OMX_GetRolesOfComponent; pOMX_SetupTunnel = OMX_SetupTunnel; pOMX_GetContentPipe = OMX_GetContentPipe; pOMXConfigParser = OMXConfigParser; };

我们介绍的上述构造函数,都是在文件“opencore/codecs_v2/omx/omx_common/src/pv_omxcore.cpp” 中实现的,此文件实现了 OpenMax 的核心功能。 文件“opencore/codecs_v2/omx/omx_common/src/pv_omxregistry.cpp”的功能是注册 OpenMax 模块,其主要实现代码如下所示。

//注册 MP3 解码器 OMX_ERRORTYPE Mp3Register() { ComponentRegistrationType *pCRT = (ComponentRegistrationType *) oscl_malloc(sizeof (ComponentRegistrationType)); if (pCRT) { pCRT->ComponentName = (OMX_STRING)"OMX.PV.mp3dec";//组件名 pCRT->RoleString[0] = (OMX_STRING)"audio_decoder.mp3"; pCRT->NumberOfRolesSupported = 1; pCRT->SharedLibraryOsclUuid = NULL; #if USE_DYNAMIC_LOAD_OMX_COMPONENTS pCRT->FunctionPtrCreateComponent = &OmxComponentFactoryDynamicCreate; pCRT->FunctionPtrDestroyComponent = &OmxComponentFactoryDynamicDestructor; pCRT->SharedLibraryName = (OMX_STRING)"libomx_mp3dec_sharedlibrary.so"; pCRT->SharedLibraryPtr = NULL; OsclUuid *temp = (OsclUuid *) oscl_malloc(sizeof(OsclUuid)); if (temp == NULL) { oscl_free(pCRT); // 释放内存 return OMX_ErrorInsufficientResources; } OSCL_PLACEMENT_NEW(temp, PV_OMX_MP3DEC_UUID);

392 第 13 章 Android 多媒体插件框架

pCRT->SharedLibraryOsclUuid = (OMX_PTR) temp; pCRT->SharedLibraryRefCounter = 0; #endif #if REGISTER_OMX_MP3_COMPONENT #if (DYNAMIC_LOAD_OMX_MP3_COMPONENT == 0) pCRT->FunctionPtrCreateComponent = &Mp3OmxComponentFactory; pCRT->FunctionPtrDestroyComponent = &Mp3OmxComponentDestructor; pCRT->SharedLibraryName = NULL; pCRT->SharedLibraryPtr = NULL; if (pCRT->SharedLibraryOsclUuid) oscl_free(pCRT->SharedLibraryOsclUuid); pCRT->SharedLibraryOsclUuid = NULL; pCRT->SharedLibraryRefCounter = 0; #endif #endif } else { return OMX_ErrorInsufficientResources; } return ComponentRegister(pCRT); } //WMA 格式解码 OMX_ERRORTYPE WmaRegister() { ComponentRegistrationType *pCRT = (ComponentRegistrationType *) oscl_malloc(sizeof (ComponentRegistrationType)); if (pCRT) { pCRT->ComponentName = (OMX_STRING)"OMX.PV.wmadec"; pCRT->RoleString[0] = (OMX_STRING)"audio_decoder.wma"; pCRT->NumberOfRolesSupported = 1; pCRT->SharedLibraryOsclUuid = NULL; #if USE_DYNAMIC_LOAD_OMX_COMPONENTS pCRT->FunctionPtrCreateComponent = &OmxComponentFactoryDynamicCreate; pCRT->FunctionPtrDestroyComponent = &OmxComponentFactoryDynamicDestructor; pCRT->SharedLibraryName = (OMX_STRING)"libomx_wmadec_sharedlibrary.so"; pCRT->SharedLibraryPtr = NULL; OsclUuid *temp = (OsclUuid *) oscl_malloc(sizeof(OsclUuid)); if (temp == NULL) { oscl_free(pCRT); //释放内存 return OMX_ErrorInsufficientResources; } OSCL_PLACEMENT_NEW(temp, PV_OMX_WMADEC_UUID); pCRT->SharedLibraryOsclUuid = (OMX_PTR) temp; pCRT->SharedLibraryRefCounter = 0; #endif #if REGISTER_OMX_WMA_COMPONENT #if (DYNAMIC_LOAD_OMX_WMA_COMPONENT == 0)

393 Android 驱动开发与移植实战详解

pCRT->FunctionPtrCreateComponent = &WmaOmxComponentFactory; pCRT->FunctionPtrDestroyComponent = &WmaOmxComponentDestructor; pCRT->SharedLibraryName = NULL; pCRT->SharedLibraryPtr = NULL; if (pCRT->SharedLibraryOsclUuid) oscl_free(pCRT->SharedLibraryOsclUuid); pCRT->SharedLibraryOsclUuid = NULL; pCRT->SharedLibraryRefCounter = 0; #endif #endif } else { return OMX_ErrorInsufficientResources; } return ComponentRegister(pCRT); }

4.实现 OpenMax 编码/解码组件

OpenMax 的主要功能是通过解码/编码组件实现的,各个组件的基本结构类似,它们的实现内 容实际上就是文件“opencore/codecs_v2/omx/omx_baseclass/include/pv_omxcomponent.h”中定义的 类 OmxComponentBase。假如要实现 MP3 格式文件的解码处理,则在如下目录中实现了 MP3 的解 码功能。

opencore/codecs_v2/omx/mp3

在上述目录中,文件 Android.mk 生成了名为 libomx_mp3_component_lib.so 的库,此静态库将 被连接生成动态库 libomx_mp3dec_sharedlibrary_lib。此 Android.mk 文件的主要代码如下所示。

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := \ src/mp3_dec.cpp \ src/omx_mp3_component.cpp \ src/mp3_timestamp.cpp LOCAL_MODULE := libomx_mp3_component_lib LOCAL_CFLAGS := $(PV_CFLAGS) LOCAL_ARM_MODE := arm LOCAL_STATIC_LIBRARIES := LOCAL_SHARED_LIBRARIES := LOCAL_C_INCLUDES := \ $(PV_TOP)/codecs_v2/omx/omx_mp3/src \ $(PV_TOP)/codecs_v2/omx/omx_mp3/include \ $(PV_TOP)/extern_libs_v2/khronos/openmax/include \ $(PV_TOP)/codecs_v2/omx/omx_baseclass/include \ $(PV_TOP)/codecs_v2/audio/mp3/dec/src \ $(PV_TOP)/codecs_v2/audio/mp3/dec/include \

394 第 13 章 Android 多媒体插件框架

$(PV_INCLUDES) LOCAL_COPY_HEADERS_TO := $(PV_COPY_HEADERS_TO) LOCAL_COPY_HEADERS := \ include/mp3_dec.h \ include/omx_mp3_component.h \ include/mp3_timestamp.h include $(BUILD_STATIC_LIBRARY)

在目录“opencore/codecs_v2/omx/omx_mp3/src/”中存在了如下三个文件。 mp3_dec.cpp:能够调用 MP3 解码器组件。 mp3_timestamp.cpp:能够实现时间戳功能。 omx_mp3_component.cpp:定义了 MP3 解码器组件。 在文件“opencore/codecs_v2/omx/omx_mp3/include/omx_mp3_component.h ”中定义类 OpenmaxMp3AO,此类继承了 OmxComponentAudio 类。

class OpenmaxMp3AO : public OmxComponentAudio { public: OpenmaxMp3AO(); ~OpenmaxMp3AO(); OMX_ERRORTYPE ConstructComponent(OMX_PTR pAppData, OMX_PTR pProxy); OMX_ERRORTYPE DestroyComponent(); OMX_ERRORTYPE ComponentInit(); OMX_ERRORTYPE ComponentDeInit(); static void ComponentGetRolesOfComponent(OMX_STRING* aRoleString); void ProcessData(); void SyncWithInputTimestamp(); void ProcessInBufferFlag(); void ResetComponent(); OMX_ERRORTYPE GetConfig( OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_INDEXTYPE nIndex, OMX_INOUT OMX_PTR pComponentConfigStructure); private: void CheckForSilenceInsertion(); void DoSilenceInsertion(); Mp3Decoder* ipMp3Dec; Mp3TimeStampCalc iCurrentFrameTS; };

在文件 omx_mp3_component.cpp 中定义了 MP3 解码器组件,通过函数 ProcessData()实现对 MP3 文件的解码处理。实现函数 ProcessData()的代码如下所示。

void OpenmaxMp3AO::ProcessData() { PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData IN"));

QueueType* pInputQueue = ipPorts[OMX_PORT_INPUTPORT_INDEX]->pBufferQueue; QueueType* pOutputQueue = ipPorts[OMX_PORT_OUTPUTPORT_INDEX]->pBufferQueue;

395 Android 驱动开发与移植实战详解

ComponentPortType* pInPort = (ComponentPortType*) ipPorts[OMX_PORT_INPUTPORT_INDEX]; ComponentPortType* pOutPort = ipPorts[OMX_PORT_OUTPUTPORT_INDEX]; OMX_COMPONENTTYPE* pHandle = &iOmxComponent;

OMX_U8* pOutBuffer;//输出缓冲区的指针 OMX_U32 OutputLength; //输出缓冲区的长度 OMX_S32 DecodeReturn; OMX_BOOL ResizeNeeded = OMX_FALSE;

OMX_U32 TempInputBufferSize = (2 * sizeof(uint8) * (ipPorts[OMX_PORT_INPUTPORT_ INDEX]->PortParam.nBufferSize));

if ((!iIsInputBufferEnded) || iEndofStream) { if (OMX_TRUE == iSilenceInsertionInProgress) { DoSilenceInsertion(); //If the flag is still true, come back to this routine again if (OMX_TRUE == iSilenceInsertionInProgress) { return; } }

//证实 prev 是否发布了 buffer if (OMX_TRUE == iNewOutBufRequired) { //证实是否一个新的输出缓冲区是可利用的 if (0 == (GetQueueNumElem(pOutputQueue))) { PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData OUT output buffer unavailable")); return; }

ipOutputBuffer = (OMX_BUFFERHEADERTYPE*) DeQueue(pOutputQueue); if (NULL == ipOutputBuffer) { PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData Error, Output Buffer Dequeue returned NULL, OUT")); return; } ipOutputBuffer->nFilledLen = 0; iNewOutBufRequired = OMX_FALSE;

//设置当前时间戳对输出缓冲区时间戳 ipOutputBuffer->nTimeStamp = iCurrentFrameTS.GetConvertedTs();

//在动态重组之前复制在当地存放的输出缓冲区

396 第 13 章 Android 多媒体插件框架

//被接受的新的 OMX 缓冲 if (OMX_TRUE == iSendOutBufferAfterPortReconfigFlag) { if ((ipTempOutBufferForPortReconfig) && (iSizeOutBufferForPortReconfig <= ipOutputBuffer->nAllocLen)) { oscl_memcpy(ipOutputBuffer->pBuffer, ipTempOutBufferForPortReconfig, iSizeOutBufferForPortReconfig); ipOutputBuffer->nFilledLen = iSizeOutBufferForPortReconfig; ipOutputBuffer->nTimeStamp = iTimestampOutBufferForPortReconfig; }

iSendOutBufferAfterPortReconfigFlag = OMX_FALSE;

//只有当充满时退还输出缓冲区 if ((ipOutputBuffer->nAllocLen - ipOutputBuffer->nFilledLen) < iOutputFrameLength) { ReturnOutputBuffer(ipOutputBuffer, pOutPort); }

//释放临时输出缓冲区 if (ipTempOutBufferForPortReconfig) { oscl_free(ipTempOutBufferForPortReconfig); ipTempOutBufferForPortReconfig = NULL; iSizeOutBufferForPortReconfig = 0; }

//Dequeue new output buffer if required to continue decoding the next frame if (OMX_TRUE == iNewOutBufRequired) { if (0 == (GetQueueNumElem(pOutputQueue))) { PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData OUT, output buffer unavailable")); return; } ipOutputBuffer = (OMX_BUFFERHEADERTYPE*) DeQueue(pOutputQueue); if (NULL == ipOutputBuffer) { PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData Error, Output Buffer Dequeue returned NULL, OUT")); return; } ipOutputBuffer->nFilledLen = 0; iNewOutBufRequired = OMX_FALSE; ipOutputBuffer->nTimeStamp = iCurrentFrameTS.GetConvertedTs(); } }

397 Android 驱动开发与移植实战详解

} /*标号缓冲的代码 * 根据 hMarkTargetComponent 设置规格 */ if (ipMark != NULL) { ipOutputBuffer->hMarkTargetComponent = ipMark->hMarkTargetComponent; ipOutputBuffer->pMarkData = ipMark->pMarkData; ipMark = NULL; }

if (ipTargetComponent != NULL) { ipOutputBuffer->hMarkTargetComponent = ipTargetComponent; ipOutputBuffer->pMarkData = iTargetMarkData; ipTargetComponent = NULL;

} //在此标记缓冲代码末端

pOutBuffer = &ipOutputBuffer->pBuffer[ipOutputBuffer->nFilledLen]; OutputLength = 0;

/*复制临时被存放的前个输入缓冲区的残余数据 *缓冲接踵而来的数据流 */ if (iTempInputBufferLength > 0 && ((iInputCurrLength + iTempInputBufferLength) < TempInputBufferSize)) { oscl_memcpy(&ipTempInputBuffer[iTempInputBufferLength], ipFrameDecodeBuffer, iInputCurrLength); iInputCurrLength += iTempInputBufferLength; iTempInputBufferLength = 0; ipFrameDecodeBuffer = ipTempInputBuffer; }

//输出缓冲区通过作为指针 DecodeReturn = ipMp3Dec->Mp3DecodeAudio(//设置 ipMp3Dec 的类型是 Mp3Decode (OMX_S16*) pOutBuffer,//输出缓冲区的指针 (OMX_U32*) & OutputLength,//输出缓冲区的长度 &(ipFrameDecodeBuffer), &iInputCurrLength, &iFrameCount,

&(ipPorts[OMX_PORT_OUTPUTPORT_INDEX]->AudioPcmMode),

&(ipPorts[OMX_PORT_INPUTPORT_INDEX]->AudioMp3Param), iEndOfFrameFlag, &ResizeNeeded); if (ResizeNeeded == OMX_TRUE)

398 第 13 章 Android 多媒体插件框架

{ if (0 != OutputLength) { iOutputFrameLength = OutputLength * 2;

//更新时间戳 iSamplesPerFrame = OutputLength / ipPorts[OMX_PORT_OUTPUTPORT_INDEX]-> AudioPcmMode.nChannels;

iCurrentFrameTS.SetParameters(ipPorts[OMX_PORT_OUTPUTPORT_INDEX]-> AudioPcmMode.nSamplingRate, iSamplesPerFrame); iOutputMilliSecPerFrame = iCurrentFrameTS.GetFrameDuration();

} iResizePending = OMX_TRUE;

/*不要退回引起的输出缓冲区,当地存放它 *并且等待动态接口重新构造完成*/ if ((NULL == ipTempOutBufferForPortReconfig)) { ipTempOutBufferForPortReconfig = (OMX_U8*) oscl_malloc(sizeof(uint8) * OutputLength * 2); if (NULL == ipTempOutBufferForPortReconfig) { PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData error, insufficient resources")); return; } } //复制 omx 输出缓冲区对临时内部缓冲 oscl_memcpy(ipTempOutBufferForPortReconfig, pOutBuffer, OutputLength * 2); iSizeOutBufferForPortReconfig = OutputLength * 2;

//设置当前时间戳对第一个产品框架的输出缓冲区时间戳 //以后将取消 iTimestampOutBufferForPortReconfig = iCurrentFrameTS.GetConvertedTs(); iCurrentFrameTS.UpdateTimestamp(iSamplesPerFrame); OutputLength = 0; OMX_COMPONENTTYPE* pHandle = (OMX_COMPONENTTYPE*) ipAppPriv->CompHandle; (*(ipCallbacks->EventHandler)) (pHandle, iCallbackData, OMX_EventPortSettingsChanged, //The command was completed OMX_PORT_OUTPUTPORT_INDEX, 0, NULL); } ipOutputBuffer->nFilledLen += OutputLength * 2; ipOutputBuffer->nOffset = 0; if (OutputLength > 0)

399 Android 驱动开发与移植实战详解

{ iCurrentFrameTS.UpdateTimestamp(iSamplesPerFrame); }

if (OMX_TRUE == iEndofStream) { if (MP3DEC_SUCCESS != DecodeReturn) { PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData EOS callback send")); (*(ipCallbacks->EventHandler)) (pHandle, iCallbackData, OMX_EventBufferFlag, 1, OMX_BUFFERFLAG_EOS, NULL); iEndofStream = OMX_FALSE; ipOutputBuffer->nFlags |= OMX_BUFFERFLAG_EOS; ReturnOutputBuffer(ipOutputBuffer, pOutPort); ipOutputBuffer = NULL; PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData OUT"));

return; } } if (MP3DEC_SUCCESS == DecodeReturn) { ipInputBuffer->nFilledLen = iInputCurrLength; } else if (MP3DEC_INCOMPLETE_FRAME == DecodeReturn) { oscl_memcpy(ipTempInputBuffer, ipFrameDecodeBuffer, iInputCurrLength); iTempInputBufferLength = iInputCurrLength; ipInputBuffer->nFilledLen = 0; iInputCurrLength = 0; } else { ipInputBuffer->nFilledLen = 0; iInputCurrLength = 0; PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData ErrorStreamCorrupt callback send")); (*(ipCallbacks->EventHandler)) (pHandle, iCallbackData, OMX_EventError, OMX_ErrorStreamCorrupt, 0,

400 第 13 章 Android 多媒体插件框架

NULL); }

//如果它经过译码器处理,充分地消耗后会退回到输入缓冲区 if (0 == ipInputBuffer->nFilledLen) { ReturnInputBuffer(ipInputBuffer, pInPort); ipInputBuffer = NULL; iIsInputBufferEnded = OMX_TRUE; iInputCurrLength = 0; }

//当充满时送回输出缓冲区 if ((ipOutputBuffer->nAllocLen - ipOutputBuffer->nFilledLen) < (iOutputFrameLength)) { ReturnOutputBuffer(ipOutputBuffer, pOutPort); ipOutputBuffer = NULL; } /*如果有有些处理在当前缓冲中则重新编排 AO*/ if (((iInputCurrLength != 0 || GetQueueNumElem(pInputQueue) > 0) && (GetQueueNumElem(pOutputQueue) > 0) && (ResizeNeeded == OMX_FALSE)) || (OMX_TRUE == iEndofStream)) { RunIfNotReady(); } }

PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_NOTICE, (0, "OpenmaxMp3AO : ProcessData OUT")); return; }

13.3.6 OpenCore 的扩展 OpenCore 本身提供了很强大的功能,在使用过程中还可以扩展 OpenCore。

1.OpenCore Node

在扩展 OpenCore 时,一般是基于 OpenCore 的框架为其增加固定的插件,插件主要做成 Node 的形式。和编、解码相关的 Node 有 pvomxbasedecnode、pvomxaudiodecnode、pvomxvideod ecnode、 pvomxencnode。 和文件格式相关的 Node 有 Pvwavffparsernode 、 pvaacffparsernode 、 pvamrffparsernode 、 pvmp3ffparsern ode、pvmp4ffparsernode、pvvideoparsernode、pvmp4f fcomposernode。 和输入输出相关的 Node 有 Pvmediainputnode、pvmediaoutputnode、pvdummyinput node、 pvdummyoutputnode、pvfileoutputnode、pvdownloadmanagernode 。 除了上述 Node 外,还包括 pvsocketnode 和 pvdownloadmanagernode 等其他功能的 Node。

401 Android 驱动开发与移植实战详解

2.MediaIO

MediaIO 的缩写是 MIO,在“opencore/pvmi/pvmf/include/”目录中有如下头文件定义。 pvmiMIOControl.h。 pvmi_media_transfer.h。 在实现的过程中只需要继承和构建其中的接口,然后由框架最终实现成为 Node 在 OpenCore 系统中使用。其实 MediaIO 是对 Node 的一种封装,将其封装成多媒体的输入输出环节。 在文件 pvmi_mio_control.h 中,定义类 PvmiMIOControl 来表示 MIO 的控制类接口,定义此类 的代码如下所示。

class PvmiMIOControl{ public: virtual ~PvmiMIOControl() {} virtual PVMFStatus connect(PvmiMIOSession& aSession, PvmiMIOObserver* aObserver) = 0; virtual PVMFStatus disconnect(PvmiMIOSession aSession) = 0; virtual PvmiMediaTransfer* createMediaTransfer( PvmiMIOSession& aSession, PvmiKvp* read_formats = NULL, int32 read_flags = 0, PvmiKvp* write_formats = NULL, int32 write_flags = 0) = 0; virtual void deleteMediaTransfer( PvmiMIOSession& aSession, PvmiMediaTransfer* media_transfer) = 0; virtual PVMFCommandId QueryUUID(const PvmfMimeString& aMimeType, Oscl_Vector& aUuids, bool aExactUuidsOnly = false, const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId QueryInterface(const PVUuid& aUuid, PVInterface*& aInterfacePtr, const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId Init(const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId Reset(const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId Start(const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId Pause(const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId Flush(const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId DiscardData(const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId DiscardData(PVMFTimestamp aTimestamp, const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId Stop(const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId CancelCommand(PVMFCommandId aCmd, const OsclAny* aContext = NULL) = 0; virtual PVMFCommandId CancelAllCommands(const OsclAny* aContext = NULL) = 0; virtual void ThreadLogon() = 0; virtual void ThreadLogoff() = 0; };

在上述代码中,有很多函数使用 OsclAny 类型的指针作为参数,这样的好处是可以使用所有数

402 第 13 章 Android 多媒体插件框架

据结构。其中接口 Init() 、 Reset() 、 Start() 、 Pause() 、 Flush() 和 Stop() 实现流控制,而函数 createMediaTransfer 用于得到类 PvmiMediaTransfer。 而在文件 pvmi_media_transfer.h 中,定义类 PvmiMediaTransfer 来表示 MIO 的数据接口,定义 此类的代码如下所示。

class PvmiMediaTransfer { public: virtual ~PvmiMediaTransfer() {} virtual void setPeer(PvmiMediaTransfer* aPeer) = 0; virtual void useMemoryAllocators(OsclMemAllocator* read_write_alloc) = 0; virtual PVMFCommandId writeAsync( uint8 format_type, int32 format_index, uint8* data, uint32 data_len, const PvmiMediaXferHeader& data_header_info, OsclAny* aContext = NULL) = 0; virtual void writeComplete( PVMFStatus aStatus, PVMFCommandId write_cmd_id, OsclAny* aContext) = 0; virtual PVMFCommandId readAsync( uint8* data, uint32 max_data_len, OsclAny* aContext = NULL, int32* formats = NULL, uint16 num_formats = 0) = 0; virtual void readComplete( PVMFStatus aStatus, PVMFCommandId read_cmd_id, int32 format_index, const PvmiMediaXferHeader& data_header_info, OsclAny* aContext) = 0; virtual void statusUpdate(uint32 status_flags) = 0; virtual void cancelCommand(PVMFCommandId command_id) = 0; virtual void cancelAllCommands() = 0; };

3.OpenCore Player

OpenCore 的 Player 的编译文件是“pvplayer/Android.mk ”,编译后将生成动态库文件 libopencoreplayer.so,在此库中包含了如下两方面的内容。 Player 的 Engine(引擎)。 为 Android 构建的 Player,是一个适配器(Adapter),Engine 的路径是“engine/player”, Adapter 的路径是“android”。 在库 libopencoreplayer.so 中包含了下面的内容。 解码工具。 文件的解析器。 解码工具对应的 Node。

403 Android 驱动开发与移植实战详解

Player 的引擎部分,编译文件是“engines/player/Android.mk”。 为 Android 构建的 Player 适配器,编译文件是“android/Android.mk”。 识别工具,目录是“pvmi/recognizer”。 编解码工具中的 OpenMAX 部分,目录是“codecs_v2/omx”。 对应插件 Node 的注册。 由此可见,库 libopencoreplayer.so 中的内容较多,其中主要功能是为各个文件解析器和解码器。 PVPlayer 的核心功能在文件“engines/player/Android.mk”中。而文件“android/Android.mk”的内 容比较特殊,功能上是在 PVPlayer 之上构建的一个可以供 Android 使用的播放器。 库 libopencoreplayer.so 的具体结构如图 13-5 所示。

PVPlayer

Video Output Audio Output Player Driver

PVMF

OpenCore 的公共库(libopencoreplayer .so)

▲图 13-5 库 libopencoreplayer.so 的结构图

(1)Player Engine(播放引擎)。 OpenCore 中的 Player Engine 具有明确的接口,不同的系统在此接口上可以根据具体情况实现 不同的 Player 。 Player Engine 位于 OpenCore 中的“engines/player/ ”目录下,其中在 “engines/player/include ”目录中保存的是接口头文件,在“engines/player/src”目录中保存的是源 文件和私有头文件。Player Engine 的类结构如图 13-6 所示。 (2)PVPlayer。 PVPlayer 的结构如图 13-7 所示。

404 第 13 章 Android 多媒体插件框架

PVPlayerInterface PVPlayerFactory

+GetPVPlayerState() +AddDataSource() +Init() PVPlayerDataSink PVPlayerDataSource +AddDataSink() +Prepare() +Start() +Pause() +Resume() +Stop() +RemoveDataSink() +Reset() PVPlayerDataSourceURL PVPlayerDataSinkPVMFNode PVPlayerDataSinkFilename +RemoveDataSource() PVPlayerDataSourcePVMFNode

PVPlayerEngine

▲图 13-6 Player Engine 的类结构图

android_audio_mio

Media android_audio_stream android_audio_output URL

android_surface_output

PV Player Engine Vidio Output Audio Output

▲图 13-7 PVPlayer 结构图

在图 13-7 中,Sink Node 会接收上一个 Node 写的动作。 PVPlayer 的类结构如图 13-8 所示。 (3)Author。 OpenCore 的 Author 的编译文件是“pvauthor/Android.mk”,编译后将生成动态库文件 libopencoreauthor.so,此库和 Player 类似,在里面包含了如下两方面的内容。 Author 的 Engine。 为 Android 构建的 Author。

405 Android 驱动开发与移植实战详解

MedioPlayerInterface

AndroidSurfaceOutput AndroidAudioOutput PVPlayer

PVPlayerDriver PVPlayerFactory

PVPlayerInterface PVPlayerFactory

+GetPVPlayerState() +AddDataSource() +Init() PVPlayerDataSink +AddData Sink() +Prepare() +Start() +Pause() +Stop() +RemoveDataSink() +Reset() PVPlayerDataSinkPVMFNode PVPlayerDataSinkFilename RemoveDataSource()

▲ 图 13-8 PVPlayer 的类结构图

OpenCore Author 的基本结构如图 13-9 所示。 在图 13-9 所示的结构中,目录“OpenCore/engines/author/”是 Author 引擎目录,在里面主要 包含了“include”和“src”两个目录。并且在头文件 pvauthorenginefactory.h 和 pvauthorengineinterface.h 中是接口。 OpenCore 中 Author 的主要功能文件是 pvauthorengine.cpp,类 Author Engine 的结构如图 13-10 所示。 PVAuthor 的结构如图 13-11 所示。

406 第 13 章 Android 多媒体插件框架

Libopencoreauthor.so

PVAuthor

Camera Input Author Audio Input Driver

Player Engine

PVMF

▲图 13-9 OpenCore Author 的基本结构图

pvauthorenginefactory pvauthorengineinterface

pvauthorengine

▲图 13-10 类 Author Engine 的结构图

android_audio_input

PVMediaRecorder Media File android_camera_input

Author Camera PV Author Engine Input

▲图 13-11 PVAuthor 的结构图

407 Android 驱动开发与移植实战详解

13.4 Stagefright 引擎

在 Android 上,预设的多媒体框架(Multimedia Framework)是 OpenCORE。OpenCORE 的特点 是兼顾了跨平台的移植性,而且已经过多方验证,所以相对来说比较稳定;但是其缺点是庞大复杂, 需要耗费相当多的时间去维护。从 Android 2.0 开始,Google 开始引入了架构稍微简单的 Stagefright, 并且有逐渐取代 OpenCORE 的趋势。

13.4.1 代码结构 Stagefright 是一个轻量级的多媒体框架,其主要功能是基于 OpenMax 实现的。在 Stagefright 中提供了媒体播放等接口,这些接口可以为 Android 框架层所使用。 在 Android 开源代码中,Stagefright 的头文件是“frameworks/base/include/media/stagefright/”。 实现 Stagefright 功能的文件是“frameworks/base/media/libstagefright/”。实现 Stagefright 播放器和录 音器功能的文件路径是“frameworks/base/media/libmediaplayerservice/”。测试 Stagefright 功能的文 件是“frameworks/base/cmds/stagefright/”。

13.4.2 实现 OpenMax 接口 Stagefright 可以实现 Android 系统中的 OpenMax 接口,可以让 Stagefright 引擎内的 OMXCode 调用实现的 OpenMax 接口,最终目的是使用 OpenMax IL 编码/解码功能。 由此可见,在 Android 系统中是通过 Stagefrigh 来定义 OpenMax 接口的,具体实现内容保存在 “omx”目录中。在头文件“media/libstagefright/include/OMX.h”中实现了 Android 标准的 IOMX 类, 此文件的主要代码如下所示。

class OMX : public BnOMX, public IBinder::DeathRecipient { public: OMX(); virtual bool livesLocally(pid_t pid); virtual status_t listNodes(List *list); virtual status_t allocateNode( const char *name, const sp &observer, node_id *node); virtual status_t freeNode(node_id node); virtual status_t sendCommand( node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param); virtual status_t getParameter( node_id node, OMX_INDEXTYPE index, void *params, size_t size); …………………………………………… virtual status_t emptyBuffer( node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, OMX_U32 flags, OMX_TICKS timestamp);

408 第 13 章 Android 多媒体插件框架

virtual status_t getExtensionIndex( node_id node, const char *parameter_name, OMX_INDEXTYPE *index); virtual sp createRenderer( const sp &surface, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, size_t encodedWidth, size_t encodedHeight, size_t displayWidth, size_t displayHeight, int32_t rotationDegrees);

文件“frameworks/base/media/libstagefright/omx/OMX.cpp”是上述 OMX.h 的实现文件,首先 定义函数 createRenderer()来创建映射,建立了一个“hardware renderer—SharedVideoRenderer (libstagefrighthw.so)”,如果失败则建立“software renderer—SoftwareRenderer (surface)”。实现此函 数的主要代码如下所示。

sp OMX::createRenderer( const sp &surface, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, size_t encodedWidth, size_t encodedHeight, size_t displayWidth, size_t displayHeight, int32_t rotationDegrees) { Mutex::Autolock autoLock(mLock); VideoRenderer *impl = NULL; void *libHandle = dlopen("libstagefrighthw.so", RTLD_NOW); if (libHandle) { typedef VideoRenderer *(*CreateRendererWithRotationFunc)( const sp &surface, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, size_t displayWidth, size_t displayHeight, size_t decodedWidth, size_t decodedHeight, int32_t rotationDegrees); typedef VideoRenderer *(*CreateRendererFunc)( const sp &surface, const char *componentName, OMX_COLOR_FORMATTYPE colorFormat, size_t displayWidth, size_t displayHeight, size_t decodedWidth, size_t decodedHeight); CreateRendererWithRotationFunc funcWithRotation = (CreateRendererWithRotationFunc)dlsym( libHandle, "_Z26createRendererWithRotationRKN7android2spINS_8" "ISurfaceEEEPKc20OMX_COLOR_FORMATTYPEjjjji"); if (funcWithRotation) { impl = (*funcWithRotation)( surface, componentName, colorFormat, displayWidth, displayHeight, encodedWidth, encodedHeight,

409 Android 驱动开发与移植实战详解

rotationDegrees); } else { CreateRendererFunc func = (CreateRendererFunc)dlsym( libHandle, "_Z14createRendererRKN7android2spINS_8ISurfaceEEEPKc20" "OMX_COLOR_FORMATTYPEjjjj"); if (func) { impl = (*func)(surface, componentName, colorFormat, displayWidth, displayHeight, encodedWidth, encodedHeight); } } if (impl) { impl = new SharedVideoRenderer(libHandle, impl); libHandle = NULL; } if (libHandle) { dlclose(libHandle); libHandle = NULL; } } if (!impl) { LOGW("Using software renderer."); impl = new SoftwareRenderer( colorFormat, surface, displayWidth, displayHeight, encodedWidth, encodedHeight); if (((SoftwareRenderer *)impl)->initCheck() != OK) { delete impl; impl = NULL; return NULL; } } return new OMXRenderer(impl); }

由此可见,OMXMaster 是 OMX.cpp 的真正实现者,并且能够管理 OpenMax 插件的类,这些 功能是通过头文件 OMXMaster.h 和源码文件 OMXMaster.cpp 实现的。其中在文件 “frameworks/base/include/media/stagefright/OMXMaster.h”中定义了类 OMXMaster,主要代码如下 所示。

struct OMXCodec : public MediaSource, public MediaBufferObserver { enum CreationFlags { kPreferSoftwareCodecs = 1, kIgnoreCodecSpecificData = 2, kClientNeedsFramebuffer = 4, }; static sp Create( //创建类 MediaSource

410 第 13 章 Android 多媒体插件框架

const sp &omx, const sp &meta, bool createEncoder, const sp &source, const char *matchComponentName = NULL, uint32_t flags = 0); static void setComponentRole( //设置组件的职责 const sp &omx, IOMX::node_id node, bool isEncoder, const char *mime); virtual status_t start(MetaData *params = NULL); virtual status_t stop(); virtual sp getFormat(); //下面是声明函数代码 ………………………………………………

在文件“frameworks/base/media/libstagefright/OMXMaster.cpp”中,定义静态函数 Create()将 MediaSource 作为 IOMX 插件给 OMXCode。函数 Create()的主要代码如下所示。

sp OMXCodec::Create( const sp &omx, const sp &meta, bool createEncoder, const sp &source, const char *matchComponentName, uint32_t flags) { const char *mime; bool success = meta->findCString(kKeyMIMEType, &mime);//获取 mime 信息 CHECK(success); Vector matchingCodecs; findMatchingCodecs( mime, createEncoder, matchComponentName, flags, &matchingCodecs); if (matchingCodecs.isEmpty()) { return NULL; } sp observer = new OMXCodecObserver; IOMX::node_id node = 0; const char *componentName; for (size_t i = 0; i < matchingCodecs.size(); ++i) {//使用 for 循环查找插件 componentName = matchingCodecs[i].string(); sp softwareCodec = createEncoder? InstantiateSoftwareEncoder(componentName, source, meta): InstantiateSoftwareCodec(componentName, source); if (softwareCodec != NULL) { LOGV("Successfully allocated software codec '%s'", componentName); return softwareCodec; } LOGV("Attempting to allocate OMX node '%s'", componentName); uint32_t quirks = getComponentQuirks(componentName, createEncoder); if (!createEncoder && (quirks & kOutputBuffersAreUnreadable) && (flags & kClientNeedsFramebuffer)) { if (strncmp(componentName, "OMX.SEC.", 8)) {

411 Android 驱动开发与移植实战详解

LOGW("Component '%s' does not give the client access to " "the framebuffer contents. Skipping.", componentName); continue; } } status_t err = omx->allocateNode(componentName, observer, &node); if (err == OK) { LOGV("Successfully allocated OMX node '%s'", componentName); sp codec = new OMXCodec( //新建类 OMXCodec omx, node, quirks, createEncoder, mime, componentName, source); observer->setCodec(codec); //设置编码/解码器 err = codec->configureCodec(meta, flags); if (err == OK) { return codec; } LOGV("Failed to configure codec '%s'", componentName); } } return NULL; }

13.4.3 Video Buffer 传输流程 视频播放的过程是处理 Video Buffer 的过程,在 Stagefright 框架中需要使用 VideoRenderer 插 件来实现处理功能。在接下来的内容中,将简要讲解在 Stagefright 框架中使用插件来传输 Video Buffer 的流程。 (1)OMXCodec 会在一开始的时候通过函数 read()来传送未解码 data 数据给 decoder,并且要 求 decoder 将解码后的 data 传回来。对应的代码如下所示。

status_t OMXCodec::read(...) { if (mInitialBufferSubmit) { mInitialBufferSubmit = false; drainInputBuffers(); <----- OMX_EmptyThisBuffer fillOutputBuffers(); <----- OMX_FillThisBuffer } ... } void OMXCodec::drainInputBuffers() { Vector *buffers = &mPortBuffers[kPortIndexInput];

for (i = 0; i < buffers->size(); ++i) { drainInputBuffer(&buffers->editItemAt(i));

412 第 13 章 Android 多媒体插件框架

} } void OMXCodec::drainInputBuffer(BufferInfo *info) { mOMX->emptyBuffer(...); } void OMXCodec::fillOutputBuffers() { Vector *buffers = &mPortBuffers[kPortIndexOutput]; for (i = 0; i < buffers->size(); ++i) { fillOutputBuffer(&buffers->editItemAt(i)); } } void OMXCodec::fillOutputBuffer(BufferInfo *info) { mOMX->fillBuffer(...); }

(2)Decoder 从 input port 获取资料,然后进行解码处理,并且回传 EmptyBufferDone 以通知 OMXCodec 当前的工作。对应的代码如下所示。

void OMXCodec::on_message(const omx_message &msg) { switch (msg.type) { case omx_message::EMPTY_BUFFER_DONE: { IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer; drainInputBuffer(&buffers->editItemAt(i)); } } }

(3)当 OMXCodec 接收到 EMPTY_BUFFER_DONE 之后,继续传送下一个未解码的资料给 decoder。Decoder 解码后的资料被传送到 output port,并回传 FillBufferDone 以通知 OMXCodec。 对应的代码如下所示。

void OMXCodec::on_message(const omx_message &msg) { switch (msg.type) { case omx_message::FILL_BUFFER_DONE: { IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer; fillOutputBuffer(info); mFilledBuffers.push_back(i); mBufferFilled.signal(); } }

413 Android 驱动开发与移植实战详解

}

当 OMXCodec 收到 FILL_BUFFER_DONE 后,将解码后的资料放入 mFilledBuffers,然后发出 mBufferFilled 新号,并要求 decoder 继续发出资料。 (4)使 用 函 数 read()等待 mBufferFilled 新号,当 mFilledBuffers 被填入资料后,函数 read()将其 指定给 buffer,并回传给 AwesomePlayer。对应的代码如下所示。

status_t OMXCodec::read(MediaBuffer **buffer, ...) { ... while (mFilledBuffers.empty()) { mBufferFilled.wait(mLock); } BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index); info->mMediaBuffer->add_ref(); *buffer = info->mMediaBuffer; }

函数 AwesomePlayer::onVideoEvent 除了通过 OMXCodec::read 取得解码后的资料外,还需要将 这些资料(mVideoBuffer)传给 video renderer,以便在屏幕上显示。此功能的实现过程如下所示。 (1)在将 mVideoBuffer 中的资料输出之前,必须先建立 mVideoRenderer。对应的代码如下所示。

void AwesomePlayer::onVideoEvent() { ... if (mVideoRenderer == NULL) { initRenderer_l(); } ... } void AwesomePlayer::initRenderer_l() { if (!strncmp("OMX.", component, 4)) { mVideoRenderer = new AwesomeRemoteRenderer( mClient.interface()->createRenderer( mISurface, component, ...)); } else { mVideoRenderer = new AwesomeLocalRenderer( ..., component, mISurface); }

414 第 13 章 Android 多媒体插件框架

}

(2)如果 video decoder 是 OMX component,则需要建立一个 AwesomeRemoteRenderer 作为 mVideoRenderer。从上面步骤(1 )中的代码看,AwesomeRemoteRenderer 的本质是由函数 OMX::createRenderer() 创建的。函数 createRenderer() 先建立一个“hardware renderer -- SharedVideoRenderer (libstagefrighthw.so)”,如果失败则建立“software renderer -- SoftwareRenderer (surface)”。 对应的代码如下所示。

sp OMX::createRenderer(...) { VideoRenderer *impl = NULL; libHandle = dlopen("libstagefrighthw.so", RTLD_NOW); if (libHandle) { CreateRendererFunc func = dlsym(libHandle, ...); impl = (*func)(...); <------Hardware Renderer } if (!impl) { impl = new SoftwareRenderer(...); <---- Software Renderer } }

(3)如 果 video decoder 是 software component,则需要建立一个 AwesomeLocalRenderer 来作为 mVideoRenderer。AwesomeLocalRenderer 的 constructor 会呼叫本身的函数 init(),其功能和函数 OMX::createRenderer()一样。对应的代码如下所示。

void AwesomeLocalRenderer::init(...) { mLibHandle = dlopen("libstagefrighthw.so", RTLD_NOW); if (mLibHandle) { CreateRendererFunc func = dlsym(...); mTarget = (*func)(...); <------Hardware Renderer } if (mTarget == NULL) { mTarget = new SoftwareRenderer(...); <--- Software Renderer } }

(4)建立 mVideoRenderer 后就可以开始将解码后的资料回传给它,对应的代码如下所示。

void AwesomePlayer::onVideoEvent() { if (!mVideoBuffer) { mVideoSource->read(&mVideoBuffer, ...); }

415 Android 驱动开发与移植实战详解

[Check Timestamp] if (mVideoRenderer == NULL) { initRenderer_l(); } mVideoRenderer->render(mVideoBuffer); <----- Render Data }

经过上述操作之后,Renderer 的处理过程介绍完毕。在播放多媒体的时候,需要使用 audio 来 实现处理功能。在 Stagefright 框架中,audio 的部分内容是由 AudioPlayer 来处理的,在函数 AwesomePlayer::play_l()中被建立。在接下来的内容中,介绍使用 audio 的基本流程。 (1)当要求播放影音时,会同时建立并启动 AudioPlayer。对应的代码如下所示。

status_t AwesomePlayer::play_l() { ... mAudioPlayer = new AudioPlayer(mAudioSink, ...); mAudioPlayer->start(...); ... }

(2)在启动 AudioPlayer 的过程中会先读取第一笔解码后的资料,并开启 audio output。对应的 代码如下所示。

status_t AudioPlayer::start(...) { mSource->read(&mFirstBuffer); if (mAudioSink.get() != NULL) { mAudioSink->open(..., &AudioPlayer::AudioSinkCallback, ...); mAudioSink->start(); } else { mAudioTrack = new AudioTrack(..., &AudioPlayer::AudioCallback, ...); mAudioTrack->start(); } }

在上述代码中,AudioPlayer 并没有将 mFirstBuffer 传给 audio output。 (3)在开启 audio output 的同时,AudioPlayer 将启用函数 callback(),这样每当函数 callback() 被呼叫时 AudioPlayer 会去 audio decoder 读取解码后的资料。读取解码后的 audio 资料工作是由函 数 callback()所驱动的,fillBuffer 将资料(mInputBuffer)复制到数据 data 后,audio output 回去取 用 data。实现函数 AudioSinkCallback()的代码如下所示。

size_t AudioPlayer::AudioSinkCallback(audioSink, buffer, size, ...) { return fillBuffer(buffer, size);

416 第 13 章 Android 多媒体插件框架

} void AudioPlayer::AudioCallback(..., info) { buffer = info; fillBuffer(buffer->raw, buffer->size); } size_t AudioPlayer::fillBuffer(data, size) { mSource->read(&mInputBuffer, ...); memcpy(data, mInputBuffer->data(), ...); }

417

第 14 章 Camera 照相机驱动

无论是智能手机还是普通的手机,基本上都支持手机拍照和录制视频功能,虽然像素和清晰度 各有不同。在 Android 系统中,照相机功能是通过 Camera 系统实现的。在本章将详细讲解 Android 平台中 Camera 系统的基本知识和移植方法,为读者步入本书后面知识的学习打下基础。

14.1 Camera 系统的结构

Camera 作为一个照相机系统,提供了取景器、视频录制和拍摄相片等功能,并且还提供了各 种控制类的接口。Camera 系统分别提供了 Java 层的接口和本地接口,其中 Java 框架中的 Camera 类实现了 Java 层相机接口,为照相机和扫描类使用。而 Camera 的本地接口可以给本地程序调用, 作为视频输入环节应用于摄像机和视频通话领域。 Android 照相机系统的基本层次结构如图 14-1 所示。

照相机应用和扫描类应用 平台API

本地框架 Camera的Java类

Android系统

本地框架 Camera JNI、ui-Camera库、 CameraService和硬件抽象层

信号处理和摄像头传感器等设备 硬件和驱动

▲图 14-1 照相机系统的层次结构

Android 的 Camera 系统包括了 Camera 驱动程序层、Camera 硬件抽象层、AudioService、Camera 本地库、Camera 的 Java 框架类和 Java 应用层对 Camera 系统的调用。Camera 的系统结构如图 14-2

第 14 章 Camera 照相机驱动

所示。

Java的Camera类—android.hadware.camera Java框架

Camera JNI Camera API

Libui.so Camera Service

Camera Camera Hardware interface

Camera HAL实现 C框架

内核空间 Camera Driver

▲图 14-2 Camera 的系统结构

(1)Camera 的 Java 程序,代码目录是“frameworks/base/core/java/android/hardware/”。其中文 件 Camera.java 是主要实现的文件,对应的 Java 层次的类是 android.hardware.Camera,这个类和 JNI 中定义的类是一个,有些方法通过 JNI 的方式调用本地代码得到,有些方法自己实现。 ( 2 ) Camera 的 JAVA 本地调用部分(JNI ),代码路径是“frameworks/base/core/jni/ android_hardware_Camera.cpp”,这部分内容编译成为目标是 libandroid_runtime.so,主要的头文件 保存在目录“frameworks/base/include/ui/”中。 (3)Camera 本地框架,其中头文件路径是“frameworks/base/include/ui/”或“frameworks/base/ include/camera/”,源代码路径是“frameworks/base/libs/ui/”或“frameworks/base/libs/camera/”。这 部分的内容被编译成库 libui.so 或 libcamera_client.so。 (4)Camera 服务部分,代码路径是“frameworks/base/camera/libcameraservice/”,这部分内容 被编译成库 libcameraservice.so。为了实现一个具体功能的 Camera,在最底层还需要一个硬件相关 的 Camera 库(例如通过调用 video for linux 驱动程序和 Jpeg 编码程序实现)。这个库将被 Camera 的服务库 libcameraservice.so 调用。 (5)摄像头驱动程序,基于 Linux 的 Video for Linux 视频驱动框架。 (6)硬件抽象层,接口代码路径是“frameworks/base/include/ui/”或 “frameworks/base/include/camera/”。其中的核心文件是 CameraHardwareInterface.h。

419 Android 驱动开发与移植实战详解

在 Camera 系统的各个库中,库 libui.so 位于核心的位置,它对上层的提供的接口主要是 Camera 类,类 libandroid_runtime.so 通过调用 Camera 类提供对 Java 的接口,并且实现了 android.hardware.camera 类。 库 libcameraservice.so 是 Camera 的服务器程序,它通过继承 libui.so 的类实现服务器的功能, 并且与 libui.so 中的另外一部分内容通过进程间通信(即 Binder 机制)的方式进行通信。 库 libandroid_runtime.so 和 libui.so 是公用库,在里面除了 Camera 外还有其他方面的功能。 Camera 部分的头文件被保存在“frameworks/base/include/ui/”目录中,此目录是和库 libmedia.so 的源文件目录“frameworks/base/libs/ui/”相对应的。 在 Camera 中主要包含下面的头文件。 ICameraClient.h。 Camera.h。 ICamera.h。 ICameraService.h。 CameraHardwareInterface.h。 文件 Camera.h 提供了对上层的接口,而其他的几个头文件都是提供一些接口类(即包含了纯 虚函数的类),这些接口类必须被实现类继承才能够使用。 当整个 Camera 在运行的时候,可以大致上分成 Client 和 Server 两个部分,它们分别在两个进程 中运行,它们之间使用 Binder 机制实现进程间通信。这样在客户端调用接口,功能则在服务器中实 现,但是在客户端中调用就好像直接调用服务器中的功能,进程间通信的部分对上层程序不可见。 从框架结构上来看,文件 ICameraService.h、ICameraClient.h 和 ICamera.h 定义了 Camera 的接 口和架构,ICameraService.cpp 和 Camera.cpp 两个文件用于实现 Camera 架构,Camera 的具体功能 在下层调用硬件相关的接口来实现。 由于 Android 系统是架构在 Linux 内核上的开源操作系统,所以驱动层的实现就是在 Linux 内 核的基础上加上 Android 特有的一些机制,对于 Camera 系统,驱动层就是采用 Linux 系统上通用 的 V4L2 接口实现数据采集、格式转换、大小缩放、数据传输的功能。 V4L 全称为 Video4Linux,是 Linux 系统上关于视频设备的通用驱动接口,现在的 V4L2 是在 V4L 上开发的第二代视频设备驱动,Android 就是基于这套标准的驱动架构来实现 Camera 功能的, 当 Video 设备加载成功后,会在“/dev/”目录下生成设备节点,如图 14-3 所示。

▲图 14-3 生成的设备节点

由此可见,一共有 3 个 Video 设备,上层应用可以通过调用 Video 暴露的接口来实现 Camera 功能。

420 第 14 章 Camera 照相机驱动

14.2 移植的内容

因为 Camera 系统的标准化部分是硬件抽象层接口,所以在某平台移植 Camera 系统时,主要 工作是移植 Camera 驱动程序和 Camera 硬件抽象层。 在 Linux 系统中,Camera 驱动程序使用了 Linux 标准的 Video for Linux 2(V4L2)驱动程序。 无论是内核空间还是用户空间,都使用 V4L2 驱动程序框架来定义数据类和控制类。所以在移植 Android 中的 Camera 系统时,也是用标准的 V4L2 驱动程序作为 Camera 的驱动程序。 Camera 的硬件抽象层是 V4L2 和 CameraService 之间的接口,是一个 C++接口类,我们需要具 体的实现者来继承这个类,并且实现里面的虚函数。Camera 的硬件抽象层需要具备取景器、视频 录制、相片拍摄等功能。在 Camera 系统中,具体任务分配如下所示。 V4L2 驱动程序:任务是获得 Video 数据。 Camera 的硬件抽象层:任务是将纯视频流和取景器、实现预览、向上层发送数据等功能组 织起来。 其他算法库和硬件:任务是实现自动对焦和成像增强等功能。

14.2.1 fimc 驱动模块的加载 S3c_fimc_core.c 是 Camera 驱动的核心框架程序,负责管理驱动函数的注册。代码如下:

"drivers/media/video/Samsung/fimc/S3c_fimc_core.c"

文件中以模块的方式加载驱动,会向系统中注册一个 fimc 驱动 s3c_fimc_driver,结构体定义 如下:

static struct platform_driver s3c_fimc_driver = { .probe = s3c_fimc_probe, .remove = s3c_fimc_remove, .suspend = s3c_fimc_suspend, .resume = s3c_fimc_resume, .driver = { .name = "s3c-fimc", .owner = THIS_MODULE, }, };

当系统加载时会运行 s3c_fimc_probe()方法来注册 fimc 的控制器,其中控制器中的主要接口就 是由 V4L2 驱动实现的,同时还会向系统注册 Video 设备,代码如下:

static int s3c_fimc_probe(struct platform_device *pdev) { struct s3c_platform_fimc *pdata; struct s3c_fimc_control *ctrl; struct clk *srclk; int ret;

421 Android 驱动开发与移植实战详解

//注册 fimc 的控制器 ctrl = s3c_fimc_register_controller(pdev); if (!ctrl) { err("cannot register fimc controller\n"); goto err_fimc; } pdata = to_fimc_plat(&pdev->dev); if (pdata->cfg_gpio) pdata->cfg_gpio(pdev); //获取时钟 srclk = clk_get(&pdev->dev, pdata->srclk_name); if (IS_ERR(srclk)) { err("failed to get source clock of fimc\n"); goto err_clk_io; } //获得 fimc 的时钟 ctrl->clock = clk_get(&pdev->dev, pdata->clk_name); if (IS_ERR(ctrl->clock)) { err("failed to get fimc clock source\n"); goto err_clk_io; } if (ctrl->clock->set_parent) ctrl->clock->set_parent(ctrl->clock, srclk); if (ctrl->clock->set_rate) ctrl->clock->set_rate(ctrl->clock, pdata->clockrate); clk_enable(ctrl->clock); //全局变量的初始化 if (ctrl->id == 0) { ret = s3c_fimc_init_global(pdev); if (ret) goto err_global; } //注册 Video 设备 ret = video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id); if (ret) { err("cannot register video driver\n"); goto err_video; } return 0; err_video: clk_put(s3c_fimc.cam_clock); err_global: clk_disable(ctrl->clock); clk_put(ctrl->clock); err_clk_io: s3c_fimc_unregister_controller(pdev); err_fimc: return -EINVAL; } 其中 s3c_fimc_register_controller()这个函数将 V4L2 的接口注册进 fimc 驱动中,从而给应用层

422 第 14 章 Camera 照相机驱动

提供了接口,例如 open()、close()等函数的实现都在这个文件中。V4l2 的驱动定义在 S3c_fimc_v4l2.c 中,文件路径是“drivers/media/video/samsung/fimc/S3c_fimc_v4l2.c”。 S3c_fimc_v4l2.c 中主要定义了 Video 设备的 ioctl 操作接口,并实现了这些接口,代码如下:

const struct v4l2_ioctl_ops s3c_fimc_v4l2_ops = { .vidioc_querycap = s3c_fimc_v4l2_querycap, .vidioc_g_fbuf = s3c_fimc_v4l2_g_fbuf, .vidioc_s_fbuf = s3c_fimc_v4l2_s_fbuf, .vidioc_enum_fmt_vid_cap = s3c_fimc_v4l2_enum_fmt_vid_cap, .vidioc_g_fmt_vid_cap = s3c_fimc_v4l2_g_fmt_vid_cap, .vidioc_s_fmt_vid_cap = s3c_fimc_v4l2_s_fmt_vid_cap, .vidioc_try_fmt_vid_cap = s3c_fimc_v4l2_try_fmt_vid_cap, .vidioc_try_fmt_vid_overlay = s3c_fimc_v4l2_try_fmt_overlay, .vidioc_overlay = s3c_fimc_v4l2_overlay, .vidioc_g_ctrl = s3c_fimc_v4l2_g_ctrl, .vidioc_s_ctrl = s3c_fimc_v4l2_s_ctrl, .vidioc_streamon = s3c_fimc_v4l2_streamon, .vidioc_streamoff = s3c_fimc_v4l2_streamoff, .vidioc_g_input = s3c_fimc_v4l2_g_input, .vidioc_s_input = s3c_fimc_v4l2_s_input, .vidioc_g_output = s3c_fimc_v4l2_g_output, .vidioc_s_output = s3c_fimc_v4l2_s_output, .vidioc_enum_input = s3c_fimc_v4l2_enum_input, .vidioc_enum_output = s3c_fimc_v4l2_enum_output, .vidioc_reqbufs = s3c_fimc_v4l2_reqbufs, .vidioc_querybuf = s3c_fimc_v4l2_querybuf, .vidioc_qbuf = s3c_fimc_v4l2_qbuf, .vidioc_dqbuf = s3c_fimc_v4l2_dqbuf, .vidioc_cropcap = s3c_fimc_v4l2_cropcap, .vidioc_g_crop = s3c_fimc_v4l2_g_crop, .vidioc_s_crop = s3c_fimc_v4l2_s_crop, .vidioc_s_parm = s3c_fimc_v4l2_s_parm, };

这些接口分别对应了上层应用程序的 ioctl 的命令,来实现不同的功能。例如设置数据格式的 命令 VIDIOC_S_FMT 的实现为

static int s3c_fimc_v4l2_s_fmt_vid_cap(struct file *filp, void *fh, struct v4l2_format *f){ struct s3c_fimc_control *ctrl = (struct s3c_fimc_control *) fh; struct s3c_fimc_out_frame *frame = &ctrl->out_frame; //判断数据流的类型,是输入还是输出 if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; ctrl->v4l2.frmbuf.fmt = f->fmt.pix; if (frame->hq && f->fmt.pix.pixelformat == V4L2_PIX_FMT_JPEG) { frame->jpeg.enabled = 1; frame->jpeg.thumb = 1; }

423 Android 驱动开发与移植实战详解

if (frame->hq && f->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) frame->jpeg.enabled = 1; if (ctrl->in_type != PATH_IN_DMA) f->fmt.pix.priv = V4L2_FMT_OUT; if (f->fmt.pix.priv == V4L2_FMT_IN) s3c_fimc_set_input_frame(ctrl, &f->fmt.pix); else s3c_fimc_set_output_frame(ctrl, &f->fmt.pix); return 0; }

这是个拍照模式的设置数据格式的实现,代码中首先判断数据流的类型是否为 V4L2_BUF_TYPE_VIDEO_CAPTURE,如果数据格式为 V4L2_PIX_FMT_JPEG,则最后会以 jpeg 的格式保存照片,然后对数据的输入输出进行判断,设定数据流的路由。具体的 V4L2 的标准接口 和数据结构可以查看文件“include/linux/videodev2.h”。 ioctl 命令可以描述相关的数据结构,例如下面是描述 buffer 类型的 enum:

enum v4l2_buf_type { V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, V4L2_BUF_TYPE_VBI_CAPTURE = 4, V4L2_BUF_TYPE_VBI_OUTPUT = 5, V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, #if 1 /* Experimental */ V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, #endif V4L2_BUF_TYPE_PRIVATE = 0x80, };

下面是申请帧缓冲的结构体:

struct v4l2_requestbuffers { __u32 count;//申请的个数 enum v4l2_buf_type type; enum v4l2_memory memory; __u32 reserved[2]; };

下面是描述 Video 设备能力的结构体:

struct v4l2_capability { __u8 driver[16]; __u8 card[32]; __u8 bus_info[32]; __u32 version; __u32 capabilities; __u32 reserved[4];

424 第 14 章 Camera 照相机驱动

};

下面是描述视频格式的结构体:

struct v4l2_format { enum v4l2_buf_type type; union { struct v4l2_pix_format pix; //像素格式 struct v4l2_window win; struct v4l2_vbi_format vbi; struct v4l2_sliced_vbi_format sliced; __u8 raw_data[200]; } fmt; };

下面是描述视频中的一帧:

struct v4l2_buffer { __u32 index; enum v4l2_buf_type type;//帧的类型 __u32 bytesused; __u32 flags; enum v4l2_field field; struct timeval timestamp; struct v4l2_timecode timecode; __u32 sequence; enum v4l2_memory memory;//内存地址 union { __u32 offset; unsigned long userptr; } m; __u32 length; __u32 input; __u32 reserved; };

14.2.2 V4l2 驱动的用法 V4L2 驱动对 Video 设备的操作有一套标准的流程,具体说明如下。 (1)打开设备文件。

int fd=open(″/dev/video0″,O_RDWR);

(2)取得设备的 capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入 输出等。

VIDIOC_QUERYCAP,struct v4l2_capability

(3)选择视频输入,一个视频设备可以有多个视频输入。

VIDIOC_S_INPUT,struct v4l2_input

425 Android 驱动开发与移植实战详解

(4)设置视频的制式和帧格式,制式包括 PAL、NTSC,帧的格式包括宽度和高度等。

VIDIOC_S_STD,VIDIOC_S_FMT,struct v4l2_std_id,struct v4l2_format

(5)向驱动申请帧缓冲,一般不超过 5 个。

struct v4l2_requestbuffers

(6)将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。

mmap

(7)将申请到的帧缓冲全部入队列,以便存放采集到的数据。

VIDIOC_QBUF,struct v4l2_buffer

(8)开始视频的采集。

VIDIOC_STREAMON

(9)出队列以取得已采集数据的帧缓冲,取得原始采集数据。

VIDIOC_DQBUF

(10)将缓冲重新入队列尾,这样可以循环采集。

VIDIOC_QBUF

(11)停止视频的采集。

VIDIOC_STREAMOFF

(12)关闭视频设备。

close(fd);

14.3 移植和调试

经过本章前面内容的讲解,已经了解了 Camera 系统的基本结构和我们需要移植的任务。在本 节将详细讲解在 Android 平台中移植和调试 Camera 系统的方法,为读者步入本书后面知识的学习 打下基础。

14.3.1 V4L2 驱动程序 在 Linux 系统中,Camera 驱动程序使用了 Linux 标准的 Video for Linux 2(V4L2)驱动程序。 无论是内核空间还是用户空间,都使用 V4L2 驱动程序框架来定义数据类和控制类。所以在移植 Android 中的 Camera 系统时,也是用标准的 V4L2 驱动程序作为 Camera 的驱动程序。在 Camera 系统中,V4L2 驱动程序的任务是获得 Video 数据。

426 第 14 章 Camera 照相机驱动

1.V4L2 API

V4L2 是 V4L 的升级版本,为 Linux 下视频设备程序提供了一套接口规范。包括一套数据结构 和底层 V4L2 驱动接口。V4L2 驱动程序向用户空间提供字符设备,主设备号是 81。对于视频设备 来说,次设备号是 0-63。如果次设备号在 64~127 之间的是 Radio 设备,次设备号在 192~223 之 间的是 Teletext 设备,次设备号在 224~255 之间的是 VBI 设备。 V4L2 中常用的结构体在内核文件“include/linux/videodev2.h”中定义。

struct v4l2_requestbuffers //申请帧缓冲,对应命令 VIDIOC_REQBUFS struct v4l2_capability //视频设备的功能,对应命令 VIDIOC_QUERYCAP struct v4l2_input //视频输入信息,对应命令 VIDIOC_ENUMINPUT struct v4l2_standard //视频的制式,比如 PAL,NTSC,对应命令 VIDIOC_ENUMSTD struct v4l2_format //帧的格式,对应命令 VIDIOC_G_FMT、VIDIOC_S_FMT 等 struct v4l2_buffer //驱动中的一帧图像缓存,对应命令 VIDIOC_QUERYBUF struct v4l2_crop //视频信号矩形边框 v4l2_std_id //视频制式

常用的 ioctl 接口命令也在文件“include/linux/videodev2.h”中定义。

VIDIOC_REQBUFS //分配内存 VIDIOC_QUERYBUF //把 VIDIOC_REQBUFS 中分配的数据缓存转换成物理地址 VIDIOC_QUERYCAP //查询驱动功能 VIDIOC_ENUM_FMT //获取当前驱动支持的视频格式 VIDIOC_S_FMT //设置当前驱动的频捕获格式 VIDIOC_G_FMT //读取当前驱动的频捕获格式 VIDIOC_TRY_FMT //验证当前驱动的显示格式 VIDIOC_CROPCAP //查询驱动的修剪能力 VIDIOC_S_CROP //设置视频信号的矩形边框 VIDIOC_G_CROP //读取视频信号的矩形边框 VIDIOC_QBUF //把数据从缓存中读取出来 VIDIOC_DQBUF //把数据放回缓存队列 VIDIOC_STREAMON //开始视频显示函数 VIDIOC_STREAMOFF //结束视频显示函数 VIDIOC_QUERYSTD //检查当前视频设备支持的标准,例如 PAL 或 NTSC。

2.操作 V4L2 的流程

在 V4L2 中提供了很多访问接口,虽然可以根据具体需要选择操作方法,但是很少有驱动完全 能够实现所有的接口功能。所以建议在使用时参考驱动源码,或仔细阅读驱动提供者的使用说明。 下面简单列举一种 V4L2 的操作流程供读者朋友们参考。 (1)打开设备文件。如果需要使用非阻塞模式调用视频设备,当没有可用的视频数据时不会阻 塞,而会立刻返回。

int fd = open(Devicename,mode); Devicename:/dev/video0、/dev/video1 …… Mode:O_RDWR [| O_NONBLOCK]

427 Android 驱动开发与移植实战详解

(2)获取设备的 capability。在此需要查看设备具有什么功能,比如是否具有视频输入特性。

struct v4l2_capability capability; int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);

(3)选择视频输入。每一个视频设备可以有多个视频输入,如果只有一路输入,则可以没有这 个功能。

struct v4l2_input input; //……开始初始化 input int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);

(4)检测视频支持的制式。

v4l2_std_id std; do { ret = ioctl(fd, VIDIOC_QUERYSTD, &std); } while (ret == -1 && errno == EAGAIN); switch (std) { case V4L2_STD_NTSC: //…… case V4L2_STD_PAL: //…… }

(5)设置视频捕获格式。

struct v4l2_format fmt; fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; fmt.fmt.pix.height = height; fmt.fmt.pix.width = width; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; ret = ioctl(fd, VIDIOC_S_FMT, &fmt); if(ret) { perror("VIDIOC_S_FMT\n"); close(fd); return -1; }

(6)向驱动申请帧缓存。在结构 v4l2_requestbuffers 中定义了缓存的数量,驱动会根据这个申 请对应数量的视频缓存。通过多个缓存可以建立 FIFO,这样可以提高视频采集的效率。

struct v4l2_requestbuffers req; if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { return -1; }

(7)获取每个缓存的信息,并 mmap 到用户空间。

typedef struct VideoBuffer {

428 第 14 章 Camera 照相机驱动

void *start; size_t length; } VideoBuffer; VideoBuffer* buffers = calloc( req.count, sizeof(*buffers) ); struct v4l2_buffer buf; for (numBufs = 0; numBufs < req.count; numBufs++) {//映射所有的缓存 memset( &buf, 0, sizeof(buf) ); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = numBufs; if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {//获取到对应 index 的缓存信息,此处主要利 用 length 信息及 offset 信息来完成后面的 mmap 操作 return -1; } buffers[numBufs].length = buf.length; // 转换成相对地址 buffers[numBufs].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffers[numBufs].start == MAP_FAILED) { return -1; }

(8)开始采集视频。

int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);

(9)取 出 FIFO 缓存中已经采样的帧缓存。可以根据返回的 buf.index 找到对应的 mmap 映射好 的缓存,实现取出视频数据的功能。

struct v4l2_buffer buf; memset(&buf,0,sizeof(buf)); buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory=V4L2_MEMORY_MMAP; buf.index=0;//此值由下面的 ioctl 返回 if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) { return -1; }

(10)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集。

if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { return -1; }

(11)停止视频的采集。

int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);

429 Android 驱动开发与移植实战详解

(12)关闭视频设备。

close(fd);

3.V4L2 驱动框架

在上述使用 V4L2 的流程中,各个操作都需要有底层 V4L2 驱动的支持。在内核中有一些非常 完善的例子。例如在 Linux-2.6.26 内核目录“/drivers/media/video//zc301/”中,文件 zc301_core.c 实现了 ZC301 视频驱动代码。 (1)V4L2 驱动注册、注销函数。 在 Video 核心层文件“drivers/media/video/videodev.c”中提供了注册函数。

int video_register_device(struct video_device *vfd, int type, int nr)

video_device:要构建的核心数据结构。 Type:表示设备类型,此设备号的基地址受此变量的影响。 Nr:如果 end-base>nr>0,次设备号=base(基准值,受 type 影响)+nr,否则将系统自动分 配合适的次设备号。 我们具体需要的驱动只需构建 video_device 结构,然后调用注册函数即可。例如在文件 zc301_core.c 中的如下实现代码。

err = video_register_device(cam->v4ldev, VFL_TYPE_GRABBER,video_nr[dev_nr]);

在 Video 核心层文件“drivers/media/video/videodev.c”中提供了如下注销函数。

void video_unregister_device(struct video_device *vfd)

(2)构建 struct video_device。 在结构 video_device 中包含了视频设备的属性和操作方法,具体可以参考文件 zc301_core.c。

strcpy(cam->v4ldev->name, "ZC0301[P] PC Camera"); cam->v4ldev->owner = THIS_MODULE; cam->v4ldev->type = VID_TYPE_CAPTURE | VID_TYPE_SCALES; cam->v4ldev->fops = &zc0301_fops; cam->v4ldev->minor = video_nr[dev_nr]; cam->v4ldev->release = video_device_release; video_set_drvdata(cam->v4ldev, cam);

在上述 zc301 的驱动中,并没有实现 struct video_device 中的很多操作函数,例如 vidioc_querycap、vidioc_g_fmt_cap,这是因为在 struct file_operations zc0301_fops 中的 zc0301_ioctl 实现了前面的所有 ioctl 操作,所以无须在 struct video_device 再次实现 struct video_device 中的操作。 除此之外,也可以使用下面的代码来构建 struct video_device。

static struct video_device camif_dev = { .name = "s3c2440 camif", .type = VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_SUBCAPTURE,

430 第 14 章 Camera 照相机驱动

.fops = &camif_fops, .minor = -1, .release = camif_dev_release, .vidioc_querycap = vidioc_querycap, .vidioc_enum_fmt_cap = vidioc_enum_fmt_cap, .vidioc_g_fmt_cap = vidioc_g_fmt_cap, .vidioc_s_fmt_cap = vidioc_s_fmt_cap, .vidioc_queryctrl = vidioc_queryctrl, .vidioc_g_ctrl = vidioc_g_ctrl, .vidioc_s_ctrl = vidioc_s_ctrl, }; static struct file_operations camif_fops = { .owner = THIS_MODULE, .open = camif_open, .release = camif_release, .read = camif_read, .poll = camif_poll, .ioctl = video_ioctl2, /* V4L2 ioctl handler */ .mmap = camif_mmap, .llseek = no_llseek, };

结构 video_ioctl2 是在文件 videodev.c 中实现的,video_ioctl2 中会根据 ioctl 不同的 cmd 来调用 video_device 中的操作方法。

4.实现 Video 核心层

具体实现代码请参考内核文件“/drivers/media/videodev.c”,具体实现流程如下所示。 (1)注册 256 个视频设备。注册了 256 个视频设备和 video_class 类,video_fops 是这 256 个设 备共同的操作方法。

static int __init videodev_init(void) { int ret; if (register_chrdev(VIDEO_MAJOR, VIDEO_NAME, &video_fops)) { return -EIO; } ret = class_register(&video_class); …… }

(2)实现 V4L2 驱动的注册函数。注册 V4L2 驱动的过程只是创建了设备节点,例如 “/dev/video0”。并且保存了 video_device 结构指针。

int video_register_device(struct video_device *vfd, int type, int nr) { int i=0; int base;

431 Android 驱动开发与移植实战详解

int end; int ret; char *name_base; switch(type) //根据不同的 type 确定设备名称、次设备号 { case VFL_TYPE_GRABBER: base=MINOR_VFL_TYPE_GRABBER_MIN; end=MINOR_VFL_TYPE_GRABBER_MAX+1; name_base = "video"; break; case VFL_TYPE_VTX: base=MINOR_VFL_TYPE_VTX_MIN; end=MINOR_VFL_TYPE_VTX_MAX+1; name_base = "vtx"; break; case VFL_TYPE_VBI: base=MINOR_VFL_TYPE_VBI_MIN; end=MINOR_VFL_TYPE_VBI_MAX+1; name_base = "vbi"; break; case VFL_TYPE_RADIO: base=MINOR_VFL_TYPE_RADIO_MIN; end=MINOR_VFL_TYPE_RADIO_MAX+1; name_base = "radio"; break; default: printk(KERN_ERR "%s called with unknown type: %d\n", __func__, type); return -1; } /* 计算出次设备号 */ mutex_lock(&videodev_lock); if (nr >= 0 && nr < end-base) { /* use the one the driver asked for */ i = base+nr; if (NULL != video_device[i]) { mutex_unlock(&videodev_lock); return -ENFILE; } } else { /* use first free */ for(i=base;i

432 第 14 章 Camera 照相机驱动

和 i 相关 vfd->minor=i; mutex_unlock(&videodev_lock); mutex_init(&vfd->lock); /* sysfs class */ memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev)); if (vfd->dev) vfd->class_dev.parent = vfd->dev; vfd->class_dev.class = &video_class; vfd->class_dev.devt = MKDEV(VIDEO_MAJOR, vfd->minor); sprintf(vfd->class_dev.bus_id, "%s%d", name_base, i - base);//最后在/dev 目录 下的名称 ret = device_register(&vfd->class_dev);//结合 udev 或 mdev 可以实现自动在/dev 下创建设备节点 …… }

(3)打开视频驱动。 使用下面的代码在用户空间调用 open()函数打开对应的视频文件。

int fd = open(/dev/video0, O_RDWR);

对应“/dev/video0”目录的文件操作结构是在文件“/drivers/media/videodev.c”中定义的 video_fops。代码如下所示。

static const struct file_operations video_fops= { .owner = THIS_MODULE, .llseek = no_llseek, .open = video_open, };

上述代码只是实现了 open 操作,后面的其他操作需要使用 video_open()来实现。

static int video_open(struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); int err = 0; struct video_device *vfl; const struct file_operations *old_fops; if(minor>=VIDEO_NUM_DEVICES) return -ENODEV; mutex_lock(&videodev_lock); vfl=video_device[minor]; if(vfl==NULL) { mutex_unlock(&videodev_lock); request_module("char-major-%d-%d", VIDEO_MAJOR, minor); mutex_lock(&videodev_lock); vfl=video_device[minor]; //根据次设备号取出 video_device 结构 if (vfl==NULL) { mutex_unlock(&videodev_lock);

433 Android 驱动开发与移植实战详解

return -ENODEV; } } old_fops = file->f_op; file->f_op = fops_get(vfl->fops);//替换此打开文件的 file_operation 结构,后面 的其他针对此文件的操作都由新的结构来负责了,也就是由每个具体的 video_device 的 fops 负责 if(file->f_op->open) err = file->f_op->open(inode,file); if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } …… }

14.3.2 硬件抽象层 在 Android 2.1 及其以前的版本中,Camera 系统的硬件抽象层的头文件保存在 “frameworks/base/include/ui/”目录。在 Android 2.2 及其以后的版本中,Camera 系统的硬件抽象层 的头文件保存在“frameworks/base/include/camera/”目录。在上述目录中主要包含了下面的头文件。 CameraHardwareInterface.h:在里面定义了 C++接口类,此类需要根据系统的情况来实现继承。 CameraParameters.h:在里面定义了 Camera 系统的参数,可以在本地系统的各个层次中使 用这些参数。 Camera.h:在里面提供了 Camera 系统本地对上层的接口。

1.Android 2.1 及其以前的版本

在 Android 2.1 及其以前的版本中,在文件 CameraHardwareInterface.h 中首先定义了硬件抽象 层接口的回调函数类型,对应代码如下所示。

/** startPreview()使用的回调函数*/ typedef void (*preview_callback)(const sp& mem, void* user);

/** startRecord()使用的回调函数*/ typedef void (*recording_callback)(const sp& mem, void* user);

/** takePicture()使用的回调函数*/ typedef void (*shutter_callback)(void* user);

/** takePicture()使用的回调函数*/ typedef void (*raw_callback)(const sp& mem, void* user);

/** takePicture()使用的回调函数*/ typedef void (*jpeg_callback)(const sp& mem, void* user);

/** autoFocus()使用的回调函数*/ typedef void (*autofocus_callback)(bool focused, void* user);

434 第 14 章 Camera 照相机驱动

然后定义类 CameraHardwareInterface,在类中定义了各个接口函数。代码如下所示。

class CameraHardwareInterface : public virtual RefBase { public: virtual ~CameraHardwareInterface() { } virtual sp getPreviewHeap() const = 0; virtual sp getRawHeap() const = 0; virtual status_t startPreview(preview_callback cb, void* user) = 0; virtual bool useOverlay() {return false;} virtual status_t setOverlay(const sp &overlay) {return BAD_VALUE;} virtual void stopPreview() = 0; virtual bool previewEnabled() = 0; virtual status_t startRecording(recording_callback cb, void* user) = 0; virtual void stopRecording() = 0; virtual bool recordingEnabled() = 0; virtual void releaseRecordingFrame(const sp& mem) = 0; virtual status_t autoFocus(autofocus_callback, void* user) = 0; virtual status_t takePicture(shutter_callback, raw_callback, jpeg_callback, void* user) = 0; virtual status_t cancelPicture(bool cancel_shutter, bool cancel_raw, bool cancel_jpeg) = 0; /**返回 camera 系统的参数 */ virtual CameraParameters getParameters() const = 0; virtual void release() = 0; virtual status_t dump(int fd, const Vector& args) const = 0; }; extern "C" sp openCameraHardware(); };

可以将上述代码中的接口分为如下几类。 取景预览:startPreview、stopPreview、useOverlay 和 setOverlay。 录制视频:startRecording、stopRecording、recordingEnabled 和 releaseRecordingFrame。 拍摄照片:takePicture 和 cancelPicture。 辅助功能:autoFocus(自动对焦)、setParameters 和 getParameters。

2.Android 2.2 及其以后的版本

在 Android 2.2 及其以前的版本中,在文件 Camera.h 中首先定义了通知信息的枚举值,对应代 码如下所示。

enum { CAMERA_MSG_ERROR = 0x001, //错误信息 CAMERA_MSG_SHUTTER = 0x002, //快门信息 CAMERA_MSG_FOCUS = 0x004, //聚焦信息 CAMERA_MSG_ZOOM = 0x008, //缩放信息

435 Android 驱动开发与移植实战详解

CAMERA_MSG_PREVIEW_FRAME = 0x010, //帧预览信息 CAMERA_MSG_VIDEO_FRAME = 0x020, //视频帧信息 CAMERA_MSG_POSTVIEW_FRAME = 0x040, //拍照后停止帧信息 CAMERA_MSG_RAW_IMAGE = 0x080, //原始数据格式照片信息 CAMERA_MSG_COMPRESSED_IMAGE = 0x100, //压缩格式照片信息 CAMERA_MSG_ALL_MSGS = 0x1FF //所有信息 };

然后在文件 CameraHardwareInterface.h 中定义如下三个回调函数。

//通知回调 typedef void (*notify_callback)(int32_t msgType, int32_t ext1, int32_t ext2, void* user); //数据回调 typedef void (*data_callback)(int32_t msgType, const sp& dataPtr, void* user); //带有时间戳的数据回调 typedef void (*data_callback_timestamp)(nsecs_t timestamp, int32_t msgType, const sp& dataPtr, void* user);

然后定义类 CameraHardwareInterface,在类中的各个函数和其他 Android 版本的相同。区别是 回调函数不再由各个函数分别设置,所以在 startPreview 和 startRecording 缺少了回调函数的指针和 void*类型的附加参数。主要代码如下所示。

class CameraHardwareInterface : public virtual RefBase { public: virtual ~CameraHardwareInterface() { } virtual sp getPreviewHeap() const = 0; virtual sp getRawHeap() const = 0; virtual void setCallbacks(notify_callback notify_cb, data_callback data_cb, data_callback_timestamp data_cb_timestamp, virtual void enableMsgType(int32_t msgType) = 0; virtual void disableMsgType(int32_t msgType) = 0; virtual bool msgTypeEnabled(int32_t msgType) = 0; virtual status_t startPreview() = 0; virtual status_t getBufferInfo(sp& Frame, size_t *alignedSize) = 0; virtual bool useOverlay() {return false;} virtual status_t setOverlay(const sp &overlay) {return BAD_VALUE;} virtual void stopPreview() = 0; virtual bool previewEnabled() = 0; virtual status_t startRecording() = 0; virtual void stopRecording() = 0; virtual bool recordingEnabled() = 0; virtual void releaseRecordingFrame(const sp& mem) = 0;

436 第 14 章 Camera 照相机驱动

virtual status_t autoFocus() = 0; virtual status_t cancelAutoFocus() = 0; virtual status_t takePicture() = 0; virtual status_t cancelPicture() = 0; virtual CameraParameters getParameters() const = 0; virtual status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) = 0; virtual void release() = 0; virtual status_t d }

因为在新版本的 Camera 系统中增加了 sendCommand(),所以需要在文件 Camera.h 中增加新命 令和返回值。具体代码如下所示。

// 函数 sendCommand()使用的命令类型 enum { CAMERA_CMD_START_SMOOTH_ZOOM = 1, CAMERA_CMD_STOP_SMOOTH_ZOOM = 2, CAMERA_CMD_SET_DISPLAY_ORIENTATION = 3, };

// 错误类型 enum { CAMERA_ERROR_UKNOWN = 1, CAMERA_ERROR_SERVER_DIED = 100 };

3.实现 Camera 硬件抽象层

在 startPreview()实现中保存预览回调函数并建立预览线程,在预览线程的循环中等待视频数 据的到达。当视频帧到达后调用预览回调函数送出视频帧。 取景器实现预览的过程如下所示。 (1)在初始化的过程中,建立预览数据的内存队列(多种方式)。 (2)在 startPreview()中建立预览线程。 (3)在预览线程的循环中,等待视频数据到达。 (4)视频到达后使用预览回调机制将视频向上传送。 在此过程不需要使用预览回调函数,可以直接将视频数据输入到 Overlay 上。 如果使用 Overlay 实现取景器,则需要有以下两个变化。 在 setOverlay()函数中,从 ISurface 接口中取得 Overlay 类。 在预览线程的循环中,不是用预览回调函数直接将数据输入到 Overlay 上。 录制视频的主要过程如下所示。 (1)在 startRecording()的实现(或者在 setCallbacks)中保存录制视频回调函数。 (2)录制视频可以使用自己的线程,也可以使用预览线程。 (3)通过录制回调函数将视频帧送出。 当调用 releaseRecordingFrame()后,表示上层通知 Camera 硬件抽象层,这一帧的内存已经用

437 Android 驱动开发与移植实战详解

完,可以进行下一次的处理。如果在 V4L2 驱动程序中使用原始数据(RAW),则视频录制的数 据和取景器预览的数据为同一数据。当调用 releaseRecordingFrame()时,通常表示编码器已经完成 了对当前视频帧的编码,对这块内存进行释放。在这个函数的实现中,可以设置标志位,标记帧内 存可以再次使用。 由此可见,对于 Linux 系统来说,摄像头驱动部分大多使用 Video for Linux 2 (V4L2)驱动 程序,在此处主要的处理流程可以如下所示。 ( 1 )如果使用映射内核内存的方式(V4L2_MEMORY_MMAP ),构建预览的内存 MemoryHeapBase 需要从 V4L2 驱动程序中得到内存指针。 (2)如果使用用户空间内存的方式(V4L2_MEMORY_USERPTR),MemoryHeapBase 中开辟 的内存是在用户空间建立的。 (3)在预览的线程中,使用 VIDIOC_DQBUF 调用阻塞等待视频帧的到来,处理完成后使用 VIDIOC_QBUF 调用将帧内存再次压入队列,然后等待下一帧的到来。

14.4 实现 Camera 系统的硬件抽象层

在 Android 系统中已经实现了一个 Camera 硬件抽象层的“桩”,这样可以根据“宏”来配置。 此“桩”使用假的方式实现取景器预览和照片拍摄功能。在 Camera 系统的“桩”实现中使用黑白 格子来代替来自硬件的视频流,这样可以在不接触硬件的情况下让 Camera 系统不用硬件也可以运 行。因为没有视频输出设备,所以不会使用 Overlay 来实现 Camera 硬件抽象层的“桩”。

14.4.1 Java 程序部分 在文件“packages/apps/Camera/src/com/android/camera/Camera.java”中,已经包含了对 Camera 的调用。在文件 Camera.java 中,对包的引用代码如下所示。

import android.hardware.Camera.PictureCallback; import android.hardware.Camera.Size;

然后定义类 Camera ,此类继承了活动 Activity 类,在它的内部包含了一个 android.hardware.Camera。对应代码如下所示。

public class Camera extends Activity implements View.OnClickListener, SurfaceHolder. Callback{ android.hardware.Camera mCameraDevice; }

调用 Camera 功能的代码如下所示。

mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback); mCameraDevice.startPreview(); mCameraDevice.stopPreview(); startPreview 、 stopPreview 和 takePicture 等接口就是通过 JAVA 本地调用(JNI )来实现的, //frameworks/base/ core/java/android/hardware/目录中的 Camera.java 文件提供了一个 JAVA 类:

438 第 14 章 Camera 照相机驱动

Camera public class Camera { }

在类 Camera 中,大部分代码使用 JNI 调用下层得到,例如下面的代码。

public void setParameters(Parameters params) { Log.e(TAG, "setParameters()"); //params.dump(); native_setParameters(params.flatten()); }

还有下面的代码。

public final void setPreviewDisplay(SurfaceHolder holder) { setPreviewDisplay(holder.getSurface()); } private native final void setPreviewDisplay(Surface surface);

在上面的两段代码中,两个 setPreviewDisplay 参数不同,后一个是本地方法,参数为 Surface 类型,前一个通过调用后一个实现,但自己的参数以 SurfaceHolder 为类型。

14.4.2 Java 本地调用部分 Camera 的 Java 本地调用(JNI)部分在如下文件中实现。

frameworks/base/core/jni/android_hardware_Camera.cpp

在文件 android_hardware_Camera.cpp 中定义了一个 JNINativeMethod(Java 本地调用方法)类 型的数组 gMethods,具体代码如下所示。

static JNINativeMethod camMethods[] = { {"native_setup","(Ljava/lang/Object;)V",(void*)android_hardware_Camera_native_setup }, {"native_release","()V",(void*)android_hardware_Camera_release }, {"setPreviewDisplay","(Landroid/view/Surface;)V",(void *)android_hardware_Camera_setPreviewDisplay }, {"startPreview","()V",(void *)android_hardware_Camera_startPreview }, {"stopPreview", "()V", (void *)android_hardware_Camera_stopPreview }, {"setHasPreviewCallback","(Z)V",(void *)android_hardware_Camera_setHasPreview Callback }, {"native_autoFocus","()V",(void *)android_hardware_Camera_autoFocus }, {"native_takePicture", "()V", (void *)android_hardware_Camera_takePicture }, {"native_setParameters","(Ljava/lang/String;)V",(void *)android_hardware_Camera_setParameters }, {"native_getParameters", "()Ljava/lang/String;",(void *)android_hardware_Camera_ getParameters } };

JNINativeMethod 的第一个成员是一个字符串,表示 Java 本地调用方法的名称,此名称是在 Java 程序中调用的名称;第二个成员也是一个字符串,表示 Java 本地调用方法的参数和返回值;第三

439 Android 驱动开发与移植实战详解

个成员是 Java 本地调用方法对应的 C 语言函数。 通过函数 register_android_hardware_Camera()将 gMethods 注册为类“android/media/Camera”, 其主要的实现如下所示。

int register_android_hardware_Camera(JNIEnv *env) { // 定义本地寄存器 return AndroidRuntime::registerNativeMethods(env, "android/hardware/Camera",camMethods, NELEM(camMethods)); }

其中类“android/hardware/Camera”和 Java 类 android.hardware.Camera 相对应。

14.4.3 本地库 libui.so 文件“frameworks/base/libs/ui/Camera.cpp”的功能是实现文件 Camera.h 提供的接口,主要代码 片段如下所示。

const sp& Camera::getCameraService() { Mutex::Autolock _l(mLock); if (mCameraService.get() == 0) { sp sm = defaultServiceManager(); sp binder; do { binder = sm->getService(String16("media.camera")); if (binder != 0) break; LOGW("CameraService not published, waiting..."); usleep(500000); // 0.5 s } while(true); if (mDeathNotifier == NULL) { mDeathNotifier = new DeathNotifier(); } binder->linkToDeath(mDeathNotifier); mCameraService = interface_cast(binder); } LOGE_IF(mCameraService==0, "no CameraService!?"); return mCameraService; }

上述代码通过如下调用代码得到一个名称为“media.camera”的服务,此调用返回值的类型是 IBinder,根据实现将其转换成类型 ICameraService 使用。

binder = sm->getService(String16("media.camera"));

函数 connect()的实现代码如下所示。

sp Camera::connect() {

440 第 14 章 Camera 照相机驱动

sp c = new Camera(); const sp& cs = getCameraService(); if (cs != 0) { c->mCamera = cs->connect(c); } if (c->mCamera != 0) { c->mCamera->asBinder()->linkToDeath(c); c->mStatus = NO_ERROR; } return c; }

函数 connect()通过调用 getCameraService 得到一个 ICameraService,通过 ICameraService 的 “cs->connect(c)”得到一个 ICamera 类型的指针,调用函数 connect()会得到一个 Camera 类型的指针。 在正常情况下,已经初始化完成了 Camera 的成员 mCamera。 函数 startPreview()的实现代码如下所示:

status_t Camera::startPreview(){ return mCamera->startPreview(); }

其他函数的实现过程与函数 setDataSource()的类似,在此不再详细介绍。

14.4.4 Camera 服务 libcameraservice.so 目录“frameworks/base/camera/libcameraservice/”中的文件实现了一个 Camera 服务,此服务是 继承 ICameraService 的具体实现。在此目录中有如下和硬件抽象层中“桩”实现相关的文件。 CameraHardwareStub.cpp:Camera 硬件抽象层“桩”实现。 CameraHardwareStub.h:Camera 硬件抽象层“桩”实现的接口。 CannedJpeg.h:包含一块 JPEG 数据,在拍照片时作为 JPEG 数据。 FakeCamera.h 和 FakeCamera.cpp:实现假的 Camera 黑白格取景器效果。 在文件 Android.mk 中,使用宏 USE_CAMERA_STUB 决定是否使用真的 Camera,如果宏为真, 则使用 CameraHardwareStub.cpp 和 FakeCamera.cpp 构造一个假的 Camera,如果为假则使用 CameraService.cpp 构造一个实际上的 Camera 服务。文件 Android.mk 的主要代码如下所示。

LOCAL_MODULE:= libcamerastub LOCAL_SHARED_LIBRARIES:= libui include $(BUILD_STATIC_LIBRARY) endif # USE_CAMERA_STUB # # libcameraservice #

include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ CameraService.cpp LOCAL_SHARED_LIBRARIES:= \

441 Android 驱动开发与移植实战详解

libui \ libutils \ libcutils \ libmedia LOCAL_MODULE:= libcameraservice LOCAL_CFLAGS+=-DLOG_TAG=\"CameraService\" ifeq ($(USE_CAMERA_STUB), true) LOCAL_STATIC_LIBRARIES += libcamerastub LOCAL_CFLAGS += -include CameraHardwareStub.h else LOCAL_SHARED_LIBRARIES += libcamera endif include $(BUILD_SHARED_LIBRARY)

文件 CameraService.cpp 继承了 BnCameraService 的实现,在此类内部又定义了类 Client, CameraService::Client 继承了 BnCamera。在运作的过程中,函数 CameraService::connect()用于得到 一个 CameraService::Client。在使用过程中,主要是通过调用这个类的接口来实现完成 Camera 的功 能。因为 CameraService::Client 本身继承了 BnCamera 类,而 BnCamera 类继承了 ICamera,所以可 以将此类当成 ICamera 来使用。 类 CameraService 和 CameraService::Client 的继承代码如下所示。

class CameraService : public BnCameraService { class Client : public BnCamera {}; wp mClient; }

在 CameraService 中,静态函数 instantiate()的功能是初始化一个 Camera 服务。

void CameraService::instantiate() { defaultServiceManager()->addService( String16("media.camera"), new CameraService()); }

其实函数 CameraService::instantiate()注册了一个名称为“media.camera”的服务,此服务和文 件 Camera.cpp 中调用的名称相对应。 Camera 整个运作机制是:在文件 Camera.cpp 中调用 ICameraService 的接口,此时实际上调用 的是 BpCameraService。而 BpCameraService 通过 Binder 机制和 BnCameraService 实现两个进程的 通信。因为 BpCameraService 的实现就是此处的 CameraService,所以 Camera.cpp 虽然是在另外一 个进程中运行,但是调用 ICameraService 的接口就像直接调用一样,从函数 connect()中可以得到一 个 ICamera 类型的指针,整个指针的实现实际上是 CameraService::Client。 上述 Camera 功能是 CameraService::Client 所实现的,其构造函数如下所示。

CameraService::Client::Client(const sp& cameraService, const sp& cameraClient) : mCameraService(cameraService), mCameraClient(cameraClient), mHardware(0) { mHardware = openCameraHardware();

442 第 14 章 Camera 照相机驱动

mHasFrameCallback = false; }

在构造函数中,通过调用 openCameraHardware()得到一个 CameraHardwareInterface 类型的指 针,并作为其成员 mHardware。以后对实际的 Camera 的操作都通过对这个指针进行,这是一个简 单的直接调用关系。 其实真正的 Camera 功能已经通过实现 CameraHardwareInterface 类来完成。在这个库中,文件 CameraHardwareStub.h 和 CameraHardwareStub.cpp 定义了一个“桩”模块的接口,可以在没有 Camera 硬件的情况下使用。例如在仿真器的情况下使用的文件就是文件 CameraHardwareStub.cpp 和它依赖 的文件 FakeCamera.cpp。 类 CameraHardwareStub 的结构如下所示。

class CameraHardwareStub : public CameraHardwareInterface { class PreviewThread : public Thread { }; };

在类 CameraHardwareStub 中包含了线程类 PreviewThread,此线程可以处理 PreView,即负责 刷新取景器的内容。实际的 Camera 硬件接口通常可以通过对 V4L2 捕获驱动的调用来实现,同时 还需要一个 JPEG 编码程序将从驱动中取出的数据编码成 JPEG 文件。 在文件 FakeCamera.h 和 FakeCamera.cpp 中实现了类 FakeCamera,用于实现一个假的摄像头 输入数据的内存。定义代码如下所示。

class FakeCamera { public: FakeCamera(int width, int height); ~FakeCamera();

void setSize(int width, int height); void getNextFrameAsRgb565(uint16_t *buffer);//获取 RGB565 格式的预览帧 void getNextFrameAsYuv422(uint8_t *buffer);//获取 Yuv422 格式的预览帧 status_t dump(int fd, const Vector& args);

private: void drawSquare(uint16_t *buffer, int x, int y, int size, int color, int shadow); void drawCheckerboard(uint16_t *buffer, int size);

static const int kRed = 0xf800; static const int kGreen = 0x07c0; static const int kBlue = 0x003e;

int mWidth, mHeight; int mCounter; int mCheckX, mCheckY; uint16_t *mTmpRgb16Buffer; };

443 Android 驱动开发与移植实战详解

当在 CameraHardwareStub 中设置参数后会调用函数 initHeapLocked(),此函数的实现代码如下 所示。

void CameraHardwareStub::initHeapLocked() { int picture_width, picture_height; mParameters.getPictureSize(&picture_width, &picture_height); //建立内存堆栈,创建两块内存 mRawHeap = new MemoryHeapBase(picture_width * 2 * picture_height);

int preview_width, preview_height; mParameters.getPreviewSize(&preview_width, &preview_height); LOGD("initHeapLocked: preview size=%dx%d", preview_width, preview_height);

// 从参数中获取信息 int how_big = preview_width * preview_height * 2;

if (how_big == mPreviewFrameSize) return;

mPreviewFrameSize = how_big;

mPreviewHeap = new MemoryHeapBase(mPreviewFrameSize * kBufferCount); // 建立内存队列 for (int i = 0; i < kBufferCount; i++) { mBuffers[i] = new MemoryBase(mPreviewHeap, i * mPreviewFrameSize, mPreviewFrameSize); }

delete mFakeCamera; mFakeCamera = new FakeCamera(preview_width, preview_height); }

定义函数 startPrevie()来创建一个线程,此函数的实现代码如下所示。

status_t CameraHardwareStub::startPreview(preview_callback cb, void* user) { Mutex::Autolock lock(mLock); if (mPreviewThread != 0) { // 已经启动 return INVALID_OPERATION; } mPreviewCallback = cb; mPreviewCallbackCookie = user; mPreviewThread = new PreviewThread(this);//建立视频预览线程 return NO_ERROR; }

通过上面建立的线程可以调用预览回调机制,将预览的数据传递给上层的 CameraService。 创建预览线程函数 previewThread(),建立一个循环以得到假的摄像头输入数据的来源,并通过 预览回调函数将输出传送到上层中去。函数 previewThread()的主要代码如下所示。

444 第 14 章 Camera 照相机驱动

int CameraHardwareStub::previewThread() { mLock.lock(); int previewFrameRate = mParameters.getPreviewFrameRate(); //获取当前预览缓冲区域的垂距 ssize_t offset = mCurrentPreviewFrame * mPreviewFrameSize; sp heap = mPreviewHeap; // 假设假照相机内部状态没有变化 // (or is thread safe) FakeCamera* fakeCamera = mFakeCamera; sp buffer = mBuffers[mCurrentPreviewFrame]; mLock.unlock(); if (buffer != 0) { //计算在预览框架等待多久 int delay = (int)(1000000.0f / float(previewFrameRate)); //这总是合法的,即使内存消亡仍然在我们的过程中被映射 void *base = heap->base(); //用假照相机填装当前框架 uint8_t *frame = ((uint8_t *)base) + offset; fakeCamera->getNextFrameAsYuv422(frame);

// Notify the client of a new frame. mPreviewCallback(buffer, mPreviewCallbackCookie); //推进缓冲区 mCurrentPreviewFrame = (mCurrentPreviewFrame + 1) % kBufferCount; //等待它... usleep(delay); } return NO_ERROR; }

在上述文件中还定义了其他的函数,函数的功能一看便知,在此为节省篇幅将不再一一进行详 细讲解,请读者参考开源的代码文件。

14.5 实现 Camera 系统

在本节将分别讲解在 MSM 平台实现 Camera 系统和在 OMAP 平台实现 Camera 系统的过程, 为读者步入本书后面知识的学习打下基础。

14.5.1 在 MSM 平台实现 Camera 系统 在 MSM 平台中,和 Camera 系统相关的文件如下所示。 drivers/media/video/msm/msm_v4l2.c:是 V4L2 驱动程序的入口文件。 drivers/media/video/msm/ msm_camera.c:是公用库函数。 drivers/media/video/msm/s5k3e2fx.c:摄像头传感器驱动文件,使用 i2c 接口控制。 文件 msm_camera.h 是和摄像头相关的头文件,在里面定义了各种额外的 ioctl 命令。

445 Android 驱动开发与移植实战详解

#define MSM_CAM_IOCTL_MAGIC 'm' #define MSM_CAM_IOCTL_GET_SENSOR_INFO _IOR(MSM_CAM_IOCTL_MAGIC, 1, struct msm_ camsensor_info *) #define MSM_CAM_IOCTL_REGISTER_PMEM _IOW(MSM_CAM_IOCTL_MAGIC, 2, struct msm_pmem_info *) #define MSM_CAM_IOCTL_UNREGISTER_PMEM _IOW(MSM_CAM_IOCTL_MAGIC, 3, unsigned) #define MSM_CAM_IOCTL_CTRL_COMMAND _IOW(MSM_CAM_IOCTL_MAGIC, 4, struct msm_ctrl_cmd *) #define MSM_CAM_IOCTL_CONFIG_VFE _IOW(MSM_CAM_IOCTL_MAGIC, 5, struct msm_camera _vfe_cfg_cmd *) #define MSM_CAM_IOCTL_GET_STATS _IOR(MSM_CAM_IOCTL_MAGIC, 6, struct msm_camera_stats _event_ctrl *) #define MSM_CAM_IOCTL_GETFRAME _IOR(MSM_CAM_IOCTL_MAGIC, 7, struct msm_camera_get_ frame *) #define MSM_CAM_IOCTL_ENABLE_VFE _IOW(MSM_CAM_IOCTL_MAGIC, 8, struct camera_enable_cmd *) #define MSM_CAM_IOCTL_CTRL_CMD_DONE _IOW(MSM_CAM_IOCTL_MAGIC, 9, struct camera_cmd *) #define MSM_CAM_IOCTL_CONFIG_CMD _IOW(MSM_CAM_IOCTL_MAGIC, 10, struct camera_cmd *) #define MSM_CAM_IOCTL_DISABLE_VFE _IOW(MSM_CAM_IOCTL_MAGIC, 11, struct camera_enable _cmd *) #define MSM_CAM_IOCTL_PAD_REG_RESET2 _IOW(MSM_CAM_IOCTL_MAGIC, 12, struct camera_ enable_cmd *) #define MSM_CAM_IOCTL_VFE_APPS_RESET _IOW(MSM_CAM_IOCTL_MAGIC, 13, struct camera_ enable_cmd *) #define MSM_CAM_IOCTL_RELEASE_FRAME_BUFFER _IOW(MSM_CAM_IOCTL_MAGIC, 14, struct camera _enable_cmd *) #define MSM_CAM_IOCTL_RELEASE_STATS_BUFFER _IOW(MSM_CAM_IOCTL_MAGIC, 15, struct msm_ stats_buf *) #define MSM_CAM_IOCTL_AXI_CONFIG _IOW(MSM_CAM_IOCTL_MAGIC, 16, struct msm_camera_vfe _cfg_cmd *) #define MSM_CAM_IOCTL_GET_PICTURE _IOW(MSM_CAM_IOCTL_MAGIC, 17, struct msm_camera_ ctrl_cmd *) #define MSM_CAM_IOCTL_SET_CROP _IOW(MSM_CAM_IOCTL_MAGIC, 18, struct crop_info *) #define MSM_CAM_IOCTL_PICT_PP _IOW(MSM_CAM_IOCTL_MAGIC, 19, uint8_t *) #define MSM_CAM_IOCTL_PICT_PP_DONE _IOW(MSM_CAM_IOCTL_MAGIC, 20, struct msm_snapshot_ pp_status *) #define MSM_CAM_IOCTL_SENSOR_IO_CFG _IOW(MSM_CAM_IOCTL_MAGIC, 21, struct sensor_cfg_ data *) #define MSM_CAMERA_LED_OFF 0 #define MSM_CAMERA_LED_LOW 1 #define MSM_CAMERA_LED_HIGH 2 #define MSM_CAM_IOCTL_FLASH_LED_CFG _IOW(MSM_CAM_IOCTL_MAGIC, 22, unsigned *) #define MSM_CAM_IOCTL_UNBLOCK_POLL_FRAME _IO(MSM_CAM_IOCTL_MAGIC, 23) #define MSM_CAM_IOCTL_CTRL_COMMAND_2 _IOW(MSM_CAM_IOCTL_MAGIC, 24, struct msm_ctrl_cmd *)

文件 msm_camera.c 辅助实现 Camera 系统的功能,在里面包含了供内核调用的文件,也提供 了给用户空间的接口。其中在用户空间的设备节点就是“dev/msm_camera/”中的三个设备:配置 设备 config0、控制设备 control0 和帧数据设备 frame0,上面的 ioctl 命令都是为这些设备节点使用 的。

446 第 14 章 Camera 照相机驱动

在文件 msm_camera.c 中为内核空间提供了接口。

int msm_v4l2_register(struct msm_v4l2_driver *drv)//注册 msm_v4l2_driver 驱动 { if (list_empty(&msm_sensors)) return -ENODEV; drv->sync = list_first_entry(&msm_sensors, struct msm_sync, list); drv->open = __msm_open; drv->release = __msm_release; drv->ctrl = __msm_v4l2_control; drv->reg_pmem = __msm_register_pmem; drv->get_frame = __msm_get_frame; drv->put_frame = __msm_put_frame_buf; drv->get_pict = __msm_get_pic; drv->drv_poll = __msm_poll_frame; return 0; } EXPORT_SYMBOL(msm_v4l2_register); //注销 msm_v4l2_driver 驱动 int msm_v4l2_unregister(struct msm_v4l2_driver *drv) { drv->sync = NULL; return 0; } static int msm_device_init(struct msm_cam_device *pmsm,//开始注册 Camera 驱动 struct msm_sync *sync, int node)

MSM 平台中的 Camera 硬件抽象层已经包含在 Android 代码中,此部分的内容保存在如下文件中。 文件 hardware/msm7k/libcamera/camera_ifc.h:定义 Camera 接口中的常量。 文件 hardware/msm7k/libcamera/QualcommCameraHardware.h:是硬件抽象层的头文件。 文件 hardware/msm7k/libcamera/QualcommCameraHardware.cpp:是硬件抽象层的实现。 在文件 QualcommCameraHardware.h 中定义了类 MemPool,此类表示一个内存。类 AshmemPool 和 PmemPool 是 MemPool 的继承者,PreviewPmemPool 和 RawPmemPool 是 MemPool 的继承者。 实现代码如下所示。

struct MemPool : public RefBase { MemPool(int buffer_size, int num_buffers, int frame_size, int frame_offset, const char *name); virtual ~MemPool() = 0; void completeInitialization(); bool initialized() const { return mHeap != NULL && mHeap->base() != MAP_FAILED; } virtual status_t dump(int fd, const Vector& args) const; int mBufferSize; int mNumBuffers;

447 Android 驱动开发与移植实战详解

int mFrameSize; int mFrameOffset; sp mHeap; sp *mBuffers; const char *mName; }; struct AshmemPool : public MemPool { AshmemPool(int buffer_size, int num_buffers, int frame_size, int frame_offset, const char *name); }; struct PmemPool : public MemPool { PmemPool(const char *pmem_pool, int buffer_size, int num_buffers, int frame_size, int frame_offset, const char *name); virtual ~PmemPool() { } int mFd; uint32_t mAlignedSize; struct pmem_region mSize; }; struct PreviewPmemPool : public PmemPool { virtual ~PreviewPmemPool(); PreviewPmemPool(int buffer_size, int num_buffers, int frame_size, int frame_offset, const char *name); }; struct RawPmemPool : public PmemPool { virtual ~RawPmemPool(); RawPmemPool(const char *pmem_pool, int buffer_size, int num_buffers, int frame_size, int frame_offset, const char *name); };

14.5.2 OMAP 平台实现 Camera 系统 在 OMAP 平台中,可以使用高级的 ISP(图像信号处理)模块通过外接(i2c 方式连接)的 Camera Sensor 驱动来获取视频帧的数据。 OMAP 平台中 Camera 系统相关的文件保存在“drivers/media/video/”目录中。此目录主要由如 下三部分组成。 Vedio for Linux 2 设备:实现文件是 omap34xxcam.h 和 omap34xxcam.c。 ISP:实 现 文 件 是“ isp”目录中的 isp.c、isph3a.c、isppreview.c、ispresizer.c,提供了通过 ISP 进行的 3A、预览、改变尺寸等功能。

448 第 14 章 Camera 照相机驱动

Camera Sensor 驱动:lv8093.c 或 imx046.c,使用 v4l2-int-device 结构来注册。 在文件 omap34xxcam.c 中通过 v4l2_int_master 定义了 v4l2_int 主设备,对应的代码如下所示。

static struct v4l2_int_master omap34xxcam_master = { .attach = omap34xxcam_device_register, //注册设备 .detach = omap34xxcam_device_unregister, //注销设备 };

还需要定义 omap34xxcam_fops 来注册 video 中的 v4l2_file_operations 结构,定义代码如下所示。

static struct v4l2_file_operations omap34xxcam_fops = { .owner = THIS_MODULE, .unlocked_ioctl = video_ioctl2, .poll = omap34xxcam_poll, .mmap = omap34xxcam_mmap, .open = omap34xxcam_open, .release = omap34xxcam_release, };

另外还需要通过文件 lv8093.c 或 imx046.c 实现 Camera 系统的传感器功能,并连接在系统的 i2c 总线上。通过结构 v4l2-int-device 从设备进行注册,在运行时被文件 omap34xxcam.c 直接调用。 OMAP 平台的 Camera 硬件抽象层是基于 OMAP 的 V4L2 驱动程序实现的,并调用 Overlay 系 统作为视频输出,所以 Camera 硬件抽象层的 useOverlay()的返回值是 true。为了提高性能,需要直 接映射 Overlay 中的内存以作为 Camera 输出的内存。当在 OMAP 的 Camera 硬件抽象层中调用 V4L2 驱动程序的时候,需要使用 V4L2_MEMORY_USERPTR 标识来表示来自用户空间的内存。 在 OMAP 平台的 Camera 硬件抽象层中可以使用自动对焦 AutoFocus、自动增强 AutoEnhance 和自动平衡 AutoWhiteBalance 等增强型功能。上述增强型功能是通过 OMAP SOC 内部的 ISP 模块 提供的基本机制实现的,算法部分功能是由用户空间库所支持的。

14.6 借助 Sensor 驱动使用照相机系统

Sensor 是 Camera 的感光器件,是加载在 I2C 上的一个从设备,不同的 Sensor 有不同的像素值、 曝光能力等,这里用的是 Ov3640 这款感光器,驱动文件为 Ov3640.c ,其路径为 “kernel/drivers/media/video/samsung/fimc/Ov3640.c”。其中在模块加载的时候会将 Ov3640 的驱动加 载到 I2C 总线上,代码如下:

static __init int ov3640_init(void) { //向 I2c 总线上添加 Ov3640 的驱动 return i2c_add_driver(&ov3640_i2c_driver); }

Ov3640 的驱动结构 ov3640_i2c_driver 如下。

static struct i2c_driver ov3640_i2c_driver = {

449 Android 驱动开发与移植实战详解

.driver = { .name = "ov3640", }, .id = I2C_DRIVERID_OV3640, //I2C 探测器函数 .attach_adapter = ov3640_attach_adapter, .detach_client = ov3640_detach, .command = ov3640_command, };

在驱动加载的时候会调用,下面是实现代码。

static int ov3640_attach_adapter(struct i2c_adapter *adap) { int ret = 0; //向 fimc 中注册 ov3640 数据 s3c_fimc_register_camera(&ov3640_data); ret = i2c_probe(adap, &addr_data, ov3640_attach); if (ret) { err("failed to attach ov3640 driver\n"); ret = -ENODEV; } return ret; }

当探测到 Ov3640 设备的时候,会将其作为一个 I2C 从设备挂到 I2C 总线上,代码如下:

static int ov3640_attach(struct i2c_adapter *adap, int addr, int kind) {

//定义一个 i2c_client struct i2c_client *c; c = kmalloc(sizeof(*c), GFP_KERNEL); if (!c) return -ENOMEM; memset(c, 0, sizeof(struct i2c_client)); strcpy(c->name, "ov3640"); c->addr = addr; c->adapter = adap; //将 ov3640_i2c_driver 作为这个 I2C 客户端的驱动 c->driver = &ov3640_i2c_driver; ov3640_data.client = c; return i2c_attach_client(c); }

当 Ov3640 挂载到 I2C 总线上,就会受到 I2C 总线的调度了,会有一个响应命令的函数。

static int ov3640_command(struct i2c_client *client, u32 cmd, void *arg) { switch (cmd) { //开始采集数据

450 第 14 章 Camera 照相机驱动

case I2C_CAM_INIT: ov3640_start(client); printk("external camera initialized\n"); break; //改变分辨率 case I2C_CAM_RESOLUTION: ov3640_change_resolution(client, (int) arg); break; default: err("unexpect command\n"); break; } return 0; }

I2C 总线上发送的命令会在这里进行响应,如开始采集数据,改变分辨率等。

static void ov3640_start(struct i2c_client *client) { int i; for (i = 0; i < sizeof(ov3640_setting_15fps_VGA_640_480) / sizeof(ov3640_setting _15fps_VGA_640_480[0]); i++) { s3c_fimc_i2c_write(client, ov3640_setting_15fps_VGA_640_480[i],sizeof(ov3640_setting_15fps_VGA_640_480[i])); } }

开始采集数据的时候,ov3640 会加载一个初始化的寄存器配置,以这个配置来采集数据,寄 存器的配置对数据的影响非常大,包括采集的数据的大小、格式等属性是直接由寄存器值来决定的, 这个一般是由摄像头厂家给出的配置方案,不过开发者也可以在手册的指导下进行局部微调,比如 白平衡、曝光时间等,Camera 使用效果如图 14-4 所示。

▲图 14-4 开发板上的摄像机系统效果

451

第 15 章 传感器系统驱动

很多读者可能对传感器有点陌生,但是随着物联网概念的推出和发展,传感器这一名词逐渐走 进了我们程序员的开发生活中。其实传感器在大家日常的生活中经常见到甚至是用到,比如楼宇的 楼梯灯、马路上的路灯等。在本章将详细讲解 Android 系统传感器系统的基本知识和移植方法,为 读者步入本书后面知识的学习打下基础。

15.1 传感器系统的结构

在 Android 中提供的传感器有加速度传感器、磁场、方向、陀螺仪、光线、压力、温度和接近 等。传感器系统会主动对上层报告传感器精度和数据的变化,并且提供了设置传感器精度的接口, 这些接口可以在 Java 应用和 Java 框架中使用。 Android 传感器系统的基本层次结构如图 15-1 所示。

Android应用

本地框架 屏幕方向管理 Sensor的Java类 Android系统 本地框架 Sensor JNI和硬件抽象层

加速度、磁场、温度等传感器设备 硬件和驱动

▲图 15-1 传感器系统的层次结构

Android 传感器系统从上到下分别是 Java 应用层、Java 框架对传感器的应用、传感器类、传感 器硬件抽象层、传感器驱动。Android 传感器的系统的结构如图 15-2 所示。

第 15 章 传感器系统驱动

Java Sensor Class SensorListener SensorEventListener SensorManager Sensor SensorEvent Java Framework

Sensor JNI (android.hardware.SensorManager)

Sensor Hardware Interface

Sensor Hardware Module Native Framework

Kernel Space Sensor Driver Sensor Driver

▲图 15-2 Android 传感器的系统结构

(1)传感器系统的 Java 部分。 代码路径是“frameworks/base/include/core/java/android/hardware ”,对应的实现文件是 Sensor*.java。 (2)传感器系统的 JNI 部分。 代码路径是“frameworks/base/core/jni/android_hardware_SensorManager.cpp”,在此部分中提供 了对类 android.hardware.Sensor.Manage 的本地支持。 (3)传感器系统 HAL 层。 头文件路径是“hardware/libhardware/include/hardware/sensors.h”,传感器系统的硬件抽象层需 要具体的实现。 (4)驱动层。 驱动层的代码路径是“kernel/driver/hwmon/$(PROJECT)/sensor”,在库 sensor.so 中提供了如下 8 个 API 函数。 控制方面:在结构体 ensors_control_device_t 中定义。

¾ int (*open_data_source)(struct sensors_control_device_t *dev)

¾ int (*activate)(struct sensors_control_device_t *dev, int handle, int enabled)

¾ int (*set_delay)(struct sensors_control_device_t *dev, int32_t ms)

¾ int (*wake)(struct sensors_control_device_t *dev) 数据方面:在结构体 sensors_data_device_t 中定义。

453 Android 驱动开发与移植实战详解

¾ int (*data_open)(struct sensors_data_device_t *dev, int fd)

¾ int (*data_close)(struct sensors_data_device_t *dev)

¾ int (*poll)(struct sensors_data_device_t *dev, sensors_data_t* data) 模块方面:在结构体 sensors_module_t 中定义,包括一个函数 int (*get_sensors_list)(struct sensors_module_t* module, struct sensor_t const** list)。 在 Java 层 Sensor 的状态控制由 SensorService 来负责,它的 Java 代码和 JNI 代码分别位于文件 “frameworks/base/services/java/com/android/server/SensorService.java”和“frameworks/base/services/ jni/com_android_server_SensorService.cpp”中。 SensorManager 来负责在 Java 层 Sensor 的数据控制,它的 Java 代码和 JNI 代码分别位于文件 “ frameworks/base/core/java/android/hardware/SensorManager.java ”和“frameworks/base/core/jni/ android_hardware_SensorManager.cpp”中。 在 Android Framework 中,与 sensor 的通信功能是通过文件 sensorService.java 和 sensorManager.java 实现的。文件 sensorService.java 的通信功能是通过 JNI 调用 sensorService.cpp 中 的方法实现的,而文件 sensorManager.java 的通信功能是通过 JNI 调用 sensorManager.cpp 中的方法 实现的。 文件 sensorService.cpp 和 sensorManger.cpp 通过文件 hardware.c 与 sensor.so 通信。其中文件 sensorService.cpp 负责控制 sensor 的状态,文件 sensorManger.cpp 负责控制 sensor 的数据。库 sensor.so 通过 ioctl 控制 sensor driver 的状态,通过打开 sensor driver 对应的设备文件读取 G-sensor 采集的数据。

15.2 移植 Sensor 驱动

Android 系统中的 Sensor 驱动程序是非标准的,只是为了满足硬件抽象层而推出的。在 Android 传感器系统中,因为从硬件抽象层接口下面都是非标准的,所以 Android 传感器系统的移植包括移 植传感器驱动程序和移植传感器硬件抽象层。Sensor 硬件抽象层被 Sensor JNI 调用,而 Sensor JNI 被 Java 程序调用。由此可见,实现传感器系统的核心是实现硬件抽象层,Sensor 的 HAL 必须满足 硬件抽象层的接口。在 Sensor 硬件抽象层中,使用 Android 的标准硬件模块接口是一种纯 C 语言 接口,需要依靠函数和指针来实现。

15.2.1 移植驱动程序 因为 Android 系统中的 Sensor 驱动程序是非标准的,所以在构建 Sensor 驱动程序时也是没有 标准可以遵循的。我们编写的 Sensor 驱动程序的目的是从硬件中获取传感器的信息,并通过接口 将这些信息传递给上层。我们可以通过如下接口来实现 Sensor 驱动程序。 使用 Event 设备:因为传感器本身就是一种获取信息的工具,所以使用 Event 设备非常自 然。通过使用 Event 设备,可以实现用于阻塞 poll 调用,在中断到来的时候将 poll 解除阻塞,然后 通过 read 调用将数据传递给用户空间。当使用 Event 设备时,可以使用 input 驱动框架中定义的数 据类型。 使用 Misc 杂项字符设备:和使用 Event 设备方式类似,可以直接通过 file_operations 中的

454 第 15 章 传感器系统驱动 read、poll 和 ioctl 接口来实现对应的功能。 实现一个字符设备的主设备:和上面的使用 Misc 杂项字符设备方式相同。 使用 Sys 文件系统:可以实现基本的读、写功能,对应驱动中的 show 和 store 接口实现。 虽然使用 Sys 文件系统可以实现阻塞,但是通常不这样做。

15.2.2 移植硬件抽象层 Sensor 传感器系统 HAL 层的实现文件目录是“hardware/libhardware/include/hardware/”。我们 先看看其中的 Android.mk 文件,其代码如下所示。

LOCAL_PATH:= $(call my-dir) LOCAL_PRELINK_MODULE := false LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw LOCAL_SHARED_LIBRARIES := libcutils libc liblog LOCAL_SRC_FILES := 适配文件 LOCAL_MODULE := sensors.$(PROJECT) include $(BUILD_SHARED_LIBRARY)

在此需要注意对 LOCAL_MODULE 的赋值,这里的模块名字都是定义好了的,具体可以参考 文件“hardware/libhardware/hardware.c”。 在文件 sensors.h 中实现了 Sensor 传感器系统硬件层的接口,这是一个标准的 Android 硬件模 块,其中在里面定义了如下几个重要的结构体。 通过结构体 sensor_module_t 来定义 Sensor 模块,其中 get_sensors_list()用来获得传感器列表。

struct sensor_module_t { struct hw_module_t common; int (*get_sensors_list) (struct sensors_module_t *module, struct sensor_t const**list); };

定义 sensor_t 表示对一个传感器的描述。

struct sensor_t{ const char* name; //传感器名称 const char* vendor; //传感器的 Vendor int version; //传感器的版本 int handle; //传感器的句柄 int type; //传感器类型 float maxRange; //传感器的最大范围 float resolution; //传感器的解析度 float power; //传感器的耗能,单位为 mA void* reserved[9]; }

定义结构体 sensors_data_t 来表示传感器的数据。

typedef struct{ int sensor; //Sensor 标识符 unio{

455 Android 驱动开发与移植实战详解

sensors_vec_t vector; //x,y,z 矢量 sensors_vec_t orientation; //方向值,单位为角度 sensors_vec_t acceleration; //加速度值, 单位为 m/s2 sensors_vec_t magnetic; //磁矢量,单位为 uT float temperature; //温度,单位为℃ float distance; //距离,单位为 cm float light; //光线亮度,单位为 lux } int64_t time; //ns uint32_t reserved; }sensors_data_t;

除了上述结构体外,还有两个十分重要的结构体,分别是 sensors_control_device_t 和 sensors_data_device_t,这两个结构体都实现了对函数指针的定义。

struct sensors_control_device_t { struct hw_device_t common; int (*open_data_source)(struct sensors_control_device_t *dev); int (*activate)(struct sensors_control_device_t *dev, int handle, int enabled); int (*set_delay)(struct sensors_control_device_t *dev, int32_t ms); int (*wake)(struct sensors_control_device_t *dev); }; struct sensors_data_device_t { struct hw_device_t common; int (*data_open)(struct sensors_data_device_t *dev, int fd); int (*data_close)(struct sensors_data_device_t *dev); int (*poll)(struct sensors_data_device_t *dev, sensors_data_t* data); };

15.2.3 实现上层部分 在 Sensor 传感器系统中,上层部分包含了下面的三个模块。 传感器的 JNI 部分和 Java 框架。 在 Java Framework 中调用传感器。 在应用程序中调用传感器。 (1)实现传感器的 JNI 部分和 Java 框架部分。 Android 中 Sensor 传感器系统的 JNI 部分的实现文件是“frameworks/base/core/jni/android_ hardware_SensorManager.cpp”,此文件提供了对类 android.hardware.Sensor.Manage 的本地支持。此 文件是 Sensor 的 Java 部分和硬件抽象层的接口,Sensor 的 JNI 部分将直接调用硬件抽象层,在里 面包含了本地的“hardware/sensors.h”。文件 com_android_server_SensorService.cpp 和 android_hardware_ SensorManager.cpp 联合使用,通过文件“android\hardware\libhardware\hardware.c”与 sensor.so 实 现通信。 文件 android_hardware_SensorManager.cpp。 首先实现方法注册,在里面包含了文件中的功能方法。

456 第 15 章 传感器系统驱动

static JNINativeMethod gMethods[] = { {"nativeClassInit", "()V", (void*)nativeClassInit }, {"sensors_module_init","()I", (void*)sensors_module_init }, {"sensors_module_get_next_sensor","(Landroid/hardware/Sensor;I)I", (void*)sensors_module_get_next_sensor }, {"sensors_data_init", "()I", (void*)sensors_data_init }, {"sensors_data_uninit", "()I", (void*)sensors_data_uninit }, {"sensors_data_open", "(Ljava/io/FileDescriptor;)I", (void*)sensors_data_open }, {"sensors_data_close", "()I", (void*)sensors_data_close }, {"sensors_data_poll", "([F[I[J)I", (void*)sensors_data_poll }, };

定义函数 sensors_module_init()调用函数 hw_get_module()打开 Sensor 的硬件模块,定义函数 sensors_module_init()的代码如下所示。

static jint sensors_module_init(JNIEnv *env, jclass clazz) { int err = 0; sensors_module_t const* module; //打开 Sensor 硬件模块 err = hw_get_module(SENSORS_HARDWARE_MODULE_ID, (const hw_module_t **)&module); if (err == 0) sSensorModule = (sensors_module_t*)module; return err; }

使用函数 nativeClassInit()初始化 Java 中的 Sensor 类。

static void nativeClassInit (JNIEnv *_env, jclass _this) { jclass sensorClass = _env->FindClass("android/hardware/Sensor"); SensorOffsets& sensorOffsets = gSensorOffsets; sensorOffsets.name = _env->GetFieldID(sensorClass, "mName", "Ljava/lang/String;"); sensorOffsets.vendor = _env->GetFieldID(sensorClass, "mVendor", "Ljava/lang/String;"); sensorOffsets.version = _env->GetFieldID(sensorClass, "mVersion", "I"); sensorOffsets.handle = _env->GetFieldID(sensorClass, "mHandle", "I"); sensorOffsets.type = _env->GetFieldID(sensorClass, "mType", "I"); sensorOffsets.range = _env->GetFieldID(sensorClass, "mMaxRange", "F"); sensorOffsets.resolution = _env->GetFieldID(sensorClass, "mResolution","F"); sensorOffsets.power = _env->GetFieldID(sensorClass, "mPower", "F"); }

定义函数 sensors_data_poll()实现了 Senso JNI 的核心内容。向上层传递了传感器数据、精度和 日期三个变量,这是三个浮点型数据。例如在一个加速度传感器中,传递的三个参数是三个方向的 加速度。而对于温度传感器来说,第一个数据是温度信息,而第二个和第三个是无用的。函数

457 Android 驱动开发与移植实战详解

sensors_data_poll()的实现代码如下所示。

static jint sensors_data_poll(JNIEnv *env, jclass clazz, jfloatArray values, jintArray status, jlongArray timestamp) { sensors_data_t data; int res = sSensorDevice->poll(sSensorDevice, &data); if (res >= 0) { jint accuracy = data.vector.status; env->SetFloatArrayRegion(values, 0, 3, data.vector.v); //传感器的数据 env->SetIntArrayRegion(status, 0, 1, &accuracy); //精度 env->SetLongArrayRegion(timestamp, 0, 1, &data.time); //日期 } return res; }

(2)在 Java Framework 中调用传感器的部分。 Sensor 传感器系统的 Java 部分在目录“frameworks/base/include/core/java/android/hardware/”中 定义。在此目录中主要包含下面的文件。 SensorManager.java:实现传感器系统的管理类 SensorManager。 Sensor.java:描述了一个单一的传感器,此类是通过 SensorManager 实现的。类 Sensor 的初 始化工作是在 SensorManager JNI 代码中实现的,在 SensorManager.java 中维护了一个 Sensor 列表。 SensorEvent.java:实现传感器系统的事件类 SensorEvent。 sensorEventListener.java:通过在 sensorManager.java 中注册可以监听特定类型的 Sensor 传 来的数据。 SensorManager、SensorEvent 和 Sensor 是三个类,而 sensorEventListener 和 sensorListener 是两 个接口,上述文件都是 Android 系统的 API 接口。 定义类 Sensor 的代码如下所示。

public class Sensor { public static final int TYPE_ACCELEROMETER = 1; public static final int TYPE_MAGNETIC_FIELD = 2; public static final int TYPE_ORIENTATION = 3; public static final int TYPE_GYROSCOPE = 4; public static final int TYPE_LIGHT = 5; public static final int TYPE_PRESSURE = 6; public static final int TYPE_TEMPERATURE = 7; public static final int TYPE_PROXIMITY = 8; public static final int TYPE_ALL = -1; private String mName; private String mVendor; private int mVersion; private int mHandle; private int mType; private float mMaxRange; private float mResolution;

458 第 15 章 传感器系统驱动

private float mPower; private int mLegacyType; Sensor() { } public String getName() { return mName; } public String getVendor() { return mVendor; } public int getType() { return mType; } public int getVersion() { return mVersion; } public float getMaximumRange() { return mMaxRange; } public float getResolution() { return mResolution; } public float getPower() { return mPower; } int getHandle() { return mHandle; } void setRange(float max, float res) { mMaxRange = max; mResolution = res; } void setLegacyType(int legacyType) { mLegacyType = legacyType; } int getLegacyType() { return mLegacyType; } }

在上述代码中,“TYPE_”格式的常量用数字 1~8 来表示,表示了在 Android 系统中可以支持 的传感器的类型,而最后一行“TYPE_ALL= -1”表示支持所有的类型。 定义类 SensorEvent 的代码如下所示。

public class SensorEvent { public final float[] values;//数值 public Sensor sensor; public int accuracy;//精度 public long timestamp;//时间戳 SensorEvent(int size) {

459 Android 驱动开发与移植实战详解

values = new float[size]; } }

由此可见,类 SensorEvent 比 Sensor 增加了 values、accuracy 和 timestamp。 类 SensorManager 是 Sensor 系统的核心,主要代码如下所示。

public class SensorManager { private static final String TAG = "SensorManager"; private static final float[] mTempMatrix = new float[16]; public static final int SENSOR_ORIENTATION = 1 << 0; public static final int SENSOR_ACCELEROMETER = 1 << 1; public static final int SENSOR_TEMPERATURE = 1 << 2; ...... public static final float GRAVITY_SUN = 275.0f; public static final float GRAVITY_MERCURY = 3.70f; public static final float GRAVITY_VENUS = 8.87f; public static final float GRAVITY_EARTH = 9.80665f; public static final float GRAVITY_MOON = 1.6f; public static final float GRAVITY_MARS = 3.71f; public static final float GRAVITY_JUPITER = 23.12f; public static final float GRAVITY_SATURN = 8.96f; public static final float GRAVITY_URANUS = 8.69f; public static final float GRAVITY_NEPTUNE = 11.0f; public static final float GRAVITY_PLUTO = 0.6f; public static final float GRAVITY_DEATH_STAR_I = 0.000000353036145f; public static final float GRAVITY_THE_ISLAND = 4.815162342f; ………………………………………

(3)在应用程序中调用传感器。 在 Java 应用层中可以调用 SensorManager,通常通过 SensorEventListener 注册回调函数的方式 实现对 Sensor 系统的调试。

15.3 实现传感器

Android 系统为模拟器提供了一个 Sensor 硬件抽象层实例,读者在此实例的基础上进行简单修 改即可实现自己需要的功能。 在 Donut 及其以前的版本中,Sensor 硬件抽象层的代码路径是“development/emulator/sensor/”。 在 Eclair 及其以后的版本中,Sensor 硬件抽象层的代码路径是“sdk/emulator/sensor/”。无论是什么 版本,在上述目录下都会包含 Android.mk 和 sensors_qemu.c 这两个文件。 经过编译以后会形成一个单独的模块:sensors.goldfish.so(goldfish 表示产品名),此模块将被 放在标准文件系统的“system/lib/hw”目录中,在运行时将作为一个硬件被加载。 在文件 sensors_qemu.c 中定义所需常量,具体代码如下所示。

#define ID_BASE SENSORS_HANDLE_BASE

460 第 15 章 传感器系统驱动

#define ID_ACCELERATION (ID_BASE+0) #define ID_MAGNETIC_FIELD (ID_BASE+1) #define ID_ORIENTATION (ID_BASE+2) #define ID_TEMPERATURE (ID_BASE+3) #define ID_PROXIMITY (ID_BASE+4)

#define SENSORS_ACCELERATION (1 << ID_ACCELERATION) #define SENSORS_MAGNETIC_FIELD (1 << ID_MAGNETIC_FIELD) #define SENSORS_ORIENTATION (1 << ID_ORIENTATION) #define SENSORS_TEMPERATURE (1 << ID_TEMPERATURE) #define SENSORS_PROXIMITY (1 << ID_PROXIMITY)

然后定义传感器链表,具体代码如下所示。

#define ID_CHECK(x) ((unsigned)((x)-ID_BASE) < MAX_NUM_SENSORS) #define SENSORS_LIST \ SENSOR_(ACCELERATION,"acceleration") \ SENSOR_(MAGNETIC_FIELD,"magnetic-field") \ SENSOR_(ORIENTATION,"orientation") \ SENSOR_(TEMPERATURE,"temperature") \ SENSOR_(PROXIMITY,"proximity") \ static const struct { const char* name; int id; } _sensorIds[MAX_NUM_SENSORS] = { #define SENSOR_(x,y) { y, ID_##x }, SENSORS_LIST #undef SENSOR_ };

在上述代码中,SENSOR_(x,y)是一个宏,功能是创建一个传感器描述的数据结构,在上述代 码中,分别创建了加速度、磁场、方向和温度四个传感器。 在文件 sensors_qemu.c 中通过如下代码打开传感器硬件模块。

static struct hw_module_methods_t sensors_module_methods = { .open = open_sensors };

在上述代码中,函数 open_sensor()是一个打开函数,用于构建 Sensor 控制设备和数据设备。 函数 open_sensor()的实现代码如下所示。

static int open_sensors(const struct hw_module_t* module, const char* name, struct hw_device_t* *device) { int status = -EINVAL; D("%s: name=%s", __FUNCTION__, name); if (!strcmp(name, SENSORS_HARDWARE_POLL)) { SensorPoll *dev = malloc(sizeof(*dev));

461 Android 驱动开发与移植实战详解

memset(dev, 0, sizeof(*dev));//分配传感器控制设备 dev->device.common.tag = HARDWARE_DEVICE_TAG; dev->device.common.version = 0; dev->device.common.module = (struct hw_module_t*) module; dev->device.common.close = poll__close; dev->device.poll = poll__poll; dev->device.activate = poll__activate; dev->device.setDelay = poll__setDelay; dev->events_fd = -1; dev->fd = -1; *device = &dev->device.common; status = 0; } return status; }

定义结构体 sensors_module_t,功能是增加这里的传感器的私有数据作为上下文。结构体 sensors_module_t 的实现代码如下所示。

const struct sensors_module_t HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_MODULE_TAG, .version_major = 1, .version_minor = 0, .id = SENSORS_HARDWARE_MODULE_ID, .name = "Goldfish SENSORS Module", .author = "The Android Open Source Project", .methods = &sensors_module_methods, }, .get_sensors_list = sensors__get_sensors_list };

定义数组 sSensorListInit,此数组是一个 sensor_t 类型的数组,在里面列出了系统可以支持的 传感器。在硬件抽象层中定义了 4 个传感器,分别是加速度、磁场、方向和温度。定义数组 sSensorListInit 的代码如下所示。

static const struct sensor_t sSensorListInit[] = { { .name = "Goldfish 3-axis Accelerometer", .vendor = "The Android Open Source Project", .version = 1, .handle = ID_ACCELERATION, .type = SENSOR_TYPE_ACCELEROMETER,//加速度传感器 .maxRange = 2.8f, .resolution = 1.0f/4032.0f, .power = 3.0f, .reserved = {} }, { .name = "Goldfish 3-axis Magnetic field sensor", .vendor = "The Android Open Source Project", .version = 1,

462 第 15 章 传感器系统驱动

.handle = ID_MAGNETIC_FIELD, .type = SENSOR_TYPE_MAGNETIC_FIELD, .maxRange = 2000.0f, .resolution = 1.0f, .power = 6.7f, .reserved = {} }, { .name = "Goldfish Orientation sensor", .vendor = "The Android Open Source Project", .version = 1, .handle = ID_ORIENTATION, .type = SENSOR_TYPE_ORIENTATION, .maxRange = 360.0f, .resolution = 1.0f, .power = 9.7f, .reserved = {} }, { .name = "Goldfish Temperature sensor", .vendor = "The Android Open Source Project", .version = 1, .handle = ID_TEMPERATURE, .type = SENSOR_TYPE_TEMPERATURE, .maxRange = 80.0f, .resolution = 1.0f, .power = 0.0f, .reserved = {} }, { .name = "Goldfish Proximity sensor", .vendor = "The Android Open Source Project", .version = 1, .handle = ID_PROXIMITY, .type = SENSOR_TYPE_PROXIMITY, .maxRange = 1.0f, .resolution = 1.0f, .power = 20.0f, .reserved = {} }, };

文件 sensors_qemu.c 的核心功能是通过函数 data__poll()实现的,先使用 if 语句分类读取传感 器中的数据,然后给 SensorData 结构赋值。读者需要注意的是,我们上述代码实例只是仿真演示, 所以读取信息的内容是来自软件的 Buffer,设置结果需要通过数据结构 sensor_data_t 来体现。函数 data__poll()的实现代码如下所示。

static int data__poll(struct sensors_poll_device_t *dev, sensors_event_t* values) { SensorPoll* data = (void*)dev; int fd = data->events_fd; D("%s: data=%p", __FUNCTION__, dev);

463 Android 驱动开发与移植实战详解

if (data->pendingSensors) { return pick_sensor(data, values); } uint32_t new_sensors = 0; while (1) { char buff[256]; int len = qemud_channel_recv(data->events_fd, buff, sizeof buff-1); float params[3]; int64_t event_time; if (len < 0) { E("%s: len=%d, errno=%d: %s", __FUNCTION__, len, errno, strerror(errno)); return -errno; } buff[len] = 0;//读取传感器的信息 if (!strcmp((const char*)data, "wake")) { return 0x7FFFFFFF; } //加速度传感器处理 if (sscanf(buff, "acceleration:%g:%g:%g", params+0, params+1, params+2) == 3) { new_sensors |= SENSORS_ACCELERATION; data->sensors[ID_ACCELERATION].acceleration.x = params[0]; data->sensors[ID_ACCELERATION].acceleration.y = params[1]; data->sensors[ID_ACCELERATION].acceleration.z = params[2]; continue; } //方向传感器处理 if (sscanf(buff, "orientation:%g:%g:%g", params+0, params+1, params+2) == 3) { new_sensors |= SENSORS_ORIENTATION; data->sensors[ID_ORIENTATION].orientation.azimuth = params[0]; data->sensors[ID_ORIENTATION].orientation.pitch = params[1]; data->sensors[ID_ORIENTATION].orientation.roll = params[2]; continue; } //磁场传感器处理 if (sscanf(buff, "magnetic:%g:%g:%g", params+0, params+1, params+2) == 3) { new_sensors |= SENSORS_MAGNETIC_FIELD; data->sensors[ID_MAGNETIC_FIELD].magnetic.x = params[0]; data->sensors[ID_MAGNETIC_FIELD].magnetic.y = params[1]; data->sensors[ID_MAGNETIC_FIELD].magnetic.z = params[2]; continue; } //温度传感器处理 if (sscanf(buff, "temperature:%g", params+0) == 2) { new_sensors |= SENSORS_TEMPERATURE; data->sensors[ID_TEMPERATURE].temperature = params[0]; continue; } if (sscanf(buff, "proximity:%g", params+0) == 1) { new_sensors |= SENSORS_PROXIMITY;

464 第 15 章 传感器系统驱动

data->sensors[ID_PROXIMITY].distance = params[0]; continue; } if (sscanf(buff, "sync:%lld", &event_time) == 1) { if (new_sensors) { data->pendingSensors = new_sensors; int64_t t = event_time * 1000LL; /* convert to nano-seconds */ if (data->timeStart == 0) { data->timeStart = data__now_ns(); data->timeOffset = data->timeStart - t; } t += data->timeOffset; while (new_sensors) { uint32_t i = 31 - __builtin_clz(new_sensors); new_sensors &= ~(1<sensors[i].timestamp = t; } return pick_sensor(data, values); } else { D("huh ? sync without any sensor data ?"); } continue; } D("huh ? unsupported command"); } return -1; }

465

第 16 章 Wi-Fi 系统、 蓝牙系统和 GPS 系统

在本章将详细讲解 Android 平台中的三种驱动系统,分别是 Wi-Fi 系统、蓝牙系统和 GPS 系统, 这三种系统都和连接有关。在本章将详细讲解上述三大驱动系统的基本知识和移植方法,为读者步 入本书后面知识的学习打下基础。

16.1 Wi-Fi 系统的应用和移植

Wi-Fi 是一种可以将个人电脑、手持设备(如 PDA、手机)等终端以无线方式互相连接的技术。 Wi-Fi 是一个无线网路通信技术的品牌,目的是改善基于 IEEE 802.11 标准的无线网路产品之间的 互通性。一般人会把 Wi-Fi 及 IEEE 802.11 混为一谈,甚至直接把 Wi-Fi 等同于无线网际网路。

16.1.1 Wi-Fi 系统的结构 其实 Android 中 Wi-Fi 的架构大体和其他模块的结构一样,也是从应用层、JNI 层到 HAL 层, 只不过并不是 HAL 层直接和驱动层打交道,而是在两者之间加了一个 wpa_supplicant 层, wpa_supplicant 是 WPA 应用层认证客户端,通过对 wpa_supplicant 发送命令从而可以和驱动层进行 交互,wpa_supplicant 通过 WEXT(wireless_ext)接口向驱动层发消息。 Wi-Fi 系统的上层接口包括数据部分和控制部分,数据部分通常是一个和以太网卡类似的网络 设备,控制部分用于实现接入点操作和安全验证处理。在软件层,Wi-Fi 系统包括 Linux 内核程序 和协议,还包括本地部分、Java 框架类。 Wi-Fi 系统的基本层次结构如图 16-1 所示。 Android 平台中 Wi-Fi 系统从上到下主要包括 Java 框架类、Android 适配器库、wpa_supplicant 守护进程、驱动程序和协议,这几部分的系统结构如图 16-2 所示。 图 16-2 中各个部分的具体说明如下所示。 (1)Wi-Fi 用户空间的程序和库,对应路径是“external/wpa_supplicant/”,在此生成库 libwpaclient.so 和守护进程 wpa_supplicant。 (2)Wi-Fi 管理库即适配器库,通过调用库 libwpaclient.so 成为 wpa_supplicant 在 Android 中的 客户端,对应路径是“hardware/libhardware_legary/wifi/”。 (3)JNI 部分的对应路径是“frameworks/base/core/jni/android_net_wifi_Wifi.cpp”。 (4)Java 框架部分的对应路径是“frameworks/base/services/java/com/android/server/”和“ frameworks/

第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统 base/wifi/java/android/net/wifi/”,在 android.net.wifi 将作为 Android 平台的 API 供 Java 应用程序层 使用。 (5)Wi-Fi Settings 应用程序的对应路径是“packages/apps/Settings/src/com/android/settings/wifi/”。

Wi-Fi系统的管理类 平台API

本地框架 Android.net.wifi

Android系统

本地框架 Wi-Fi JNI、Wi-Fi适配器层、 wpa_supplicant

Wi-Fi设备 硬件和驱动

▲图 16-1 Wi-Fi 系统的层次结构

Settings、Wifitcher 等 Java应用层

Java框架层 Android.net.wifi包

Wi-Fi的JNI

WPA适配器层

配置文件 数据 Wpa_supplicant.conf 通道 wpa_supplicant守护进程 协议 协议 协议 C框架层 Wlan网络设备 驱动 驱动 驱动

内核空间层

Wi-Fi协议

Wi-Fi特定驱动

▲图 16-2 Wi-Fi 的系统结构

467 Android 驱动开发与移植实战详解

16.1.2 移植的内容 接下来看 Wi-Fi 在 Android 中的工作过程,Android 使用一个修改版 wpa_supplicant 作为 daemon 来控制 Wi-Fi,代码位于目录“external/wpa_supplicant”。wpa_supplicant 是通过 socket 与文件 “ hardware/libhardware_legacy/wifi/wifi.c ”进行通信。UI 通过 android.net.wifi package (frameworks/base/wifi/java/android/net/wifi/)发送命令给文件 wifi.c。相应的 JNI 实现位于文件 “ frameworks/base/core/jni/android_net_wifi_Wifi.cpp ”中,更高一级的网络管理位于目录 “frameworks/base/core/java/android/net”。 在 Android 中的无线局域网部分是标准的系统,并且针对特定的硬件平台,所以需要移植和改 动的内容并不多。 在 Linux 内核中有 Wi-Fi 的标准协议,不同硬件平台的差异仅仅体现在 Wi-Fi 芯片驱动程序。 除了这些芯片级驱动的差异外,在 Android 中实现其他无线局域网部分的方法在 Linux 内核中已经 给出了具体方案。 而在 Android 用户空间中,使用了标准的 wpa_supplicant 守护进程,这也是一个标准的实现, 所以我们不需要为 Wi-Fi 增加单独的硬件抽象层代码,只需进行简单的配置工作即可。

16.1.3 移植和调试

1.本地实现

本地实现部分主要包括 wpa_supplicant 以及 wpa_supplicant 适配层。WPA 是 Wi-Fi Protected Access 的缩写,中文含义为“Wi-Fi 网络安全存取”。WPA 是一种基于标准的可互操作的 WLAN 安 全性增强解决方案,可大大增强现有以及未来无线局域网系统的数据保护和访问控制水平。 在移植过程中,需要特别注意 wpa_supplicant,这是一个标准的开源项目,已经被移植到很多 平台上,我们比较关心的是 wpa_supplicant 在接收到上层的命令后怎么将命令发给 DRIVER, DRIVER 在接收到命令后的解析的动作和之后调用驱动功能函数的流程,以及驱动对寄存器控制的 细节。由于需要注意代码保密,所以之后不会提及具体使用了哪块 Wi-Fi 芯片,也不会提及此 Wi-Fi DRIVER 是在什么平台什么产品。 wpa_supplicant 适配层是通用的 wpa_supplicant 的封装,在 Android 中作为 Wi-Fi 部分的硬件抽 象层来使用。wpa_supplicant 适配层主要用于封装与 wpa_supplicant 守护进程的通信,以提供给 Android 框架使用。它实现了加载、控制和消息监控等功能。wpa_supplicant 适配层的头文件是 “hardware/libhardware_legacy/include/hardware_legacy/wifi.h”。 wpa_supplicant 的标准结构框图如图 16-3 所示。 在文件 Driver.h 中有一个名为 wpa_driver_ops 的结构体,此结构体在文件 Driver.c 中被声明如下。

#ifdef CONFIG_DRIVER_WEXT extern struct wpa_driver_ops wpa_driver_wext_ops;

Android 中驱动模块的加载是通过在 wifi.c 中的 wifi_load_driver()完成的,路径如下:

"hardware/libhardware_legacy/wifi/wifi.c"

468 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

▲图 16-3 wpa_supplicant 的标准结构框图

加载 Wi-Fi 驱动模块的函数为 wifi_load_driver(),代码如下: int wifi_load_driver() { char driver_status[PROPERTY_VALUE_MAX]; int count = 100; /* wait at most 20 seconds for completion */ if (check_driver_loaded()) { return 0; } //加载驱动模块 if (insmod(DRIVER_MODULE_PATH, DRIVER_MODULE_ARG) < 0) return -1; if (strcmp(FIRMWARE_LOADER,"") == 0) { usleep(WIFI_DRIVER_LOADER_DELAY); property_set(DRIVER_PROP_NAME, "ok"); } else { property_set("ctl.start", FIRMWARE_LOADER); } sched_yield(); while (count-- > 0) { if (property_get(DRIVER_PROP_NAME, driver_status, NULL)) { if (strcmp(driver_status, "ok") == 0) return 0;

469 Android 驱动开发与移植实战详解

else if (strcmp(DRIVER_PROP_NAME, "failed") == 0) { wifi_unload_driver(); return -1; } } usleep(200000); } property_set(DRIVER_PROP_NAME, "timeout"); wifi_unload_driver(); return -1; }

其中第一个参数定义路径驱动模块的路径:

#define WIFI_DRIVER_MODULE_PATH "/system/lib/modules/wlan.ko"

这个需要根据不同的硬件平台去设置,当驱动成功加载后,还需要 firmware 文件,这里我们 以 Marvel 8686 SDIO 为例,当 Wi-Fi 设备加载的时候,需要去指定路径寻找 firmware 文件 sd8686.bin sd8686_helper.bin,因为天线接收和发送回来的都是 802.11 的帧,而主机接收和传送出来的数据都 必须是 802.3 的帧,所以必须由 firmware 来负责 802.3 的帧和 802.11 帧之间的转换。 当 Wi-Fi 设备成功加载后,最后和驱动通信是由 wpa_supplicant 的接口完成的,这些接口定义 在 Driver_wext.c 中。下面是填写了该结构体成员的代码。

const struct wpa_driver_ops wpa_driver_wext_ops = { .name = "wext", .desc = "Linux wireless extensions (generic)", .get_bssid = wpa_driver_wext_get_bssid, .get_ssid = wpa_driver_wext_get_ssid, .set_wpa = wpa_driver_wext_set_wpa, .set_key = wpa_driver_wext_set_key, .set_countermeasures = wpa_driver_wext_set_countermeasures, .set_drop_unencrypted = wpa_driver_wext_set_drop_unencrypted, .scan = wpa_driver_wext_scan, .combo_scan = wpa_driver_wext_combo_scan, .get_scan_results2 = wpa_driver_wext_get_scan_results, .deauthenticate = wpa_driver_wext_deauthenticate, .disassociate = wpa_driver_wext_disassociate, .set_mode = wpa_driver_wext_set_mode, .associate = wpa_driver_wext_associate, .set_auth_alg = wpa_driver_wext_set_auth_alg, .init = wpa_driver_wext_init, .deinit = wpa_driver_wext_deinit, .add_pmkid = wpa_driver_wext_add_pmkid, .remove_pmkid = wpa_driver_wext_remove_pmkid, .flush_pmkid = wpa_driver_wext_flush_pmkid, .get_capa = wpa_driver_wext_get_capa, .set_operstate = wpa_driver_wext_set_operstate, #ifdef ANDROID .driver_cmd = wpa_driver_priv_driver_cmd,

470 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

#endif };

上述成员都是驱动和 wpa_supplicant 的接口,例如 SCAN 的代码如下所示。

int wpa_driver_wext_scan(void *priv, const u8 *ssid, size_t ssid_len)

再看下面的代码。

if (ioctl(drv->ioctl_sock, SIOCSIWSCAN, &iwr) < 0)

通过上述代码可以看出,wpa_cupplicant 是通过 IOCTL 来调用 SOCKET 与 DRIVER 进行通信 的,并给 DRIVER 下达 SIOCSIWSCAN 命令。 因为 Wi-Fi 模块是采用 SDIO 总线来控制的,所以需要先记录 CLIENT DRIVER 的 SDIO 部分 的结构,此部分的 SDIO 分为三层,分别是 SdioDrv、SdioAdapter、SdioBusDrv。其中 SdioBusDrv 是 Client Driver 中 SDIO 与 Wi-Fi 模块的接口,SdioAdapter 是 SdioDrv 和 SdioBusDrv 之间的适配 层,SdioDrv 是 Client Driver 中 SDIO 与 LINUX KERNEL 中的 MMC SDIO 的接口。这三部分只需 要关注 SdioDrv 即可,另外两层只是对它的封装。 在 SdioDrv 中提供了下面的功能。

static struct sdio_driver tiwlan_sdio_drv = { .probe = tiwlan_sdio_probe, .remove = tiwlan_sdio_remove, .name = "sdio_tiwlan", .id_table = tiwl12xx_devices, }; int sdioDrv_EnableFunction(unsigned int uFunc) int sdioDrv_EnableInterrupt(unsigned int uFunc)

SDIO 在读写时实际是调用了“MMC\Core”中的如下功能函数。

static int mmc_io_rw_direct_host()

SDIO 功能部分我们无须了解,因为 HOST 芯片厂商会提供完整的解决方案,我们的主要任务 还是 Wi-Fi 模块。 接下来看 Wi-Fi 模块的入口函数 wlanDrvIf_ModuleInit(),此函数调用了 wlanDrvIf_Create()。

static int wlanDrvIf_Create (void) { TWlanDrvIfObj *drv; //这个结构体为代表设备,包含 LINUX 网络设备结构体 net_device pDrvStaticHandle = drv; drv->pWorkQueue = create_singlethread_workqueue (TIWLAN_DRV_NAME);//创建了工作队列 rc = wlanDrvIf_SetupNetif (drv); drv->wl_sock = netlink_kernel_create( NETLINK_USERSOCK, 0, NULL, NULL, THIS_MODULE ); // 创建了接受 wpa_supplicant 的 SOCKET 接口 rc = drvMain_Create (drv, &drv->tCommon.hDrvMain, &drv->tCommon.hCmdHndlr,

471 Android 驱动开发与移植实战详解

&drv->tCommon.hContext, &drv->tCommon.hTxDataQ, &drv->tCommon.hTxMgmtQ, &drv->tCommon.hTxCtrl, &drv->tCommon.hTWD, &drv->tCommon.hEvHandler, &drv->tCommon.hCmdDispatch, &drv->tCommon.hReport, &drv->tCommon.hPwrState); rc = hPlatform_initInterrupt (drv, (void*)wlanDrvIf_HandleInterrupt); return 0; }

在调用 wlanDrvIf_Create()函数后,实际上 Wi-Fi 模块的初始化就结束了,接下来开始分析如何 初始化。先看函数 wlanDrvIf_SetupNetif (drv)的主体,对应代码如下所示。

static int wlanDrvIf_SetupNetif (TWlanDrvIfObj *drv) { struct net_device *dev; int res; dev = alloc_etherdev (0);//申请 LINUX 网络设备 if (dev == NULL) ether_setup (dev);//建立网络接口 ,这两个都是 LINUX 网络设备驱动的标准函数 dev->netdev_ops = &wlan_netdev_ops; wlanDrvWext_Init (dev); res = register_netdev (dev); hPlatform_SetupPm(wlanDrvIf_Suspend, wlanDrvIf_Resume, pDrvStaticHandle); }

上述代码初始化了 wlanDrvWext_Inti(dev),wpa_supplicant 与 Driver 直接的联系走的是 WEXT 这条路,也就是说 event 的接收处理也是在 WEXT 部分来实现的。 接下来需要注册网络设备 dev,在 wlan_netdev_ops 中的定义代码如下所示。

static const struct net_device_ops wlan_netdev_ops = { .ndo_open = wlanDrvIf_Open, .ndo_stop = wlanDrvIf_Release, .ndo_do_ioctl = NULL, .ndo_start_xmit = wlanDrvIf_Xmit, .ndo_get_stats = wlanDrvIf_NetGetStat, .ndo_validate_addr = NULL, };

上述代码名字对应的都是 Linux 网络设备驱动的命令字,最后需要调用“rc=drvMain_CreateI”, 通过此函数完成了相关模块的初始化工作。

2.JNI 层

Android 中的 Wi-Fi 系统的 JNI 部分实现的源码文件是“frameworks/base/core/jni/android_net_wifi_ Wifi.cpp”。

472 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

JNI 层的接口注册到 Java 层的源代码文件是“frameworks/base/wifi/java/android/net/wifi/WifiNative. java”。 WifiNative 将为 WifiService、WifiStateTracker、WifiMonitor 等几个 Wi-Fi 框架内部组件提供底 层操作支持。此处实现的本地函数都是通过调用 wpa_supplicant 适配层的接口来实现的(包含适配 层的头文件 wifi.h)。wpa_supplicant 适配层是通用的 wpa_supplicant 的封装,在 Android 中作为 Wi-Fi 部分的硬件抽象层来使用。wpa_supplicant 适配层主要用于封装与 wpa_supplicant 守护进程的通信, 以提供给 Android 框架使用。它实现了加载、控制和消息监控等功能。wpa_supplicant 适配层的头 文件是“hardware/libhardware_legacy/include/hardware_legacy/wifi.h”。 文件 wifi.h 是 Wi-Fi 适配器层对 JNI 部分的接口,在里面包含了一些加载和连接的控制接口, 其中最重要的是如下两个接口。 wifi_command():负责将命令发送到 Wi-Fi 下层。 wifi_wait_for_event():负责事件进入通道,此函数将被阻塞,一直到收到一个 Wi-Fi 事件 为止,并且以字符串的形式返回。 在文件 wifi.h 中定义上述接口的代码如下所示。

int wifi_command(const char *command, char *reply, size_t *reply_len); int wifi_wait_for_event(char *buf, size_t len);

在文件 wifi.c 中实现了上述两个接口。

int wifi_command(const char *command, char *reply, size_t *reply_len) { return wifi_send_command(ctrl_conn, command, reply, reply_len); } int wifi_wait_for_event(char *buf, size_t buflen) { size_t nread = buflen - 1; int fd; fd_set rfds; int result; struct timeval tval; struct timeval *tptr;

if (monitor_conn == NULL) return 0;

result = wpa_ctrl_recv(monitor_conn, buf, &nread); if (result < 0) { LOGD("wpa_ctrl_recv failed: %s\n", strerror(errno)); return -1; } buf[nread] = '\0'; if (result == 0 && nread == 0) { /* Fabricate an event to pass up */ LOGD("Received EOF on supplicant socket\n"); strncpy(buf, WPA_EVENT_TERMINATING " - signal 0 received", buflen-1);

473 Android 驱动开发与移植实战详解

buf[buflen-1] = '\0'; return strlen(buf); } if (buf[0] == '<') { char *match = strchr(buf, '>'); if (match != NULL) { nread -= (match+1-buf); memmove(buf, match+1, nread+1); } } return nread; }

3.Java Framework 层

Wi-Fi 系统的 Java 部分代码实现的目录如下所示。 frameworks/base/wifi/java/android/net/wifi/:Wi-Fi 服务层的内容。 frameworks/base/services/java/com/android/server/:Wi-Fi 部分的接口。 在 Wi-Fi 系统 Java 层中,其核心是根据 IWifiManger 接口所创建的 Binder 服务器端和客户端, 服务器端是 WifiService,客户端是 WifiManger。 编译 IWifiManger.aidl 生成文件 IWifiManger.java,并生成 IWifiManger.Stub(服务器端抽象类) 和 IWifiManger.Stub.Proxy(客户端代理实现类)。WifiService 通过继承 IWifiManger.Stub 实现,而 客户端通过 getService()函数获取 IWifiManger.Stub.Proxy(即 Service 的代理类),将其作为参数传 递给 WifiManger,供其与 WifiService 通信时使用。 Wi-Fi 系统 Java 部分的核心是根据 IWifiManager 接口所创建的 Binder 服务器端和客户端,服 务器端是 WifiService,客户端是 WifiManager。具体结构如图 16-4 所示。 下面是图中主要结构元素的详细说明。 (1)WiFiManger 是 Wi-Fi 部分与外界的接口,用户通过它来访问 Wi-Fi 的核心功能。 WifiWatchdogService 这一系统组件也是用 WifiManger 来执行一些具体操作。 (2)WifiService 是服务器端的实现,作为 Wi-Fi 的核心,处理实际的驱动加载、扫描、链接、 断开等命令,已经底层上报的事件。对于主动的命令控制,Wi-Fi 是一个简单的封装,针对来自客 户端的控制命令,调用相应的 WifiNative 底层实现。 当接收到客户端的命令后,一般会将其转换成对应的自身消息塞入消息队列中,以便客户端的 调用可以及时返回,然后在 WifiHandler 的 handleMessage()中处理对应的消息。而底层上报的事件, WifiService 则通过启动 WifiStateTracker 来负责处理。WifiStateTracker 和 WifiMonitor 的具体功能如 下所示。 WifiStateTracker 除了负责 Wi-Fi 的电源管理模式等功能外,其核心是 WifiMonitor 所实现的 事件轮询机制,以及消息处理函数 handleMessage()。 WifiMonitor 通过开启一个 MonitorThread 来实现事件的轮询,轮询的关键函数是前面提到 的阻塞式函数 WifiNative.waitForEvent()。获取事件后,WifiMonitor 通过一系列的 Handler 通知给 WifiStateTracker。这 里 WifiMonitor 的通知机制是将底层事件转换成 WifiStateTracker 所能识别的消

474 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

息,塞入 WifiStateTracker 的消息循环中,最终在 handleMessage()中由 WifiStateTracker 完成对应的 处理。

▲图 16-4 JNI 接口结构

WifiStateTracker 同样是 Wi-Fi 部分与外界的接口,它不像 WifiManger 那样直接被实例化来操 作,而是通过 Intent 机制来发消息通知给客户端注册的 BroadcastReceiver,以完成和客户端的接口。 (3)WifiWatchdogService 是 ConnectivityService 所启动的服务,但它并不是通过 Binder 来实现 的服务。它的作用是监控同一个网络内的接入点(Access Point),如果当前接入点的 DNS 无法 ping 通,就自动切换到下一个接入点。WifiWatchdogService 通过 WifiManger 和 WifiStateTracker 辅助完 成具体的控制动作。在 WifiWatchdogService 初始化时,通过 registerForWifiBroadcasts 注册获取网 络变化的 BroadcastReceiver,也就是捕获 WifiStateTracker 所发出的通知消息,并开启一个 WifiWatchdogThread 线程来处理获取的消息。通过更改 Setting.Secure.WIFI_WARCHDOG_ON 的配 置,可以开启和关闭 WifiWatchdogService。

4.Setting 中的 Wi-Fi 设置

Android 的 Settings 应用程序对 WI-FI 的使用,是典型的 Wi-Fi 应用方式,也是用户可见的 Android Wi-Fi 管理程序。此部分源代码的目录是“packages/apps/Settings/src/com/android/settings/wifi/”。

475 Android 驱动开发与移植实战详解

Setting 里的 Wi-Fi 部分是用户可见的设置界面,提供 Wi-Fi 开关、扫描 AP、链接/断开的基本 功能。另外 i,通过实现 WifiLayer.Callback 接口提供了一组回调函数,用以响应用户关心的 Wi-Fi 状态的变化。 WifiEnabler 和 WifiLayer 都是 WifiSettings 的组成部分,同样通过 WifiManger 来完成实际的功 能,也同样注册一个 BroadcastReceiver 来响应 WifiStateTracker 所发出的通知消息。WifiEnabler 其 实是一个比较简单的类,提供开启和关闭 Wi-Fi 的功能,设置里面的外层 Wi-Fi 开关菜单,就是直 接通过它来做到的;而 WifiLayer 则提供更复杂的一些 Wi-Fi 功能,如 AP 选择等以供用户自定义。 Setting 中 Wi-Fi 的设置结构如图 16-5 所示。

▲图 16-5 Setting 中的 Wi-Fi 设置结构

16.1.4 OMAP 平台实现 Wi-Fi 在 OPMAP 平台的 Wi-Fi 系统中,内核部分符合标准的 Linux 框架,在 Android 开源工程中, 通过如下目录中的文件实现了和局域网系统相关功能。

system/wlan/ti/wilink_6_1/external_drivers system/wlan/ti/wilink_6_1/platforms system/wlan/ti/wilink_6_1/stad

其中文件“system/wlan/ti/wilink_6_1/platforms/os/linux/src/WlanDrvIf.c”是驱动程序的入口,初 始化函数是 wlanDrvIf_ModuleInit(),通过调用函数 wlanDrvIf_Create()来注册 Wi-Fi 网络设备。具 体代码如下所示。

static int __init wlanDrvIf_ModuleInit (void) { printk(KERN_INFO "TIWLAN: driver init\n"); #ifndef TI_SDIO_STANDALONE #ifndef CONFIG_MMC_EMBEDDED_SDIO sdioDrv_init(sdc_ctrl); #endif #endif

476 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

return wlanDrvIf_Create (); }

函数 wlanDrvIf_Create()的具体代码如下所示。

static int wlanDrvIf_Create (void) { TWlanDrvIfObj *drv; int rc; drv = kmalloc (sizeof(TWlanDrvIfObj), GFP_KERNEL); if (!drv) { return -ENOMEM; }

在 OMAP 平台中,使用另一个较为特殊的驱动来代替 wpa_supplicant 中的 wext 标准驱动。此 特殊部分驱动的实现文件保存在目录“system/wlan/ti/wilink_6_1/wpa_supplicant_lib/”中 。此 目 录 中 的文件 Android.mk 的主要代码如下所示。

L_CFLAGS += -DCONFIG_DRIVER_CUSTOM -DHOST_COMPILE -D__BYTE_ORDER_LITTLE_ENDIAN OBJS = driver_ti.c $(LIB)/scanmerge.c $(LIB)/shlist.c

ifdef CONFIG_NO_STDOUT_DEBUG L_CFLAGS += -DCONFIG_NO_STDOUT_DEBUG endif

ifdef CONFIG_DEBUG_FILE L_CFLAGS += -DCONFIG_DEBUG_FILE endif

ifdef CONFIG_IEEE8021X_EAPOL L_CFLAGS += -DIEEE8021X_EAPOL endif

######################## include $(CLEAR_VARS) LOCAL_MODULE := libCustomWifi LOCAL_SHARED_LIBRARIES := libc libcutils LOCAL_CFLAGS := $(L_CFLAGS) LOCAL_SRC_FILES := $(OBJS) LOCAL_C_INCLUDES := $(INCLUDES) include $(BUILD_STATIC_LIBRARY) ########################

这样经过编译后会生成静态库 libCustomWifi.a,此库被 wpa_supplicant 可执行程序连接,以作 为插件来使用。 然后使用文件“system/wlan/ti/wilink_6_1/config/wpa_supplicant.conf”配置 OMAPpigtail。

##### wpa_supplicant configuration file template ##### update_config=1 ctrl_interface=tiwlan0

477 Android 驱动开发与移植实战详解

eapol_version=1 ap_scan=1 fast_reauth=1

在文件“system/wlan/ti/wilink_6_1/wpa_supplicant_lib/driver_ti.c”中定义了 wpa_driver_ops 类 型的结构体 wpa_driver_custom_ops,在里面列出了对 WPA 驱动操作。

const struct wpa_driver_ops wpa_driver_custom_ops = { .name = TIWLAN_DRV_NAME, .desc = "TI Station Driver (1271)", .get_bssid = wpa_driver_tista_get_bssid, .get_ssid = wpa_driver_tista_get_ssid, .set_wpa = wpa_driver_tista_set_wpa, .set_key = wpa_driver_tista_set_key, .set_countermeasures = wpa_driver_tista_set_countermeasures, .set_drop_unencrypted = wpa_driver_tista_set_drop_unencrypted, .scan = wpa_driver_tista_scan, .get_scan_results = wpa_driver_tista_get_scan_results, .deauthenticate = wpa_driver_tista_deauthenticate, .disassociate = wpa_driver_tista_disassociate, .associate = wpa_driver_tista_associate, .set_auth_alg = wpa_driver_tista_set_auth_alg, .get_mac_addr = wpa_driver_tista_get_mac_addr, .init = wpa_driver_tista_init, .deinit = wpa_driver_tista_deinit, .add_pmkid = wpa_driver_tista_add_pmkid, .remove_pmkid = wpa_driver_tista_remove_pmkid, .flush_pmkid = wpa_driver_tista_flush_pmkid, .set_operstate = wpa_driver_tista_set_operstate, .driver_cmd = wpa_driver_tista_driver_cmd };

16.1.5 配置 Wi-Fi 配置 Wi-Fi 系统的基本流程如下所示。 (1)配置 Android 支持 Wi-Fi,在文件 BoardConfig.mk 中添加如下代码。

BOARD_HAVE_WIFI := true BOARD_WPA_SUPPLICANT_DRIVER := WEXT

此时在文件“external/wpa_supplicant/Android.mk”中设置“WPA_BUILD_SUPPLICANT”为 true,默认使用驱动文件 driver_wext.c。如果使用定制的 wpa_supplicant 驱动(例如 madwifi),可以 进行如下设置。

BOARD_WPA_SUPPLICANT_DRIVER := MADWIFI

(2)使 wpa_supplicant 能够调试信息。wpa_supplicant 的默认设置是 MSG_INFO,为了输出更 多信息,可进行如下修改。 首先在文件 common.c 中设置:

478 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

wpa_debug_level = MSG_DEBUG;

然后在文件 common.c 中把#define wpa_printf 宏中的

if ((level) >= MSG_INFO)

改为

if ((level) >= MSG_DEBUG)

(3)配 置 wpa_supplicant.conf。wpa_supplicant 通过 wpa_supplicant.conf 中的如下语句来指定控 制 socket。

ctrl_interface=

我们应该在文件 AndroidBoard.mk 中配置好,然后复制到“$(TARGET_OUT_ETC)/wifi”(即 文件“/system/etc/wifi/wpa_supplicant.conf”),此位置会在 init.rc 中再次检测。 通常将 wpa_supplicant.conf 进行如下配置。

ctrl_interface=DIR=/data/system/wpa_supplicant GROUP=wifi update_config=1 fast_reauth=1

有时需要增加驱动:

ap_scan=1

如果遇到 AP 连接问题,需要修改“ap_scan=0”以让驱动连接来代替 wpa_supplicant。 如果要连接到“non-WPA or open wireless networks”,需要增加下面的代码。

network={ key_mgmt=NONE }

(4)配置路径和权限。 Google 修改的 wpa_supplicant 需要运行在 Wi-Fi 用户和组下,具体代码请参阅文件 “wpa_supplicant/os_unix.c”中的函数 os_program_init()。如果配置错误则会出现下面错误:

E/WifiHW ( ): Unable to open connection to supplicant on "/data/system/wpa_supplicant/wlan0": No such file or directory will appear.

此时需要确认文件 init.rc 中有如下配置:

mkdir /system/etc/wifi 0770 wifi wifi chmod 0770 /system/etc/wifi chmod 0660 /system/etc/wifi/wpa_supplicant.conf chown wifi wifi /system/etc/wifi/wpa_supplicant.conf # wpa_supplicant socket mkdir /data/system/wpa_supplicant 0771 wifi wifi chmod 0771 /data/system/wpa_supplicant

479 Android 驱动开发与移植实战详解

#wpa_supplicant control socket for android wifi.c mkdir /data/misc/wifi 0770 wifi wifi mkdir /data/misc/wifi/sockets 0770 wifi wifi chmod 0770 /data/misc/wifi chmod 0660 /data/misc/wifi/wpa_supplicant.conf

如果系统的“/system”目录为只读,那应该使用路径“/data/misc/wifi/wpa_supplicant.conf”。 (5)运行 wpa_supplicant 和 dhcpcd。 在文件 init.rc 中必须确保有如下语句。

service wpa_supplicant /system/bin/logwrapper /system/bin/wpa_supplicant -dd -Dwext -iwlan0 -c /data/misc/wifi/wpa_supplicant.conf user root group wifi inet socket wpa_wlan0 dgram 660 wifi wifi oneshot service dhcpcd /system/bin/logwrapper /system/bin/dhcpcd -d -B wlan0 disabled oneshot

根据所用的 Wi-Fi 驱动名字,将 wlan0 修改为自己驱动的名字。 (6)编译 Wi-Fi 驱动为 module 或 kernel built in。 编译为 module。 在文件 BoardConfig.mk 中添加如下代码。

WIFI_DRIVER_MODULE_PATH := "/system/lib/modules/ar6000.ko" WIFI_DRIVER_MODULE_ARG := "" #for example nohwcrypt WIFI_DRIVER_MODULE_NAME := "ar6000" #for example wlan0 WIFI_FIRMWARE_LOADER := ""

编译为 kernel built in。 首先在文件“hardware/libhardware_legacy/wifi/wifi.c”中修改 interface 的名字;然后在文件 init.rc 中添加如下代码。

setprop wifi.interface "wlan0"

最后在文件“hardware/libhardware_legacy/wifi/wifi.c”中 当“ insmod/rmmod”时 直 接“ return 0”。 (7)Wi-Fi 需要的 firmware。 Android 不使用标准的 hotplug binary,Wi-Fi 需要的 firmware 要复制到“/etc/firmware”目录, 或者复制到 Wi-Fi 驱动指定的位置,然后 Wi-Fi 驱动会自动加载。 (8)修改 Wi-Fi 驱动以适合 Android。 Google 修改的 wpa_supplicant 要求 SIOCSIWPRIV ioctl 发送命令到驱动和接收信息,例如 signal strength、mac address of the AP 和 link speed 等。所以要正确实现 Wi-Fi 驱动,需要从 SIOCSIWPRIV ioctl 返回 RSSI (signal strength)和 MACADDR 信息。如果没实现这个 ioctl,则会出现如下错误。

E/wpa_supplicant( ): wpa_driver_priv_driver_cmd failed

480 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

wpa_driver_priv_driver_cmd RSSI len = 4096 E/wpa_supplicant( ): wpa_driver_priv_driver_cmd failed D/wpa_supplicant( ): wpa_driver_priv_driver_cmd LINKSPEED len = 4096 E/wpa_supplicant( ): wpa_driver_priv_driver_cmd failed I/wpa_supplicant( ): CTRL-EVENT-DRIVER-STATE HANGED

(9)设置 dhcpcd.conf。 建议将文件“/system/etc/dhcpcd/dhcpcd.conf”配置为

interface wlan0 option subnet_mask, routers, domain_name_servers

到此为止,我们的工作全部完成,此时在 Android 上面就可以运行我们自己的 Wi-Fi 系统了。

16.1.6 SDIO 设备的移植 现在市面上大部分的 Wi-Fi 和蓝牙芯片都是通过 SDIO 接口和 CPU 连接的,SDIO 卡是在 SD 内存卡接口的基础上发展起来的接口,SDIO 接口兼容以前的 SD 内存卡,并且可以连接 SDIO 接 口的设备,如 Wi-Fi、蓝牙、电视卡。这里介绍的 Marvel 8686 就是一个 SDIO 设备,是通过 SDIO 驱动挂载到总线上去的,下面就来介绍 SDIO 驱动的加载过程。 首先是 SDIO 总线驱动的加载和注册,SDIO 总线是一个标准的 Linux 总线设备,所以也符合 总线加载的一般过程。文件路径是“drivers/mmc/core/Sdio_bus.c”,在其中定义了 SDIO bus 这个结 构体,里面定义了 SDIO 总线设备的一些属性。

static struct bus_type sdio_bus_type = { .name = "sdio", .dev_attrs = sdio_dev_attrs, .match = sdio_bus_match, .uevent = sdio_bus_uevent, .probe = sdio_bus_probe, .remove = sdio_bus_remove, };

头文件 Sdio_func.h 中定义了 SDIO 的驱动,会注册到 SDIO 总线中。文件路径是 “include/linux/mmc/Sdio_func.h”。其中定义 SDIO 驱动 sdio_driver 的代码如下:

struct sdio_driver { char *name;//设备名字 const struct sdio_device_id *id_table; int (*probe)(struct sdio_func *, const struct sdio_device_id *); void (*remove)(struct sdio_func *); //通用设备的驱动 struct device_driver drv; };

在 Sdio_bus.c 中,注册 SDIO 总线,代码如下:

//SDIO 总线注册 int sdio_register_bus(void)

481 Android 驱动开发与移植实战详解

{ return bus_register(&sdio_bus_type); }

注册 SDIO 的设备驱动,代码如下:

int sdio_register_driver(struct sdio_driver *drv) { drv->drv.name = drv->name; drv->drv.bus = &sdio_bus_type; return driver_register(&drv->drv); }

当完成了 SDIO 总线和驱动的注册后,要调用 sdio_add_func()在总线上面增加 SDIO 设备,例 如 Marvel 8686,代码如下:

//增加一个新的 SDIO 设备到驱动中去 //sdio_func 结构体用来描述一个 SDIO 设备的信息 int sdio_add_func(struct sdio_func *func) { int ret; dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num); //增加一个设备 ret = device_add(&func->dev); if (ret == 0) sdio_func_set_present(func); return ret; }

在完成了 SDIO 总线加载、驱动注册和挂载 SDIO 设备之后,接下来就是 Wi-Fi 这个 SDIO 设 备的驱动程序注册和初始化了。文件路径是“drivers/net/wireless/libertas/If_sdio.c”。 在文件 If_sdio.c 中在加载模块的时候 if_sdio_init_module()会注册 if_sdio_driver,代码如下:

//SDIO 模块的初始化 static int __init if_sdio_init_module(void) { int ret = 0; lbs_deb_enter(LBS_DEB_SDIO); printk(KERN_INFO "libertas_sdio: Libertas SDIO driver\n"); printk(KERN_INFO "libertas_sdio: Copyright Pierre Ossman\n"); //设备驱动的注册 ret = sdio_register_driver(&if_sdio_driver); lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); return ret; }

if_sdio_driver 结构体就是上面介绍的 SDIO 设备驱动的一个具体实现(以后有可能是蓝牙、电 视卡等设备),具体定义如下:

static struct sdio_driver if_sdio_driver = {

482 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

.name = "libertas_sdio", .id_table = if_sdio_ids, .probe = if_sdio_probe, .remove = if_sdio_remove, };

sdio_register_driver()的具体实现代码为

//注册驱动 int sdio_register_driver(struct sdio_driver *drv) { drv->drv.name = drv->name; //挂载总线类型为 SDIO 总线 drv->drv.bus = &sdio_bus_type; return driver_register(&drv->drv); }

当探测到 Wi-Fi 设备的时候会调用 if_sdio_probe()函数,代码如下: static int if_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { struct if_sdio_card *card; struct lbs_private *priv; int ret, i; unsigned int model; struct if_sdio_packet *packet; lbs_deb_enter(LBS_DEB_SDIO); for (i = 0;i < func->card->num_info;i++) { if (sscanf(func->card->info[i], "802.11 SDIO ID: %x", &model) == 1) break; if (sscanf(func->card->info[i], "ID: %x", &model) == 1) break; if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) { model = 4; break; } } if (i == func->card->num_info) { lbs_pr_err("unable to identify card model\n"); return -ENODEV; } card = kzalloc(sizeof(struct if_sdio_card), GFP_KERNEL); if (!card) return -ENOMEM; card->func = func; card->model = model; spin_lock_init(&card->lock); INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);

483 Android 驱动开发与移植实战详解

for (i = 0;i < ARRAY_SIZE(if_sdio_models);i++) { if (card->model == if_sdio_models[i].model) break; } if (i == ARRAY_SIZE(if_sdio_models)) { lbs_pr_err("unkown card model 0x%x\n", card->model); ret = -ENODEV; goto free; } card->helper = if_sdio_models[i].helper; card->firmware = if_sdio_models[i].firmware; if (lbs_helper_name) { lbs_deb_sdio("overriding helper firmware: %s\n", lbs_helper_name); card->helper = lbs_helper_name; } if (lbs_fw_name) { lbs_deb_sdio("overriding firmware: %s\n", lbs_fw_name); card->firmware = lbs_fw_name; } sdio_claim_host(func); //设置一些 SDIO 的函数和变量 ret = sdio_enable_func(func); if (ret) goto release; ret = sdio_claim_irq(func, if_sdio_interrupt); if (ret) goto disable; card->ioport = sdio_readb(func, IF_SDIO_IOPORT, &ret); if (ret) goto release_int; card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 1, &ret) << 8; if (ret) goto release_int; card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 2, &ret) << 16; if (ret) goto release_int; sdio_release_host(func); sdio_set_drvdata(func, card); lbs_deb_sdio("class = 0x%X, vendor = 0x%X, " "device = 0x%X, model = 0x%X, ioport = 0x%X\n", func->class, func->vendor, func->device, model, (unsigned)card->ioport); ret = if_sdio_prog_firmware(card); if (ret) goto reclaim; priv = lbs_add_card(card, &func->dev); if (!priv) { ret = -ENOMEM; goto reclaim;

484 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

} card->priv = priv; priv->card = card; priv->hw_host_to_card = if_sdio_host_to_card; priv->fw_ready = 1; //使能中断,所有的都已就绪 sdio_claim_host(func); sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret); sdio_release_host(func); if (ret) goto reclaim; ret = lbs_start_card(priv); if (ret) goto err_activate_card; out: lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); return ret; err_activate_card: flush_scheduled_work(); free_netdev(priv->dev); kfree(priv); reclaim: sdio_claim_host(func); release_int: sdio_release_irq(func); disable: sdio_disable_func(func); release: sdio_release_host(func); free: while (card->packets) { packet = card->packets; card->packets = card->packets->next; kfree(packet); } kfree(card); goto out; }

这样就完成了一个具体的 SDIO 设备的注册,并将其添加到了 SDIO 总线上,接下来谈谈如何 将 Marvel 8686 设备移植到 Android 系统上。

16.1.7 移植 Wi-Fi 驱动的注意事项 对于 Marvel 8686 的移植,需要注意以下两个方面。 (1)生成 firmw 固件和驱动模块。 准备好 firmware 文件和驱动模块 ko 文件(有源码也行,可以自己编译),将 firmware 文件 sd8686.bin 和 sd8686_helper.bin 文件移至“/system/etc/firmware”目录,后面会自动在这个路径下寻

485 Android 驱动开发与移植实战详解

找,用 insmod 命令加载驱动模块到 Android 内核。 (2)修改配置文件。 修改 BoardConfig.mk,将

BOARD_WPA_SUPPLICANT_DRIVER :=true

替换成

BOARD_WPA_SUPPLICANT_DRIVER := WEXT

修改 external/wpa_supplicant/Android.mk,将

WPA_BUILD_SUPPLICANT :=false

修改为

WPA_BUILD_SUPPLICANT := true

移动 wpa_supplicant.conf ,将 wpa_supplicant.conf 移至/system/ect/wifi/ 目录下,这是 wpa_supplicant 命令的配置文件。 修改 init.rc 文件,打开 Wi-Fi 设备的操作权限,加入如下操作:

mkdir /system/etc/wifi 0770 wifi wifi chmod 0777 /system/etc/wifi chmod 0666 /system/etc/wifi/wpa_supplicant.conf chown wifi wifi /system/etc/wifi/wpa_supplicant.conf

修改 init.rc 文件,运行 wpa_supplicant 和 dhcpcd,加入如下操作:

service wpa_supplicant /system/bin/wpa_supplicant -Dwext -ieth1 -c /data/misc/wifi/wpa_ supplicant.conf socket wpa_eth1 dgram 0660 wifi wifi disabled oneshot service dhcpcd /system/bin/logwrapper /system/bin/dhcpcd -d eth1 disabled oneshot group system dhcp

(3)修改文件 hardware/libhardware_legacy/wifi/wifi.c。 重新定义 WIFI_DRIVER_MODULE_PATH 和 WIFI_DRIVER_MODULE_NAME 为自己的名 字。如果有问题,请给各个目录设置为 0777 属性,其他 SDIO 的 Wi-Fi 芯片方法基本相同,Wi-Fi 使用效果如图 16-6 所示。

16.2 蓝牙系统的应用和移植

蓝牙是一种支持设备短距离通信(一般 10m 内)的无线电技术,能够在移动电话、PDA、无

486 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

线耳机、笔记本电脑、相关外设等众多设备之间进行无线信息交换。利用“蓝牙”技术,能够有效 地简化移动通信终端设备之间的通信,也能够成功地简化设备与因特网 Internet 之间的通信,从而 使数据传输变得更加迅速高效。

▲图 16-6 Wi-Fi 使用效果

16.2.1 蓝牙结构 Android 中的蓝牙系统底层驱动的通信是采用 socket 方式传递消息的,在 BlueZ 中是通过 HCI (Host Controller Interface)这个统一接口来与底层通信的,HCI 实际上是一个封装层,它能抽象底 层的具体实现,将一套标准接口暴露给应用层,起着承上启下的作用,下面着重介绍 HCI 中的几 个重要方法,针对本地适配器和远程适配器的连接。 Android 平台的蓝牙系统是基于 BlueZ 实现的,是通过 Linux 中一套完整的蓝牙协议栈开源实 现的。当前 BlueZ 被广泛应用于各种 Linux 版本中,并被芯片公司移植到各种芯片平台上使用。在 Linux2.6 内核中已经包含了完整的 BlueZ 协议栈,在 Android 系统中已经移植并嵌入进了 BlueZ 的 用户空间实现,并且随着硬件技术的发展而不断更新。 在 Android 系统中的蓝牙除了使用 kernel 支持外,还需要用户空间的 BlueZ 的支持。在 Android 中 Wi-Fi 系统的基本层次结构如图 16-7 所示。 蓝牙系统从上到下主要包括 Java 框架中的 BlueTooth 类、Android 适配库、BlueZ 库、驱动程 序和协议,这几部分的系统结构如图 16-8 所示。 在图 16-6 中各个层次结构的具体说明如下所示。 (1)BlueZ 库。 Android 蓝牙设备管理的库的路径是“external/bluez/”,可以分别生成库 libbluetooth.so、 libbluedroid.so 和 hcidump 等众多相关工具和库。BlueZ 库提供了对用户空间蓝牙的支持,在里面包

487 Android 驱动开发与移植实战详解

含了主机控制协议 HCI 以及其他众多内核实现协议的接口,并且实现了所有蓝牙应用模式 Profile。 (2)蓝牙的 JNI 部分。 此部分的代码路径是“frameworks/base/core/jni/”。

蓝牙应用 平台API

本地框架 Android.bluetooth包

Android系统

本地框架 Bluetooth JNI bluetooth适配层和BluetZ库

蓝牙设备 硬件和驱动

▲图 16-7 蓝牙系统的层次结构

Headset/Handsfree 蓝牙Settings 电话相关 Java应用层

Android.bluetooth包中的各个类

Java框架层 D-BUS

bluez适配器

用户空间 bluez C框架层 Sco.Rfcomn Socket HCI等socket

蓝牙协议层

内核空间 蓝牙驱动

▲图 16-8 蓝牙系统结构

488 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

(3)Java 框架层。 此层的实现代码保存在下面的路径中。

frameworks/base/core/java/android/bluetooth //蓝牙部分对应应用程序的 API frameworks/base/core/java/android/Server //蓝牙的服务部分

蓝牙的服务部分负责管理并使用底层本地服务,并封装成系统服务。而在 android.bluetooth 部 分中包含了各个蓝牙平台的 API 部分供应用程序层所使用。 (4)BlueTooth 的适配库。 BlueTooth 适配库的代码路径是“system/bluetooth/”,此层用于生成库 libbluedroid.so 以及相关 工具和库,能够实现对蓝牙设备的管理,例如实现对电源的管理。

16.2.2 移植的内容 在 Android 的蓝牙系统中,本地部分和框架层都是标准程序,我们需要移植的内容是蓝牙驱动 程序。蓝牙驱动程序包括针对硬件接口的 USB、SDIO 和 UART 驱动,此部分驱动的内容也是标准 的。如果使用 UART 蓝牙芯片,则需要使用芯片特定的高速串口。另外在驱动中还包含了电源管 理和芯片配置。因为通常硬件接口都比较标准,所以有很多蓝牙芯片通过用户空间的初始化代码直 接对芯片进行写入操作。

1.Bluez

Android 所采用的蓝牙空间库是 BlueZ,这是一套 Linux 平台的蓝牙协议栈完整开源实现,广 泛用在各 Linux 发行版,并被移植到众多移动平台上。BlueZ 提供了很多分散的应用,例如守护进 程和一些工具,BlueZ 通过 D-BUS IPC 机制来提供应用层接口。

2.适配层

BlueZ 的适配层 BlueZ 在 Android 中使用,需要经过 Android 的 BlueZ 适配层的封装。BlueZ 适配层源代码及头文件路径是“system/bluetooth/ ”,在该目录中除了包含生成适配层库 libbluedroid.so 的源码外,还包含了 BlueZ 头文件和 BlueZ 配置文件等目录。由于 BlueZ 使用 D-BUS 作为与上层沟通的接口,适配层构造比较简单,在里面封装了蓝牙开关和射频开关。

3.JNI 和 Java 部分

在 Android 还定义了 Bluetooth 通过 JNI 到上层的接口,此功能保存在 “frameworks/base/core/jni/”目录中。此目录中的主要实现文件如下所示。 android_bluetooth_BluetoothAudioGateway.cpp。 android_bluetooth_common.cpp。 android_bluetooth_Database.cpp。 android_bluetooth_ScoSocket.cpp。 android_bluetooth_RfcommSocket.cpp。

489 Android 驱动开发与移植实战详解

android_bluetooth_HeadsetBase.cpp。

16.2.3 具体移植

1.驱动程序

在 Android 系统的蓝牙驱动程序中,主要包括针对硬件接口的 USB、SDIO 和 UART 驱动,此 部分驱动的内容也是标准的。如果使用 UART 蓝牙芯片,则需要使用芯片特定的高速串口。另外 在驱动中还包含了电源管理和芯片配置。此部分代码保存在目录“driver/serial”和“ driver/usb”中 。 Android 系统的蓝牙驱动程序保存在“driver/bluetooth”目录中,和其他驱动程序相比,蓝牙系 统的协议层位于内核空间的上层部分,接下来将简要介绍这些驱动的实现过程。 (1)连接硬件部分。 在蓝牙驱动程序中,主要包括针对硬件接口的 USB、SDIO 和 UART 驱动,除了 UART 蓝牙 芯片外,都是标准的内核驱动程序。无论使用上述哪一种接口,都需要在内核中开启驱动支持,通 过 make menuconfig 配置路径为

Networking Support=>Bluetooth subsystem support=> Bluetooth device drivers

进入后可以根据需要来开启使用的硬件连接方式。 (2)蓝牙协议栈。 在 Linux2.6 的内核中已经包含了蓝牙协议栈,通过 make menuconfig 配置路径为 Networking Support=>Bluetooth subsystem support,在该层中已经列出了各种协议支持,其中最重要的是 HCIP、 L2CAP、SCO、RFCOMM、BNEP 等。为了避免不必要的麻烦,在此建议全部开启。 (3)电源管理。 在 Android 中使用标准的 Linux rfkill 接口来管理蓝牙芯片的电源,需要实现一个平台设备和 rfkill 的逻辑控制。接口 rfkill 的内核在头文件“include/linux/rfkill.h”中定义。

2.本地代码

在本地代码部分主要完成配置性的工作,例如初始化蓝牙芯片和配置蓝牙服务。 (1)初始化蓝牙芯片。 初始化蓝牙芯片是通过 BlueZ 工具 Hciattach 进行的,此工具在“external/bluetooth/tools”目录 的文件中实现。命令 Hciattach 主要用来初始化蓝牙设备,使用此命令格式如下所示。

hciattach [-n] [-p] [-b] [-t timeout] [-s initial_speed] [speed] [flow|noflow] [bdaddr]

其中最重要的参数就是 type 和 speed,type 决定了要初始化的设备的型号,可以使用 Hciattach –l 来列出所支持的设备型号。 并不是所有的参数对所有的设备都是适用的,有些设备会忽略一些参数设置,例如:查看 Hciattach 的代码就可以看到,多数设备都忽略 bdaddr 参数。Hciattach 命令内部的工作步骤是:首 先打开制定的 tty 设备,然后做一些通用的设置,如 flow 等,然后设置波特率为 initial_speed,然

490 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

后根据 type 调用各自的初始化代码,最后将波特率重新设置为 speed。所 以 调 用 Hciattach 时,要根 据你的实际情况,设置好 initial_speed 和 speed。 对于 type BCSP 来说,它的初始化代码只做了一件事,就是完成 BCSP 协议的同步操作,它并 不对蓝牙芯片做任何的 pskey 的设置。 (2)蓝牙服务。 在蓝牙服务方面一般不要我们自己定义,只需要使用初始化脚本文件 init.rc 中的默认内容即 可。例如下面的代码。

service bluetoothd /system/bin/logwrapper /system/bin/bluetoothd -d -n socket bluetooth stream 660 bluetooth bluetooth socket dbus_bluetooth stream 660 bluetooth bluetooth # init.rc does not yet support applying capabilities, so run as root and # let bluetoothd drop uid to bluetooth with the right linux capabilities group bluetooth net_bt_admin misc disabled

# baudrate change 115200 to 1152000(Bluetooth) service changebaudrate /system/bin/logwrapper /system/xbin/bccmd_115200 -t bcsp -d /dev/s3c2410_serial1 psset -r 0x1be 0x126e user bluetooth group bluetooth net_bt_admin disabled oneshot

#service hciattach /system/bin/logwrapper /system/bin/hciattach -n -s 1152000 /dev/ s3c2410_serial1 bcsp 1152000 service hciattach /system/bin/logwrapper /system/bin/hciattach -n -s 115200 /dev/ s3c2410_serial1 bcsp 115200 user bluetooth group bluetooth net_bt_admin misc disabled

service hfag /system/bin/sdptool add --channel=10 HFAG user bluetooth group bluetooth net_bt_admin disabled oneshot

service hsag /system/bin/sdptool add --channel=11 HSAG user bluetooth group bluetooth net_bt_admin disabled oneshot

service opush /system/bin/sdptool add --channel=12 OPUSH user bluetooth group bluetooth net_bt_admin disabled

491 Android 驱动开发与移植实战详解

oneshot

service pbap /system/bin/sdptool add --channel=19 PBAP user bluetooth group bluetooth net_bt_admin disabled oneshot

在上述代码中,每一个“service”后面列出了一种 Android 服务。 (3)管理蓝牙电源。 在 Android 系统的“system/bluetooth/”目录中实现了 libbluedroid。我们可以调用 rfkill 接口来 控制电源管理。如果已经实现了 rfkill 接口,则我们无须再进行配置。如果在文件 init.rc 中已经实 现了 Hciattach 服务,则说明在 libbluedroid 中已经实现对其调用以操作蓝牙的初始化。

16.2.4 MSM 平台的蓝牙驱动 在 MSM 平台中,通常使用高通自带的芯片实现 UART 高速连接。

1.驱动部分

电源管理功能的实现文件保存在“arch/arm/mach-msm/ ”目录中。其中在文件 board-mahimahi-rfkill.c 中定义函数 mahimahi_rfkill_probe(),此函数的功能是初始化操作 rfkill 接口。

static int mahimahi_rfkill_probe(struct platform_device *pdev) { int rc = 0; bool default_state = true; /* 关闭 */ rc = gpio_request(MAHIMAHI_GPIO_BT_RESET_N, "bt_reset"); if (rc) goto err_gpio_reset; rc = gpio_request(MAHIMAHI_GPIO_BT_SHUTDOWN_N, "bt_shutdown"); if (rc) goto err_gpio_shutdown; bluetooth_set_power(NULL, default_state); bt_rfk = rfkill_alloc(bt_name, &pdev->dev, RFKILL_TYPE_BLUETOOTH,//开始分配 rfkill &mahimahi_rfkill_ops, NULL); if (!bt_rfk) { rc = -ENOMEM; goto err_rfkill_alloc; } rfkill_set_states(bt_rfk, default_state, false); /* 用户空间不能独占控制 */ rc = rfkill_register(bt_rfk);//开始注册 rfkill if (rc) goto err_rfkill_reg; return 0; err_rfkill_reg: rfkill_destroy(bt_rfk); err_rfkill_alloc:

492 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

gpio_free(MAHIMAHI_GPIO_BT_SHUTDOWN_N); err_gpio_shutdown: gpio_free(MAHIMAHI_GPIO_BT_RESET_N); err_gpio_reset: return rc; }

在上述代码中,bt_rfk 是一个由 rfkill_allocate 分配出来的 rfkill 类型的结构体,在完成初始化 工作以后,使用 rfkill_register()将其注册到系统中。 定义函数 bluetooth_set_power(),此函数是一个 rfkill 结构的 toogle_radio 指针,此函数实现了 蓝牙的开关功能。

static int bluetooth_set_power(void *data, bool blocked) { if (!blocked) { gpio_direction_output(MAHIMAHI_GPIO_BT_RESET_N, 1); gpio_direction_output(MAHIMAHI_GPIO_BT_SHUTDOWN_N, 1); } else { gpio_direction_output(MAHIMAHI_GPIO_BT_SHUTDOWN_N, 0); gpio_direction_output(MAHIMAHI_GPIO_BT_RESET_N, 0); } return 0; }

2.用户空间部分

因为高通公司有自己的芯片产品,所以在 MSM 平台上配备了专门的初始化程序 hci_qcom_init。 此部分代码并没有在开源代码中,我们可以将其视为 Hciattach 之前提前进行的一部分初始化工作。 其中和 Hciattach 有关的代码如下所示。

start_hciattach () { echo 1 > $BLUETOOTH_SLEEP_PATH /system/bin/hciattach -n $QSOC_DEVICE $QSOC_TYPE $QSOC_BAUD flow & hciattach_pid=$! logi "start_hciattach: pid = $hciattach_pid" }

kill_hciattach () { logi "kill_hciattach: pid = $hciattach_pid" ## careful not to kill zero or null! kill -TERM $hciattach_pid echo 0 > $BLUETOOTH_SLEEP_PATH # this shell doesn't exit now -- wait returns for normal exit }

完成初始化工作以后表明调用 kill_hciattach 结束,通过 Hciattach 服务直接调用 sh 来解析执行 脚本。当 libbuedroid 启动该服务的时候执行该脚本,当停止服务的时候回调该脚本中的

493 Android 驱动开发与移植实战详解

kill_hciattach。

16.2.5 本地适配器连接过程 (1)得到本地蓝牙适配器的数目和信息 hci_for_each_dev()。

int hci_for_each_dev(int flag, int (*func)(int dd, int dev_id, long arg), long arg) { struct hci_dev_list_req *dl; struct hci_dev_req *dr; int dev_id = -1; int i, sk, err = 0; //打开一个 socket sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); if (sk < 0) return -1; dl = malloc(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); if (!dl) { err = errno; goto done; } memset(dl, 0, HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); dl->dev_num = HCI_MAX_DEV; dr = dl->dev_req; //得到所有设备的 ID if (ioctl(sk, HCIGETDEVLIST, (void *) dl) < 0) { err = errno; goto free; } for (i = 0; i < dl->dev_num; i++, dr++) { if (hci_test_bit(flag, &dr->dev_opt)) if (!func || func(sk, dr->dev_id, arg)) { dev_id = dr->dev_id; break; } } if (dev_id < 0) err = ENODEV; free: free(dl); done: close(sk); errno = err; return dev_id; }

hci_for_each_dev()会驱动所有蓝牙设备的 ID 并保存起来。 (2)打开一个 HCI 的 socket hci_open_dev()。

int hci_open_dev(int dev_id)

494 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

{ struct sockaddr_hci a; int dd, err; //创建一个 HCI 的 socket dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); if (dd < 0) return dd; //将 socket 绑定到 HCI 设备上 memset(&a, 0, sizeof(a)); a.hci_family = AF_BLUETOOTH; a.hci_dev = dev_id; if (bind(dd, (struct sockaddr *) &a, sizeof(a)) < 0) goto failed; return dd; failed: err = errno; close(dd); errno = err; return -1; }

里面会建立一个 HCI 的 socket,并将此 socket 和一个 HCI 设备绑定,功能是建立和这个设备 通信的通道。 (3)发送命令 hci_send_req()。

int hci_send_req(int dd, struct hci_request *r, int to) { unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr; uint16_t opcode = htobs(cmd_opcode_pack(r->ogf, r->ocf)); struct hci_filter nf, of; socklen_t olen; hci_event_hdr *hdr; int err, try; olen = sizeof(of); if (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) return -1; hci_filter_clear(&nf); hci_filter_set_ptype(HCI_EVENT_PKT, &nf); hci_filter_set_event(EVT_CMD_STATUS, &nf); hci_filter_set_event(EVT_CMD_COMPLETE, &nf); hci_filter_set_event(EVT_LE_META_EVENT, &nf); hci_filter_set_event(r->event, &nf); hci_filter_set_opcode(opcode, &nf); if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) return -1; if (hci_send_cmd(dd, r->ogf, r->ocf, r->clen, r->cparam) < 0) goto failed; try = 10; //尝试 10 次 while (try--) {

495 Android 驱动开发与移植实战详解

evt_cmd_complete *cc; evt_cmd_status *cs; evt_remote_name_req_complete *rn; evt_le_meta_event *me; remote_name_req_cp *cp; int len; if (to) { struct pollfd p; int n; p.fd = dd; p.events = POLLIN; while ((n = poll(&p, 1, to)) < 0) { if (errno == EAGAIN || errno == EINTR) continue; goto failed; } if (!n) { errno = ETIMEDOUT; goto failed; } to -= 10; if (to < 0) to = 0; } while ((len = read(dd, buf, sizeof(buf))) < 0) { if (errno == EAGAIN || errno == EINTR) continue; goto failed; } hdr = (void *) (buf + 1); ptr = buf + (1 + HCI_EVENT_HDR_SIZE); len -= (1 + HCI_EVENT_HDR_SIZE); switch (hdr->evt) { //状态命令 case EVT_CMD_STATUS: cs = (void *) ptr; if (cs->opcode != opcode) continue; if (r->event != EVT_CMD_STATUS) { if (cs->status) { errno = EIO; goto failed; } break; } r->rlen = MIN(len, r->rlen); memcpy(r->rparam, ptr, r->rlen); goto done; //完成命令 case EVT_CMD_COMPLETE: cc = (void *) ptr; if (cc->opcode != opcode)

496 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

continue; ptr += EVT_CMD_COMPLETE_SIZE; len -= EVT_CMD_COMPLETE_SIZE; r->rlen = MIN(len, r->rlen); memcpy(r->rparam, ptr, r->rlen); goto done; //远程请求完成命令 case EVT_REMOTE_NAME_REQ_COMPLETE: if (hdr->evt != r->event) break; rn = (void *) ptr; cp = r->cparam; if (bacmp(&rn->bdaddr, &cp->bdaddr)) continue; r->rlen = MIN(len, r->rlen); memcpy(r->rparam, ptr, r->rlen); goto done; case EVT_LE_META_EVENT: me = (void *) ptr; if (me->subevent != r->event) continue; len -= 1; r->rlen = MIN(len, r->rlen); memcpy(r->rparam, me->data, r->rlen); goto done; default: if (hdr->evt != r->event) break; r->rlen = MIN(len, r->rlen); memcpy(r->rparam, ptr, r->rlen); goto done; } } errno = ETIMEDOUT; failed: err = errno; setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); errno = err; return -1; done: setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); return 0; }

此方法的功能是向已经连接的适配器发送各种命令。 (4)关闭 sockethci_close_dev()。 int hci_close_dev(int dd) { return close(dd);

497 Android 驱动开发与移植实战详解

}

此种方法的功能是关闭 hci_open_dev()方法打开的 socket。

16.2.6 远程适配器连接过程 (1)搜索远程蓝牙适配器 hci_inquiry()。

int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap, inquiry_info **ii, long flags) { struct hci_inquiry_req *ir; uint8_t num_rsp = nrsp; void *buf; int dd, size, err, ret = -1; if (nrsp <= 0) { num_rsp = 0; nrsp = 255; } if (dev_id < 0) { dev_id = hci_get_route(NULL); if (dev_id < 0) { errno = ENODEV; return -1; } } //创建识别 socket 的描述符 dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); if (dd < 0) return dd; buf = malloc(sizeof(*ir) + (sizeof(inquiry_info) * (nrsp))); if (!buf) goto done; ir = buf; ir->dev_id = dev_id; ir->num_rsp = num_rsp; ir->length = len; ir->flags = flags; if (lap) { memcpy(ir->lap, lap, 3); } else { ir->lap[0] = 0x33; ir->lap[1] = 0x8b; ir->lap[2] = 0x9e; } //使用 ioctl 接口往驱动层发命令 ret = ioctl(dd, HCIINQUIRY, (unsigned long) buf); if (ret < 0) goto free; size = sizeof(inquiry_info) * ir->num_rsp;

498 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

if (!*ii) *ii = malloc(size); if (*ii) { memcpy((void *) *ii, buf + sizeof(*ir), size); ret = ir->num_rsp; } else ret = -1; free: free(buf); done: err = errno; close(dd); errno = err; return ret; }

这个方法用指定的本地适配器去搜索附近范围的适配器,并将搜索到的设备地址传回来。 (2)得到远程设备的名字 hci_read_remote_name()。 int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to) { return hci_read_remote_name_with_clock_offset(dd, bdaddr, 0x02, 0x0000, len, name, to); }

这个方法得到指定地址的适配器的名字,名字保存到 name 变量中。 (3)读取连接信号强度 hci_read_rssi()。 int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to) { read_rssi_rp rp; struct hci_request rq; memset(&rq, 0, sizeof(rq)); rq.ogf = OGF_STATUS_PARAM; rq.ocf = OCF_READ_RSSI; rq.cparam = &handle; rq.clen = 2; rq.rparam = &rp; rq.rlen = READ_RSSI_RP_SIZE; if (hci_send_req(dd, &rq, to) < 0) return -1; if (rp.status) { errno = EIO; return -1; } *rssi = rp.rssi; return 0; }

对于每一个连接,都会产生一个 handle,这个方法返回指定 handle 连接的信号强度。

499 Android 驱动开发与移植实战详解

16.2.7 分析 6410 的蓝牙驱动 Android 中的蓝牙驱动大致可以分为三个部分:蓝牙中主要协议的驱动,HCI 的驱动,以及 USB 蓝牙适配器的驱动。

1.HCI 的驱动

代码路径为

"/net/Bluetooth/Hci_core.c"//HCI 框架的实现 "/net/bluetooth/Hci_conn.c"//对连接进行管理认证相关 "/net/bluetooth/Hci_event."//消息的发送 "/net/Bluetooth/Hci_sock.c"//为上层提供 socket 接口

HCI 的驱动的主要作用是为上层协议提供一个统一的接口,让上层协议不依赖具体的硬件实 现,同时管理 socket 连接,以及上层和驱动之间消息的交互,起着承上启下的作用。 在 hci_core.c 文件中,通过如下方法管理对消息的发送和接收。 (1)hci_cmd_task()和 hci_cmd_task()方法将从 hdev->cmd_q 队列中读取 CMD 命令,然后调用 hci_send_frame()将这个 CMD 命令发送出去。

static void hci_cmd_task(unsigned long arg) { struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; if (!atomic_read(&hdev->cmd_cnt) && time_after(jiffies, hdev->cmd_last_tx + HZ)) { BT_ERR("%s command tx timeout", hdev->name); atomic_set(&hdev->cmd_cnt, 1); } //从队列中读取 CMD 命令 if (atomic_read(&hdev->cmd_cnt) && (skb = skb_dequeue(&hdev->cmd_q))) { if (hdev->sent_cmd) kfree_skb(hdev->sent_cmd); if ((hdev->sent_cmd = skb_clone(skb, GFP_ATOMIC))) { atomic_dec(&hdev->cmd_cnt); //发送 CMD 命令 hci_send_frame(skb); hdev->cmd_last_tx = jiffies; } else { skb_queue_head(&hdev->cmd_q, skb); hci_sched_cmd(hdev); } } }

(2)hci_rx_task()负责接收传来的数据,从 hdev->rx_q 中取到数据,然后根据数据的类型调用 上层函数进行处理。

static void hci_rx_task(unsigned long arg)

500 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

{ struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; BT_DBG("%s", hdev->name); read_lock(&hci_task_lock); while ((skb = skb_dequeue(&hdev->rx_q))) { if (atomic_read(&hdev->promisc)) { /* Send copy to the sockets */ hci_send_to_sock(hdev, skb); } if (test_bit(HCI_RAW, &hdev->flags)) { kfree_skb(skb); continue; } if (test_bit(HCI_INIT, &hdev->flags)) { /* Don't process data packets in this states. */ switch (bt_cb(skb)->pkt_type) { case HCI_ACLDATA_PKT: case HCI_SCODATA_PKT: kfree_skb(skb); continue; } } //处理得到的数据任务,根据不同的数据类型进行相应的处理 switch (bt_cb(skb)->pkt_type) {

//处理通信事件,比如连接建立,连接断开 case HCI_EVENT_PKT: hci_event_packet(hdev, skb); break; //处理异步非连接的数据包 case HCI_ACLDATA_PKT: BT_DBG("%s ACL data packet", hdev->name); hci_acldata_packet(hdev, skb); break; //处理同步面向连接的数据包 case HCI_SCODATA_PKT: BT_DBG("%s SCO data packet", hdev->name); hci_scodata_packet(hdev, skb); break; default: kfree_skb(skb); break; } } read_unlock(&hci_task_lock); }

(3)hci_tx_task()方法与 hci_rx_task()相反,此函数负责发送数据包的任务,它会发送所有连接 了的 ACL(同步定向连接,主要传递数据包)和 SCO(异步无连接,主要传递语音)数据。

501 Android 驱动开发与移植实战详解

static void hci_tx_task(unsigned long arg) { struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; read_lock(&hci_task_lock); BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt); //设定时间表,向 HCI 驱动发信息 hci_sched_acl(hdev); hci_sched_sco(hdev); hci_sched_esco(hdev); //发下一包数据 while ((skb = skb_dequeue(&hdev->raw_q))) hci_send_frame(skb); read_unlock(&hci_task_lock); }

Hci_event.c 文件用于处理事件,负责状态机的维护,这些事件通常会使连接从一个状态转移到 另一个状态,其中几个重要的函数如下。 hci_si_event()函数负责事件的发送。

void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data) { struct hci_event_hdr *hdr; struct hci_ev_stack_internal *ev; struct sk_buff *skb; skb = bt_skb_alloc(HCI_EVENT_HDR_SIZE + sizeof(*ev) + dlen, GFP_ATOMIC); if (!skb) return; hdr = (void *) skb_put(skb, HCI_EVENT_HDR_SIZE); hdr->evt = HCI_EV_STACK_INTERNAL; hdr->plen = sizeof(*ev) + dlen; ev = (void *) skb_put(skb, sizeof(*ev) + dlen); ev->type = type; memcpy(ev->data, data, dlen); bt_cb(skb)->incoming = 1; __net_timestamp(skb); //设置事件类型 bt_cb(skb)->pkt_type = HCI_EVENT_PKT; skb->dev = (void *) hdev; //将事件发送给 scoket hci_send_to_sock(hdev, skb); kfree_skb(skb); }

hci_event_packet()函数,用于处理底层上报的事件,会在 hci_rx_task()函数里面被调用。 (4)Hci_sock.c 文件能够给上层应用暴露 socket 接口,上层应用可以通过 scoket 的方式来访问 HCI,几个重要的函数如下。 hci_sock_init()函数,会注册蓝牙中的协议族。

502 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统 int __init hci_sock_init(void) { int err; err = proto_register(&hci_sk_proto, 0); if (err < 0) return err; //注册蓝牙中的协议族 err = bt_sock_register(BTPROTO_HCI, &hci_sock_family_ops); if (err < 0) goto error; hci_register_notifier(&hci_sock_nblock); BT_INFO("HCI socket layer initialized"); return 0; error: BT_ERR("HCI socket registration failed"); proto_unregister(&hci_sk_proto); return err; }

int hci_sock_create()函数用来创建和初始化一个 socket,供以后发送和接收消息使用。 static int hci_sock_create(struct net *net, struct socket *sock, int protocol) { struct sock *sk; BT_DBG("sock %p", sock); if (sock->type != SOCK_RAW) return -ESOCKTNOSUPPORT; //初始化 socket 的接口 sock->ops = &hci_sock_ops; sk = sk_alloc(net, PF_BLUETOOTH, GFP_ATOMIC, &hci_sk_proto); if (!sk) return -ENOMEM; //初始化 socket 对象 sock_init_data(sock, sk); sock_reset_flag(sk, SOCK_ZAPPED); sk->sk_protocol = protocol; sock->state = SS_UNCONNECTED; sk->sk_state = BT_OPEN; bt_sock_link(&hci_sk_list, sk); return 0; }

hci_sock_sendmsg()函数发送消息,根据消息类型将消息放到适当的队列中去。 static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len) { struct sock *sk = sock->sk; struct hci_dev *hdev; struct sk_buff *skb; int err;

503 Android 驱动开发与移植实战详解

BT_DBG("sock %p sk %p", sock, sk); if (msg->msg_flags & MSG_OOB) return -EOPNOTSUPP; if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_NOSIGNAL|MSG_ERRQUEUE)) return -EINVAL; if (len < 4 || len > HCI_MAX_FRAME_SIZE) return -EINVAL; lock_sock(sk); if (!(hdev = hci_pi(sk)->hdev)) { err = -EBADFD; goto done; } if (!(skb = bt_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err))) goto done; if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) { err = -EFAULT; goto drop; } bt_cb(skb)->pkt_type = *((unsigned char *) skb->data); skb_pull(skb, 1); skb->dev = (void *) hdev; if (bt_cb(skb)->pkt_type == HCI_COMMAND_PKT) { u16 opcode = get_unaligned_le16(skb->data); u16 ogf = hci_opcode_ogf(opcode); u16 ocf = hci_opcode_ocf(opcode); if (((ogf > HCI_SFLT_MAX_OGF) || !hci_test_bit(ocf & HCI_FLT_OCF_BITS, &hci_sec_filter.ocf_mask[ogf])) && !capable(CAP_NET_RAW)) { err = -EPERM; goto drop; } if (test_bit(HCI_RAW, &hdev->flags) || (ogf == 0x3f)) { skb_queue_tail(&hdev->raw_q, skb); hci_sched_tx(hdev); } else { skb_queue_tail(&hdev->cmd_q, skb); hci_sched_cmd(hdev); } } else { if (!capable(CAP_NET_RAW)) { err = -EPERM; goto drop; } skb_queue_tail(&hdev->raw_q, skb); hci_sched_tx(hdev); } err = len; done: release_sock(sk); return err;

504 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统 drop: kfree_skb(skb); goto done; }

hci_sock_recvmsg()函数负责接收消息,从接收队列中取消息。 static int hci_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len, int flags) { int noblock = flags & MSG_DONTWAIT; struct sock *sk = sock->sk; struct sk_buff *skb; int copied, err; BT_DBG("sock %p, sk %p", sock, sk); if (flags & (MSG_OOB)) return -EOPNOTSUPP; if (sk->sk_state == BT_CLOSED) return 0; if (!(skb = skb_recv_datagram(sk, flags, noblock, &err))) return err; msg->msg_namelen = 0; copied = skb->len; if (len < copied) { msg->msg_flags |= MSG_TRUNC; copied = len; } skb_reset_transport_header(skb); err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); hci_sock_cmsg(sk, msg, skb); skb_free_datagram(sk, skb); return err ? : copied; }

hci_sock_ioctl()函数实现 HCI 中的 ioctl 接口,上层应用程序调用的 ioctl 方法在这里面实现。 static int hci_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) { struct sock *sk = sock->sk; void __user *argp = (void __user *) arg; int err; BT_DBG("cmd %x arg %lx", cmd, arg); //定义了各种 ioctl 命令 switch (cmd) { case HCIGETDEVLIST: return hci_get_dev_list(argp); case HCIGETDEVINFO: return hci_get_dev_info(argp); case HCIGETCONNLIST: return hci_get_conn_list(argp); case HCIDEVUP:

505 Android 驱动开发与移植实战详解

if (!capable(CAP_NET_ADMIN)) return -EACCES; return hci_dev_open(arg); case HCIDEVDOWN: if (!capable(CAP_NET_ADMIN)) return -EACCES; return hci_dev_close(arg); case HCIDEVRESET: if (!capable(CAP_NET_ADMIN)) return -EACCES; return hci_dev_reset(arg); case HCIDEVRESTAT: if (!capable(CAP_NET_ADMIN)) return -EACCES; return hci_dev_reset_stat(arg); case HCISETSCAN: case HCISETAUTH: case HCISETENCRYPT: case HCISETPTYPE: case HCISETLINKPOL: case HCISETLINKMODE: case HCISETACLMTU: case HCISETSCOMTU: if (!capable(CAP_NET_ADMIN)) return -EACCES; return hci_dev_cmd(cmd, argp); case HCIINQUIRY: return hci_inquiry(argp); default: lock_sock(sk); err = hci_sock_bound_ioctl(sk, cmd, arg); release_sock(sk); return err; } }

2.蓝牙中主要协议的驱动

代码路径为

"/net/Bluetooth/L2cap.c" "/net/bluetooth/sco.c" "/net/Bluetooth/Rfcomm/*"

文件 L2cap.c 的功能是实现 L2cap 协议,包括创建 L2cap 的 socket,打开连接,确认连接加密 确认等操作,在模块初始化的时候会调用 l2cap_init()对 L2cap 协议以及 L2cap 的 socket 进行注册, 代码如下:

static int __init l2cap_init(void) {

506 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

int err; err = proto_register(&l2cap_proto, 0); if (err < 0) return err; //注册 L2CAP 的 socket err = bt_sock_register(BTPROTO_L2CAP, &l2cap_sock_family_ops); if (err < 0) { BT_ERR("L2CAP socket registration failed"); goto error; } //向 HCI 驱动中注册 L2cap 协议 err = hci_register_proto(&l2cap_hci_proto); if (err < 0) { BT_ERR("L2CAP protocol registration failed"); bt_sock_unregister(BTPROTO_L2CAP); goto error; } if (class_create_file(bt_class, &class_attr_l2cap) < 0) BT_ERR("Failed to create L2CAP info file"); BT_INFO("L2CAP ver %s", VERSION); BT_INFO("L2CAP socket layer initialized"); return 0; error: proto_unregister(&l2cap_proto); return err; }

文件 Sco.c 的功能是实现 sco 协议,负责实现面向连接的传输(语音传输),在 Sco 模块加载的 时候会对 Sco 初始化,调用函数 sco_init(),代码如下:

static int __init sco_init(void) { int err; err = proto_register(&sco_proto, 0); if (err < 0) return err; //注册 sco 的 socket err = bt_sock_register(BTPROTO_SCO, &sco_sock_family_ops); if (err < 0) { BT_ERR("SCO socket registration failed"); goto error; } //向 HCI 驱动中注册 Sco 协议 err = hci_register_proto(&sco_hci_proto); if (err < 0) { BT_ERR("SCO protocol registration failed"); bt_sock_unregister(BTPROTO_SCO); goto error; } //创建 sco 文件

507 Android 驱动开发与移植实战详解

if (class_create_file(bt_class, &class_attr_sco) < 0) BT_ERR("Failed to create SCO info file"); BT_INFO("SCO (Voice Link) ver %s", VERSION); BT_INFO("SCO socket layer initialized"); return 0; error: proto_unregister(&sco_proto); return err; }

sco_init()会注册 sco 的 socket,向 HCI 驱动中注册 sco 的协议,同时创建 soc 文件。 Rfcomm 文件夹下的文件实现了 rfcomm 协议,实现了在蓝牙协议之上封装完整的 RS232 串口, 在 Core.c 中会对 Rfcomm 协议进行初始化,代码如下:

static int __init rfcomm_init(void) { l2cap_load(); hci_register_cb(&rfcomm_cb); rfcomm_thread = kthread_run(rfcomm_run, NULL, "krfcommd"); if (IS_ERR(rfcomm_thread)) { hci_unregister_cb(&rfcomm_cb); return PTR_ERR(rfcomm_thread); } //创建设备文件 if (class_create_file(bt_class, &class_attr_rfcomm_dlc) < 0) BT_ERR("Failed to create RFCOMM info file"); //初始化 rfcomm 的 socket rfcomm_init_sockets(); #ifdef CONFIG_BT_RFCOMM_TTY rfcomm_init_ttys(); #endif BT_INFO("RFCOMM ver %s", VERSION); return 0; }

rfcomm_init()会创建 rfcomm 线程,初始化 rfcomm 的 socket。

3.USB 蓝牙适配器的驱动

代码路径是

"/drivers/Bluetooth/Hci_usb.c"

蓝牙适配器与适配器的通信方式一般有 USB、UART 和 PC CARD 几种,我们这边以 USB 连 接为例来了解一下 USB 适配器驱动的加载过程。 USB 适配器以模块的方式加载驱动,调用 hci_usb_init()函数来加载 USB 适配器的驱动,在 hci_usb_probe()函数中会调用 hci_register_dev()向 Hci_core 中注册设备。 当设备注册到 Hci_core 中后,Hci 驱动可以调用 hci_usb_send_frame()函数将数据包放入

508 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

__transmit_q 队列中。

static int hci_usb_send_frame(struct sk_buff *skb) { struct hci_dev *hdev = (struct hci_dev *) skb->dev; struct hci_usb *husb; if (!hdev) { BT_ERR("frame for uknown device (hdev=NULL)"); return -ENODEV; } if (!test_bit(HCI_RUNNING, &hdev->flags)) return -EBUSY; BT_DBG("%s type %d len %d", hdev->name, bt_cb(skb)->pkt_type, skb->len); husb = (struct hci_usb *) hdev->driver_data; //根据消息类型进行相应处理 switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: hdev->stat.cmd_tx++; break; case HCI_ACLDATA_PKT: hdev->stat.acl_tx++; break; #ifdef CONFIG_BT_HCIUSB_SCO case HCI_SCODATA_PKT: hdev->stat.sco_tx++; break; #endif default: kfree_skb(skb); return 0; } read_lock(&husb->completion_lock); //将数据发送到__transmit_q 队列中去 skb_queue_tail(__transmit_q(husb, bt_cb(skb)->pkt_type), skb); hci_usb_tx_wakeup(husb); read_unlock(&husb->completion_lock); return 0; }

然后函数 hci_usb_tx_process()会根据数据的类型把数据通过 USB 接口发给蓝牙适配器。

static void hci_usb_tx_process(struct hci_usb *husb) { struct sk_buff_head *q; struct sk_buff *skb; BT_DBG("%s", husb->hdev->name); do { clear_bit(HCI_USB_TX_WAKEUP, &husb->state); //从__transmit_q 队列中取消息,发送给蓝牙适配器 q = __transmit_q(husb, HCI_COMMAND_PKT); if (!atomic_read(__pending_tx(husb, HCI_COMMAND_PKT)) &&

509 Android 驱动开发与移植实战详解

(skb = skb_dequeue(q))) { if (hci_usb_send_ctrl(husb, skb) < 0) skb_queue_head(q, skb); } #ifdef CONFIG_BT_HCIUSB_SCO //处理 SCO 类型的数据 q = __transmit_q(husb, HCI_SCODATA_PKT); if (atomic_read(__pending_tx(husb, HCI_SCODATA_PKT)) < HCI_MAX_ISOC_TX && (skb = skb_dequeue(q))) { if (hci_usb_send_isoc(husb, skb) < 0) skb_queue_head(q, skb); } #endif //处理 ACL 类型的数据 q = __transmit_q(husb, HCI_ACLDATA_PKT); while (atomic_read(__pending_tx(husb, HCI_ACLDATA_PKT)) < HCI_MAX_BULK_TX && (skb = skb_dequeue(q))) { if (hci_usb_send_bulk(husb, skb) < 0) { skb_queue_head(q, skb); break; } } } while(test_bit(HCI_USB_TX_WAKEUP, &husb->state)); }

上述三部分的驱动代码构成 Android 中蓝牙驱动模块,层次比较清晰,主要是 HCI 核心层负责 蓝牙设备的注册,给上层应用和驱动建立通信通道,并且管理蓝牙中的主要协议的工作,协议的驱 动主要是负责实现 rfcomm、l2cap 等协议,蓝牙适配器的驱动主要负责具体适配器设备的注册和加 载,并且往硬件中发送命令。

16.3 定位系统

定位系统(Global Positioning System)是一个由覆盖全球的 24 颗卫星组成的卫星系统,这个 系统可以实现导航、定位、授时等功能,通过此技术可以引导飞机、船舶、车辆以及个人,安全、 准确地沿着选定的路线,准时到达目的地。在当前市面中的很多智能手机中,也具备定位系统的功 能。在本节将简要讲解 Android 平台中定位系统驱动的移植知识,为读者步入本书后面知识的学习 打下基础。

GPS 是通过串口和 CPU 连接的,驱动就是串口驱动,本身是不用驱动的。本章 注意 主要介绍 GPS 的底层接口,以及如何读取卫星数据。

16.3.1 系统结构 Android 的定位系统有如下两个数据来源。 GPS 定位:使用 GPS 设备实现定位,现实应用中最常用的定位方式。

510 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

Network 定位:使用 Cell 基站或 Wi-Fi 热点定位。 对 Android 源码来说,GPS 部分是开源项目的一部分,而 Network 定位部分只在开源代码中提 供了接口。两者虽然底层技术实现不同,但作为定位数据的提供方都有着很多共同的地方,并使用 同一套框架。Android 定位系统为上层应用提供了统一的接口,可以广泛地应用于 Google 地图等现 实应用中。Android 定位系统的基本层次结构如图 16-9 所示。

定位应用 平台API

本地框架 和定位相关的包

Android系统

本地框架 GPS JNI 硬件抽象层

GPS设备 硬件和驱动

▲图 16-9 定位系统的层次结构

Android 平台中定位系统从上到下主要包括 Java 框架类、硬件抽象层和驱动程序,这几部分的 系统结构如图 16-10 所示。

定位相关应用——GoogleMap,Tab4Me

Android.location NETWork定位组件

定位的 GPS JNI NETWork定位的JNI

GPS定位适配层 NETWork定位适配层 libhardware_legacy.so RIL和Wi-Fi提供

GPS驱动 Cell和Wi-Fi定位等相关驱动

▲图 16-10 定位系统结构

511 Android 驱动开发与移植实战详解

在图 16-9 中各个层次结构的具体说明如下所示。 (1)硬件抽象层(HAL 层)。 硬件抽象层的主要功能被保存在如下两个目录中。 hardware\libhardware_legacy\gps。 hardware\libhardware_legacy\include\hardware_legacy\gps.h。 HAL 层相当于一个 Linux 应用程序接口,通过 open()和 close()等函数操作硬件设备。Android 的源代码只实现了模拟器的 GPS 接口,具体在文件 gps_qemu.c 中实现。在 2.2 版本中提供了对 QCOM 公司的 GPS 的实现,保存在“\hardware\qcom”目录中。 (2)JNI 层。 GPS 部分的 JNI 的本地部分实现源码保存在文件“frameworks/base/core/jni/android_location_ GpsLocationProvider.cpp”中。由此可见,JNI 层只有一个文件,起了承上启下的作用。上层承接 Framework,下层调用 HAL 层具体硬件抽象实现。 (3)Java 框架层。 GPS 部分的 Java 层的实现代码保存在下面的两个目录中。 frameworks/base/location/java/android/location/:外部 API。 frameworks/base/location/java/com/android/internal/location/:提供了内部使用部分。

16.3.2 移植的内容 在 Android 平台中,我们需要移植的 GPS 内容位于 JNI 层下面,即驱动层硬件抽象层。GPS 硬件的驱动程序通常是串口驱动程序,实现比较简单,在 Linux 系统的用户空间中通常用 tty 设备 来表示。 而对于 GPS 硬件抽象层来说是比较复杂的,在 Android 平台中给出了少量的使用参考信息, 这些信息保存在文件 gps_qemu.c 中。虽然此文件里面的结构可以重复使用,但是需要在此基础上 做大量的修改工作和升级工作之后才能使用。

16.3.3 移植和调试

1.驱动程序

GPS 硬件设备分为硬 GPS 和软 GPS 两种。其中硬 GPS 一般是功能独立的模块,一般不需要 特别多的控制,通电之后就可以运行,可以直接输出 NMEA 数据,驱动方法十分简单。而软 GPS 通常需要主控芯片控制其运行状态,输出的大多是卫星数据,需要主控方进行计算,才能得到最终 的 NMEA 数据。两者的共同点是最终的输出都是 NMEA 数据。

NMEA(National Marine Electronics Association,国际海洋电子协会)一般是指 注意 NMEA0183,这是一套工业标准的接收机信号输出协议。

2.硬件抽象层 GPS 系统的硬件抽象层在头文件“hardware\libhardware_legacy\include\hardware_legacy\gps.h”

512 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

中定义。由此可见 GPS 功能是 hardware_legacy 系统的一部分,此部分使用了非标准的接口。 首先在文件 gps.h 中定义了如下三个入口。

const GpsInterface* gps_get_hardware_interface();//返回实际硬件的实现接口 const GpsInterface* gps_get_qemu_interface();//返回模拟器的实现接口 const GpsInterface* gps_get_interface();//获取 GpsInterface 接口

定义接口 GpsInterface 的代码如下所示。

typedef struct { int (*init)( GpsCallbacks* callbacks );//初始化 GPS int (*start)( void ); //开始导航 int (*stop)( void ); //停止导航 void (*cleanup)( void ); //关闭接口 int (*inject_time)(GpsUtcTime time, int64_t timeReference, int uncertainty); //置入当前时间 int (*inject_location)(double latitude, double longitude, float accuracy); //植入当前位置 void (*delete_aiding_data)(GpsAidingData flags); //删除帮助信息 int (*set_position_mode)(GpsPositionMode mode, int fix_frequency); //设置位置模式 const void* (*get_extension)(const char* name); //获得扩展信息的指针 } GpsInterface;

接口 GpsInterface 是 GPS 模块中最重要的数据结构,它是底层驱动实现的接口,如果要 porting 到自己的板子上,就需要实现这些接口。该接口的定义在 gps.h 中,模拟器实现在 gps_qemu.c 中。 通过 GPSGpsCallbacks 定义 GPS 回调函数指针组。

/** 位置信息回调函数 */ typedef void (* gps_location_callback)(GpsLocation* location);

/** 状态信息回调函数*/ typedef void (* gps_status_callback)(GpsStatus* status);

/** 卫星状态信息回调函数 */ typedef void (* gps_sv_status_callback)(GpsSvStatus* sv_info);

/** 直接上传 NMEA 数据的回调函数*/ typedef void (* gps_nmea_callback)(GpsUtcTime timestamp, const char* nmea, int length); typedef struct { gps_location_callback location_cb; gps_status_callback status_cb; gps_sv_status_callback sv_status_cb; gps_nmea_callback nmea_cb; } GpsCallbacks;

在文件 com_android_server_android_location_GpsLocationProvider.cpp 中实现了回调函数, Google 已经实现了 GpsCallbacks(),我们不需要做任何工作。 然后定义GpsLocation 表示Locatin 数据信息,底层驱动获得 Location 的raw 信息,通常是 MMEA 码,然后通过解析就得到了 Location 信息。其中 Android2.3 比 Android2.2 新增加了 size 属性以获

513 Android 驱动开发与移植实战详解

取其大小。

typedef struct { /** 标志位 */ uint16_t flags; /** 维度*/ double latitude; /** 经度 */ double longitude; /** 使用 WGS 84 坐标表示高度信息 */ double altitude; /** 速度 */ float speed; /** 方向 */ float bearing; /** 精确度 */ float accuracy; /** 时间戳 */ GpsUtcTime timestamp; } GpsLocation;

定义 GpsStatus 表示 GPS 的状态信息。

typedef struct { GpsStatusValue status; } GpsStatus;

定义 GpsXtraInterface 用于从网络上下载星历。

typedef struct { /** * 扩展 XTRA 支持 */ int (*init)( GpsXtraCallbacks* callbacks ); /** Injects XTRA data into the GPS. */ int (*inject_xtra_data)( char* data, int length ); } GpsXtraInterface;

定义 AgpsInterface,表示使用手机基站的定位方式。

typedef struct { /* 初始化注册回调函数*/ void (*init)( AGpsCallbacks* callbacks ); /** 通知已经打开数据连接 */ int (*data_conn_open)( const char* apn ); /** * 通知已经关闭数据连接 */ int (*data_conn_closed)(); /**

514 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

* 通知数据连接失败 */ int (*data_conn_failed)(); /** * 设置访问 APGS 服务器的相关配置 */ int (*set_server)( AGpsType type, const char* hostname, int port ); } AGpsInterface;

用 AgpsCallbacks 表示 AGPS 系统的回调函数。

typedef struct { agps_status_callback status_cb; } AGpsCallbacks;

定义 AgpsStatus 表示在回调函数中使用的参数类型,用于获取 AGPS 的状态信息。

typedef struct { AGpsType type; AGpsStatusValue status; } AGpsStatus;

3.上层应用

(1)LocationManager。 LocationManager 服务是在文件 SystemServer.java 中启动的,在系统启动之后此服务就已经启 动了。 在文件 SystemServer.java(framework\base\services\java\com\android\server)中,使用函数 init2() 启动了一个线程来注册 Android 的诸多服务,例如 Bluetooth Service、NetworkManagement Service 和 Notification Manager 等,当然也包括 Location Service。定义函数 init2()的代码如下所示。

public static final void init2() { Slog.i(TAG, "Entered the Android system server!"); Thread thr = new ServerThread(); thr.setName("android.server.ServerThread"); thr.start(); }

在 ServerThread 线程的 run()函数中,定义 LocationManager 服务的代码如下所示。

//2.1 版本 try { Log.i(TAG, "Location Manager"); ServiceManager.addService(Context.LOCATION_SERVICE, new LocationManagerService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Location Manager", e); } //2.2 版本 try {

515 Android 驱动开发与移植实战详解

Slog.i(TAG, "Location Manager"); location = new LocationManagerService(context); ServiceManager.addService(Context.LOCATION_SERVICE, location); } catch (Throwable e) { Slog.e(TAG, "Failure starting Location Manager

在 run()函数中的后半部分是服务对系统的反馈,就是 systemReady()函数。LocationManager 服 务对应的反馈函数如下所示。

if (locationF != null) locationF.systemReady();

其中 locationF 是 LocationManagerService 的 final 类型,赋值后不能更改。

final LocationManagerService locationF = location;

在 Android 2.1 版本中 LocationManagerService 的构造函数的实现文件是 LocationManagerService.java(frameworks\base\services\java\com\android\server),定义代码如下所示。

public LocationManagerService(Context context) { super(); mContext = context; Thread thread = new Thread(null, this, "LocationManagerService"); thread.start(); if (LOCAL_LOGV) { Log.v(TAG, "Constructed LocationManager Service"); } }

而其他版本的定义代码如下所示。

public LocationManagerService(Context context) { super(); mContext = context; if (LOCAL_LOGV) { Slog.v(TAG, "Constructed LocationManager Service"); } }

由此可见 2.1 版本是在构造函数的时候就启动一个自身服务线程,2.2 版本是在反馈机制中通 过 systemReady()函数启动自身服务线程实现。

void systemReady() { // we defer starting up the service until the system is ready Thread thread = new Thread(null, this, "LocationManagerService"); thread.start(); }

通过线程函数 run()来调用函数 initialize(),对应代码如下所示。

public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

516 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

Looper.prepare(); mLocationHandler = new LocationWorkerHandler(); initialize(); Looper.loop(); }

(2)initialize()函数。 在文件 LocationManagerService.java(frameworks\base\services\java\com\android\server)中定义 函数 initialize()的代码如下所示。

private void initialize() { PowerManager powerManager = (PowerManager) mContext.getSystemService(Context. POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); loadProviders(); ...

在上述代码中,核心功能是通过 loadProviders()函数实现的,该函数先调用 loadProvidersLocked, 然后 loadProvidersLocked()函数又调用_loadProvidersLocked()函数。函数_loadProvidersLocked 的实 现代码如下所示。

private void _loadProvidersLocked() { // Attempt to load "real" providers first if (GpsLocationProvider.isSupported()) { // Create a gps location provider GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); addProvider(gpsProvider); mGpsLocationProvider = gpsProvider; } 上面的 if 语句很重要,因为在上述代码中得到了 HAL 层的 GPS 接口 GpsInterface,此功能是 通过调用 GpsLocationProvider 的 isSupported()函数才调用到文件 gps.cpp(hardware/libhardware_ legacy/gps)中的 gps_get_interface()。 (3)在文件 GpsLocationProvider.cpp(frameworks\base\location\java\com\android\internal\location) 中定义了如下代码。

public static boolean isSupported() { return native_is_supported(); } 通过上述一段简短的代码就调用了 native 方法,也就是 JNI 层定义的方法。函数 native_is_supported()对于 JNI 层来说是 android_location_GpsLocationProvider_is_supported 方法。 (4)文件 android_location_GpsLocationProvider.cpp(frameworks\base\core\jni)。 此文件的核心代码如下所示。

static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass

517 Android 驱动开发与移植实战详解

clazz) { if (!sGpsInterface) sGpsInterface = gps_get_interface(); return (sGpsInterface != NULL); }

前面已经提到 JNI 起到承上启下的作用,函数 gps_get_interface()是在文件 gps.cpp 中实现的, 属于 HAL 层的调用。 (5)文件 gps.cpp(hardware\libhardware_legacy\gps)里面定义了下面的代码。

const GpsInterface* gps_get_interface() { if (sGpsInterface == NULL) gps_find_hardware(); return sGpsInterface; }

通过函数 gps_find_hardware()得到 GPS 接口,下面是模拟器中的 gpsinterface 代码。

static void gps_find_hardware( void ) { #ifdef HAVE_QEMU_GPS_HARDWARE if (qemu_check()) { sGpsInterface = gps_get_qemu_interface(); if (sGpsInterface) { LOGD("using QEMU GPS Hardware emulation\n"); return; } } #endif #ifdef HAVE_GPS_HARDWARE sGpsInterface = gps_get_hardware_interface(); #endif if (!sGpsInterface) LOGD("no GPS hardware on this device\n"); }

(6)在文件 gps_qemu.c(hardware\libhardware_legacy\gps)中实现 gps_get_qemu_interface,对 应代码如下所示。

const GpsInterface* gps_get_qemu_interface() { return &qemuGpsInterface; }

在文件 gps_qemu.c 中实现 qemuGpsInterface,对应代码如下所示。

static const GpsInterface qemuGpsInterface = { qemu_gps_init,

518 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

qemu_gps_start, qemu_gps_stop, qemu_gps_cleanup, qemu_gps_inject_time, qemu_gps_inject_location, qemu_gps_delete_aiding_data, qemu_gps_set_position_mode, qemu_gps_get_extension, };

(7)构造函数 GpsLocationProvider()。 当在底层得到 GPS 的接口之后,需要通过如下代码新建一个 GpsLocationProvider 对象。

GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this);

构造函数 GpsLocationProvider()里面的两个参数,分别是 mContext 和 this。我们先看看构造函 数 GpsLocationProvider()前面的几句代码。

public GpsLocationProvider(Context context, ILocationManager locationManager) { mContext = context; mLocationManager = locationManager; mNIHandler = new GpsNetInitiatedHandler(context, this);

由此可见,在 GpsLocationProvider 类里面的成员变量 mLocationManager 是构造函数的第二个 参数,就是说是 LocationManagerService 对象。 接着看函数_loadProvidersLocked(),代码如下所示。

private void _loadProvidersLocked() { // Attempt to load "real" providers first if (GpsLocationProvider.isSupported()) { // Create a gps location provider GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); addProvider(gpsProvider); mGpsLocationProvider = gpsProvider; } // create a passive location provider, which is always enabled PassiveProvider passiveProvider = new PassiveProvider(this); addProvider(passiveProvider); mEnabledProviders.add(passiveProvider.getName()); // initialize external network location and geocoder services Resources resources = mContext.getResources(); String serviceName = resources.getString( com.android.internal.R.string.config_networkLocationProvider); if (serviceName != null) { mNetworkLocationProvider = new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER, serviceName, mLocationHandler); addProvider(mNetworkLocationProvider);

519 Android 驱动开发与移植实战详解

} serviceName = resources.getString(com.android.internal.R.string.config_ geocodeProvider); if (serviceName != null) { mGeocodeProvider = new GeocoderProxy(mContext, serviceName); } updateProvidersLocked(); }

在构造完 GpsLocationProvider 之后将其添加到全局变量 ArrayList mProviders 中,以备以后调用。 在函数_loadProvidersLocked()中,最后一行代码的功能是调用函数 updateProvidersLocked(), 此代码仍然在文件 LocationManagerServic.java 中实现。

private void updateProvidersLocked() { for (int i = mProviders.size() - 1; i >= 0; i--) { LocationProviderInterface p = mProviders.get(i); boolean isEnabled = p.isEnabled(); String name = p.getName(); boolean shouldBeEnabled = isAllowedBySettingsLocked(name); if (isEnabled && !shouldBeEnabled) { updateProviderListenersLocked(name, false); } else if (!isEnabled && shouldBeEnabled) { updateProviderListenersLocked(name, true); } } }

从上面_loadProvidersLocked 函数代码来看,在 mProviders 这个 ArrayList 中有两个元素,分别 是 gpsProvider 和 passiveProvider。其中 gpsProvider 是 GpsLocationProvider 类型的,它的 isEnabled() 函数返回的是 false,因为它并没有被 enable。而 passiveProvider 是 PassiveProvider 类型,它总是 enable 的。所以 gpsProvider 会调用 else 语句中的 updateProviderListenersLocked(name, true)函数。 我们主要分析这个 else 语句,对于 passiveProvider 不做分析。

private void updateProviderListenersLocked(String provider, boolean enabled) { int listeners = 0; LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { return; } ArrayList deadReceivers = null; ArrayList records = mRecordsByProvider.get(provider); if (records != null) { final int N = records.size(); for (int i=0; i

520 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

if (deadReceivers == null) { deadReceivers = new ArrayList(); } deadReceivers.add(record.mReceiver); } listeners++; } } if (deadReceivers != null) { for (int i=deadReceivers.size()-1; i>=0; i--) { removeUpdatesLocked(deadReceivers.get(i)); } } if (enabled) { //enabled 的值是 true p.enable(); if (listeners > 0) { p.setMinTime(getMinTimeLocked(provider)); p.enableLocationTracking(true); } } else { p.enableLocationTracking(false); p.disable(); } }

在上述“if(enabled)”语句段里面启动了 GPS 的服务。 (8)函数 enable()。 通过调用 GpsLocationProvider 类中的函数 enable()和 enableLocationTracking()就启动了 GPS 的 LocationManager 服务。下面对这两个函数进行分析。 首先看文件 GpsLocationProvider.java 中的函数 enable(),具体代码如下所示。

public void enable() { synchronized (mHandler) { mHandler.removeMessages(ENABLE); Message m = Message.obtain(mHandler, ENABLE); m.arg1 = 1; mHandler.sendMessage(m); } }

在函数 handleMessage()中定义了各种 message 对应的处理函数。对于 enable 消息还带有一个参 数,enable 函数里面带的参数值为 1,所以调用 handleEnable 函数。

private void handleEnable() { if (DEBUG) Log.d(TAG, "handleEnable"); if (mEnabled) return; mEnabled = native_init(); if (mEnabled) { if (mSuplServerHost != null) {

521 Android 驱动开发与移植实战详解

native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); } if (mC2KServerHost != null) { native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); } mEventThread = new GpsEventThread(); mEventThread.start(); } else { Log.w(TAG, "Failed to enable location provider"); } }

在函数 handleEnable()中主要实现了下面的 2 个功能。 调用 native 的初始化方法对 GPS 进行初始化。 调用 native 方法 native_init(),就是 JNI 层的方法 android_location_GpsLocationProvider_init(), 此方法是在文件 andoird_location_GpsLocationProvider.cpp 中定义的,定义代码如下所示。

static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj) { if (!sGpsInterface) sGpsInterface = gps_get_interface(); if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0) return false; ... return true; }

在初始化函数中会确认是否已经得到 GpsInterface,如果没有得到则通过 gps_get_interface()方 法再次尝试得到,其实该接口已经在 android_location_GpsLocationProvider_is_supported 函数中得到 了。然后在第二个 if 语句中调用初始化方法 sGpsInterface->init。 在 android_location_GpsLocationProvider_init 的后半部分,试图通过 GpsInterface->get_extension 方法去得到 GPS 相关的扩展接口,可是在 2.2 的模拟器实现中并没有实现这个函数,在文件 gps_qume.c 中明显写着 return NULL。

static int qemu_gps_init(GpsCallbacks* callbacks) { GpsState* s = _gps_state; if (!s->init) gps_state_init(s); if (s->fd < 0) return -1; s->callbacks = *callbacks; return 0; }

在 sGpsInterface->init 中,也就是在 qemu_gps_init 方法中先调用了 gps_state_init,然后注册了 回调函数,此回调函数就是在 JNI 层实现的,而且有 JNI 层传下来的函数。对应代码如下所示。

522 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

static void gps_state_init( GpsState* state ) { state->init = 1; state->control[0] = -1; state->control[1] = -1; state->fd = -1; state->fd = qemu_channel_open( &state->channel, QEMU_CHANNEL_NAME, O_RDONLY ); if (state->fd < 0) { D("no gps emulation detected"); return; } D("gps emulation will read from '%s' qemud channel", QEMU_CHANNEL_NAME ); if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, state->control ) < 0 ) { LOGE("could not create thread control socket pair: %s", strerror(errno)); goto Fail; } if ( pthread_create( &state->thread, NULL, gps_state_thread, state ) != 0 ) { LOGE("could not create gps thread: %s", strerror(errno)); goto Fail; } D("gps state initialized"); return; Fail: gps_state_done( state ); }

在上述 gps_state_init()函数中,首先打开串口,然后建立 socket 通信并将线程监听到的底层数 据上报。 启动一个线程去监听事件。 建立线程监听事件的代码如下所示。

mEventThread = new GpsEventThread(); mEventThread.start();

我们先来看 GpsEventThread 中的函数 run(),代码如下所示。

public void run() { if (DEBUG) Log.d(TAG, "GpsEventThread starting"); while (mEnabled) { native_wait_for_event(); } if (DEBUG) Log.d(TAG, "GpsEventThread exiting"); } }

在函数 run()中还是需要调用 native()函数 android_location_GpsLocationProvider_wait_for_event(),

523 Android 驱动开发与移植实战详解

这个函数就是在一个 while 循环里面等待事件的触发(由回调函数触发),然后调用 GpsLocationProvider 类的数据上报函数(Location 数据)。主要代码如下所示。

static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, jobject obj) { pthread_mutex_lock(&sEventMutex); while (sPendingCallbacks == 0) { pthread_cond_wait(&sEventCond, &sEventMutex); } }

(9)函数 enableLocationTracking()。 函数 enableLocationTracking()在文件 GpsLocationProvider.java 中实现,定义代码如下所示。

public void enableLocationTracking(boolean enable) { synchronized (mHandler) { mHandler.removeMessages(ENABLE_TRACKING); Message m = Message.obtain(mHandler, ENABLE_TRACKING); m.arg1 = (enable ? 1 : 0); mHandler.sendMessage(m); } }

同样也可以使用 Handler 的方式调用函数 handleEnableLocationTracking(),代码如下所示。

private void handleEnableLocationTracking(boolean enable) { if (enable) { mTTFF = 0; mLastFixTime = 0; startNavigating(); } else { mAlarmManager.cancel(mWakeupIntent); mAlarmManager.cancel(mTimeoutIntent); stopNavigating(); } }

然后调用函数 startNavigating(),此函数的核心语句是调用 native 方法 native_start,通过此代码 调用了 JNI 层的函数 android_location_GpsLocationProvider_start()。函数 startNavigating()的实现代码 如下所示。

private void startNavigating() { if (!mStarted) { if (DEBUG) Log.d(TAG, "startNavigating"); mStarted = true; int positionMode; if (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { positionMode = GPS_POSITION_MODE_MS_BASED;

524 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

} else { positionMode = GPS_POSITION_MODE_STANDALONE; } if (!native_start(positionMode, false, 1)) { mStarted = false; Log.e(TAG, "native_start failed in startNavigating()"); return; }

16.3.4 GPS 的串口驱动和数据读取 GPS 模块一般是通过串口与 CPU 相连的,但是目前的笔记本很少有支持串口的,所以一般都 采用了 USB 转串口的芯片来支持 USB 连接,这里我们用的是 FT232RL 芯片,它能完成 USB 和串 口电平的转换,所以串口 GPS 设备在 Android 系统会被识别为 ttyUSB0 设备,如图 16-11 所示。

▲图 16-11 识别设备

如果要移植一个自己的 GPS 模块,首先要实现一个硬件平台相关的 GPS 库文件供上层调用, 在这个库文件中首先需要实现 Android 中 GPS 操作的几个标准接口,下面是实现代码:

static const GpsInterface HwGpsInterface = { sizeof(GpsInterface), hw_gps_init, hw_gps_start, hw_gps_stop, hw_gps_cleanup, hw_gps_inject_time, hw_gps_inject_location, hw_gps_delete_aiding_data, hw_gps_set_position_mode, hw_gps_get_extension, };

将这些函数地址,赋值给 GPS.h 中 GpsInterface 这个结构体的函数指针,这里这些函数才是真 正实现跟驱动进行交互。 我们要获得 GPS 数据,首先得打开 ttyUSB0 设备,在 hw_gps_init 中我们会打开 GPS 模块, 并设置一些串口参数,代码如下:

static int hw_gps_init(GpsCallbacks* callbacks) { D("hw_gps_init start"); GpsState* s = _gps_state; //s->callbacks = *callbacks;

525 Android 驱动开发与移植实战详解

//GPS 模块初始化 if (!s->init) gps_state_init(s, callbacks); if (s->fd < 0) return -1; return 0; }

在 gps_state_init()函数中,最终会调用 gps_channel_open()打开 GPS 模块。

int gps_channel_open(O2_Channel* channel, const char* name, int mode) { int fd = -1; if (!channel->is_inited) { channel->is_inited = 1; } D("channel->name: %s name: %s\n", channel->device, name); do { //打开 ttyUSB0 设备 fd = open(name, mode); } while (fd < 0 && errno == EINTR); if(fd < 0) { LOGE("open %s failed reason:%s", name, strerror(errno)); } //设置串口通信的参数 if (fd >= 0 && channel->is_tty) { struct termios ios; tcgetattr( fd, &ios ); ios.c_lflag = 0; /* disable ECHO, ICANON, etc... */ tcsetattr( fd, TCSANOW, &ios ); } return fd; }

当打开 GPS 设备之后,会启动一个线程来循环监听设备上状态的变化,判断是否发送控制命 令或者是产生了 GPS 数据,如果是控制命令则进行相应的处理,开始或者停止,如果是产生了 GPS 数据则将数据解析后通过回调函数返回上层应用,具体代码如下:

static voidgps_state_thread( void* arg ) { GpsState* state = (GpsState*) arg; NmeaReader reader[1]; int epoll_fd = epoll_create(2); int started = 0; int gps_fd = state->fd; int control_fd = state->control[1];

526 第 16 章 Wi-Fi系统、蓝牙系统和 GPS 系统

nmea_reader_init( reader ); //注册一个 poll 的文件描述符,监听 GPS 设备的变化 epoll_register( epoll_fd, gps_fd ); epoll_register( epoll_fd, control_fd ); //开始循环 for (;;) { struct epoll_event events[2]; int ne, nevents; //等待事件的产生 nevents = epoll_wait( epoll_fd, events, 2, -1 ); if (nevents < 0) { if (errno != EINTR) continue; } for (ne = 0; ne < nevents; ne++) { if ((events[ne].events & (EPOLLERR|EPOLLHUP)) != 0) { goto Exit; } //设备上有事件输入 if ((events[ne].events & EPOLLIN) != 0) { int fd = events[ne].data.fd; //如果是控制命令 if (fd == control_fd) { char cmd = 255; int ret; D("gps control fd event"); do { ret = read( fd, &cmd, 1 ); } while (ret < 0 && errno == EINTR);

if (cmd == CMD_QUIT) { goto Exit; } else if (cmd == CMD_START) { if (!started) { started = 1; //开启线程 nmea_reader_set_callback( reader, state->callbacks.location_cb ); } } else if (cmd == CMD_STOP) { if (started) { started = 0;

//停止线程 nmea_reader_set_callback( reader, NULL ); } } }

527 Android 驱动开发与移植实战详解

//如果是产生 GPS 数据 else if (fd == gps_fd) { char buff[32]; for (;;) { int nn, ret; //从设备上读 GPS 原始信息 ret = read( fd, buff, sizeof(buff) ); if (ret < 0) { if (errno == EINTR) continue; if (errno != EWOULDBLOCK) break; } D("received %d bytes: %.*s", ret, ret, buff); for (nn = 0; nn < ret; nn++) //解析 GPS 数据 nmea_reader_addc( reader, buff[nn] ); } D("gps fd event end"); } else { LOGE("epoll_wait() returned unkown fd %d ?", fd); } } } } Exit: return NULL; }

如果没有通过 USB 转串口进行转换,直接连接串口,则需要修改打开的设备文件名(可能是 ttySX,也可能多个串口设备),其余类似。

528

第 17 章 振动器驱动和警报器驱动

在众多的手机应用中,警报器是比较常见的功能之一。在 Android 系统中,通过警报器系统可 以实现闹钟功能。在本章将详细讲解 Android 警报器系统的应用和移植知识,为读者步入本书后面 知识的学习打下基础。

17.1 Alarm 系统基础

在 Android 中,警报器系统又叫时钟系统或闹钟系统,闹钟是 Android 系统中在标准 RTC 驱 动上开发的一个新的驱动,提供了一个定时器用于把设备从睡眠状态唤醒。因为它是依赖 RTC 驱 动的,所以它同时还可以为系统提供一个掉电下还能运行的实时时钟。当系统断电时,主板上的 RTC 芯片将继续维持系统的时间,这样保证再次开机后系统的时间不会错误。当系统开始时,内 核从 RTC 中读取时间来初始化系统时间,关机时便又将系统时间写回到 RTC 中,关机阶段将有 主板上另外的电池来供应 RTC 计时。Android 中的 Alarm 在设备处于睡眠模式时仍保持活跃,它 可以设置来唤醒设备。 Android 中的警报器(Alarm)系统提供了警报和事件设置方面的支持,其实现的基础一般是 实时时钟设备,在 Linux 内核中,需要有实时时钟设备驱动程序和 Android 的 Alarm 驱动程序,会 在 Android 系统中生成/dev/alarm 设备文件,用来实现警报等功能。

17.1.1 Alarm 系统的结构 Android 平台中 Alarm 系统的基本层次结构如图 17-1 所示。 由图 17-1 可知,Android 平台中 Wi-Fi 系统从上到下主要包括 AlarmManager 、 AlarmManagerService Java、AlarmManagerService JNI、Alarm 驱动程序和实时时钟(RTC)驱动程 序,这几部分的系统结构如图 17-2 所示。 图 17-2 中各个部分的具体说明如下所示。 (1)RTC 驱动程序。 Linux 的 Alarm 驱动程序代码路径在内核的“drivers/rtc/”目录中,各个具体硬件的实现不同。 (2)Alarm 驱动程序。 这是 Android 特定内核的组件,调用 RTC 系统的功能,但是本身和硬件无关。 (3)本地 JNI 部分。

Android 驱动开发与移植实战详解

Android应用 平台API

本地框架 AlarmManager AlarmManagerService Android系统 本地框架 AlarmManagerService JNI

Alarm设备 硬件和驱动

▲图 17-1 Alarm 系统的层次结构

Java应用层

AlarmManager

AlarmManagerService Java框架

AlarmManagerService JNI 本地框架

Alarm RTC Driver

▲图 17-2 Alarm 的系统结构

代码路径如下所示。

frameworks/base/services/jni/com_android_server_ AlarmManagerService.cpp

此文件是 Alarm 部分的本地代码,也同时提供了 JNI 的接口。 (4)Java 部分。 此部分的代码路径如下所示。

frameworks/base/services/java/com/android/server/AlarmManagerSe rvice.java

530 第 17 章 振动器驱动和警报器驱动

frameworks/base/core/java/android/app/AlarmManager.java

在文件 AlarmManagerService.java 中实现了 android.server 包中的 AlarmManagerService, 在文 件 AlarmManager.java 中实现了 android.app 包中 AlarmManager 类,它通过使用 AlarmManagerService 服务实现,并对 Java 层提供了平台 API。 下面是打开 Alarm 设备的代码:

static jint android_server_AlarmManagerService_init(JNIEnv* env, jobject obj) { #if HAVE_ANDROID_OS //打开 Alarm 设备 return open("/dev/alarm", O_RDWR); #else return -1; #endif }

下面是设置 Alarm 的代码:

static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, jint type, jlong seconds, jlong nanoseconds) { #if HAVE_ANDROID_OS struct timespec ts; ts.tv_sec = seconds; ts.tv_nsec = nanoseconds; //通过 ioctl 接口与驱动层交互 int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts); if (result < 0) { LOGE("Unable to set alarm to %lld.%09lld: %s\n", seconds, nanoseconds, strerror(errno)); } #endif }

下面是等待 Alarm 响应的代码:

static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject obj, jint fd) { #if HAVE_ANDROID_OS int result = 0; do { //调用 ioctl 接口 result = ioctl(fd, ANDROID_ALARM_WAIT); } while (result < 0 && errno == EINTR);

if (result < 0)

531 Android 驱动开发与移植实战详解

{ LOGE("Unable to wait on alarm: %s\n", strerror(errno)); return 0; }

return result; #endif }

下面是关闭 Alarm 设备代码:

static void android_server_AlarmManagerService_close(JNIEnv* env, jobject obj, jint fd) { #if HAVE_ANDROID_OS //关闭 Alarm 设备 close(fd); #endif }

由此可以看到,Alarm 的操作都是围绕 Alarm 设备文件展开的,主要是通过 ioctl()方法调用驱 动中的函数实现的。

17.1.2 移植的内容 Android 的 Alarm 系统的 Java 层、本地部分的代码都是标准的,所以不需要更改。内核中的 Alarm 驱动程序与硬件无关,在 Android 系统中都是相同的。所以警报器系统的移植实际上就是 RTC 驱动 程序的移植。RTC 驱动程序是 Linux 中一种标准的驱动程序,它在用户空间也提供了设备节点(自 定义的字符设备或 MISC 字符设备)。根据 Android 系统的情况,不直接使用 RTC 驱动程序,而是 通过 Alarm 驱动程序调用 RTC 系统,而 Android 系统的用户空间只调用 Alarm 驱动程序。

17.2 移植和调试

1.RTC 驱动程序

RTC 驱动程序 RTC 是 Linux 中标准的 Alarm 驱动程序框架。驱动程序的框架内容在内核文件 inlcude/linux/rtc.h 中定义。首先在此文件中定义如下两个函数,分别实现注册和注销 RTC 设备。具 体代码如下所示。

extern struct rtc_device *rtc_device_register(const char *name, struct device *dev, const struct rtc_class_ops *ops, struct module *owner); extern void rtc_device_unregister(struct rtc_device *rtc);

然后定义结构体 rtc_class_ops,具体代码如下所示。

struct rtc_class_ops {

532 第 17 章 振动器驱动和警报器驱动

int (*open)(struct device *); void (*release)(struct device *); int (*ioctl)(struct device *, unsigned int, unsigned long); int (*read_time)(struct device *, struct rtc_time *); int (*set_time)(struct device *, struct rtc_time *); int (*read_alarm)(struct device *, struct rtc_wkalrm *); int (*set_alarm)(struct device *, struct rtc_wkalrm *); int (*proc)(struct device *, struct seq_file *); int (*set_mmss)(struct device *, unsigned long secs); int (*read_callback)(struct device *, int data); int (*alarm_irq_enable)(struct device *, unsigned int enabled); };

结构体 struct rtc_device 是对 struct device 的扩展,在 RTC 驱动程序中使用,其中也包含了 rtc_class_ops 结构。RTC 驱动程序的实现实际上就是实现了 rtc_class_ops 中的函数指针,主要包括 了时间和警报器这两方面的内容。 在用户空间中,可以通过 RTC 驱动程序的设备节点对其进行调试,调试的方法是通过 ioctl 命 令。这些命令是在文件 rtc.h 中定义的,以 RTC 为开头。例如下面就是 4 个命令。

#define RTC_ALM_SET _IOW('p', 0x07, struct rtc_time) /* 设置警报器时间 */ #define RTC_ALM_READ _IOR('p', 0x08, struct rtc_time) /* 读取警报器时间 */ #define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time) /* 读取 RTC 时间 */ #define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time) /* 设置 RTC 时间 */

2.Alarm 驱动程序

Alarm 驱动程序为用户空间提供了设备节点“/dev/alarm”,这是一个主设备号为 10 的 Misc 字 符设备,并且其次设备号是动态生成的。Alarm 驱动程序由内核代码中的如下文件实现。

drivers/rtc/alarm.c drivers/rtc/alarm-dev.c

在头文件 include/linux/android_alarm.h 中提供了到用户空间的各 ioctl 命令接口。在 Alarm 设 备的 Suspend 和 Resume 过程中,通过调用 RTC 中的函数 rtc_read_time()和 rtc_set_alarm()进行操作, 表示通过 RTC 系统存/取当前的状态。主要代码如下所示。

rtc_read_time(alarm_rtc_dev,&rtc_current_rtc_time);/* 获得时间 */ rtc_current_timespec.tv_nsec=0; rtc_tm_to_time(&rtc_current_rtc_time,&rtc_current_timespec.tv_sec); save_time_delta(&rtc_delta,&rtc_current_timespec);/* 设置时间变化 */ set_normalized_timespec(&elapsed_realtime_alarm_time, alarm_time[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP] .tv_sec+elapsed_rtc_delta.tv_sec, alarm_time[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP] .tv_nsec+elapsed_rtc_delta.tv_nsec);

3.注意的几个问题

Alarm 在用户空间中的本地 JNI 部分的代码保存在“frameworks/base/services/jni/”目录,由文

533 Android 驱动开发与移植实战详解

件 com_android_server_AlarmManagerService.cpp 实现。此文件调用了 Alarm 驱动程序,向上层提 供了 JNI 的接口。其中方法列表如下所示。

static JNINativeMethodsMethods[]={ {"init","()I",(void*)android_server_AlarmManagerService_init}, {"close","(I)V",(void*)android_server_AlarmManagerService_close}, {"set","(IIJJ)V",(void*)android_server_AlarmManagerService_set}, {"waitForAlarm","(I)I", (void*)android_server_AlarmManagerService_waitForAlarm}, }

在此提供了对 android.server 包中的 AlarmManagerService 类的支持。

static jint android_server_AlarmManagerService_waitForAlarm( JNIEnv* env, jobject obj, jint fd) { #if HAVE_ANDROID_OS int result = 0; do{ result = ioctl(fd, ANDROID_ALARM_WAIT); // fd 是从“/dev/alarm”中打开的 } while (result < 0 && errno == EINTR); ...... return result; #endif }

在 Java 代码方面,通过如下文件实现了 android.server 中的 AlarmManagerService 类。

frameworks/base/services/java/com/android/server/AlarmManagerService.java

此文件调用了 JNI 部分的代码 ,实现了一个 Android 中的服务。AlarmManagerService 等服务 类一般不作为平台的 API 给 Java 应用程序的层使用。文件“frameworks/base/core/java/android/app/ AlarmManager.java ”是 android.app 包中的 AlarmManager 类,这个类是平台的 API。文件 AlarmManager.java 配合同一目录中的 IAlarmManager.aidl 联合使用,实现调用服务内容的功能。

模拟器环境的具体实现 Alarm 系统和其他系统相比,比较特殊的是只有模拟器的 RTC 驱动程序,具体是在文件 “drivers/rtc/rtc-goldfish.c 中实现的。GoldFish 的 Alarm 驱动由模拟器的虚拟环境触发中断,并填充 相关的寄存器,在驱动程序中取得信息。 函数 goldfish_rtc_read_time()用于获取当前的时间,具体代码如下所示。

static int goldfish_rtc_read_time(struct device *dev, struct rtc_time *tm) { int64_t time; struct goldfish_rtc *qrtc = platform_get_drvdata(to_platform_device(dev)); time = readl(qrtc >base + TIMER_TIME_LOW); /* 读取虚拟的寄存器 */ time |= (int64_t)readl(qrtc >base + TIMER_TIME_HIGH) << 32; do_div(time, NSEC_PER_SEC);

534 第 17 章 振动器驱动和警报器驱动

rtc_time_to_tm(time, tm); return 0; }

在上述代码中,通过读取 TIME_TIME_LOW 和 TIME_TIME_HIGH 这两个虚拟寄存器来获得 当前的时间。

17.3 实现 Alarm 驱动

在 Android 的 Alarm 系统中,驱动程序由 Alarm 驱动和 RTC 驱动两部分组成,其中 Alarm 驱 动完成设备的注册和加载,并提供接口给上层应用使用,RTC 驱动部分是 Linux 标准的实时时钟驱 动,不同的实时时钟芯片也是在这个标准上具体实现的,所以结构层次应该是上层应用调用 Alarm 驱动,然后调用 RTC 驱动,最后实现报警功能,驱动中设计的主要代码如下:

"drivers/rtc/Alarm.c"//Alarm 驱动 "drivers/rtc/Interface.c"//Alarm 驱动和 RTC 驱动的中间接口 "drivers/rtc/Rtc-s3c.c"//三星 RTC 驱动 "include/linux/Android_alarm.h"//Alarm 使用的结构体

在文件 Android_alarm.h 中定义了一个枚举结构来描述闹钟的类型,代码如下:

enum android_alarm_type { //在指定的时间触发,并唤醒设备 ANDROID_ALARM_RTC_WAKEUP, //在指定的时间触发,不唤醒设备 ANDROID_ALARM_RTC, //在设备启动后经过多长时间触发,并唤醒设备 ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, //在设备启动后经过多长时间触发,不唤醒设备 ANDROID_ALARM_ELAPSED_REALTIME, //读取系统时间 ANDROID_ALARM_SYSTEMTIME, ANDROID_ALARM_TYPE_COUNT, };

文件 Alarm.c 负责完成 Alarm 设备的初始化和加载,以及在系统中注册设备驱动以提供接口给 上层应用使用。Alarm 驱动以模块方式加载,在 alarm_init()函数中设置了定时器的回调函数,注册 了 Alarm 的驱动,代码如下:

static int __init alarm_init(void) { int err; int i; for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) { hrtimer_init(&alarm_timer[i], CLOCK_REALTIME, HRTIMER_MODE_ABS); //设置定时器的回调函数 alarm_timer[i].function = alarm_timer_triggered; }

535 Android 驱动开发与移植实战详解

hrtimer_init(&alarm_timer[ANDROID_ALARM_SYSTEMTIME], CLOCK_MONOTONIC, HRTIMER_MODE_ABS); alarm_timer[ANDROID_ALARM_SYSTEMTIME].function = alarm_timer_triggered; //注册驱动程序 err = platform_driver_register(&alarm_driver); if (err < 0) goto err1; wake_lock_init(&alarm_wake_lock, WAKE_LOCK_SUSPEND, "alarm"); wake_lock_init(&alarm_rtc_wake_lock, WAKE_LOCK_SUSPEND, "alarm_rtc"); rtc_alarm_interface.class = rtc_class; //注册 class 的接口 err = class_interface_register(&rtc_alarm_interface); if (err < 0) goto err2; return 0; err2: wake_lock_destroy(&alarm_rtc_wake_lock); wake_lock_destroy(&alarm_wake_lock); platform_driver_unregister(&alarm_driver); err1: return err; }

下面是定义 alarm_driver 的代码:

static struct platform_driver alarm_driver = { .suspend = alarm_suspend, .resume = alarm_resume, .driver = { .name = "alarm" } };

在 alarm_driver 中包含两个方法:alarm_resume()和 alarm_suspend(),功能是分别对系统的正常 和休眠两种状态来设置 Alarm,下面是定义方法 alarm_resume()和方法 alarm_suspend()的代码。

int alarm_resume(struct platform_device *pdev) { struct rtc_wkalrm alarm; ANDROID_ALARM_DPRINTF(ANDROID_ALARM_PRINT_FLOW, "alarm_resume(%p)\n", pdev); if (alarm_enabled & ANDROID_ALARM_WAKEUP_MASK) { memset(&alarm, 0, sizeof(alarm)); alarm.enabled = 0; //设置 rtc 时钟,最终会在具体的 RTC 芯片中实现这个函数 rtc_set_alarm(alarm_rtc_dev, &alarm); alarm_start_hrtimer(ANDROID_ALARM_RTC_WAKEUP); alarm_start_hrtimer(ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP); } return 0; }

536 第 17 章 振动器驱动和警报器驱动

系统在正常状态的时候会执行 alarm_resume()函数来写/dev/alarm 设备。 int alarm_suspend(struct platform_device *pdev, pm_message_t state) { int err = 0; unsigned long flags; struct rtc_wkalrm rtc_alarm; struct rtc_time rtc_current_rtc_time; unsigned long rtc_current_time; unsigned long rtc_alarm_time; struct timespec rtc_current_timespec; struct timespec rtc_delta; struct timespec elapsed_realtime_alarm_time; ANDROID_ALARM_DPRINTF(ANDROID_ALARM_PRINT_FLOW, "alarm_suspend(%p, %d)\n", pdev, state.event); spin_lock_irqsave(&alarm_slock, flags); if (alarm_pending && !wake_lock_active(&alarm_wake_lock)) { ANDROID_ALARM_DPRINTF(ANDROID_ALARM_PRINT_INFO, "alarm pending\n"); err = -EBUSY; goto err1; } if (alarm_enabled & ANDROID_ALARM_WAKEUP_MASK) { spin_unlock_irqrestore(&alarm_slock, flags); if (alarm_enabled & ANDROID_ALARM_RTC_WAKEUP_MASK) hrtimer_cancel(&alarm_timer[ANDROID_ALARM_RTC_WAKEUP]); if (alarm_enabled & ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK) hrtimer_cancel(&alarm_timer[ ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP]); rtc_read_time(alarm_rtc_dev, &rtc_current_rtc_time); rtc_current_timespec.tv_nsec = 0; rtc_tm_to_time(&rtc_current_rtc_time, &rtc_current_timespec.tv_sec); save_time_delta(&rtc_delta, &rtc_current_timespec); set_normalized_timespec(&elapsed_realtime_alarm_time, alarm_time[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP] .tv_sec + elapsed_rtc_delta.tv_sec, alarm_time[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP] .tv_nsec + elapsed_rtc_delta.tv_nsec); if ((alarm_enabled & ANDROID_ALARM_RTC_WAKEUP_MASK) && (!(alarm_enabled & ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK) || timespec_compare(&alarm_time[ANDROID_ALARM_RTC_WAKEUP], &elapsed_realtime_alarm_time) < 0)) rtc_alarm_time = timespec_sub( alarm_time[ANDROID_ALARM_RTC_WAKEUP], rtc_delta).tv_sec; else rtc_alarm_time = timespec_sub( elapsed_realtime_alarm_time, rtc_delta).tv_sec;

537 Android 驱动开发与移植实战详解

rtc_time_to_tm(rtc_alarm_time, &rtc_alarm.time); rtc_alarm.enabled = 1; rtc_set_alarm(alarm_rtc_dev, &rtc_alarm); rtc_read_time(alarm_rtc_dev, &rtc_current_rtc_time); rtc_tm_to_time(&rtc_current_rtc_time, &rtc_current_time); ANDROID_ALARM_DPRINTF(ANDROID_ALARM_PRINT_INFO, "rtc alarm set at %ld, now %ld, rtc delta %ld.%09ld\n", rtc_alarm_time, rtc_current_time, rtc_delta.tv_sec, rtc_delta.tv_nsec); if (rtc_current_time + 1 >= rtc_alarm_time) { ANDROID_ALARM_DPRINTF(ANDROID_ALARM_PRINT_INFO, "alarm about to go off\n"); memset(&rtc_alarm, 0, sizeof(rtc_alarm)); rtc_alarm.enabled = 0; //设置 rtc 时钟 rtc_set_alarm(alarm_rtc_dev, &rtc_alarm); spin_lock_irqsave(&alarm_slock, flags); wake_lock_timeout(&alarm_rtc_wake_lock, 2 * HZ); alarm_start_hrtimer(ANDROID_ALARM_RTC_WAKEUP); alarm_start_hrtimer( ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP); err = -EBUSY; spin_unlock_irqrestore(&alarm_slock, flags); } } else { err1: spin_unlock_irqrestore(&alarm_slock, flags); } return err; }

当系统休眠的时候,会调用 alarm_suspend()函数来将闹钟时间之类的数据写入 RTC 芯片的寄 存器中去,如果闹钟时间到,则唤醒系统产生报警。 下面是定义 rtc_alarm_interface 结构体的代码:

static struct class_interface rtc_alarm_interface = { .add_dev = &rtc_alarm_add_device, .remove_dev = &rtc_alarm_remove_device, };

rtc_alarm_add_device 的功能是在驱动框架上新增一个 Alarm 设备并注册和初始化,代码如下:

static int rtc_alarm_add_device(struct device *dev, struct class_interface *class_intf) { int err; struct rtc_device *rtc = to_rtc_device(dev); mutex_lock(&alarm_setrtc_mutex); if (alarm_rtc_dev) { err = -EBUSY;

538 第 17 章 振动器驱动和警报器驱动

goto err1; } //注册一个 Alarm 设备,暴露接口给上层应用 err = misc_register(&alarm_device); if (err) goto err1; alarm_platform_dev = platform_device_register_simple("alarm", -1, NULL, 0); if (IS_ERR(alarm_platform_dev)) { err = PTR_ERR(alarm_platform_dev); goto err2; } //注册 RTC 中断 err = rtc_irq_register(rtc, &alarm_rtc_task); if (err) goto err3; alarm_rtc_dev = rtc; mutex_unlock(&alarm_setrtc_mutex); …… return err; }

当 Alarm 设备注册到系统中去之后,也暴露接口给上层应用,上层应用就可以调用 open、close、 ioctl 方法来访问驱动程序,例如在 JNI 中的设置报警器的方法中会使用 ioctl(fd, ANDROID_ALARM_SET(type), &ts)来设置 Alarm 驱动中的相关参数,相关 ioctl 方法在驱动中的实 现如下:

static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ …… //设置闹钟 case ANDROID_ALARM_SET(0): if (copy_from_user(&new_alarm_time, (void __user *)arg, sizeof(new_alarm_time))) { rv = -EFAULT; goto err1; } from_old_alarm_set: spin_lock_irqsave(&alarm_slock, flags); ANDROID_ALARM_DPRINTF(ANDROID_ALARM_PRINT_IO, "alarm %d set %ld.%09ld\n", alarm_type, new_alarm_time.tv_sec, new_alarm_time.tv_nsec); alarm_time[alarm_type] = new_alarm_time; alarm_enabled |= alarm_type_mask; //设置闹钟 alarm_start_hrtimer(alarm_type); spin_unlock_irqrestore(&alarm_slock, flags); if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_SET_AND_WAIT(0) && cmd != ANDROID_ALARM_SET_AND_WAIT_OLD) break;

539 Android 驱动开发与移植实战详解

//等待 Alarm 响应 case ANDROID_ALARM_WAIT: spin_lock_irqsave(&alarm_slock, flags); ANDROID_ALARM_DPRINTF(ANDROID_ALARM_PRINT_IO, "alarm wait\n"); if (!alarm_pending && wait_pending) { wake_unlock(&alarm_wake_lock); wait_pending = 0; } spin_unlock_irqrestore(&alarm_slock, flags); //等待 alarm_wait_queue 队列中最先到达的报警器 rv = wait_event_interruptible(alarm_wait_queue, alarm_pending); if (rv) goto err1; spin_lock_irqsave(&alarm_slock, flags); rv = alarm_pending; wait_pending = 1; alarm_pending = 0; if (rv & ANDROID_ALARM_WAKEUP_MASK) wake_unlock(&alarm_rtc_wake_lock); spin_unlock_irqrestore(&alarm_slock, flags); break; …… }

文件 Rtc-s3c.c 的主要功能是实现三星的 RTC 芯片驱动,在 platform_driver_register()函数中会 注册三星的 RTC 时钟芯片驱动,代码如下:

static int __init s3c_rtc_init(void) { printk(banner); return platform_driver_register(&s3c2410_rtc_driver); }

下面是定义 s3c2410_rtc_driver 结构体的代码:

static struct platform_driver s3c2410_rtc_driver = { .probe = s3c_rtc_probe, .remove = s3c_rtc_remove, .suspend = s3c_rtc_suspend, .resume = s3c_rtc_resume, .driver = { .name = "s3c2410-rtc", .owner = THIS_MODULE, }, };

在加载 RTC 芯片的时候会调用 s3c_rtc_probe(),代码如下:

static int s3c_rtc_probe(struct platform_device *pdev) { struct rtc_device *rtc;

540 第 17 章 振动器驱动和警报器驱动

struct resource *res; int ret; unsigned char bcd_tmp,bcd_loop; pr_debug("%s: probe=%p\n", __func__, pdev); //找到中断 s3c_rtc_tickno = platform_get_irq(pdev, 1); if (s3c_rtc_tickno < 0) { dev_err(&pdev->dev, "no irq for rtc tick\n"); return -ENOENT; } //找到中断 s3c_rtc_alarmno = platform_get_irq(pdev, 0); if (s3c_rtc_alarmno < 0) { dev_err(&pdev->dev, "no irq for alarm\n"); return -ENOENT; } printk("s3c2410_rtc: tick irq %d, alarm irq %d\n", s3c_rtc_tickno, s3c_rtc_alarmno); //活得内存空间 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(&pdev->dev, "failed to get memory region resource\n"); return -ENOENT; } //分配内存 s3c_rtc_mem = request_mem_region(res->start, res->end-res->start+1, pdev->name); if (s3c_rtc_mem == NULL) { dev_err(&pdev->dev, "failed to reserve memory region\n"); ret = -ENOENT; goto err_nores; } //内存地址映射 s3c_rtc_base = ioremap(res->start, res->end - res->start + 1); if (s3c_rtc_base == NULL) { dev_err(&pdev->dev, "failed ioremap()\n"); ret = -EINVAL; goto err_nomap; } //检查是否就绪 s3c_rtc_enable(pdev, 1); pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(s3c_rtc_base + S3C2410_RTCCON)); s3c_rtc_setfreq(&pdev->dev, 1); device_init_wakeup(&pdev->dev, 1); //注册 s3c_rtcops rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops, THIS_MODULE); if (IS_ERR(rtc)) {

541 Android 驱动开发与移植实战详解

dev_err(&pdev->dev, "cannot attach rtc\n"); ret = PTR_ERR(rtc); goto err_nortc; } rtc->max_user_freq = S3C_MAX_CNT; //检查 RTC 时间 for (bcd_loop = S3C2410_RTCSEC ; bcd_loop <= S3C2410_RTCYEAR ; bcd_loop +=0x4) { bcd_tmp = readb(s3c_rtc_base + bcd_loop); if(((bcd_tmp & 0xf) > 0x9) || ((bcd_tmp & 0xf0) > 0x90)) writeb(0, s3c_rtc_base + bcd_loop); } platform_set_drvdata(pdev, rtc); return 0; err_nortc: s3c_rtc_enable(pdev, 0); iounmap(s3c_rtc_base); err_nomap: release_resource(s3c_rtc_mem); err_nores: return ret; }

s3c_rtc_probe()负责完成申请分配内存的工作,并且将 s3c_rtcops 注册进设备,s3c_rtcops 的代 码如下:

//具体的时钟芯片的接口实现 static const struct rtc_class_ops s3c_rtcops = { .open = s3c_rtc_open, .release = s3c_rtc_release, .ioctl = s3c_rtc_ioctl, .read_time = s3c_rtc_gettime, .set_time = s3c_rtc_settime, .read_alarm = s3c_rtc_getalarm, .set_alarm = s3c_rtc_setalarm, .irq_set_freq = s3c_rtc_setfreq, .irq_set_state = s3c_rtc_setpie, .proc = s3c_rtc_proc, };

其中很多对 Alarm 设备的操作都会具体到这里实现,大部分都是通过读写寄存器实现,这样就 完成了从上层应用到 Alarm 驱动到 RTC 时钟芯片的实现的一整套流程。

17.4 MSM 平台实现 Alarm

在 MSM 平台中,Alarm 驱动程序是在文件 drivers/rtc/ rtc-MSM7k00a.c 中实现的,具体的功能 是调用 RPC(远程过程调用)完成的。通过函数 msmrtc_probe()来实现探测初始化,具体代码如下 所示。

542 第 17 章 振动器驱动和警报器驱动

static int msmrtc_probe(struct platform_device *pdev) { struct rpcsvr_platform_device *rdev = container_of(pdev, struct rpcsvr_platform_device, base); ep = msm_rpc_connect(rdev >prog, rdev >vers, 0); /* 连接到 RPC */ ...... rtc = rtc_device_register("msm_rtc", &pdev >dev, /* 建立 RTC 设备 */ &msm_rtc_ops, THIS_MODULE); /* ...... 省略部分错误处理的内容 */ return 0; }

msm_rtc_ops 是一个 rtc_class_ops 类型的结构体,在里面实现了成员 read_time、set_time 和 set_alarm。 其中函数 msmrtc_timeremote_read_time()负责读取当前的时间。具体代码如下所示。

static int msmrtc_timeremote_read_time(struct device *dev, struct rtc_time *tm){ int rc; struct timeremote_get_julian_req { /* 表示 RPC 请求的结构体 */ struct rpc_request_hdr hdr; uint32_t julian_time_not_null; } req; struct timeremote_get_julian_rep { /* 表示 RPC 回应的结构体 */ struct rpc_reply_hdr hdr; uint32_t opt_arg; struct rpc_time_julian time; } rep; req.julian_time_not_null = cpu_to_be32(1); rc = msm_rpc_call_reply(ep, TIMEREMOTE_PROCEEDURE_GET_JULIAN, /* RPC 调用 */ &req,sizeof(req),&rep, sizeof(rep), 5 * HZ); ...... tm >tm_year = be32_to_cpu(rep.time.year); /* 填充 rtc_time 结构 */ tm >tm_mon = be32_to_cpu(rep.time.month); tm >tm_mday = be32_to_cpu(rep.time.day); tm >tm_hour = be32_to_cpu(rep.time.hour); tm >tm_min = be32_to_cpu(rep.time.minute); tm >tm_sec = be32_to_cpu(rep.time.second); tm >tm_wday = be32_to_cpu(rep.time.day_of_week); tm >tm_year = 1900; /* 设置从 1900 开始的 RTC */ tm >tm_mon; /* RTC 月份的调整 */ ...... return 0; }

具体功能的实现过程是通过统一的 RPC 在远端完成的,此处调用的是 RPC 的 TIMEREMOTE_PROCEEDURE_GET_JULIAN 命令。 函数 msmrtc_suspend()用于实现此驱动程序模块的挂起功能,在进行处理时,先根据所设置的 警报器时间得到距离系统现在时间的差值,然后调用 PM (电源管理)函数 msm_pm_set_max_sleep_time()把数值设置为睡眠的时间。这样在时间到达的时候就可以进行 PM 系统的唤醒。

543

第 18 章 光系统驱动和电池系统驱动

在本书前面的内容中,已经讲解了 Android 平台中主要的和硬件相关的驱动系统。其实除了前 面已经讲解的驱动系统外,在 Android 平台中还有其他几种常见的系统。在本章将简单讲解 Android 平台中其他几种常见的驱动系统,分别是光系统和电池系统。希望通过本章内容的学习,为本书画 上一个圆满的句号。

18.1 Lights 光系统的应用和移植

在 Android 平台中,Lights 光系统用于统一控制系统中的各个光源,例如屏幕背光、键盘按键 光、电池光等。Lights 光系统基本上是一个用于输出控制的系统。Lights 光系统从驱动程序、硬件 抽象层、本地框架到 Java 层都具有内容,但是没有向应用程序层提供直接 API 。

18.1.1 Lights 系统的结构 Android 平台中 Lights 光系统的基本层次结构如图 18-1 所示。

平台API

本地框架 电源管理和LightsService

Android系统 本地框架 LightsService JNI

光设备 硬件和驱动

▲图 18-1 Lights 光系统的层次结构

Lights 光系统的系统结构如图 18-2 所示。

第 18 章 光系统驱动和电池系统驱动

PowerManager/System Server

LightsService Java框架

LightsService JNI

Lights Hardware Interface

Lights Hardware Module 本地框架

内核 Lights驱动

▲图 18-2 Lights 光系统的系统结构

图 18-2 中各个部分的具体说明如下所示。 (1)驱动程序。 特定硬件平台光系统的驱动程序,可以使用 Linux 中的 LED 驱动程序实现。 (2)硬件抽象层。 光系统硬件抽象层的接口路径是“hardware/libhardware/include/hardware/lights.h”,此文件是一 个 Android 中标准的硬件模块。 (3)本地类。 实现本地类代码路径是“frameworks/base/services/jni/com_android_server_LightsService.cpp”, 本地类调用了硬件抽象层,也同时提供了 JNI 的接口。 (4)Java 类。 实现 Java 类的代码路径是“frameworks/base/services/java/com/android/server/LightsService.java”。 文件 LightsService.java 通过调用 LightsService JNI 来实现 com.android.server 包中的 LightsService 类。此类不是平台的 API,被 Android 系统 Java 框架中的其他一些部分所调用。

18.1.2 移植的内容 在移植 Android 光系统时,需要移植两个方面的内容:光系统的硬件抽象层和驱动程序。光系 统本身只是简单的控制功能,所以其驱动程序的实现过程比较简单,在 Linux 系统中,LED 驱动程 序框架可以作为光系统驱动程序。LED 驱动程序可以将空间 sys 文件系统提供给用户空间作为接口。 光系统的硬件抽象层是一个 Android 中标准的硬件模块,其底层和 Android 系统本地部分的接 口能够控制各个光源的状态。

545 Android 驱动开发与移植实战详解

18.1.3 移植和调试

1.驱动程序

Android 的光系统的驱动比较简单,可以使用字符设备的文件来操作 file_operation 和 sys 文件 系统等。在 Linux 系统中,LED 驱动程序框架比较适合作为光系统,其头文件是“linclude/linux/ leds.h”。在 Linux 菜单配置的 Device Drivers 选项中,可以打开 NEW_LEDS 和 LEDS_CLASS 以获 取 LED 驱动的支持。 在文件 leds.h 中,使用结构体 led_classdev 来表示 LED 设备,此结构体是 LED 驱动程序的核心。

struct led_classdev { const char *name; /* 设备名称 */ int brightness; /* 亮度 */ int flags; #define LED_SUSPENDED (1 << 0) #define LED_CORE_SUSPENDRESUME (1 << 16) /* 设置亮度级别 */ void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); /* 获得亮度级别 */ enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); /* 激活硬件加速的闪烁 */ int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off); struct device *dev; struct list_head node; /* LED 设备链表 */ ...... };

当实现了一个 led_classdev 并完成注册后,系统会出现一个 led 设备,同时在 sys 文件系统的 “/sys/class/leds/”目录中出现一个名为“led_classdev”的设备名称成员的子目录。在每个设备子目 录中将出现一个“brightness”文件,通过读写这个文件可以获得 LED 设备的亮度。 对于 LED 驱动程序来说,在调试时可以直接读写 sys 文件系统中相应设备的 brightness 文件。

2.硬件抽象层

Android 光系统的硬件抽象层是一个 Android 标准硬件模块,其接口在文件“hardware/ libhardware/include/hardware/lights.h”中定义,在此文件中定义结构体 ight_state_t 来表示光设备的 状态。

struct light_state_t { unsigned int color; /* 光源的颜色 */ int flashMode; /* Flash 的模式、开关时间 */ int flashOnMS; int flashOffMS; int brightnessMode; /* 亮度模式 */ };

然后定义光设备结构体 light_device_t。

546 第 18 章 光系统驱动和电池系统驱动

struct light_device_t { struct hw_device_t common; int (*set_light)(struct light_device_t* dev, /* 设置光源 */ struct light_state_t const* state); };

实现 Android 光系统的硬件抽象层的方法比较简单,只需实现相应的设置接口即可。相对于 Android 中的其他硬件模块,光系统的主要特点是需要为每一台光源实现一台设备 light_device_t。 当在 Android 中打开模块函数(hw_module_methods_t 中的 open)时需要通过参数“返回”一个 light_device_t 类型的指针。这个指针表示的是一台光设备,在模块的打开函数中通过指定的名称来 确定得到哪一个设备。当实现光系统的硬件抽象层后,将生成名称为 liblights.XXX.so 的动态库, 可以将其放置在目标文件系统的“/system/lib/hw”目录中。

3.上层实现

Android 光系统的本地代码兼 JNI 部分在文件“frameworks/base/services/jni/com_android_server_ LightsService.cpp”中实现。文件 com_android_server_LightsService.cpp 提供了对 Java 层次的 JNI 接口,定义方法列表如下所示。

static JNINativeMethod method_table[] = { { "init_native", "()I", (void*)init_native }, { "finalize_native", "(I)V", (void*)finalize_native }, { "setLight_native", "(IIIIIII)V", (void*)setLight_native }, };

然后定义一个 light_device_t 类型的结构体 Devices。

struct Devices { light_device_t* lights[LIGHT_COUNT]; }; // 默认为 LIGHT_ID_ 的数目

还需要定义函数 setLight_native()来设置光线的情况。

static void setLight_native(JNIEnv *env, jobject clazz, int ptr, int light, int colorARGB, int flashMode, int onMS, int offMS, int brightnessMode) { Devices* devices = (Devices*)ptr; light_state_t state; ...... memset(&state, 0, sizeof(light_state_t)); state.color = colorARGB; state.flashMode = flashMode; state.flashOnMS = onMS; state.flashOffMS = offMS; state.brightnessMode = brightnessMode; devices >lights[light] >set_light(devices >lights[light], &state); }

18.1.4 MSM 平台实现光系统 在 MSM 的 mahimahi 平台中,虽然有不同的发光源,但是这些不同的硬件统一实现了几个 led_classdev 设备。

547 Android 驱动开发与移植实战详解

MSM 的背光设备在文件中定义,主要代码如下所示。

static struct led_classdev mahimahi_brightness_led = { .name = "lcd-backlight",//光设备名称 .brightness = LED_FULL, .brightness_set = mahimahi_brightness_set,//设置光亮度函数 };

Android 光系统的硬件抽象层在“hardware/msm7k/liblights/open_lights”目录中实现。在此需要 定义函数 set_light_backlight()来实现背光设备的 set_light,具体代码如下所示。

//定义三星平台的背光设备文件路径 char const*const LCD_FILE = "/sys/devices/platform/s3c-lcd/backlight_level"; …… static intset_light_backlight(struct light_device_t* dev, struct light_state_t const* state) { int err = 0; int brightness = rgb_to_brightness(state); pthread_mutex_lock(&g_lock); g_backlight = brightness; //调节屏幕亮度 err = write_int(LCD_FILE, brightness); if (g_haveTrackballLight) { handle_trackball_light_locked(dev); } pthread_mutex_unlock(&g_lock); return err; }

由此可以看出,硬件抽象层是通过向驱动中的设备文件写数据来达到修改灯光亮的目的。 LCD_FILE 定义为 sys 文件系统中可以控制设备的文件的路径。由于在 light_state_t 中的参数是 color,表 示 32 位的 ARGB 数据,而背光硬件只支持亮度,因此使用 rgb_to_brightness()函数将 RGB 转化为亮度的整数值。其他光源还包括键盘、按键、电池等实现方式和背光的实现类似,都是通过 sys 文件系统控制驱动完成的。

18.1.5 深入分析 Android 的光系统 Android 中的光系统一般是通过 LED 的驱动程序实现的,LED 驱动提供给上层应用的接口是 sys 文件系统,其头文件路径为:

"include/linux/Leds.h"

其中 led_classdev 结构体用来描述一个 LED 设备。

struct led_classdev { //设备名字 const char *name; //亮度 int brightness;

548 第 18 章 光系统驱动和电池系统驱动

int flags;

#define LED_SUSPENDED (1 << 0) //设置 LED 亮度,系统不能是休眠状态 void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); //得到 LED 的亮度 enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); //加快闪烁 int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off); struct device *dev; struct list_head node; //LED 设备的链表 const char *default_trigger; #ifdef CONFIG_LEDS_TRIGGERS struct rw_semaphore trigger_lock; struct led_trigger *trigger; struct list_head trig_list; void *trigger_data; #endif };

当实现了一个 led_classdev 并注册后,会在 sys 文件系统中的“/sys/devices/platform/s3c-lcd/” 目录中出现 backlight_level 文件,修改这个文件即可修改屏幕的亮度。三星平台的 sys/devices/platform/s3c-lcd 下的文件情况如图 18-3 所示。

▲图 18-3 文件情况

18.2 Battery 电池系统的应用和移植

在 Android 系统中,使用电池的方式有很多种,例如 AC、USB、Battery 等不同的模式。在应 用程序层次,通常包括了电池状态显示的功能。因此在 Android 系统的软件方面需要在一定程度上 获得电池的状态,例如包括驱动程序和用户空间内容。Android 系统中的电池系统主要负责统计电 池信息。

18.2.1 Battery 系统的结构 Android 平台中 Battery 系统的基本层次结构如图 18-4 所示。

549 Android 驱动开发与移植实战详解

平台API

本地框架 BatteryStats BatteryManager和BatteryService

Android系统 本地框架 BatteryService JNI

电池设备 硬件和驱动

▲图 18-4 Battery 系统的层次结构

由图 18-4 可知,Battery 系统从上到下主要包括 Java 框架、本地 JNI 程序和底层驱动程序。这 几部分的系统结构如图 18-5 所示。

Java框架

BatteryManager

BatteryService Java框架

BatteryService JNI 本地框架

电池信息驱动

▲图 18-5 Battery 的系统结构

(1)驱动程序。 特定硬件平台电池的驱动程序,可以使用 Linux 的 Power Supply 驱动程序向用户空间提供信息。 (2)本 地 JNI,代码路径是“frameworks/base/services/jni/com_android_server_ BatteryService.cpp”。 在此文件的类中调用 sys 文件系统访问驱动程序,并同时提供了 JNI 的接口。 (3)Java 代码部分的实现路径如下所示。

550 第 18 章 光系统驱动和电池系统驱动

frameworks/base/services/java/com/android/server/Batter yService.java。 frameworks/base/core/java/android/os/:android.os 包中和 Battery 相关的部分。 frameworks/base/core/java/com/android/internal/os/:和 Battery 相关的内部部分。 文件 BatteryService.java 通过调用 BatteryService JNI 来实现 com.android.server 包中的类 BatteryService。在文件 BatteryManager.java 中定义了 Java 应用程序层可以使用的常量。

18.2.2 移植的内容 在移植 Android 电池系统时,在驱动程序层以上的部分都是 Android 系统中默认的内容。在移 植的过程中基本不需要改动。电池系统需要移植的部分仅仅是 Battery 驱动程序。Battery 驱动程序 使用了 Linux 标准的 Power Supply 驱动程序,此驱动程序与上层的接口是 sys 文件系统,功能是读 取 sys 文件系统中的文件来获取电池相关的信息。

18.2.3 移植和调试

1.驱动程序

Android 的 Battery 驱动程序需要使用 sys 文件系统向用户空间提供接口,sys 系统的路径是由 上层的程序指定的。在 Linux 的标准 Power Supply 驱动程序中,使用的文件系统的路径是 “/sys/class/power_supply/”。在此目录中,每个子目录表示一种能源供应设备的名称。 驱动程序 Power Supply 的头文件是“include/linux/power_supply.h”,注册和注销驱动程序的函 数如下所示。

int power_supply_register(struct device *parent,struct power_supply *psy); void power_supply_unregister(struct power_supply *psy);

其中结构体 power_supply 是驱动程序需要实现的部分,主要代码如下所示。

struct power_supply { const char *name; /* 设备名称 */ enum power_supply_type type; /* 类型 */ enum power_supply_property *properties; /* 属性指针 */ size_t num_properties; /* 属性的数目 */ char **supplied_to; size_t num_supplicants; int (*get_property)(struct power_supply *psy, /* 获得属性 */ enum power_supply_property psp, union power_supply_propval *val); void (*external_power_changed)(struct power_supply *psy); ...... };

2.上层实现

文件 com_android_server_BatteryService.cpp 是 Battery 部分的本地和 JNI 代码,此文件被保存 在“frameworks/base/services/jni/”目录中。

551 Android 驱动开发与移植实战详解

在文件 com_android_server_BatteryService.cpp 中提供的方法列表代码如下所示。

static JNINativeMethod sMethods[] = { {"native_update", "()V", (void*)android_server_BatteryService_update}, };

这是 com_ android_server 包中的 BatteryService 类的本地方法,只存在一个名为 native_update 的函数,此函数既没有参数,也没有返回值。 再看函数 android_server_BatteryService_update()。

static void android_server_BatteryService_update(JNIEnv* env, jobject obj) { #if 0 setBooleanField(env, obj, AC_ONLINE_PATH, gFieldIds.mAcOnline); setBooleanField(env, obj, USB_ONLINE_PATH, gFieldIds.mUsbOnline); setBooleanField(env, obj, BATTERY_PRESENT_PATH, gFieldIds.mBatteryPresent);

setIntField(env, obj, BATTERY_CAPACITY_PATH, gFieldIds.mBatteryLevel); setIntField(env, obj, BATTERY_VOLTAGE_PATH, gFieldIds.mBatteryVoltage); setIntField(env, obj, BATTERY_TEMPERATURE_PATH, gFieldIds.mBatteryTemperature);

const int SIZE = 128; char buf[SIZE]; if (readFromFile(BATTERY_STATUS_PATH, buf, SIZE) > 0)//设置状态 env->SetIntField(obj, gFieldIds.mBatteryStatus, getBatteryStatus(buf)); if (readFromFile(BATTERY_HEALTH_PATH, buf, SIZE) > 0)//设置电量程度 env->SetIntField(obj, gFieldIds.mBatteryHealth, getBatteryHealth(buf)); if (readFromFile(BATTERY_TECHNOLOGY_PATH, buf, SIZE) > 0) env->SetObjectField(obj, gFieldIds.mBatteryTechnology, env->NewStringUTF(buf)); #else env->SetBooleanField(obj, gFieldIds.mAcOnline, true); env->SetBooleanField(obj, gFieldIds.mUsbOnline, false); env->SetBooleanField(obj, gFieldIds.mBatteryPresent, false); env->SetIntField(obj, gFieldIds.mBatteryLevel, 100); env->SetIntField(obj, gFieldIds.mBatteryVoltage, 15); env->SetIntField(obj, gFieldIds.mBatteryTemperature, 20); const int SIZE = 128; char buf[SIZE]; env->SetIntField(obj, gFieldIds.mBatteryStatus, getBatteryStatus("F")); env->SetIntField(obj, gFieldIds.mBatteryHealth, getBatteryHealth("G")); env->SetObjectField(obj, gFieldIds.mBatteryTechnology, env->NewStringUTF("Nemus")); #endif }

接下来需要定义函数 register_android_server_BatteryService(),此函数用于得到各种属性的处理。

int register_android_server_BatteryService(JNIEnv* env) { jclass clazz = env->FindClass("com/android/server/BatteryService"); if (clazz == NULL) { LOGE("Can't find com/android/server/BatteryService"); return -1; }

552 第 18 章 光系统驱动和电池系统驱动

gFieldIds.mAcOnline = env->GetFieldID(clazz, "mAcOnline", "Z"); gFieldIds.mUsbOnline = env->GetFieldID(clazz, "mUsbOnline", "Z"); gFieldIds.mBatteryStatus = env->GetFieldID(clazz, "mBatteryStatus", "I"); gFieldIds.mBatteryHealth = env->GetFieldID(clazz, "mBatteryHealth", "I"); gFieldIds.mBatteryPresent = env->GetFieldID(clazz, "mBatteryPresent", "Z"); gFieldIds.mBatteryLevel = env->GetFieldID(clazz, "mBatteryLevel", "I"); gFieldIds.mBatteryTechnology = env->GetFieldID(clazz, "mBatteryTechnology", "Ljava/lang/String;"); gFieldIds.mBatteryVoltage = env->GetFieldID(clazz, "mBatteryVoltage", "I"); gFieldIds.mBatteryTemperature = env->GetFieldID(clazz, "mBatteryTemperature", "I"); LOG_FATAL_IF(gFieldIds.mAcOnline == NULL, "Unable to find BatteryService.AC_ONLINE_PATH"); LOG_FATAL_IF(gFieldIds.mUsbOnline == NULL, "Unable to find BatteryService.USB_ONLINE_ PATH"); LOG_FATAL_IF(gFieldIds.mBatteryStatus == NULL, "Unable to find BatteryService. BATTERY_STATUS_PATH"); LOG_FATAL_IF(gFieldIds.mBatteryHealth == NULL, "Unable to find BatteryService. BATTERY_HEALTH_PATH"); LOG_FATAL_IF(gFieldIds.mBatteryPresent == NULL, "Unable to find BatteryService. BATTERY_PRESENT_PATH"); LOG_FATAL_IF(gFieldIds.mBatteryLevel == NULL, "Unable to find BatteryService. BATTERY_CAPACITY_PATH"); LOG_FATAL_IF(gFieldIds.mBatteryVoltage == NULL, "Unable to find BatteryService. BATTERY_VOLTAGE_PATH"); LOG_FATAL_IF(gFieldIds.mBatteryTemperature == NULL, "Unable to find BatteryService. BATTERY_TEMPERATURE_PATH"); LOG_FATAL_IF(gFieldIds.mBatteryTechnology == NULL, "Unable to find BatteryService. BATTERY_TECHNOLOGY_PATH"); clazz = env->FindClass("android/os/BatteryManager"); if (clazz == NULL) { LOGE("Can't find android/os/BatteryManager"); return -1; } gConstants.statusUnknown = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_STATUS_UNKNOWN", "I")); gConstants.statusCharging = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_STATUS_CHARGING", "I")); gConstants.statusDischarging = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_STATUS_DISCHARGING", "I")); gConstants.statusNotCharging = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_STATUS_NOT_CHARGING", "I")); gConstants.statusFull = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_STATUS_FULL", "I")); gConstants.healthUnknown = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_HEALTH_UNKNOWN", "I")); gConstants.healthGood = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_HEALTH_GOOD", "I")); gConstants.healthOverheat = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_HEALTH_OVERHEAT", "I")); gConstants.healthDead = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_HEALTH_DEAD", "I")); gConstants.healthOverVoltage = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_HEALTH_OVER_VOLTAGE", "I")); gConstants.healthUnspecifiedFailure = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "BATTERY_HEALTH_UNSPECIFIED_FAILURE", "I"));

553 Android 驱动开发与移植实战详解

return jniRegisterNativeMethods(env, "com/android/server/BatteryService", sMethods, NELEM(sMethods)); }

}

上述代码的处理流程是:根据设备类型判定设备后,得到各个设备的相关属性,例如是交流或 者 USB 设备,则只需得到它们是否在线(onLine);如果是电池设备,则需要得到更多的信息,例 如状态 (status)、健康程度(health)、容量(capacity)和电压(voltage_now)等。 文件 frameworks/base/services/java/com/android/server/BatteryService.java 实现了 com.android.server 包中的 BatteryService 类,此类本身继承了 Binder。文件“frameworks/base/core/java/android/os/ BatteryManager.java 实现了 Battery 系统对应用程序层的常量。 另外,在文件 frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java 中, BatteryStatsImpl.aidl 和 IBatteryStats.aidl 是其核心实现内容。和 Battery 相关的信息是通过广播的方 式由 Java 框架层传递给 Java 应用程序层的。在 Java 应用程序层中可以使用 BroadcastReceiver 广 播接收器来获取和电池相关的信息。

18.2.4 模拟器中实现电池系统 在 Goldfish 内核模拟器环境中实现了电池部分的驱动程序,实现文件为“drivers/power/ goldfish_battery.c”。 在此驱动程序中注册了 2 个 Power Supply 设备,分别是“battery” 和“ ac” 。 在函数 goldfish_battery_probe() 中构造了一个 power_supply 类型的结构来调用函数 power_supply_register() ,并将其注册到系统中。 定义函数 goldfish_battery_interrupt(),在此驱动中实现中断处理,此处使用的中断号是 17。此 函数的实现代码如下所示。

static irqreturn_t goldfish_battery_interrupt(int irq, void *dev_id) { unsigned long irq_flags; struct goldfish_battery_data *data = dev_id; uint32_t status; spin_lock_irqsave(&data->lock, irq_flags); /* 保存中断状态 */ /* 读取状态位,也会清除中断 */ status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS); status &= BATTERY_INT_MASK; if (status & BATTERY_STATUS_CHANGED) power_supply_changed(&data->battery); /* 更新 battery 设备的信息*/ if (status & AC_STATUS_CHANGED) power_supply_changed(&data->ac); /* 更新 ac 设备的信息 */ spin_unlock_irqrestore(&data->lock, irq_flags); /* 恢复中断状态 */ return status ? IRQ_HANDLED : IRQ_NONE; }

此处处理的内容是来自模拟器的虚拟寄存器和虚拟中断。当 Power Supply 发生变化的时候, 模拟器环境将自动更新虚拟寄存器信息,并触发电池相关的中断,由本驱动程序读取虚拟寄存器, 然后通过 Power Supply 驱动框架更新 sys 文件系统中的内容。

554 内核 作者简介 Android 源码 GoldFish 下的驱动 驱动开发与移植 实战详解 MSM 内核和驱动 李骏 传感器、照相机 清华大学电子信息 驱动开发与移植 Wi-Fi、GPS 工程专业学士,较 早进入 Android 开 发领域,有多年的 Android 开发经验, 熟 练 使 用 Java 和 C/C++ 进行软件开 畅销书推荐: 发,熟悉 Android 层次结构和 Linux 驱动层的 结构及其上的开发,有着丰富的 Android 底层 和驱动层的优化、移植经验,擅长利用 JNI 技 术开发 Android 上的应用程序,曾带领团队利 用 NDK 技术成功开发过具有库仑计电池芯片 的电池管理软件,以及在 Android 上成功移植 人脸识别程序,目前在凹凸电子担任 Android 架构师。 实战详解驱动开发与移植 实战详解 李骏 陈小玉 编著 陈小玉

硕士,南阳理工学 本书支持社区: 院计算机与信息工 程学院讲师,软 51CTO.com 件设计师。主要 面向实战的基于驱动和移植开发的技术书 从事 Android 应用 技术成就梦想 及游戏开发和教 学工作,熟练使用 Java 语言,具有多年的 Android 和 IOS 系统手 ●● 内容全面,涵盖了 Android 系统的所有驱动系统 机客户端软件设计经验,具备扎实的手机 / 桌

●● 每个实例都进行了详细讲解,力求读者易学、易用 面 /Web UI 设计开发基础,熟悉 Android 系统 的 UI design Guideline,熟悉人机交互、机器 ●● 凝聚一线工程师的智慧结晶,实用性强 学习和人工智能算法,擅长利用智能算法改进 Android 上的应用程序,使其更具有智能性。

封面设计:胡萍丽

分类建议:计算机 / 程序设计 / 移动开发 人民邮电出版社网址:www.ptpress.com.cn

28361 Android驱动开发与移植实战详解.indd 1-5 12-6-25 下午4:50