动了记录仪的芯片组件, 本以为像之前改 RILC
一样依葫芦画瓢就能搞定, 没想到实打实搞了一周才搞定. 芯片组件不是我的主业, 搞不好哪天就忘了, 搞个帖子记录一下.
一些背景信息
EC521S
产品中, 硬件集成了一颗支持GNSS
高精度定位的芯片, 供应商叫千寻(后面称他千寻芯片).千寻芯片
支持高精度定位需要给它喂高精度定位差分数据
, 之后千寻芯片
内部就能够输出高精度定位结果了(片内处理).- 一般情况下, 外挂的
GNSS
芯片和平台连接方式是TTL
串口,千寻芯片
也不例外. 通过向所连接的串口持续写入正确的高精度定位差分数据
, 正常情况下后续定位就可以输出高精度定位结果. 外挂的芯片使用NMEA
协议通过串口向平台输出定位结果,EC521S
平台本身已经有相关的处理代码. - 千寻除了提供芯片之外, 还提供了一套
SDK
(后面称为千寻SDK),千寻SDK
的作用主要有两个:- 通过读写串口和芯片完全零距离沟通, 除了芯片的上电下电, 其他的数据下发和读取操作由SDK代理.
- 支持对接千寻自己的高精度服务器和其他支持对应协议的服务器, 支持
OSS
和Ntrip
高精度定位协议.
- 在之前的需求中, 平台已经在
Framework
中集成了千寻SDK
, 并在EC521S
中做了特殊处理, 不启动自带的处理NMEA
数据的线程. 启动定位后, 系统将芯片上电并做简单的配置之后就将串口控制权交给了千寻SDK.
需求描述
这次需求是要支持对接中国移动的高精度定位服务器, 中国移动提供一个SDK, 启动后能够输出 高精度定位差分数据
, 需要将数据传递给 千寻芯片
.
由于之前集成千寻SDK是直接集成在 Framework
, 需求期望将千寻SDK和中移SDK从 Framework
剥离, 方便后续移植.
剥离
千寻SDK
这个好说, 我们把SDK
单拎出来做成了一个系统应用, 在Framework
中通过IPC
调用和应用通信, 能够将SDK的定位信息报给系统.
在实现中移高精度的时候, 由于高精度计算只需要千寻芯片
就能完成, SE 要求在进行中移高精度定位时,千寻SDK
不要启动. 所以需要在系统内开一个直通千寻芯片
串口的接口, 用于给中移高精度注入差分数据(千寻SDK本身有暴露注入数据的接口).
爬坑记录
EUMI
的 HAL
不工作
既然要增加 HAL
接口, 首先想到的是在 EUMI
的基础上增加接口, 从源代码中看到HW有对 GNSS
接口进行扩展, 那我就跟着再扩展一下, 美滋滋写代码, 编译, 然后傻眼了...
机器里面没有HW的 HAL
实现产物, 好家伙, HW有代码, 但是没有使用...
HAL
一般有多个实现, 改HAL
代码前先看得先确认HAL
产物是不是已经存在...只看代码是不够的!
改 AOSP
的 HAL
编不了, 编了也起不来
既然 EMUI
的接口用不了, 那就破釜沉舟, 给 AOSP
的 IGnss.hal
加接口! 说干就干, 代码写起来一把梭...编译! 报错...
默认情况下,
AOSP
不允许尝试随便修改它自带的HAL
接口, 原理是通过HAL
预编译产物哈希值校验, 原因是为了系统拥有良好的可移植性. 倘若实在头铁, 可以根据编译报错提示修改哈希值记录文件.
头铁修改哈希后编译过了, 推进系统整个服务都起不来了, 报符号表找不到...进而导致 Android Framework
不断重启, 人都麻了...
HAL
接口通过 "版本" 来实现接口平滑, 记录仪中要修改的目标版本是 1.0 的 GNSS, 但由于系统内还存在 2.0 的联发科实现, 在整机编译时会给 2.0 的实现插入用以平滑接口的函数, 我测试时只推了 1.0 版本的产物, 导致运行一直报错...
修改HAL
声明文件后, 推荐全量编译刷镜像验证...
后面看了HW飞马架构编程指导文档, 才知道 CI 那会强制检验
AOSP
的HAL
接口, 不得随意修改, 所以此路不通.
新增 HAL
服务起不来
既然 EMUI
和 AOSP
都不能动, 那我就新增加一个TD的吧, 三两下增加了方法定义, 然后编译+排错+编译+排错+编译+排错+编译+排错+编译+排错, 终于是编出来了, 火速推二进制进行验证...然后, 又怀疑人生了...看内核日志, 服务被 SELinux
给阻止了.
虽然记录仪在启动之后看是关掉
SELinux
的, 但是在系统启动过程中, 这玩意是开着的, 我推进去的小包因为没有配置SELinux
, 所以压根就没有启动的机会...
于是在利哥的指点下临时抱佛脚学了一手, 对比文档配了一把, 编译, 推包, 心想美滋滋这下总该正常了吧, 然后就又被打脸了...(诶~我为什么要说又)
最终能搞定这个问题也是一次突发奇想的尝试...
具体原因并不清楚, 涉及到
SELinux
的修改, 目前只找到编镜像刷镜像这种方式来验证, 并且刷目标镜像的同时, 还需要刷一下内核镜像才行. 比如我的修改是vendor
分区, 那就得编vendorimage
和bootimage
然后刷机来验证. 只推文件的方式...至少我还没搞定...
刷完镜像串口设备竟然没了
真的, 我人都快没了....
SELinux
的问题搞定之后, 刷完镜像等待服务启动, 一切看起来都那么自然...但是等来的确实一个报错, /dev/ttyS1 open failed
, 不慌不慌, 这一定是写代码敲错了, 改改就好了~
一顿检查, 不可能啊, 打开设备的代码我是丝毫未动...难道是权限问题? ls -lh /dev/ttyS1
一下串口设备...这还怎么玩, 设备都没了...
当天一个机缘巧合的情况, 底软冰哥找我帮忙验证一个底软修改, 提到要单刷一下设备树, 否则串口出不来, 我恍然大悟, 咱们自己编出来的镜像会影响设备树...于是顺道编了一个
dtboimage
刷进去, 串口终于正常了.
write
fd 0 竟然不报错?
到这个时候, 整个人已经完全懵了, 以至于架构出现了如此大的问题竟丝毫未察觉...
前面提到, 千寻芯片
通过串口和系统通信, 搞到这我的实现是新增加了一个 HAL
服务, 暴露向串口写入的接口实现注入差分数据. 由于是新增的 HAL
服务, 按照透传服务实现默认是在一个新的进程, 也没有主动调用打开串口设备等操作, 此时代码内 fd 一直为0, 就这样调了很久...直到被利哥突然一问, 才恍然大悟...
Linux
中串口设备应该是独占的, 进程打开设备后其他进程无法在打开设备. 其次, 句柄为0的fd指向的是设备的标准输入设备, 向其写入数据是有可能不会报错的.
融合两个 Passthrough
写法的 HAL
服务
按照现有的 GNSS
服务, 我依葫芦画瓢搞了一个新的 HAL
服务, 由于需要操作同一个串口, 我打算把他俩搞在同一个进程...
但是由于新老接口都是 Passthrough
方式的写法, 并且主接口的名称都叫 IGnss
, 一直起不来...
Android 推出绑定式的服务, 原来直通式的代码通过
defaultPassthroughServiceImplementation
函数就可以快速切换为绑定式的服务, 但内部还是按照直通式的服务去加载impl
的so
, 其中一个关键函数就是HIDL_FETCH_XXXX
, 系统通过这个函数来加载原生库, 由于我新增加的主接口和 Android 定义的接口重名了, 就会导致编译失败, 改了函数名称又会导致加载不了.
但如果拆分为两个so, 倒是能加载了, 不过又得处理hw_get_module
调用两次之后得到的结果不同的问题, 这样可能会产生一个中间库, 并不想去修改这个流程.
最终采用新的绑定式写法整合新老接口, 实现了同一个服务实现两套HAL
接口, 他们操作同一个串口设备.
通过日志打印数据发现对不上
这个蠢哭的行为, 有必要记录一下. 新增的服务刚跑起来的时候, 通过已有的日志打印发现似乎上层下传的数据和底层打印的数据不一样...于是各种加打点, 最终用MD5数据的方式得到结论: 是完全一样的!
Android
的日志系统处于性能考虑不是老老实实的打印所有日志, 除了会截断长度, 还会选择性忽略短时间内的大量打点. 我这迫于无奈, 没有找到新的方向, 逼我对原始数据做哈希运算, 这样打印的内容少且真实...结果发现不是这里的问题...