成长是螺旋式的,涉及到:深度(高度)和广度。
(是否掌握某方面的知识,掌握或不掌握的程度;是否愿意,愿意或不愿意的程度;两者之间的关系;黑与白,有过度中间有赤橙黄绿青蓝紫;)
目录:
1. makefile、Kconfig、.config的区别
2. 输入子系统(流程,TYPE-A/B协议)
3. 外设模块(升降马达,屏的知识, 自动化测试APK导入)
4. 系统启动流程
5. 性能分析(TP划线慢,)
6. 代码,函数解读(kthread_run()函数,TP代码)
7.
8.
-------------------------------------------------------------格式说明----------------------------------------------------------------
(1)大标题是.加两个空格; (2)大标题可以直接搜索,内容若和大标题一致,要加空格分隔做为区分;
-------------------------------------------------------------旅行开始----------------------------------------------------------------
1. makefile、Kconfig、.config的区别
http://www.cnblogs.com/taomaomao/archive/2012/01/05/2312816.html
Makefile:一个文本形式的文件,其中包含一些规则告诉make编译哪些文件以及怎样编译这些文件;
Kconfig:一个文本形式的文件,其中主要作用是在内核配置时候,作为配置选项;
.config:文件是在进行内核配置的时候,经过配置后生成的内核编译参考文件;
2. 输入子系统(流程,TYPE-A/B协议):
前言:安卓输入子系统流程:(里面有详细的分析)http://blog.csdn.net/wangkaiblog/article/details/12085183
在Android系统中一说到重要的服务,基本都是要从systemserver进程开始说起,因为他是Android世界的开拓者,创建了Android世界所需要个基础。同样,Input系统也是从systemserver中开始说起,首先创建一个InputManagerService对象,为这个对象设置与WindowManagerService相关的回调函数,然后调用InputManagerService的start函数。
在start函数中通过JNI调用,启动Native层的InputReaderThread,InputDispatcherThread线程,从而开始Input系统的运行。InputReaderThread主要是执行和InputReader相关的内容,主要是从EventHub中读取事件,预处理事件,然会是根据policy来处理此事件,最后发送一个消息到InputDispatcher中通知事件的产生。紧接着InputDispatcher会开始事件的分发,通过InputChannel把事件分发给WindowManager或者应用程序。所以,整个事件分发的大致流程是:
systemserver ---> InputManagerService ---> NativeInputManager ---> Eventhub ---> InputReader ---> InputDispatcher ---> InputPublisher ---> InputChannel ---> InputConsumer ---> WindowManager or Application.
由这个大致的流程开始,我们逐步来解析Android系统Input的内容。从Input的启动开始,也就是InputManagerService的创建和线程的启动开始。
1).Input系统的启动
InputManagerService构成分析
https://blog.csdn.net/warticles/article/details/80975585
InputManagerService的构造是很简单的,只是在最后通过JNI方法初始化了native层的Input系统,在native层初始化的时候,创建了一个名叫NativeInputManager的对象,这个对象是很重要的,因为它主要负责和系统的其他模块交互,而且InputReader和InputDispatcher都是只运行在Native层中,如果需要调用Java函数也是通过这个对象进行的,另外他实现了InputReaderPolicyInterface和InputDispatcherPolicyInterface,是一个重要的Policy。
NativeInputManager在构造过程中,完成了InputManager在native基本运行组件的创建,比如创建了EventHub对象,它是事件的Android系统的起源地,所有的事件都是它从驱动中读取出来的;还创建了InputReaderThread线程用来执行InputReader的功能;InputDispatcherThread用来执行InputDispatcher的功能;同时也创建了InputManager来管理EventHub,InputReader,InputReaderThread,InputDispatcher,InputDispatcherThread这些Native运行的基本对象。
不过要注意一点的是NativeInputManager是InputReaderPolicyInterface和InputDispatcherPolicyInterface的子类,因此在构造InputReader和InputDispatcher的时候要用到NativieInputManager对象。
在对象构建完成后,开始执行start方法,让之前创建的这些对象运行起来。start方法也是比较简单的,就是通过JNI调用让native层的Input系统运行起来,然后在Java层把自己列入WatchDog的监视范围内。之后定义下自己需要接受的外部通知等。这个过程看代码的话,比较容易,不再列出。那么到这里位置,整个Input系统就运行起来了,至于其中具体的功能我们再逐步分析。这部分内容叙述完毕。
2).InputReader的功能,以及执行的流程
在InputManager的start方法被调用会,会执行两个线程,分别是InputReaderThread和InputDispatcherThread,虽然它们的启动在代码上有先后之分,但是在实际执行过程中是没有先后的,所以先从哪个线程开始解析Input系统不是很重要的。不过,我是按照从事件的产生到分发开始解析的,所以这里我是选择从InputReader开始。
InputReader是Android系统中重要的部分,根据Android文档中的描述,主要功能就是:
(1) 从EventHub读取事件,这些事件是元事件,即没有经过加工或者仅仅是简单加工的处理的事件;
(2)把这些事件加工处理,生成inputEvent事件,这样封装之后的事件,可以满足Android系统的一些需求;
(3)把这些事件发送到事件监听器,即QueuedInputListener,这个监听器可以把事件传递给InputDispatcher。
下面我们就从线程开始执行的地方一步一步分析这些功能的实现。既然要看InputReader的功能,我就从InputReader的构造函数说起。
在InputReader创建的时候,这里把InputDispatcher作为参数传递进来,然后以InputDispatcher作为参数构造出了QueuedInputListener对象。所以现在有这么一个关系:InputReader持有一个QueuedInputListener,而QueuedInputListener持有InputDispatcher对象。
在这里补充一点内容: Android系统在Native层中实现了一个类似于Java中的线程对象,即C++中的Thread类。这个线程类有个特点就是,当线程开始执行后,会一直重复执行threadLoop方法,知道这个线程的强引用计数变为零为止。所以,这里的threadLoop函数会不停地执行下去,也即是mReader->loopOnce()会循环执行下去,每循环一次就能从EventHub中读取出若干事件。下面我们就以一次循环过程为例。
1.1 从EventHub获取事件(上面第一个功能说明)
EventHub对象,它是事件的Android系统的起源地,所有的事件都是它从驱动中读取出来的;从EventHub获得的事件有两种,一种是设备添加,移除类的;另一种是由输入设备产生的事件。
eventhub源码分析:
https://blog.csdn.net/warticles/article/details/80990809
EventHub这个类的主要功能就是主动监视Input驱动的变化,一旦有事件产生,就从产生事件相应的驱动中读取出这个事件。实现这个监视驱动功能,是通过Linux提供的epoll机制来实现。epoll机制简单地说就是高效地I/O多路复用机制,使用epoll_wait来监听所需要的文件描述符的变化。EventHub的主要功能是通过epoll_wait来实现的,所以EventHub所在的线程应该会阻塞在epoll_wait方法中,一直等到epoll_wait设置的超时时间。
现在我们开始看看EventHub的实现,在EventHub的构造函数中,建立了一个管道,并把这个管道的读端和写端的文件描述符添加到epoll的监视之下,以便于其他的线程或者进程能够使EventHub所在的线程从epoll_wait的阻塞中返回。
EventHub在创建完成之后,第一个被调用的方法就是getEvents,而且这个方法也是EventHub的主要功能,对于这个方法需要仔细分析,我们把getEvents方法也分成了三个部分去解析,分别是:
(1). 打开设备部分;
(2). 事件等待部分;
(3). 事件读取部分;
这三个部分中,以事件的读取部分为重点。设备打开部分一般发生在Input系统建立的时候调用,所以在系 统 启 动完成,稳定之后,这部分内容应该不会再被执行的;而等待部分较为简单。
打开设备部分: EventHub是通过扫描/dev/input/目录下所有可用的设备,然后逐一打开这些设备,打开这些设备过程中,EventHub又做了一些Input系统必要的工作,比如构造Device对象,把这些设备加入到epoll的监视队列中等,时间戳的设定等。在构造Device对象的时候,是通过InputDeviceIdentifier来构造的,主要思路就是通过ioctl函数从内容中读取出一些必要的信息,然后把这些信息经过InputDeviceIdentifier存入Device中,然后再通过ioctl函数测试设备的属性,把这些属性信息也存入Device中。
这部分代码,把InputDeviceIdentifier转化为了Device,因为Device能够存储更多的信息,是EventHub所需要的。在打开设备的时候对这些Device完成了初始化。然后就是把这些设备加入epoll的监视中,代码如下:
1 epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)
如此之后,只要设备有输入事件的产生,通过epoll就能从阻塞中返回。
之后就是设置设备的硬件时钟。在报告事件的时候,我们要使用的时钟是monotonic clock, 这时钟的特点就是在每次开机的时候初始化为0。事件发生时的时间戳在input系统中使用非常广泛,而且Input系统会假设事件的时间戳是monotonic的时间点。最后把这些设备添加到EventHub的一个Vector中,类似如下格式:
deviceId
Device*
1
Device*
2
Device*
...
...
这个数组将会在EventHub中广泛地使用,经常使用的方式是通过deviceId获取Device设备。到这里,打开设备的工作已经完成,而且为EventHub的工作创建了一些有用的变量和数组等。EventHub中的第一个功能,打开设备已经完成。
事件等待部分: 事件的等待部分很简单,主要的代码就一行,如下:
epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
注意代码中的最后一个参数timeoutMillis,前面已经说到过,一般来说这个参数是-1,意味着线程会在这个地方阻塞,无限等待下去,直到有事件的发生,而在新的设备加入的时候,这个值为0,意味着可以立即返回。所以,在系 统 启 动完成后,如果没有事件发生的话,InputReaderThread线程会阻塞在这里,一直等待事件的发生。
事件读取部分: 基本过程就是,监视到有事件的产生,把事件读取出来,不过这里读出的事件是input_event类型的,然后在逐个把input_event事件转化为InputReader需要的RawEvent类型的事件,放入InputReader提供给EventHub的数组中(通过getEvents参数传递进来的)。说起来很简单,其实也很简单。
总结一下: EventHub负责打开/dev/input/目录下的所有设备,然后为每一个设备创建一个Device,并把这个Device放入EventHub所定义的数组们Device中。之后,就是把这个设备纳入监视范围。然后就是开始等待事件的发生,一旦有事件发生,就从产生事件的设备中读取出这些设备,把这些事件转化为RawEvent类型放入InputReader提供的事件数组中,之后返回。到这里,从EventHub获取事件就结束了。
1.2 InputReader对元事件的处理
由上节的内容,我们知道,从EventHub获得的事件有两种,一种是设备添加,移除类的;另一种是由输入设备产生的事件。InputReader在处理这两类事件稍微有点不一样。先看设备添加类型的事件,这些添加设备事件的处理,为InputReader的工作打下了基础,因为InputReader可以根据添加的设备定义一些数据结构,为以后处理由此设备产生的事件打下基础。
我们对于InputReader的功能的分析就完成了。总结一下,基本过程说就是:
InputReader从EventHub中读取出来元事件,预处理加工这些元事件成为NotifyArgs,然后通过QueuedInputListener把他们通知给InputDispatcher。
2). InputDispatcher的功能: 唯一功能分发事件,及执行流程
把输入事件发送到他的目标中去。他的目标可能是应用程序,也可能是WindowManagerService。
3). Input子系统中的通信方式是什么?
4). 应用程序是如何接收到并处理事件的
举例:触摸事件工作原理
https://blog.csdn.net/warticles/article/details/81035943?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
input事件处理:
https://blog.csdn.net/coldsnow33/article/details/12841077
input事件处理流程 input driver -> input core ->event handler -> userspace 给应用程序
事件的传递过程:首先在驱动层调用inport_report_abs,然后调用input core层的input_event,input_event调用了input_handle_event对事件进行分派,调用input_pass_event,在这里他会把事件传递给具体的handler层,然后在相应handler的event处理函数中,封装一个event,然后把它投入evdev的那个client_list上的client的事件buffer中,等待用户空间来读取。
代码追踪:
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x);
input_event(dev, EV_ABS, code, value);
input_handle_event(dev, type, code, value);
(1)input_handle_abs_event(dev, code, &value);
input_is_mt_value(code)
(2)input_pass_values(dev, dev->vals, dev->num_vals)
input_to_handler(handle, vals, count);
handler->events(handle, vals, count);
.events = evdev_events,
evdev_pass_values(client, vals, count, ev_time);
__pass_event
//发送信号SIGIO信号给fasync_struct 结构体所描述的PID,触发应用程序的SIGIO信号处理函数
kill_fasync(&client->fasync, SIGIO, POLL_IN);
.fasync = evdev_fasync,
.read = evdev_read,
input_event_to_user(buffer + read, &event)
copy_to_user(buffer,&compat_event,sizeof(struct input_event_compat)
.read = evdev_read,
cdev_init(&evdev->cdev, &evdev_fops); //最终在/dev/目录里面生成了字符设备
framework
eventhub.cpp
输入子系统框架总结:
首先的话是我们核心层的,执行的时候会注册我们的设备号,然后在handler层注册input_handler,也就是evdev_handler会注册到核心层维护的链表中,这些都是内核帮你完成的,然后再device层我们需要做硬件初始化获取数据,而且需要将我们的设备注册到链表中,注册进来就就会遍历input_handler_list链表,找到对应的handler,匹配成功后会调用connect方法,connect就会帮我们分配出evdev,evdev就记录了input_handler和input_device之间的关系,
还会帮我们创建设备节点,还会注册cdev从而可以让应用调用,然后当我们应用程序调用open,read等接口的时候就会调用input_handler层实现的xxx_open,那么这个open就会帮你分配好evdev_client,最终在input_dev层上报数据的时候会自动调用input_handler,这个里面就会调用events填充上报的数据到缓冲区client,此时如果没有唤醒队列的话应用read的时候会阻塞,而唤醒队列后最终使用copy_to_user来给应用数据。
原文链接:https://blog.csdn.net/u010802169/article/details/80489602
5).输入子系统A/B协议的数据格式
https://www.cnblogs.com/ljf181275034/articles/3343222.html
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
SYN_MT_REPORT
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
…
SYN_REPORT
简略为:
SYN_MT_REPORT
SYN_REPORT
只有SYNC,没有其它任何信息,系统就会认为此次事件为UP。
B协议使用了slot,还有一个新面孔TRACKING_ID.
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID **
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID **
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT
没有SYN_MT_REPORT,那么它用什么来跟踪当前点属于哪一条线呢,用的就是ABS_MT_TRACKING_ID,当前序列中某点的ID值,如果与前一次序列中某点的ID值相等,那么他们就属于同一条线。既然如此,那么android系统中还需要做排序等运算吗?当然不需要。那么手指全部抬起的时候序列又是怎样的呢?
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID -1
SYN_REPORT
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID -1 (是fffff)
SYN_REPORT
6). 追代码记录,兼容二供TP,二供只能最多显示4点
看手机getevent -p
查看发现第二个手指的数据正常上报,但是,屏幕上没有显示,有可能是显示之前被释放了导致,所以就去排查触摸释放的,
input_mt_slot(ts->input_dev, i);
input_event(dev, EV_ABS, ABS_MT_SLOT, slot);
input_handle_event(dev, type, code, value);
input_get_disposition(dev, type, code, &value); 获得事件处理者身份
input_handle_abs_event(dev, code, &value);
经过追踪发现,这个问题产生的原因是因为touch_num 一供和二供是不同的一共是5,二供是10,两个用的都是B协议,在追踪代码的时候发现一供在申请资源的时候没哟释放,这样就会产生二供也用一共申请的方式,当时for循环到5次之后就会释放第5个手指,这样就导致了二供只能显示4个手指问题。
修改方法有两个:一个是修改touch_num都改为5,第二个是修改一共的代码, 添加遍历失败后释放申请的输入设备资源input_mt_destroy_slots(tpd->dev)。由于一共已经量产,所以我们最终选择了第一种方法,这样可以做到影响最小。
7).
2. 外设模块(升降马达,屏的知识, 自动化测试APK导入)
1).升降马达开发:
kernel-4.9/arch/arm64/boot/dts/mediatek/mt6765.dts 平台端
kernel-4.9/arch/arm64/boot/dts/mediatek/xx_hxx.dts 项目端
kernel-4.9/arch/arm64/configs/xxx_hxx_debug_defconfig
-----
2). 自动化测试APK导入(涉及的文件)
projects对应的是下面的这个路 径下的device.mk
下面的这个是查看有没有copy动作的。
如图所示,有cp动作。
总共有五个文件要修改:
新添加一个ini文件
。
和proc下的节点名字一致;所以要查看节点名字才可以设置。
自动化测试提供给应用的reallytek的路 径,把Conf_MultipleTest.ini文件放到 /device/reallytek/rlk6580_we_m/ 路 径下;这时会有脚本把ini文件拷到 /system/etc/ctp/ft5346/ 目录下;这样,在手机对应目录里就会看到ini文件。
3). 闪屏 http://blog.csdn.net/cailiwei712/article/details/8485513
调试屏的一些理论知识:http://blog.csdn.net/u012719256/article/details/54633365
实现屏的分辨力切换,通过创建节点,输入指令去控制。
cd /sys/kernel/debug/
echo _efuse_test > mtkfb _resolution_test
4).
4. 系统启动流程
1).系统启动流程,包括从preloader->lk,lk->kernel,有源码分析的,感兴趣可以了解下,有助于梳理思路
http://blog.csdn.net/forever_2015/article/details/53000643#comments
http://blog.csdn.net/forever_2015/article/details/53047993
2).安卓bootloader的流程
如下网页分析的是基于MTK平台的bootloader启动流程,很棒的分析
https://blog.csdn.net/forever_2015/category_6498649.html
例如:2017.01的uboot分析:
上一节已经分析到了uboot的board_init_r函数,并且把两个参数传递给它
https://blog.csdn.net/qq_16777851/article/list/5
从第一课到第十课很详细
https://blog.csdn.net/kai_zone/article/details/80443820
其中的启动start.s文件解析为:https://www.cnblogs.com/shengruxiahua/p/4897527.html
bootloader作用:系统上电后,需要一段程序来进行初始化:关闭看门狗,改变系统时钟,初始化存储控制器,将更多的代码复制到内存中等。
CPU上电后,会从某个地址开始执行,比如MIPS结构的CPU会从0xBFC00000取第一条指令,而ARM结构的CPU则会从0x00000000开始,嵌入式开发板中,需要把存储器件的ROM或Flash等映射到这个地址,Bootloader就存放在这个地址的开始处,一上电就开始执行。(手机中的RAM和ROM分别对应电脑的内存和硬盘)
(1)u-boot系统启动流程 大多数bootloader都分为stage1和stage2两部分,u-boot也不例外。
依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,
1.Stage1 start.S代码结构 u-boot的stage1代码通常放在start.S文件中,他用汇编语言写成,其主要代码部分如下
(1) 定义入口。: 该工作通过修改连接器脚本来完成。
(2)设置异常向量(Exception Vector)。
(3)设置CPU的速度、时钟频率及终端控制寄存器。
(4)初始化内存控制器。
(5)将ROM中的程序复制到RAM中。
(6) 关中断,关看门狗
(7)初始化堆栈,清bss段,为第二阶段准备。
(8)转到RAM中执行,该工作可使用指令ldr pc来完成。
2、Stage2
C语言代码部分 lib_arm/board.c中的_start_armboot是C语言开始的函数也是整个启动代码中C语言的主函数,同时还是
整个u-boot(armboot)的主函数,该函数只要完成如下操作:
(1)调用一系列的初始化函数。
(2)初始化存储设备
(3)初始化简单硬件如串口,lcd等
(4)初始化相关网络设备,填写IP、MAC地址等。
(5)进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。
流程图如下:
Bootloader是嵌入式系统在加电后执行的第一段代码,是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,
从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。
/*********************** 中断向量 ***********************/
.globl _start //u-boot启动入口
_start: b reset //复位向量并且跳转到reset
(pc寄存器 指向的是被取值的指令,而不是指向正在执行的指令,即取指令和更新pc值是同时进行的。
发生中断时是先将指令六(即下一条指令的地址)
放入中断模式下的r14(lr)寄存器。而pc被强制赋值为0x18,然后跳转到异常向量表中取出指令,一般异常
向量表中0x18位置处的代码为
ldr pc,_irq。_irq为中断处理函数的入口地址)。
ldr pc, _undefined_instruction //未定义的指令异常
ldr pc, _software_interrupt //软件中断异常
ldr pc, _prefetch_abort //内存操作异常
ldr pc, _data_abort //数据异常
ldr pc, _not_used //未使用
ldr pc, _irq //慢速中断异常
ldr pc, _fiq //快速中断异常
b sleep_setting //跳转到sleep_setting
(https://blog.csdn.net/IT_114/article/details/6260707?depth_1-
utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到相应的处理程序,然后再返回到主程序继续执行。)
/*系统上电或reset后,cpu的PC一般都指向0x0地址,在0x0地址上的指令是*/
reset: //复位启动子程序
/******** 设置CPU为SVC32模式(超级保护模式)***********/
mrs r0,cpsr //将CPSR状态寄存器读取,保存到R0中
bic r0,r0,#0x1f //r0寄存器最低5位清零
orr r0,r0,#0xd3
msr cpsr,r0 //将R0写入状态寄存器中
(MRS: 状态寄存器到通用寄存器的传送指令。
MSR: 通用寄存器到状态寄存器的传送指令。Msr (cond) (rd目标寄存器)
BIC―――――位清除指令
指令格式:
BIC{cond}{S} Rd,Rn,operand2
BIC指令将Rn 的值与操作数operand2 的反码按位逻辑”与”,结果存放到目的寄存器Rd 中。
指令示例:BIC R0,R0,#0x0F ;
将R0最低4位清零,其余位不变。
ORR指令的格式为:
ORR{条件}{S} 目的寄存器,操作数1,操作数2
ORR指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。操作数1应该是一
个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置
操作数1的某些位。
指令示例:
ORR R0,R0,#3 ; 该指令设置R0的0、1位,其余位保持不变。)
/************** 关闭看门狗 ******************/
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
(一般传送指令 MOV指令: 格式:
MOV目的-->除CS、IP以外的寄存器或存储器源-->寄存器、存储器、立即数
STR{条件} 源寄存器,<存储器地址>
STR指令用亍从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用。
指令示例:
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8
写入R1。)
/************** 关闭所有中断 *****************/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
ldr r2, =0x7ff
ldr r0, =INTSUBMSK
str r2, [r0]
/***************** 关键的初始化子程序 ************************/
/ * cpu初始化关键寄存器
* 设置重要寄存器
* 设置内存时钟* /
cpu_init_crit:
/** flush v4 I/D caches*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
(MCR指令将ARM处理器的寄存器中的数据传送到协处理器的寄存器中。如果协处理器不能成功地执行该
操作,将产生未定义的指令异常中断。指令的语法格式:(CP15协处理器)
MCR{<cond>} p15, 0, <Rd>, <CRn>, <CRm>{,<opcode_2>}
<Rd>作为元寄存器的ARM寄存器,其值被传送到协处理器寄存器中。
<CRn>作为目标寄存器的协处理器寄存器,其编号可能为C0,C1....C15。
<CRm>附加的目标寄存器或者原操作数寄存器,用于区分同一个编号的不同物理寄存器。当指令中不需要
提供附加信息时,将C0指定为<CRm>,否则指令操作结果不可预知。
<opcode_2>提供附加信息,用于区别同一个编号的不同物理寄存器。当指令中指定附加信息时,省略<opcode_2>或者将其指定为0,
否则指令操作结果不可预知。
MRC指令将协处理器的寄存器中数值传送到ARM处理器的寄存器中。如果协处理器不能成功地执行该操作,
将产生未定义的指令异常中断。)
/************* disable MMU stuff and caches ****************/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
(#表示立即数寻址。采用立即寻址方式的指令,在立即数前面加上立即寻址符“#”。
例如指令MOV A,#30H中30H就是立即数,指令功能为将30H赋给累加器A。
@表示寄存器的间接寻址。)
/******* 在重新定位前,我们要设置RAM的时间,因为内存时钟依赖开发板硬件的,你将会找到board
目录底下的memsetup.S。**************/
mov ip, lr
#ifndef CONFIG_S3C2440A_JTAG_BOOT
bl memsetup //调用memsetup子程序(在board/smdk2442memsetup.S)
#endif
mov lr, ip
mov pc, lr //子程序返回
memsetup:
(b用于不返回的跳转,比如跳到某个标号处,b . 其中的‘.’代表当前地址,那么 b . 就是死循环。
bl用于子程序跳转,要返回地址,返回地址存于LR中。当发生bl跳转前,会在寄存器 R14 (即LR)中保存
当前PC-4,即bl跳转指令的下一条指令的地址。所以在返回时只要 MOV pc,lr 。
lr就是连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两种:
一.是用来保存子程序返回地址;
二.是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以
根据LR的值返回到异常发生前的相应位置继续执行。)
/**************** 初始化内存 **************/
mov r1, #MEM_CTL_BASE @ 存储控制器的13个寄存器的开始地址
adrl r2, mem_cfg_val @ 这13个值的起始存储地址
add r3, r1, #52 @ 13*4 = 54
1:
ldr r4, [r2], #4 @ 读取设置值,并让r2加4
str r4, [r1], #4 @ 将此值写入寄存器,并让r1加4
cmp r1, r3 @ 判断是否设置完所有13个寄存器
bne 1b @ 若没有写成,继续bne 1b这条语句里的b是backward的意思,既然有
backward就有forward,所有就有bne 1f语句
mov pc, lr @ 返回
(加法指令 ADD(Addition) 格式: ADD A,B //A=A+B;功能: 两数相加)。
/*********** 跳转到原来进来的下一个指令(start.S文件里) ***************/
mov pc, lr //子程序返回
/****************** 建立堆栈 *******************/
ldr r0, _armboot_end //armboot_end重定位
add r0, r0, #CONFIG_STACKSIZE //向下配置堆栈空间
sub sp, r0, #12 //为abort-stack预留个3字即12
(汇编语言中SP寄存器是指的是堆栈指针寄存器,在堆栈操作中使用,PUSH和POP指令是从SP寄存器得到现行堆
栈段的段内偏移量,所以称SP寄存器为堆栈指针,SP始终指向栈顶。
Sub减法运算);
/**************** 跳转到C代码去 **************/
ldr pc, _start_armboot //跳转到start_armboot函数入口,start_armboot字保存函数入口指针
_start_armboot: .word start_armboot //start_armboot函数在lib_arm/board.c中实现从此进入第二阶段C语言代码部分
/**************** 异常处理程序 *******************/
.align 5 //ARM的.align 5就是2的5次方对齐,也就是32字节对齐, 它的含义就是使得下面的代码按一定规则对齐;
//如果不加这个,这些地址就按4个字节增加,因为一条ARM指令是4个字节。
undefined_instruction: //未定义指令
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt: //软件中断
get_bad_stack
bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort: //预取异常中止
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort: //数据异常中止
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used: //未利用
get_bad_stack
bad_save_user_regs
bl do_not_used
.align 5
irq: //中断请求
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq: //快速中断请求
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
sleep_setting: //休眠设置
@ prepare the SDRAM self-refresh mode
ldr r0, =0x48000024 @ REFRESH Register
ldr r1, [r0]
orr r1, r1,#(1bd = &bd_data;
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _armboot_end_data - _armboot_start;
/*** 调用执行init_sequence数组按顺序执行初始化 ***/
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr){
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
#if 0
/**************** 配置可用的flash单元 *************/
size = flash_init (); //初始化flash
display_flash_config (size); //显示flash的大小
/******** _arm_boot在armboot.lds链接脚本中定义 ********/
#endif
#ifdef CONFIG_VFD
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# endif
/*********** 为VFD(VFD被用来配置网卡上的虚拟端口)显示预留内存(整个页面) **********/
/******** armboot_real_end在board-specific链接脚本中定义********/
addr = (_armboot_real_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = vfd_setmem (addr);
gd->fb_base = addr;
/******* 进入下一个界面 ********/
addr += size;
addr = (addr + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
mem_malloc_init (addr);
#else
/******** armboot_real_end 在board-specific链接脚本中定义 *******/
mem_malloc_init (_armboot_real_end);
#endif /* CONFIG_VFD */
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND:");
nand_init(); /* NAND初始化 */
#endif
#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif
/********* 初始化环境 **********/
env_relocate ();
/*********** 配置环境变量,重新定位 **********/
#ifdef CONFIG_VFD
/* must do this after the framebuffer is allocated */
drv_vfd_init();
#endif
/* 从环境中得到IP地址 */
bd_data.bi_ip_addr = getenv_IPaddr ("ipaddr");
/*以太网接口MAC地址*/
{
int i;
ulong reg;
char *s, *e;
uchar tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg bd->bi_enetaddr);
#endif
#ifdef CONFIG_DRIVER_LAN91C96
if (getenv ("ethaddr")) {
smc_set_mac_addr(gd->bd->bi_enetaddr);
}
/* eth_hw_init(); */
#endif /* CONFIG_DRIVER_LAN91C96 */
/* 通过环境变量初始化*/
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if (CONFIG_COMMANDS & CFG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif /* CFG_CMD_NET */
#ifdef BOARD_POST_INIT
board_post_init ();
#endif
/* main_loop() 总是试图自动启动,循环不断执行*/
for (;;) { //死循环
main_loop (); /*主循环函数处理执行用户命令—common/main.c */
}
/* NOTREACHED - no way out of command loop except booting */
}
高通平台BootLoader的流程
https://blog.csdn.net/makeyourprogress/article/details/73920431
5. 性能分析(TP划线慢,)
1).这2篇文章写得不错 大家有空学习下 后续TP划线慢这种问题我们来主导分析,buffer是否满;
http://gityuan.com/2016/12/31/input-ipc/
http://gityuan.com/2017/01/01/input-anr/
2).
6. 代码,函数解读 (kthread_run()函数,TP代码)
1). kthread_run()函数详细说明
首先看看它的定义之处才发现它是一个宏函数,而不是一个真正意义上的函数。这个函数会创建一个名为namefmt的内核线程,这个线程刚创建时不会马上执行,要等到它将kthread_create() 返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程。touch_event_handler这个函数就是创建的运行函数。
http://blog.chinaunix.net/uid-28776666-id-3797013.html
2). TP代码
3).
————————————————