本章先接上章描述一下LCD/HDMI OUT在开机过程中显示的流程,并介绍一些UEFI阶段的权限配置与屏幕参数配置,最后是一些需要掌握的驱动中常添加功能。
注意:本文从内核开发角度介绍,接触到上层后本文一些概念不适用
一、BP侧文件的配置及程序流程
我们调试的机器开机大致可分为两个阶段,UEFI阶段以及kernel阶段,UEFI阶段类似于一段程序的初始化,而kernel阶段就是程序在执行功能函数。
上图为UEFI的运行流程,包括初始化和引导操作系统,最后进入kernel系统。
对应到项目文件,大体可以把BP的文件中的一些操作对应UEFI阶段,而AP对应kernel;对于内核调试的同事来说,BP侧管理一些权限与配置类的文件,当然因为它开机就会执行,所以一些kernel阶段的功能也可以提前实现,比如本来在kernel阶段才能让屏幕显示画面,可以把原本在AP的代码移植到BP,让它开机就显示画面。
(1)BP侧的权限问题
在本节主要说明模块一些引脚的权限。你可以把BP当作一位管理者,而AP是一位执行者,管理者规定一些规则,执行者在规则内执行任务,在引脚权限上就可以充分体现。
我们所用的模块比如高通sdm845总共就一百多个引脚,需要控制不同外设和实现不同功能,这就需要合理利用与释放引脚。在bp侧sdm845_Android10_BP\trustzone_images\core\settings\buses\qup_accesscontrol\hoya\config\QUPAC_845_Access.c这个文件中,管理了一些引脚的权限,如果不打开一些引脚的权限,在AP侧使用时就会导致重启或者进dump。
在文件的qupv3_perms_default的常量中
定义了一些se组与其对应的权限,se组号可以问硬件同事确定或者根据AP中pinctrl的定义来确定。se不同组代表了不同的桥接芯片,北桥是0-7,也就是qupv3_0_se0到qupv3_0_se7,东桥是0-5,也就是qupv3_1_se0到qupv3_1_se5,南桥是0-5,也就是qupv3_2_se0到qupv3_2_se5。比如上一章节的9611设备树为se14,并且该组脚是用作i2c通信,就需要在常量另起一行(如果已配,不需要自己写)定义QUPV3_2_se0(注意AP BP两侧都是从0开始数,se14代表第15组se,那可以从北桥开始数第十五个),后面配上QUPV3_PROTOCLO_I2C。 后面几个参数代表这几组的权限,如果需要用作普通的引脚随时可以控制的话就配成
QUPV3_MODE_FIFO,AC_HLOS, TRUE,TRUE,FALSE。
上图为确定SE组的另一种方法,注意不同文件的第一组的序号是1还是0。文件位置为sdm845_Android10_BP\boot_images\QcomPkg\SDM845Pkg\Settings\I2C\core\i2c_devcfg.c,平台不同,位置不同,有时候是core文件夹有时候是loader,如果需要对其做修改,保险一些可以两个都改。
(2)BP侧配置MIPI参数
MIPI参数就是一种可以让屏幕显示出来的参数,有些是直接接收mipi参数显示如手机屏幕,有些需要将mipi信号转换为hdmi lvds信号。mipi参数只需要将供应商提供的参数进行转换就行了,再配一些识别屏幕的文件,开机时模块就会发出mipi信号。
首先第一步是找到配置mipi参数的地方,按照目前平台规范大致分两处:
sdm845_Android10_BP\boot_images\QcomPkg\SDM845Pkg\Library\MDPPlatformLib\MDPPlatformLib.c 和 QCM6490_LA2.0_BP\boot_images\boot\QcomPkg\Settings\Panel
模块型号不同位置不同,需要自己找,因为两者没什么差别,我们先找MDDPlatformLib中的参数,打开.c文件,找到
这样一长串的常量,下面还有很多就不截图了,每一组常量代表一块屏幕的mipi参数,开机时机器会在这边解析mipi参数发出mipi信号来显示,至于怎么配这些参数以及需要加哪些逻辑代码,之后会用一个实例来讲解;最后是Settings/Panel中的xml文件,一种文件代表一种屏幕只是把上图中的双引号和换行符号去掉而已,之后该类配置也会有示例来讲。在本章中只需要了解机器开机在这些地方拿mipi信号就可以。
另外,如果未来你主要调试显示类的驱动,会接触比较多MDPPlatformLib.c这个文件,可以先看一下文件结构及各个功能函数大致是什么意思。
(3)BP的驱动移植
移植就是将AP侧的驱动复制粘贴到BP侧,让机器开机便去初始化芯片让它跑起来。但是也不是简单的复制粘贴,需要利用BP的各种函数接口改写驱动,之后也会有一示例来讲解,在这里只需要知道驱动写在AP侧和移植到BP侧有什么不同就行。
二、驱动常用的功能添加
(1)创建节点
可以在任意一个驱动创建节点来查看驱动的状态,比如读写引脚的状态,读写显示驱动中的分辨率信息等等,下面是一个读写节点示例:
1.宏定义
补充mode定义:
400 拥有者能够读,其他任何人不能进行任何操作;
644 拥有者都能够读,但只有拥有者可以编辑;
660 拥有者和组用户都可读和写,其他人不能进行任何操作;
664 所有人都可读,但只有拥有者和组用户可编辑;
700 拥有者能够读、写和执行,其他用户不能任何操作;
744 所有人都能读,但只有拥有者才能编辑和执行;
755 所有人都能读和执行,但只有拥有者才能编辑;
777 所有人都能读、写和执行(该设置通常不是好想法)
2.定义一个设备属性名为my_device_test
3.定义写和存储函数
4.宏展开
5.在init与exit中初始化与注销
init中:
exit中:
6.在终端进行cat与echo,结果如下:
(2)线程创建
进程线程的理论知识与系统线程程序的解析:
进程:处于执行期的程序及相关资源的总称,实际上,进程就是正在执行的程序代码的实时结果
线程:是在进程中活动的对象,每个线程都拥有一个独立的程序计数器、进程栈和一组进程计数器。内核调度的基本单位为线程而不是进程。
内核线程实现流程:
定义一个线程指针;编写线程函数;创建线程;开启线程;停止线程
内核通过kernel_thread函数创建一个线程
通过fork()函数创建一个子进程
具体实现:
1.包含线程创建头文件
2.定义线程指针
3.创建内核线程,调用函数启动(wake_up函数)
如上图的创建线程并执行函数:创建了一个线程指针__k,线程执行的函数为threadfn,当__k不出错时唤醒,就可以执行线程,停止线程可以利用stop函数进行停止。
创建线程并运行:
1.内核线程接口函数头文件
2.定义线程指针
3.创建内核线程,返回值为创建线程的指针
参数1为线程函数,参数2为传给线程的私有数据,参数3为线程名称
下图为线程的执行函数,实现了log的打印
4.启动线程
5.停止线程
在编译烧录后抓取到如下log信息
线程创建成功
(3)动态加载驱动
驱动如何动态加载:
静态加载就是把驱动程序直接编译到内核,系统启动后直接调用。这种办法调试起来麻烦,修改一次就需要重新编译所有程序加载,效率很低。
动态加载就是利用Linux的module特性,在系统启动后可以用命令insmod和rmmod进行驱动程序(.ko文件)加载和卸载。
生成ko文件,需要修改Makefile
需要注意的是,编译文件的顺序不能错以及书写的格式要正确,如不能有空行。
obj-m为编译时生成ko文件,下面需要添加路径名。在中断make kernel成功编译后生成以下两个ko文件
接下来进入终端连接设备。
首先获取权限,输入adb root。
再输入adb remount 如果不成功需要输入adb disable-verity,remount的作用是将system部分置入可写入模式
然后将生成的ko文件push进入设备。
以上步骤如下:
最后进行insmod。
发现有如上错误:原因是该文件没有公钥也没有提供的签名,所以无法insmod
在文件中找到这个ko文件进行insmod
终端中执行insmod成功
如果无法打印log信息,还需要加入setenforce 0关闭权限
再抓取信息后出现:
(4)建立工作队列利用中断读取设备信息
这个实践难度相对高一些,可以先熟悉中断的产生与作用。可以理解为在产生中断后去执行一些功能函数,比如在插拔HDMI线的时候产生中断,然后上报HDMI屏幕的分辨率信息等。之后会有这样一个例子来介绍。
三、总结
本章内容主要介绍了UEFI阶段机器如何获取mipi信号显示、开机对一些引脚的权限管理以及一些实践操作,关于UEFI只需要了解流程,而最后的一些功能操作希望可以去实践一下。下一章将介绍AP与BP的交互和GPIO的概念及操作。