-----------------------------------
网上摘录的一段话,觉得有点道理,出处就不记得了
1.首先要选一门赚钱的语言,然后精通之。这是大前提,方向选错了一切都白搭。语言基础语法,平台框架,算法要烂熟于心,这是一切的基础;
2.提升自我的商务技能。商务技能对于提高自己的薪资待遇有着很大的决定性因素。专注于一个行业,掌握行业知识会让你更加具有核心竞争力!只有技术和业务都懂的人,才能有希望成为IT精英。
3.提高自身的沟通表达能力。不擅长沟通表达,更不喜欢去交流,这个是IT程序员普通现象。俗话说物以稀为贵,沟通能力成为很多企业家招聘IT人才一项重要加分项!如果你善于表达自己,你会在众多“木纳”程序员中脱颖而出。
4.关注行业资讯。在生活中不要将自己封闭在一个狭小的工作空间,多了解行业咨询,关注IT新闻,工作中又离不开与同事领导交流,领导谈到圈子里面的人和事,你不至于什么都不懂,相反,你知道的越多,别人会认你你见多识广,经验丰富,同时也会提升自身的价值。
5.学会跳槽。IT行业大部分人是靠跳槽来大幅度翻倍涨薪。跳槽前要做扎实的功课,从多方面入手,使别人认识到你是一个人才,这样就能获得更多的高薪职位。
1)外在因素。骑黑马,如果骑到黑马,就可能不止几十上百万。因此,在有一定能力,就要找新兴民企,跟着企业一起发展。但多数死马会比黑马多----但这应该是最好的办法
2)创业:利用技术合伙创业或技术创业。这可以包括开一个小Studio,写APP起步 ----- 不能大富,满足月入十万应该可以。野心大些可以自己做黑马,也许您就是下一个马云呢 。
3)自身能力训练,但这并不是一个很好的办法。单纯技术能力训练的目的是什么?还是继续给别人打工,如果是一家好的民企就是1)的问题。但如果只是一个拿死工资的。所以,程序员技术是一方面,但致富不能唯技术,况且IT技术变化那么快。
-----------------
ubuntu14.04 安装分区设置:
1)双击空闲,选择主分区,大小40G,文件格式ext4,挂载在/
2)双击空闲,选择逻辑分区,大小4G,文件格式ext4,挂载在/boot
3)双击空闲,选择逻辑分区,大小32G,文件格式ext4,挂载在swap(这个大小是内存大小的2倍,我的Z600 workstation内存是16G,这里选择32G)
4)双击空闲,选择逻辑分区,大小为剩下所有空间,文件格式ext4,挂载在/home
----------------------------------
SDM429编译报错:
/home/wanghl/code/sdm439_android/kernel/msm-4.9/scripts/sign-file.c:25:30: fatal error: openssl/opensslv.h: 没有那个文件或目录
#include
^
compilation terminated.
HOSTCC scripts/genksyms/lex.lex.o
/home/wanghl/code/sdm439_android/kernel/msm-4.9/scripts/extract-cert.c:21:25: fatal error: openssl/bio.h: 没有那个文件或目录
#include
解决方法:
sudo apt-get install libssl-dev
---------------------------------
创建samba服务器:
ubuntu下配置samba服务器,让windows下可以访问共享目录
1.安装samba
sudo apt-get update
sudo apt-get install samba samba-common
2.创建共享文件夹
/home/wanghl(本来系统就存在,不用创建)
sudo chmod 777 /home/wanghl
3.配置samba
(1)备份一下配置文件
sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak
(2)用vim编辑配置文件
sudo vim /etc/samba/smb.conf
(3)添加配置信息
[UbuntuShare]---->注意这个名字就是到时候windows访问ubuntu的时候显示的文件夹名字,爱显示啥显示啥,不用与ubuntu的共享文件夹同名
comment = samba
path = /home/wanghl
available = yes
browseable = yes
public = yes
writable = yes
create mask = 0755
directory mask =0755
force user =nobody
force group = nogroup
注意:如果你指定了有效用户valid users = alan,那除了alan之外的其他用户将无法访问samba服务器。
4、重启samba服务器
sudo /etc/init.d/smbd restart
5、在其他电脑访问共享文件夹
(1)windows下最好重启下电脑
windows的文件夹中输入或WIN key+R输入:\\samba电脑的IP
比如输入:\\192.168.2.140即可,如果要映射网络驱动器应该加上共享目录的名字:\\192.168.2.140\UbuntuShare才可以
这样就可以共享啦。
6, 关闭防火墙或允许smbd端口:
sudo ufw disable
连接上后如果有必要就打开防火墙~
下面是查看Samba服务器的端口及防火墙;
查看这个有何用呢?有时你的防火墙可能会把smbd服务器的端口封掉,所以我们应该smbd服务器所占用的端口;下面查看中,我们知道smbd所占用的端口是139和445;
[root@localhost ~]#sudo netstat -tlnp |grep smb
tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN10639/smbd
tcp 0 0 0.0.0.0:445 0.0.0.0:* LISTEN10639/smbd
---------------------------------------------
有的Ubuntu 14.04没有自带右键打开终端功能,用起来非常不方便。方法如下:
$ sudo apt-get install nautilus-open-terminal
注销系统重新登录,即可。
------------------------------
Ubuntu 下Ctags的安装还是很简单的,输入命令sudo apt-get install ctags 即可
使用:
$ctags –R * ("-R"表示递归创建,也就是包括源代码根目录(当前目录)下的所有子目录。“*”表示所有的文件。这条命令会在当前目录下生成一个tags文件,当用户在当前目录中运行vim时,会自动载入此tags文件。)
$ vi –t tag (请把tag替换为您欲查找的变量或函数名)
Ctrl+ ] 跳到光标所在函数或者结构体的定义处
Ctrl+ T 返回查找或跳转
----------------------------
先列一下按照网上高人步骤,后记录上遇到的问题。
1. 添加openjdk8的第三方源
sudo add-apt-repository ppa:openjdk-r/ppa
安装过程遇到问题:
由于OpenJDK8对Ubuntu14.04不是明确支持的,故而第一步,执行后就是一个没有反应。
经过请教高人:手工修改atp的源list即可。
手工修改/etc/apt/sources.list,在最后添加如下几行
## xxx added begin : for cmd-line fail(udo add-apt-repository ppa:openjdk-r/ppa)
## this ppa is used for 10.04 only.
deb http://ppa.launchpad.net/openjdk-r/ppa/ubuntu trusty main
deb-src http://ppa.launchpad.net/openjdk-r/ppa/ubuntu trusty main
## xxx added end.
完了之后:
2. 执行更新
apt-get update
3. 安装openjdk8
sudo apt-get install openjdk-8-jdk
4. 选择版本
sudo update-alternatives –config Java
5. 确认安装成功
java -version
openjdk version "1.8.0_171"
OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-2~14.04-b11)
OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)
=========================
MSM8953 CBL_PWR_N接地后,只要接入外接电源(包括电池),机器就会直接上电开机!
----------------------------------------------
EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
--------------------------------------------------------------------------
toolbox中的getevent命令的源码目录:
/home/wanghl/code/msm8909w_android/system/core/toolbox/getevent.c
每一个扩展命令对应一个name_main函数,如ls命令,对应ls_main函数。同时,每一个扩展命令都由一个system/core/toolbox/目录下面的.c文件实现。toolbox.c会根据这个目录下面的.c文件生成tools.h头文件,并在system/core/toolbox/Android.mk文件中为每个命令生成指向toolbox的连接。toolbox的实现结构使它扩展一个命令很容易。
int getevent_main(int argc, char *argv[])
假设现在我们自己想手工添加一个shell命令mycommand,只要在system/core/toolbox/目录下面新建一个mycommand.c文件,并在里面实现一个mycommand_main函数,然后在system/core/toolbox/Android.mk中添加mycommand.c即可。Android.mk会自动把它编译进toolbox程序,并在编译生成的Android系统/system/bin目录下为这个命令生成一个指向toolbox的连接。
--------------------------------------------------
1|msm8909:/ # find sys/devices/ -name "*uart*" 2>/dev/null
sys/devices/soc.0/78b0000.serial/tty/ttyHSL1/uartclk
sys/devices/soc.0/78af000.serial/tty/ttyHSL0/uartclk
唤醒的时候:
msm8909:/ # cat sys/devices/soc.0/78b0000.serial/tty/ttyHSL1/uartclk
7372800
msm8909:/ # cat sys/devices/soc.0/78af000.serial/tty/ttyHSL0/uartclk
7372800
休眠的时候,用网络adb查看也是这个值,因为网络ADB的连接会导致系统不能正常进入休眠,实际上只是屏幕灭了,但是却没有休眠!
----------------------------------------------------------------------
MSM8909(H10)的内核版本号:3.10.49
kernel/Makefile
VERSION = 3
PATCHLEVEL = 10
SUBLEVEL = 49
1. 什么是Runtime PM(RPM)?
Runtime PM (Runtime Power Management)翻译过来就是运行时电源管理。主要的作用是: 每个设备处理好自己的电源管理,在不需要工作时进入低功耗状态。也就是"各人自扫门前雪"。
2. 为什么需要Runtime PM?
system suspend需要很长时间完成,其中还可能出现失败。比如freeze task的时候。而suspend设备速度相对system suspend快很多,而且还不需要freeze task。当设备不忙的时候就进入自己的低功耗模式,这样一来每个device(包括CPU) 都做好自己的事,整个系统就达到最大节省能源。
kernel/include/linux/pm.h
struct dev_pm_ops {
...
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
...
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
三个回调函数分别用于suspend device,resume device和idle device。通常Runtime PM framework会在合适的时机调用三个函数。
Runtime PM Framework使用rpm_status枚举类型表示device的状态
enum rpm_status {
RPM_ACTIVE = 0,//设备处于正常工作状态,处于全速全进状态。runtime_resume的回调执行完毕。
RPM_RESUMING,//设备状态正在从suspend到active转换。 runtime_resume的回调正在执行。
RPM_SUSPENDED,//设备处于低功耗状态,不能处于IO操作。runtime_suspend的回调执行完毕。
RPM_SUSPENDING,//设备状态正从active到suspend状态转换。runtime_suspend回调正在执行。
};
idle状态是suspend状态前的一个过渡而已,通常会在runtime_suspend调用之前调用一段时间runtime_idle回调。
高通串口驱动kernel/drivers/tty/serial/msm_serial_hs_lite.c并没有填充这个回调函数:
//电源管理操作结构体
static struct dev_pm_ops msm_hsl_dev_pm_ops = {
.suspend = msm_serial_hsl_suspend,
.resume = msm_serial_hsl_resume,
.runtime_suspend = msm_hsl_runtime_suspend,
.runtime_resume = msm_hsl_runtime_resume,
};
msm_hsl_dev_pm_ops这个结构存在于:platform_driver里的一个成员device_driver这个结构体
static struct platform_driver msm_hsl_platform_driver = {
.probe = msm_serial_hsl_probe,
.remove = msm_serial_hsl_remove,
.driver = {
.name = "msm_serial_hsl",
.owner = THIS_MODULE,
.pm = &msm_hsl_dev_pm_ops,//这里被赋值
.of_match_table = msm_hsl_match_table,
},
};
kernel/include/linux/platform_device.h
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;//driver是platform_driver的一个结构体成员
const struct platform_device_id *id_table;
};
kernel/include/linux/device.h
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;//Power management operations of the device which matched this driver.和这个driver驱动关联的电源管理操作
struct driver_private *p;
};
所以当msm_hsl_platform_driver这个platform_driver在msm_serial_hsl_init被注册成平台驱动的时候,就关联好了对应的电源管理操作的回调函数
static int __init msm_serial_hsl_init(void)
{
...
ret = platform_driver_register(&msm_hsl_platform_driver);//platform_driver_register
...
}
整个串口驱动进行模块初始化的地方:module_init(msm_serial_hsl_init);从而注册了平台驱动,但是实际上从休眠唤醒的log来看,
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
只有串口0,ttyHSL0在开机log中有看到调用了.runtime_resume = msm_hsl_runtime_resume,
[ 0.904547] 78af000.serial: ttyHSL0 at MMIO 0x78af000 (irq = 139) is a MSM
[ 0.904581] msm_serial_hsl 78af000.serial: [wanghl]pm_runtime: resuming
[ 0.904592] msm_hsl_runtime_resume: [wanghl] -msm_hsl_runtime_resume
[ 0.904643] msm_hsl_console_setup: console setup on port #0
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
这个需要再调查澄清!
记录下suspend的调用流程:
state_store->pm_suspend->enter_state->suspend_devices_and_enter->dpm_suspend_start->dpm_suspend->device_suspend->__device_suspend->pm_op->(ops->suspend)
kernel/drivers/base/power/main.c
static pm_callback_t pm_op(const struct dev_pm_ops *ops, pm_message_t state)
{
switch (state.event) {
#ifdef CONFIG_SUSPEND
case PM_EVENT_SUSPEND:
printk(KERN_INFO "[wanghl]pm_op\n");//按POWER按键休眠或者自动休眠的时候,确实打印了很多的这条log,所以就是走的这个流程.
return ops->suspend;//最终调用到dev_pm_ops的成员函数suspend
case PM_EVENT_RESUME:
return ops->resume;
#endif /* CONFIG_SUSPEND */
#ifdef CONFIG_HIBERNATE_CALLBACKS
case PM_EVENT_FREEZE:
case PM_EVENT_QUIESCE:
return ops->freeze;
case PM_EVENT_HIBERNATE:
return ops->poweroff;
case PM_EVENT_THAW:
case PM_EVENT_RECOVER:
return ops->thaw;
break;
case PM_EVENT_RESTORE:
return ops->restore;
#endif /* CONFIG_HIBERNATE_CALLBACKS */
}
return NULL;
}
int dpm_suspend_start(pm_message_t state)
{
int error;
error = dpm_prepare(state);
if (error) {
suspend_stats.failed_prepare++;
dpm_save_failed_step(SUSPEND_PREPARE);
} else
error = dpm_suspend(state);
return error;
}
EXPORT_SYMBOL_GPL(dpm_suspend_start);//到这里就可以导出dpm_suspend_start这个函数接口给其他模块使用了!具体是被suspend_devices_and_enter调用了
kernel/kernel/power/suspend.c
/**
* suspend_devices_and_enter - Suspend devices and enter system sleep state.
* @state: System sleep state to enter.
*/
int suspend_devices_and_enter(suspend_state_t state)
{
...
suspend_console();
ftrace_stop();
suspend_test_start();
error = dpm_suspend_start(PMSG_SUSPEND);//这里调用了suspend,这里会执行所有driver的suspend函数,suspend里面有active wakeup_source或者return 为真的话,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;
...
/*suspend_enter()会被调用, 使CPU进入省电状态. 非启动CPU会被关掉,这个函数arch_suspend_disable_irqs()会关闭arch irq, syscore_suspend这个函数是系统真正进入休眠最后调用的函数, 通常会在这个函数中作最后的检查. 如果检查没问题, 接下来休眠所有的系统设备和总线, 这时候,就已经休眠了.代码的执行也就停在这里了. */
do {
error = suspend_enter(state, &wakeup);;//这里会diable cpu
} while (!error && !wakeup && need_suspend_ops(state)
&& suspend_ops->suspend_again && suspend_ops->suspend_again());
...
}
static int enter_state(suspend_state_t state)
{
int error;
/*检查一些参数*/
if (!valid_state(state))
return -ENODEV;
if (!mutex_trylock(&pm_mutex))
return -EBUSY;
if (state == PM_SUSPEND_FREEZE)
freeze_begin();
/*同步文件系统*/
printk(KERN_INFO "PM: Syncing filesystems ... ");
sys_sync();
printk("done.\n");
pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
/*当进入到suspend_prepare()中以后, 它会给suspend分配一个虚拟终端来输出信 息, 然后广播一个系统要进入suspend的Notify, 关闭掉用户态的helper进程, 然 后一次调用suspend_freeze_processes()冻结所有的进程, 这里会保存所有进程 当前的状态, 也许有一些进程会拒绝进入冻结状态, 当有这样的进程存在的时候, 会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程. */
error = suspend_prepare(state);
if (error)
goto Unlock;
if (suspend_test(TEST_FREEZER))
goto Finish;
pr_debug("PM: Entering %s sleep\n", pm_states[state]);
pm_restrict_gfp_mask();
error = suspend_devices_and_enter(state);//这里调用了suspend_devices_and_enter
pm_restore_gfp_mask();
Finish:
pr_debug("PM: Finishing wakeup.\n");
suspend_finish();
Unlock:
mutex_unlock(&pm_mutex);
return error;
}
int pm_suspend(suspend_state_t state)
{
int error;
if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
return -EINVAL;
pm_suspend_marker("entry");//这里就是打印出休眠log的地方:PM: suspend entry 1970-01-05 23:59:53.510919083 UTC
error = enter_state(state);//enter_state会调用suspend_devices_and_enter
if (error) {
suspend_stats.fail++;
dpm_save_failed_errno(error);
} else {
suspend_stats.success++;
}
pm_suspend_marker("exit");
return error;
}
EXPORT_SYMBOL(pm_suspend);//导出pm_suspend函数接口,这个接口最后是被state_store调用:
kernel/kernel/power/main.c
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
suspend_state_t state;
int error;
printk(KERN_INFO "[wanghl] state_store");//增加的这个log在使用POWER按键灭屏幕休眠或自动休眠的时候都会打印出来
error = pm_autosleep_lock();
if (error)
return error;
if (pm_autosleep_state() > PM_SUSPEND_ON) {
error = -EBUSY;
goto out;
}
state = decode_state(buf, n);
if (state < PM_SUSPEND_MAX)
error = pm_suspend(state);//这里调用了pm_suspend()函数
else if (state == PM_SUSPEND_MAX)
error = hibernate();
else
error = -EINVAL;
out:
pm_autosleep_unlock();
return error ? error : n;
}
power_attr(state);//所以state是sysfs的一个属性值
传入的参数state范围:
typedef int __bitwise suspend_state_t;
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_FREEZE ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN PM_SUSPEND_FREEZE
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
那么关键来了:state_store什么时候会被调用呢?
在Linux中,休眠主要分三个主要的步骤:
1)
冻结用户态进程和内核态任务
2)
调用注册的设备的suspend的回调函数:顺序是按照注册顺序,也就是我们现在在分析的标准的linux休眠流程
3)
休眠核心设备和使CPU进入休眠态冻结进程是内核把进程列表中所有的进程的状态都设置为停止,并且保存下所有进程的上下文. 当这些进程被解冻的时候,他们是不知道自己被冻结过的,只是简单的继续执行.如何让Linux进入休眠呢?用户可以通过读写sys文件/sys/power/state 实现控制系统进入休眠. 比如
# echo standby > /sys/power/state
命令系统进入休眠. 也可以使用
# cat /sys/power/state
来得到内核支持哪几种休眠方式.
所以linux标准的suspend的流程是用户手动写/sys/power/state属性值的时候被调用的!
从代码里也可以看出来state支持的一些值:
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
char *s = buf;
#ifdef CONFIG_SUSPEND
int i;
for (i = 0; i < PM_SUSPEND_MAX; i++) {
if (pm_states[i] && valid_state(i))
s += sprintf(s,"%s ", pm_states[i]);//state显示的值就在pm_states[]数组里
}
#endif
#ifdef CONFIG_HIBERNATION
s += sprintf(s, "%s\n", "disk");
#else
if (s != buf)
/* convert the last space to a newline */
*(s-1) = '\n';
#endif
return (s - buf);
}
const char *const pm_states[PM_SUSPEND_MAX] = {
[PM_SUSPEND_FREEZE] = "freeze",
[PM_SUSPEND_STANDBY] = "standby",
[PM_SUSPEND_MEM] = "mem",
};
也就是freeze,standby,mem三个值!用户对于/sys/power/state 的写入会调用到 main.c中的state_store(), 用户可以写入 const char * const pm_state[] 中定义的字符串, 比如"mem", "standby".然后state_store()会调用enter_state(), 它首先会检查一些状态参数,然后同步文件系统. 最终按照注册顺序调用到各个驱动的suspend函数。
以及另外一个函数try_to_suspend调用pm_suspend:
kernel/kernel/power/autosleep.c
try_to_suspend也是一个重头戏:
static void try_to_suspend(struct work_struct *work)
{
unsigned int initial_count, final_count;
if (!pm_get_wakeup_count(&initial_count, true)) //获取initial_count,这个函数会block住,当存在active wakeup source的时候,直到wakeup source为detative状态
goto out;
mutex_lock(&autosleep_lock);
if (!pm_save_wakeup_count(initial_count) ||//保存initial_count,不会block,当然也会检查是否有active wakeup source,当有active存在再次queue work。
system_state != SYSTEM_RUNNING) {
mutex_unlock(&autosleep_lock);
goto out;
}
if (autosleep_state == PM_SUSPEND_ON) {//当为ON状态时,return。//在睡眠期间跟了很久没有遇见过这种情况
mutex_unlock(&autosleep_lock);
return;
}
if (autosleep_state >= PM_SUSPEND_MAX)
hibernate();//hibernate高通平台目前不支持
else
pm_suspend(autosleep_state);//重要!!!这里调用了休眠,也会跑到最终各个注册的驱动的suspend函数,进入pm_suspend,dmesg会有PM: suspend entry 与PM: suspend exit来标记,这里面会执行freeze task,suspend与resume,disable cpu的操作。内核PM最重要的函数。
mutex_unlock(&autosleep_lock);
if (!pm_get_wakeup_count(&final_count, false))//获取final_count,非block,当然也会检查是否有active wakeup source,当有active存在再次queue work
goto out;
/*
* If the wakeup occured for an unknown reason, wait to prevent the
* system from trying to suspend and waking up in a tight loop.
*/
if (final_count == initial_count)
schedule_timeout_uninterruptible(HZ / 2);//这里遇见未知原因,initial_count与final_count相等,超时500ms后继续往下执行。这种现象我也是跟了许久没有遇见过。
out:
queue_up_suspend_work();//调度queue work会再次循环执行该函数,实际上只要一次echo mem > sys/power/autosleep后这个进程一直会在auto_sleep cycle。
}
kernel/drivers/base/power/wakeup.c
bool pm_get_wakeup_count(unsigned int *count, bool block)
{
unsigned int cnt, inpr;
if (block) {//当block为真时,该进程可能会block住
DEFINE_WAIT(wait);
for (;;) {
prepare_to_wait(&wakeup_count_wait_queue, &wait,
TASK_INTERRUPTIBLE);
split_counters(&cnt, &inpr); //有active的wakeup_source存在就是block住
if (inpr == 0 || signal_pending(current))
break;
schedule();//在这里面block住,直到最后一个active的wakeup_source deactivate时会唤醒该进程,之后会break出来。
}
finish_wait(&wakeup_count_wait_queue, &wait);
}
split_counters(&cnt, &inpr);
*count = cnt;
return !inpr;
}
那么有2个问题,按power键唤醒系统是退出try_to_suspend了吗?-不会,会block在try_to_suspend调用的pm_get_wakeup_count()里面!
1)首先系统是如何被唤醒的?这个是由硬件中断唤醒的,比如我们这里的power键,还有其他的比如alarm等其他硬件中断,只要我们在中断申请时enbale_irq_wake(),
那么睡眠期间,只要触发该中断就可以唤醒系统。那么在try_to_suspend里面唤醒后pm_suspend()执行完resume后会退出来,接着会获取final_count,在这里是存在active
wake_up source的(在out之前加添的打印active wakeup_source,下面的dmesg证明了存在的wakeup_source),之后执行out,后调度工作队列,再次进入try_to_suspend(),在第一次获取initial_count后便会遇见active wakeup_source,这里面更多的是系统上层加的wakeup_source,那么try_to_suspend()会一直block在pm_get_wakeup_count()里面直到灭屏和所有wakeup_source deactivate时会再次进入pm_suspend()休眠。
这里也就解释了为什么只要执行一次ehco mem > sys/power/autosleep后自动可以休眠了的原因。
2)
还有一个与wakeup source无关的问题,为什么suspend后就一直停留在那里不动了?-因为CPU都停了
这个是cpu停止运转了,下面再分析下代码。
核心函数suspend_devices_and_enter():
传入的参数autosleep_state范围:
typedef int __bitwise suspend_state_t;
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_FREEZE ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN PM_SUSPEND_FREEZE
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
autosleep分析:
sys接口:sys/power/autosleep
msm8909:/ # cat sys/power/autosleep
off//PM_SUSPEND_ON,off表示:autosleep功能关闭,并且在try_to_suspend里面添加打印的时候,按POWER按键或自动休眠都不会跑到这里面来
kernel/kernel/power/autosleep.c
#ifdef CONFIG_PM_AUTOSLEEP//内核配置表有配置打开
static ssize_t autosleep_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
suspend_state_t state = pm_autosleep_state();
if (state == PM_SUSPEND_ON)//说明当串口不休眠的时候,上面的autosleep的状态显示为off,表示状态是PM_SUSPEND_ON
return sprintf(buf, "off\n");
#ifdef CONFIG_SUSPEND//MSM8909 不支持
if (state < PM_SUSPEND_MAX)
return sprintf(buf, "%s\n", valid_state(state) ?
pm_states[state] : "error");
#endif
#ifdef CONFIG_HIBERNATION//MSM8909 不支持
return sprintf(buf, "disk\n");
#else
return sprintf(buf, "error");
#endif
}
static ssize_t autosleep_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
suspend_state_t state = decode_state(buf, n);
int error;
if (state == PM_SUSPEND_ON
&& strcmp(buf, "off") && strcmp(buf, "off\n"))
return -EINVAL;
error = pm_autosleep_set_state(state);;//睡眠时写入mem state
return error ? error : n;
}
power_attr(autosleep);
int pm_autosleep_set_state(suspend_state_t state)
{
#ifndef CONFIG_HIBERNATION//不支持
if (state >= PM_SUSPEND_MAX)
return -EINVAL;
#endif
__pm_stay_awake(autosleep_ws);//防止系统休眠
mutex_lock(&autosleep_lock);
autosleep_state = state;
__pm_relax(autosleep_ws);//释放上面的wake up source
if (state > PM_SUSPEND_ON) {
pm_wakep_autosleep_enabled(true);//设置所有wake up source里面的autosleep_enabled为真,这个变量不会对休眠有影响,但是会标记active的时间,使用debugfs可以看见
queue_up_suspend_work();//调度工作队列,会执行try_to_suspend(),其实state mem执行try_to_suspend(),一次就可以了
} else {
pm_wakep_autosleep_enabled(false);//设置所有wake up source里面的autosleep_enabled为假
}
mutex_unlock(&autosleep_lock);
return 0;
}
queue_up_suspend_work()是怎么调度到try_to_suspend的:
kernel/kernel/power/autosleep.c
static DECLARE_WORK(suspend_work, try_to_suspend);编译时创建名为suspend_work的结构体变量并把函数入口地址try_to_suspend和参数地址(无)赋给它;
void queue_up_suspend_work(void)
{
if (autosleep_state > PM_SUSPEND_ON)
queue_work(autosleep_wq, &suspend_work);//调度这个suspend_work工作队列
}
所以往节点 sys/power/autosleep 写入mem,最后就能调用到try_to_suspend函数,直到调用到驱动模块的suspend
休眠的最后一条log:
[ 204.221882] CPU0:msm_cpu_pm_enter_sleep mode:3 during suspend
kernel/drivers/power/qcom/msm-pm.c
bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode, bool from_idle)
{
bool exit_stat = false;
unsigned int cpu = smp_processor_id();
if ((!from_idle && cpu_online(cpu))
|| (MSM_PM_DEBUG_IDLE & msm_pm_debug_mask))
pr_info("CPU%u:%s mode:%d during %s\n", cpu, __func__,
mode, from_idle ? "idle" : "suspend");//CPU0:msm_cpu_pm_enter_sleep mode:3 during suspend
if (execute[mode])
exit_stat = execute[mode](from_idle);
return exit_stat;
}
mode:3代表啥意思:
enum msm_pm_sleep_mode {
MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,//0
MSM_PM_SLEEP_MODE_RETENTION,//1
MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE,//2
MSM_PM_SLEEP_MODE_POWER_COLLAPSE,//3,collapse表示倒塌倒闭
MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND,//4
MSM_PM_SLEEP_MODE_NR,//5
MSM_PM_SLEEP_MODE_NOT_SELECTED,//6
};
-------------------------------
Android 休眠(suspend)
1)Early Suspend(MSM8909这个版本的内核貌似已经找不到相应的函数early_suspend,内核配置表CONFIG_HAS_EARLYSUSPEND也没有配置)
Early suspend 是android 引进的一种机制, 这种机制在上游备受争议,这里不做评论. 这个机制作用在关闭显示的时候, 在这个时候, 一些和显示有关的设备, 比如LCD背光, 比如重力感应器, 触摸屏, 这些设备都会关掉, 但是系统可能还是在运行状态(这时候还有wake lock)进行任务的处理, 例如在扫描 SD卡上的文件等. 在嵌入式设备中, 背光是一个很大的电源消耗,所以android会加入这样一种机制.
2)Late Resume(MSM8909这个版本的内核貌似已经找不到相应的函数late_resume,内核配置表CONFIG_HAS_EARLYSUSPEND也没有配置)
Late Resume 是和suspend 配套的一种机制, 是在内核唤醒完毕开始执行的. 主要就是唤醒在Early Suspend的时候休眠的设备.
3)Wake Lock(MSM8909这个版本wake_lock已经被wakeup_source替代,后面分析)
Wake Lock 在Android的电源管理系统中扮演一个核心的角色. Wake Lock是一种锁的机制, 只要有人拿着这个锁, 系统就无法进入休眠, 可以被用户态程序和内核获得. 这个锁可以是有超时的或者是没有超时的, 超时的锁会在时间过去以后自动解锁. 如果没有锁了或者超时了, 内核就会启动休眠的那套机制来进入休眠.
4)Android Suspend
当用户写入mem 或者 standby到 /sys/power/state中的时候(standby比mem的功耗高mem比disk高), state_store()会被调用, 然后Android会在这里调用request_suspend_state(这个函数在MSM8909这个版本已经不支持,找不到了) 而标准的Linux会在这里进入enter_state()这个函数. 如果请求的是休眠, 那么early_suspend这个workqueue就会被调用,并且进入early_suspend状态. 貌似就只剩下标准linux休眠流程了
休眠模式: 模式内容:
freeze 冻结I/O设备,将它们置于低功耗状态,是处理器进入空闲状态,唤醒最快,耗电比其他standby,mem,disk方式高
standby 除了冻结I/O设备外,还会暂停系统,唤醒较快,耗电比其他mem,disk高
mem 将运行的状态数据存到内存中,并关闭外设,进入等待模式,唤醒较慢,耗电比disk高
msm8909:/ # cat sys/power/state
freeze mem
可以看到我们的MSM8909设备freeze和mem两种模式
唤醒通常会是以下的几种原因:
[1]来电
如果是来电, 那么Modem会通过发送命令给rild来让rild通知WindowManager有来电响应,这样就会远程调用PowerManagerService来写"on" 到/sys/power/state 来执行resume的设备, 比如点亮屏幕等
[2]用户按键
用户按键事件会送到WindowManager中, WindowManager会处理这些按键事件,按键分为几种情况, 如果按键不是唤醒键(能够唤醒系统的按键,比如POWER键) 那么WindowManager会主动放弃WakeLock来使系统进入再次休眠, 如果按键是唤醒键,那么WindowManger就会调用PowerManagerService中的接口来执行Resume
[3]Late Resume 会依次唤醒前面调用了Early Suspend的设备.既然不支持,暂时不分析
wakeup_source简介:
linux 3.4内核PM使用了wakeup_source来保持唤醒状态,也就是keep awake。MSM8909(H10)的内核版本号:3.10.49,android7.1.2,所以也是用的wakeup_source来管理.之前android一直是基于Linux加入了wake_lock机制来阻止系统休眠,后来Linux 3.4内核加入了wakeup_source来管理,安卓4.4跟着升级内核也就摒弃了自己的繁杂的wake_lock机制,在对上层接口并不改变,在内核wake_lock实现直接基于wakeup_source来实现的。当然也会带来debug上的一些问题,比如以前的wake_lock自身带有强大的debug信息,那么我们在调试的时候可以自己看见dmesg中默认打印active wake lock XXX,很直观来辨别需要休眠的时候哪个wake lock有问题阻止了休眠。这个需要我们自己来完善。个人认为改进很大,现在使用了autosleep机制,只要不存在任何active wakeup_source了,系统自动休眠,当有active wake_source自动block住,个人认为休眠更及时,非休眠时间在减少,同时不会消耗额外的资源。使用基于queue work与进程block来管理suspend。还有这里的wakeup_source个人觉得应该叫keepawake_source或者stayawake_souce,毕竟系统的唤醒,也就是cpu的再次运行是由中断唤醒的而不是wakeup_source。同时安卓4.4还有一个重大改变就是去除了early suspend机制改为fb event通知机制。所以我们在现在的代码里才没有找到early suspend的接口,那么现在就只有suspend与resume,runtime suspend与runtime resume了。
-------------------------------------------
接下来我们来看下wake lock机制:
Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作。wake_lock一般在关闭lcd、tp但系统仍然需要正常运行的情况下使用,比如听歌、传输很大的文件,以及我们H10项目中需要用到的8小时长时间心电监测等。本文主要分析driver层wake_lock的实现。
串口驱动里定义了一个唤醒锁
struct wake_lock port_open_wake_lock;
kernel/include/linux/wakelock.h
wake_lock 定义:
struct wake_lock {
struct wakeup_source ws;//所以前面我们说:wake_lock已经被wakeup_source代替了
};
kernel/include/linux/pm_wakeup.h
struct wakeup_source {
const char *name;// 名称
struct list_head entry; // 链表节点
spinlock_t lock;
struct timer_list timer;
unsigned long timer_expires; //超时时间,也就是wake_lock_timeout()里面的时间参数,超时后会执行deactivate函数
ktime_t total_time; // 锁使用时间
ktime_t max_time; // 锁使用时间最长的一次
ktime_t last_time;// 锁上次操作时间
ktime_t start_prevent_time;
ktime_t prevent_sleep_time; // 锁阻止休眠的时间
unsigned long event_count;//event计数
unsigned long active_count;//active计数
unsigned long relax_count;
unsigned long expire_count; // 超时计数
unsigned long wakeup_count; // 唤醒计数
bool active:1;//用于判断是否是active状态,active任何wakeup_source都会执行wakeup_source_activate该函数,标记active为true,
bool autosleep_enabled:1;//这个变量是来标记active时间的,这个变量不会对休眠有影响,但是会标记active的时间,使用debugfs可以看见
};
我们看下这个唤醒锁port_open_wake_lock是怎么被初始化的?
static int msm_serial_hsl_probe(struct platform_device *pdev)
{
...
//platform_driver平台驱动在被probe的时候就已经初始化了wake lock
if (pdata && pdata->use_pm)//pdata->use_pm在dtsi文件kernel/arch/arm/boot/dts/qcom/sc806-evk/msm8909-mtp.dtsi里面blsp1_uart2配置成qcom,use-pm即可;
wake_lock_init(&msm_hsl_port->port_open_wake_lock,
WAKE_LOCK_SUSPEND,//类型:Prevent suspend
"msm_serial_hslite_port_open");//name,这个将会在msm8909:/ # cat d/wakeup_sources的name字段显示出来
...
}
wake_lock_init接口仍在在kernel/include/linux/wakelock.h定义,包括上锁和解锁操作:
/* A wake_lock prevents the system from entering suspend or other low power
* states when active. If the type is set to WAKE_LOCK_SUSPEND, the wake_lock
* prevents a full system suspend.
*/
一个wake_lock将会阻止系统由激活状态进入休眠或其他低功耗状态,如果设置wake_lock为WAKE_LOCK_SUSPEND,那这个wake_lock将会阻止系统进入完全的深度休眠!
enum {
WAKE_LOCK_SUSPEND, /* Prevent suspend */// 阻止进入深度休眠模式
WAKE_LOCK_TYPE_COUNT
};
1、wake_lock 内核空间接口
//wake_lock_init()用于初始化一个新锁,type参数指定了锁的类型
static inline void wake_lock_init(struct wake_lock *lock, int type,
const char *name)
{
wakeup_source_init(&lock->ws, name);
}
//wake_lock_destroy()则注销一个锁
static inline void wake_lock_destroy(struct wake_lock *lock)
{
wakeup_source_trash(&lock->ws);
}
//wake_lock()和wake_lock_timeout()用于将初始化完成的锁激活,使之成为有效的永久锁或者超时锁
static inline void wake_lock(struct wake_lock *lock)
{
__pm_stay_awake(&lock->ws);
}
static inline void wake_lock_timeout(struct wake_lock *lock, long timeout)
{
__pm_wakeup_event(&lock->ws, jiffies_to_msecs(timeout));
}
//wake_unlock()用于解锁使之成为无效锁。
static inline void wake_unlock(struct wake_lock *lock)
{
__pm_relax(&lock->ws);
}
//wake_lock_active()用于判断锁当前是否有效,如果有效则返回非0值
static inline int wake_lock_active(struct wake_lock *lock)
{
return lock->ws.active;
}
那现在我们来看初始化完一个wake_lock之后,怎么上锁和解锁!
static int msm_hsl_startup(struct uart_port *port)
{
...
if (pdata && pdata->use_pm)
{
pr_err("[wanghl] wake_lock in msm_hsl_startup");
wake_lock(&msm_hsl_port->port_open_wake_lock);//添加log确认,hal层里执行:state->fd_ecg = open(DEVICE_NAME_ECG, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);会跑到这里来上锁to keep awake,上锁,禁止休眠
}
...
release_wakelock://有异常的时候释放锁
if (pdata && pdata->use_pm)
wake_unlock(&msm_hsl_port->port_open_wake_lock);
...
}
static void msm_hsl_shutdown(struct uart_port *port)
{
...
if (pdata && pdata->use_pm)
{
pr_err("[wanghl] wake_unlock in msm_hsl_shutdown");
wake_unlock(&msm_hsl_port->port_open_wake_lock);///添加log确认,hal层里执行:close( state->fd_ecg );会跑到这里来解锁,允许休眠
}
...
}
static int msm_serial_hsl_remove(struct platform_device *pdev)
{
...
if (pdata && pdata->use_pm)
wake_lock_destroy(&msm_hsl_port->port_open_wake_lock);//模块卸载的时候destroy掉这个睡眠锁
...
}
当启用wake_lock后,如果只上锁保持串口驱动awake,不去测量心电ECG,系统灭屏后仍然会有50mA的功耗;正常测量心电ECG,灭屏幕后的功耗大概在140mA,总电池是1500mAh时,大概能测10h.但是不测ECG的时候或者长时间心电ECG监测(要求8h)结束之后,仍然需要串口驱动能够正常解锁,正常休眠,保持低功耗.所以需要修改HAL层代码,只在开始测量ECG的时候,open 串口设备ttyHSL1,让驱动上锁,就算灭屏幕也保持上锁,只有在测量结束的时候或者手动停止测量的情况下,才去close串口设备ttyHSL1,解锁唤醒锁,让其可以休眠.下次再打开测量,再重新open port,测完再close port!这样就能动态控制唤醒锁的上锁和解锁,控制功耗!关闭串口之后,功耗能到下来到正常的2-3mA
当进入冻结进程的时候, android首先会检查有没有wake lock,如果没有, 才会停止这些进程, 因为在开始suspend和冻结进程期间有可能有人申请了 wake lock,如果是这样, 冻结进程会被中断.系统就会停止进入休眠!
在root权限下,可以通过查看/d/wakeup_sources来查看wakelock的情况。或者是sys/kernel/debug/wakeup_sources
msm8909:/ # cat d/wakeup_sources
name active_count event_count wakeup_count expire_count active_since total_time max_time last_change prevent_suspend_time
...
msm_serial_hslite_port_open 1 1 0 0 1009942 1009942 1009942 40234 0//这个就是我们初始化设置的唤醒锁
autosleep 0 0 0 0 0 0 0 80 0
...
wakeup_sources这个节点的信息对分析耗电比较有用的数据有active_count, active_since,total_time。
active_count--上锁的次数
active_since--当前的wakelock已经持续的时间
total_time--这个锁开机以来一共lock的时间
当CPU无法睡下去时,很可能就是因为某个driver持有wakelock不放导致的。这时可以这个节点来分析,找出根源。不过,这个节点只有root权限才能查看,这是限制条件。
1)wakeup_sources节点的创建:
kernel/drivers/base/power/wakeup.c
static int __init wakeup_sources_debugfs_init(void)
{
wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources",
S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops);//创建"/d/wakeup_sources"。
return 0;
}
2)节点操作的相关函数:
static const struct file_operations wakeup_sources_stats_fops = {
.owner = THIS_MODULE,
.open = wakeup_sources_stats_open,//cat /d/wakeup_sources时调用的函数,wakeup_sources_stats_open
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
wakeup_sources,一个在当前wakeup.c文件中的全局链表list。很容易猜测到,每次申请一个wake_lock时,都会添加一个item到这个链表中。看看这个全局的list的声明和初始化。
static LIST_HEAD(wakeup_sources); //初始化。
由上面分析可知wake_lock_init是调用了wakeup_source_init:
kernel/include/linux/pm_wakeup.h
static inline void wakeup_source_init(struct wakeup_source *ws,
const char *name)
{
wakeup_source_prepare(ws, name);//准备工作
wakeup_source_add(ws);//真正的添加。 -> list_add_rcu(&ws->entry, &wakeup_sources); //添加到wakeup_sources
}
上锁和解锁的调用流程:
wake_lock->__pm_stay_awake->wakeup_source_report_event->wakeup_source_activate->freeze_wake(); //保证上锁期间CPU不会睡下去
wake_unlock->__pm_relax->wakeup_source_deactivate->wake_up//唤醒
wakup_source的申请与释放:
1:使用安卓的wake_lock接口:wake_lock(),wake_lock_timeout(),wake_unlock();
2: 使用wakeup_source自带的接口:pm_stay_awake(),pm_relax();这里的name就是device name。
kernel/drivers/base/power/wakeup.c
void pm_stay_awake(struct device *dev)
{
unsigned long flags;
if (!dev)
return;
spin_lock_irqsave(&dev->power.lock, flags);
__pm_stay_awake(dev->power.wakeup);
spin_unlock_irqrestore(&dev->power.lock, flags);
}
EXPORT_SYMBOL_GPL(pm_stay_awake);//导出接口pm_stay_awake
void pm_relax(struct device *dev)
{
unsigned long flags;
if (!dev)
return;
spin_lock_irqsave(&dev->power.lock, flags);
__pm_relax(dev->power.wakeup);
spin_unlock_irqrestore(&dev->power.lock, flags);
}
EXPORT_SYMBOL_GPL(pm_relax);//导出接口pm_relax
2、用户空间接口
wake_lock向用户空间提供了两个文件节点用于申请锁和解锁:
1)sys/power/wake_lock
2)sys/power/wake_unlock
应用程序可以根据HAL层的接口读写这两个节点。
执行下看下:
亮屏幕的时候:
msm8909:/ # cat sys/power/wake_lock
PowerManagerService.Display
灭屏幕的时候:
1|msm8909:/ # cat sys/power/wake_lock//用户空间定义的有效锁为空
msm8909:/ # cat sys/power/wake_unlock//用户空间的无效锁,不会keep awake,也就说下面的用户进程会进入休眠
KeyEvents PowerManagerService.Broadcasts PowerManagerService.Display PowerManagerService.WakeLocks qcril qcril_pre_client_init qmuxd_port_wl_0 radio-interface rmt_storage_-1379796704 rmt_storage_-1380841184 tftp_server_wakelock
msm8909:/ #
kernel/kernel/power/main.c
#ifdef CONFIG_PM_WAKELOCKS//MSM8909内核配置表有配置
// wack_lock文件的读函数,显示用户空间定义的有效锁,cat /sys/power/wake_lock的时候被调用(添加log证实确实跑这里)
static ssize_t wake_lock_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return pm_show_wakelocks(buf, true);
}
// wack_lock文件的写函数,初始化并激活用户空间定义的锁,添加log证实按POWER按键或自动休眠的时候会调用好几次
static ssize_t wake_lock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_lock(buf);
return error ? error : n;
}
power_attr(wake_lock);
// wack_unlock文件的读函数,显示用户空间的无效锁,cat /sys/power/wake_unlock的时候被调用(添加log证实确实跑这里)
static ssize_t wake_unlock_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return pm_show_wakelocks(buf, false);
}
// wack_unlock文件的写函数,用于用户空间解锁,添加log证实按POWER按键或自动休眠的时候也会调用好几次
static ssize_t wake_unlock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_unlock(buf);
return error ? error : n;
}
power_attr(wake_unlock);
#endif /* CONFIG_PM_WAKELOCKS */
系统不休眠时通过命令行查看wake lock的命令为:
adb shell dumpsys power;
打印出来的即使当前锁状态信息:
灭屏幕的时候:
$ adb shell dumpsys power
...
Wake Locks: size=1
DOZE_WAKE_LOCK 'DreamManagerService' ACQ=-3m19s610ms (uid=1000 pid=1976)
...
亮屏幕的时候:
$ adb shell dumpsys power
...
Wake Locks: size=0
...
-------------------------------
另外对比之前L8的时候,霍尔开关检测是在kernel/drivers/input/keyboard/gpio_keys.c来实现的,他也在休眠的时候能监测到霍尔开关的状态,那照例说应该也是持有一个唤醒锁来达到休眠后驱动还能检测工作的目的,那他为什么系统还能正常休眠呢?
查看代码确实probe的时候初始化了一个wakeup source,
static int gpio_keys_probe(struct platform_device *pdev)
{
...
device_init_wakeup(&pdev->dev, wakeup);//wakeup的值由dts配置:button->wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL);
...
}
static inline int device_init_wakeup(struct device *dev, bool val)
{
device_set_wakeup_capable(dev, val);//dev->power.can_wakeup = capable;
device_set_wakeup_enable(dev, val);//dev->power.should_wakeup = enable;
return 0;
}
device结构体有个成员结构体是:struct dev_pm_info power;
dev_pm_info结构体里面有个成员是:struct wakeup_source *wakeup;
struct dev_pm_info {
...
unsigned int can_wakeup:1;//只有这个变量
...
#ifdef CONFIG_PM_SLEEP//内核配置表没有定义
struct list_head entry;
struct completion completion;
struct wakeup_source *wakeup;
bool wakeup_path:1;
bool syscore:1;
#else
unsigned int should_wakeup:1;//还有这个变量
#endif
...
}
#ifdef CONFIG_PM_SLEEP//这个没配置,所以休眠唤醒不会有什么响应
static int gpio_keys_suspend(struct device *dev)
{
...
}
static int gpio_keys_resume(struct device *dev)
{
...
}
#endif
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
BUG_ON(irq != bdata->irq);
if (bdata->button->wakeup)
pm_stay_awake(bdata->input->dev.parent);//只有在中断处理函数里,也就是说只有响应了中断之后才去持有wakeup source,由前面分析,我们知道pm_stay_awake和wake_lock一样都是调用了__pm_stay_awake(dev->power.wakeup);所以最后就是中断响应之后唤醒了该驱动,唤醒了系统
if (bdata->timer_debounce)
mod_timer(&bdata->timer,
jiffies + msecs_to_jiffies(bdata->timer_debounce));
else
schedule_work(&bdata->work);//调度工作队列gpio_keys_gpio_work_func来处理键值
return IRQ_HANDLED;
}
static void gpio_keys_gpio_work_func(struct work_struct *work)
{
struct gpio_button_data *bdata =
container_of(work, struct gpio_button_data, work);
gpio_keys_gpio_report_event(bdata);
if (bdata->button->wakeup)
pm_relax(bdata->input->dev.parent);//上报完键值只有就调用pm_relax解锁,不再持有锁,所以系统仍然能进入休眠
}
所以说,dts的配置开关就是为了按键检测能够唤醒系统,报完键值就睡下去,所以不会导致系统不能进入深度休眠,就像头文件和txt文档的描述一样:
1)kernel/include/linux/gpio_keys.h
int wakeup; /* configure the button as a wake-up source */
2)
kernel/Documentation/devicetree/bindings/gpio/gpio_keys.txt
- gpio-key,wakeup: Boolean, button can wake-up the system.
我想,这也是华为手机可以拿音量下键来实现双击熄屏快拍的功能,唤醒系统相机进行拍照
所以说同样是持有wakeup source,串口能保持唤醒,使系统不休眠,而按键检测能够休眠的差别就在于持有wakeup source的时机和解锁的时机不同,串口驱动是一开串口就持有锁,直到关闭串口才会释放锁,所以这期间能够保持串口持续唤醒,系统不休眠,而gpio key的检测只在按键按下之后,系统中断(暂且认为是可唤醒的中断)产生了才上锁stay awake,处理完键值马上又释放锁睡下,所以持有锁的时间是非常短的,不会影响到系统休眠!
-------------------------------
关于RPM理解:
关于runtime sys接口在文件: /kernel/drivers/base/power/sysfs.c中描述。设备的runtime属性是在dpm_sysfs_add函数中增加的。
if (pm_runtime_callbacks_present(dev)) {
rc = sysfs_merge_group(&dev->kobj, &pm_runtime_attr_group);
if (rc)
goto err_out;
}
runtime的属性如下:
static struct attribute *runtime_attrs[] = {
#ifdef CONFIG_PM_RUNTIME//kernel/arch/arm/configs/msm8909_defconfig 内核配置表有配
#ifndef CONFIG_PM_ADVANCED_DEBUG//配置表没配
&dev_attr_runtime_status.attr,
#endif
&dev_attr_control.attr,
&dev_attr_runtime_suspended_time.attr,
&dev_attr_runtime_active_time.attr,
&dev_attr_autosuspend_delay_ms.attr,
#endif /* CONFIG_PM_RUNTIME */
NULL,
};
其中有五个属性(dev_attr_后面的字段)。分别为runtime_status,control, runtime_susupend_time, runtime_active_time, autosuspend_delay_ms,属性。
msm8909(H10)的这五个属性都在下面的目录下:/sys/devices/platform/rpm_requests.15/power/
rpm_bus: qcom,rpm-smd {
compatible = "qcom,rpm-smd";
rpm-channel-name = "rpm_requests";
rpm-channel-type = <15>; /* SMD_APPS_RPM */
};
Rpm-smd.c (drivers\soc\qcom): {.compatible = "qcom,rpm-smd"},
/sys/devices/platform/rpm_requests.15/power/control
on - 调用pm_runtime_forbid接口,增加设备的引用计数,然后resume设备。
auto - 调用pm_runtime_allow接口,减少设备的引用计数,如果设备的引用计数为0,则idle设备。
msm8909:/ # cat /sys/devices/platform/rpm_requests.15/power/control
auto//设备进入suspend状态
/sys/devices/platform/rpm_requests.15/power/runtime_status
active - 设备的状态是正常工作状态。
suspend- 设备的状态是低功耗模式。
suspending-设备的状态正在从active->suspend转化。
resuming-设备的状态正在从suspend->active转化。
error-设备runtime出现错误,此时runtime_error的标志置位。
unsupported-设备的runtime 没有使能,此时disable_depth标志置位。
msm8909:/ # cat /sys/devices/platform/rpm_requests.15/power/runtime_status
unsupported//设备的runtime 没有使能
/sys/devices/platform/rpm_requests.15/power/runtime_suspended_time
设备在suspend状态的时间
msm8909:/ # cat /sys/devices/platform/rpm_requests.15/power/runtime_suspended_time
0//suspend 状态的时间为0
/sys/devices/platform/rpm_requests.15/power/runtime_active_time
设备在active状态的时间
msm8909:/ # cat /sys/devices/platform/rpm_requests.15/power/runtime_active_time
0//active状态的时间为0
/sys/devices/platform/rpm_requests.15/power/autosuspend_delay_ms
设备在idle状态多久之后suspend,设置延迟suspend的延迟时间。
msm8909:/ # cat /sys/devices/platform/rpm_requests.15/power/autosuspend_delay_ms
/system/bin/sh: cat: /sys/devices/platform/rpm_requests.15/power/autosuspend_delay_ms: I/O error//IO错误
----------------------------------------------------------------------------------------------------------
===============================
打开被占用的串口ttyHSL0,原本被占用来做血糖测试,现在恢复其打印功能:
1)git diff device/qcom/sepolicy/common/file_contexts
-/dev/ttyHSL0 u:object_r:serial_device:s0
+/dev/ttyHSL0 u:object_r:console_device:s0
2)git diff kernel/arch/arm/configs/msm8909_defconfig
-# CONFIG_SERIAL_MSM_HSL_CONSOLE is not set
+CONFIG_SERIAL_MSM_HSL_CONSOLE=y
======================================================
MSM8909 H10,类是对象的抽象,而对象是类的具体实例。对象是具有类类型的变量。类是抽象的,不占用内存,而对象是具体的,占用存储空间。
Android Sensor使能流程:
1.APP调用,java应用程序,QSensorTest为例:
SensorManager sensorManager;//对于上层来说,只有SensorManager是可见的.
this.sensorListener = new SensorListener(this);
sensorManager.registerListener(this.sensorListener,//APP注册sensor监听
this.sensor(), streamRate < 4 ? streamRate : streamRate * 1000, handler)//三个参数
如果是自定义增加的sensor:
sm= (SensorManager)getSystemService(SENSOR_SERVICE);
sarSensor= sm.getSensorList(65552);//65552是在kernel中新增sensor时指定的
2.java framework框架层
frameworks/base/core/java/android/hardware/SensorManager.java:
public boolean registerListener(SensorEventListener listener, Sensor sensor,
int samplingPeriodUs) {
return registerListener(listener, sensor, samplingPeriodUs, null);//四个参数
}
public boolean registerListener(SensorEventListener listener, Sensor sensor,
int samplingPeriodUs, int maxReportLatencyUs) {//四个参数
int delay = getDelay(samplingPeriodUs);
return registerListenerImpl(listener, sensor, delay, null, maxReportLatencyUs, 0);//六个参数
}
调用到:frameworks/base/core/java/android/hardware/SystemSensorManager.java
@Override
protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,//注册工作是由SystemSensorManager类的registerListenerImpl方法来完成
int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) {
android.util.SeempLog.record_sensor_rate(381, sensor, delayUs);
if (listener == null || sensor == null) {
Log.e(TAG, "sensor or listener is null");
return false;
}
// Trigger Sensors should use the requestTriggerSensor call.
if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
Log.e(TAG, "Trigger Sensors should use the requestTriggerSensor.");
return false;
}
if (maxBatchReportLatencyUs < 0 || delayUs < 0) {
Log.e(TAG, "maxBatchReportLatencyUs and delayUs should be non-negative");
return false;
}
// Invariants to preserve:
// - one Looper per SensorEventListener
// - one Looper per SensorEventQueue
// We map SensorEventListener to a SensorEventQueue, which holds the looper
synchronized (mSensorListeners) {
SensorEventQueue queue = mSensorListeners.get(listener);
if (queue == null) {
Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
final String fullClassName = listener.getClass().getEnclosingClass() != null ?
listener.getClass().getEnclosingClass().getName() :
listener.getClass().getName();
queue = new SensorEventQueue(listener, looper, this, fullClassName);//a.queue = new SensorEventQueue
if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {//b.queue.addSensor
queue.dispose();
return false;
}
mSensorListeners.put(listener, queue);
return true;
} else {
return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs);
}
}
}
这个主要做了两个事情
a.queue = new SensorEventQueue//这个会让我们理解类ISensorEventConnection下的enableDisable方法
b.queue.addSensor
SensorEventQueue继承的是BaseEventQueue这个类,构成函数一开始调用了nativeInitBaseEventQueue
private static abstract class BaseEventQueue {
...
BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
if (packageName == null) packageName = "";
nSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,//构成函数一开始调用了nativeInitBaseEventQueue
new WeakReference<>(this), looper.getQueue(),
packageName, mode, manager.mContext.getOpPackageName());
mCloseGuard.open("dispose");
mManager = manager;
}
...
}
3.JNI层
找下这个nativer方法实现的地方:frameworks/base/core/jni/android_hardware_SensorManager.cpp
static const JNINativeMethod gBaseEventQueueMethods[] = {
{"nativeInitBaseEventQueue",//这里定义了jni接口,framework层java函数的接口
"(JLjava/lang/ref/WeakReference;Landroid/os/MessageQueue;Ljava/lang/String;ILjava/lang/String;)J",
(void*)nativeInitSensorEventQueue },//函数指针指向C函数
{"nativeEnableSensor",
"(JIII)I",//实际上这些字符是与函数的参数类型一一对应的。"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();"(II)V" 表示void Func(int, int);
(void*)nativeEnableSensor },
{"nativeDisableSensor",
"(JI)I",
(void*)nativeDisableSensor },
{"nativeDestroySensorEventQueue",
"(J)V",
(void*)nativeDestroySensorEventQueue },
{"nativeFlushSensor",
"(J)I",
(void*)nativeFlushSensor },
{"nativeInjectSensorData",
"(JI[FIJ)I",
(void*)nativeInjectSensorData },
};
---------------------
插播解释一下:
Andorid使用了一种Java 和 C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型是JNINativeMethod,定义如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
参数和返回值,具体的每一个字符的对应关系如下
字符 Java类型 C类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring
--------------
接着分析,JNI接口的C函数:
static jlong nativeInitSensorEventQueue(JNIEnv *env, jclass clazz, jlong sensorManager,//如果是C++,使用的是env, 如果是C,使用的是(*env),最好参考相应系统中的代码来写。
jobject eventQWeak, jobject msgQ, jstring packageName, jint mode) {
SensorManager* mgr = reinterpret_cast
ScopedUtfChars packageUtf(env, packageName);
String8 clientName(packageUtf.c_str());
sp
sp
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
//跳到原型:frameworks/native/libs/gui/SensorManager.cpp
sp
sp
Mutex::Autolock _l(mLock);
while (assertStateLocked() == NO_ERROR) {
sp
mSensorServer->createSensorEventConnection(packageName, mode, mOpPackageName);//这里得到connection
if (connection == NULL) {
// SensorService just died or the app doesn't have required permissions.
ALOGE("createEventQueue: connection is NULL.");
return NULL;
}
queue = new SensorEventQueue(connection);//在这里初始化了SensorEventQueue
break;
}
return queue;
}
//frameworks/native/libs/gui/SensorEventQueue.cpp
SensorEventQueue::SensorEventQueue(const sp
: mSensorEventConnection(connection), mRecBuffer(NULL), mAvailable(0), mConsumed(0),//关键在于传入了mSensorEventConnection = connection参数
mNumAcksToSend(0) {
mRecBuffer = new ASensorEvent[MAX_RECEIVE_BUFFER_EVENT_COUNT];
}
那这个connection 是哪里传来的,就在刚刚分析的函数里:
frameworks/native/libs/gui/SensorManager.cpp
跳转到:createSensorEventConnection
frameworks/native/services/sensorservice/SensorService.cpp
sp
int requestedMode, const String16& opPackageName) {
// Only 2 modes supported for a SensorEventConnection ... NORMAL and DATA_INJECTION.
if (requestedMode != NORMAL && requestedMode != DATA_INJECTION) {
return NULL;
}
Mutex::Autolock _l(mLock);
// To create a client in DATA_INJECTION mode to inject data, SensorService should already be
// operating in DI mode.
if (requestedMode == DATA_INJECTION) {
if (mCurrentOperatingMode != DATA_INJECTION) return NULL;
if (!isWhiteListedPackage(packageName)) return NULL;
}
uid_t uid = IPCThreadState::self()->getCallingUid();
sp
requestedMode == DATA_INJECTION, opPackageName));
if (requestedMode == DATA_INJECTION) {
if (mActiveConnections.indexOf(result) < 0) {
mActiveConnections.add(result);
}
// Add the associated file descriptor to the Looper for polling whenever there is data to
// be injected.
result->updateLooperRegistration(mLooper);
}
return result;
}
第一步先分析到这里,
接下来分析下第二步:b.queue.addSensor
frameworks/base/core/java/android/hardware/SystemSensorManager.java
public boolean addSensor(
Sensor sensor, int delayUs, int maxBatchReportLatencyUs) {
// Check if already present.
int handle = sensor.getHandle();
if (mActiveSensors.get(handle)) return false;
// Get ready to receive events before calling enable.
mActiveSensors.put(handle, true);
addSensorEvent(sensor);
if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) {//跑到这里
// Try continuous mode if batching fails.
if (maxBatchReportLatencyUs == 0 ||
maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
removeSensor(sensor, false);
return false;
}
}
return true;
}
调用到这里:
private int enableSensor(
Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
if (nSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs,//还是native方法
maxBatchReportLatencyUs);
}
frameworks/base/core/jni/android_hardware_SensorManager.cpp:
static jint nativeEnableSensor(JNIEnv *env, jclass clazz, jlong eventQ, jint handle, jint rate_us,
jint maxBatchReportLatency) {
sp
return receiver->getSensorEventQueue()->enableSensor(handle, rate_us, maxBatchReportLatency,
0);//四个参数
}
//getSensorEventQueue()取的是什么
sp
return mSensorQueue;//还是 sp
}
status_t SensorEventQueue::enableSensor(int32_t handle, int32_t samplingPeriodUs,
int maxBatchReportLatencyUs, int reservedFlags) const {
return mSensorEventConnection->enableDisable(handle, true, us2ns(samplingPeriodUs),
us2ns(maxBatchReportLatencyUs), reservedFlags);
}
我们来看下enableDisable这个方法的出处:
frameworks/native/services/sensorservice/SensorEventConnection.cpp
status_t SensorService::SensorEventConnection::enableDisable(
int handle, bool enabled, nsecs_t samplingPeriodNs, nsecs_t maxBatchReportLatencyNs,
int reservedFlags)
{
status_t err;
if (enabled) {
err = mService->enable(this, handle, samplingPeriodNs, maxBatchReportLatencyNs,
reservedFlags, mOpPackageName);
} else {
err = mService->disable(this, handle);
}
return err;
}
enable 点进去看下还是到了:frameworks/native/services/sensorservice/SensorService.cpp
我们可以这么认为,sensor操作基本是在这个文件进行。
而sensor真正的使能的是:status_t SensorService::enable函数下的这句
err = sensor->activate(connection.get(), true);
status_t SensorService::enable(const sp
int handle, nsecs_t samplingPeriodNs, nsecs_t maxBatchReportLatencyNs, int reservedFlags,
const String16& opPackageName) {
if (mInitCheck != NO_ERROR)
return mInitCheck;
sp
if (sensor == nullptr ||
!canAccessSensor(sensor->getSensor(), "Tried enabling", opPackageName)) {
return BAD_VALUE;
}
Mutex::Autolock _l(mLock);
if ((mCurrentOperatingMode == RESTRICTED || mCurrentOperatingMode == DATA_INJECTION)
&& !isWhiteListedPackage(connection->getPackageName())) {
return INVALID_OPERATION;
}
SensorRecord* rec = mActiveSensors.valueFor(handle);
if (rec == 0) {
rec = new SensorRecord(connection);
mActiveSensors.add(handle, rec);
if (sensor->isVirtual()) {
mActiveVirtualSensors.emplace(handle);
}
} else {
if (rec->addConnection(connection)) {
// this sensor is already activated, but we are adding a connection that uses it.
// Immediately send down the last known value of the requested sensor if it's not a
// "continuous" sensor.
if (sensor->getSensor().getReportingMode() == AREPORTING_MODE_ON_CHANGE) {
// NOTE: The wake_up flag of this event may get set to
// WAKE_UP_SENSOR_EVENT_NEEDS_ACK if this is a wake_up event.
auto logger = mRecentEvent.find(handle);
if (logger != mRecentEvent.end()) {
sensors_event_t event;
// It is unlikely that this buffer is empty as the sensor is already active.
// One possible corner case may be two applications activating an on-change
// sensor at the same time.
if(logger->second->populateLastEvent(&event)) {
event.sensor = handle;
if (event.version == sizeof(sensors_event_t)) {
if (isWakeUpSensorEvent(event) && !mWakeLockAcquired) {
setWakeLockAcquiredLocked(true);
}
connection->sendEvents(&event, 1, NULL);
if (!connection->needsWakeLock() && mWakeLockAcquired) {
checkWakeLockStateLocked();
}
}
}
}
}
}
}
if (connection->addSensor(handle)) {
BatteryService::enableSensor(connection->getUid(), handle);
// the sensor was added (which means it wasn't already there)
// so, see if this connection becomes active
if (mActiveConnections.indexOf(connection) < 0) {
mActiveConnections.add(connection);
}
} else {
ALOGW("sensor %08x already enabled in connection %p (ignoring)",
handle, connection.get());
}
nsecs_t minDelayNs = sensor->getSensor().getMinDelayNs();
if (samplingPeriodNs < minDelayNs) {
samplingPeriodNs = minDelayNs;
}
ALOGD_IF(DEBUG_CONNECTIONS, "Calling batch handle==%d flags=%d"
"rate=%" PRId64 " timeout== %" PRId64"",
handle, reservedFlags, samplingPeriodNs, maxBatchReportLatencyNs);
status_t err = sensor->batch(connection.get(), handle, 0, samplingPeriodNs,
maxBatchReportLatencyNs);
// Call flush() before calling activate() on the sensor. Wait for a first
// flush complete event before sending events on this connection. Ignore
// one-shot sensors which don't support flush(). Ignore on-change sensors
// to maintain the on-change logic (any on-change events except the initial
// one should be trigger by a change in value). Also if this sensor isn't
// already active, don't call flush().
if (err == NO_ERROR &&
sensor->getSensor().getReportingMode() == AREPORTING_MODE_CONTINUOUS &&
rec->getNumConnections() > 1) {
connection->setFirstFlushPending(handle, true);
status_t err_flush = sensor->flush(connection.get(), handle);
// Flush may return error if the underlying h/w sensor uses an older HAL.
if (err_flush == NO_ERROR) {
rec->addPendingFlushConnection(connection.get());
} else {
connection->setFirstFlushPending(handle, false);
}
}
if (err == NO_ERROR) {
ALOGD_IF(DEBUG_CONNECTIONS, "Calling activate on %d", handle);
err = sensor->activate(connection.get(), true);//真正使能的地方
}
if (err == NO_ERROR) {
connection->updateLooperRegistration(mLooper);
SensorRegistrationInfo ®_info =
mLastNSensorRegistrations.editItemAt(mNextSensorRegIndex);
reg_info.mSensorHandle = handle;
reg_info.mSamplingRateUs = samplingPeriodNs/1000;
reg_info.mMaxReportLatencyUs = maxBatchReportLatencyNs/1000;
reg_info.mActivated = true;
reg_info.mPackageName = connection->getPackageName();
time_t rawtime = time(NULL);
struct tm * timeinfo = localtime(&rawtime);
reg_info.mHour = timeinfo->tm_hour;
reg_info.mMin = timeinfo->tm_min;
reg_info.mSec = timeinfo->tm_sec;
mNextSensorRegIndex = (mNextSensorRegIndex + 1) % SENSOR_REGISTRATIONS_BUF_SIZE;
}
if (err != NO_ERROR) {
// batch/activate has failed, reset our state.
cleanupWithoutDisableLocked(connection, handle);
}
return err;
}
frameworks/native/services/sensorservice/SensorInterface.h
class SensorInterface : public VirtualLightRefBase {
public:
virtual ~SensorInterface() {}
virtual bool process(sensors_event_t* outEvent, const sensors_event_t& event) = 0;
virtual status_t activate(void* ident, bool enabled) = 0;//跑到这里activate
virtual status_t setDelay(void* ident, int handle, int64_t ns) = 0;
virtual status_t batch(void* ident, int handle, int /*flags*/, int64_t samplingPeriodNs,
int64_t maxBatchReportLatencyNs) = 0;
virtual status_t flush(void* /*ident*/, int /*handle*/) = 0;
virtual const Sensor& getSensor() const = 0;
virtual bool isVirtual() const = 0;
virtual void autoDisable(void* /*ident*/, int /*handle*/) = 0;
};
frameworks/native/services/sensorservice/SensorInterface.cpp
status_t HardwareSensor::activate(void* ident, bool enabled) {
return mSensorDevice.activate(ident, mSensor.getHandle(), enabled);
}
找到mSensorDevice定义的地方,看他到底是个什么结构:
BaseSensor::BaseSensor(const sensor_t& sensor) :
mSensorDevice(SensorDevice::getInstance()),//mSensorDevice应该是个SensorDevice结构
mSensor(&sensor, mSensorDevice.getHalDeviceVersion()) {
}
我们搜下SensorDevice
frameworks/native/services/sensorservice/SensorDevice.cpp
先看下构造函数:
SensorDevice::SensorDevice()
: mSensorDevice(0),
mSensorModule(0) {
status_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,// "sensors"
(hw_module_t const**)&mSensorModule);//这个是JNI和HAL 的桥梁,根据SENSORS_HARDWARE_MODULE_ID这个id进行关联
ALOGE_IF(err, "couldn't load %s module (%s)",
SENSORS_HARDWARE_MODULE_ID, strerror(-err));
if (mSensorModule) {
err = sensors_open_1(&mSensorModule->common, &mSensorDevice);//打开并赋值mSensorDevice
ALOGE_IF(err, "couldn't open device for module %s (%s)",
SENSORS_HARDWARE_MODULE_ID, strerror(-err));
if (mSensorDevice) {
if (mSensorDevice->common.version == SENSORS_DEVICE_API_VERSION_1_1 ||
mSensorDevice->common.version == SENSORS_DEVICE_API_VERSION_1_2) {
ALOGE(">>>> WARNING <<< Upgrade sensor HAL to version 1_3");
}
sensor_t const* list;
ssize_t count = mSensorModule->get_sensors_list(mSensorModule, &list);
mActivationCount.setCapacity(count);
Info model;
for (size_t i=0 ; i
mSensorDevice->activate(
reinterpret_cast
list[i].handle, 0);
}
}
}
}
4.HAL硬件抽象层
好了我们搜下SENSORS_HARDWARE_MODULE_ID,找到与JNI层相互连接的HAL层的代码
1)vendor/qcom/proprietary/sensors/dsps/libhalsensors/src/sensors_hal.cpp//追到这里就是和平台的sensor框架有关的了,以上都是和平台无关的sensor框架
struct sensors_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = (uint16_t)SENSORS_DEVICE_API_VERSION_1_3,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = SENSORS_HARDWARE_MODULE_ID,//这个是JNI和HAL 的桥梁
.name = "QTI Sensors Module",
.author = "Qualcomm Technologies, Inc.",
.methods = &sensors_module_methods,//sensors_module_methods
.dso = NULL,
.reserved = {0},
},
.get_sensors_list = sensors_get_sensors_list,
.set_operation_mode = sensors_set_operation_mode
};
static struct hw_module_methods_t sensors_module_methods = {
.open = sensors_open
};
static int sensors_open(const struct hw_module_t* module, const char* id,
struct hw_device_t** device)
{
UNREFERENCED_PARAMETER(id);
int ret = -EINVAL;
SensorsContext *dev = SensorsContext::getInstance();
memset(&dev->device, 0, sizeof(sensors_poll_device_1_t));
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = SENSORS_DEVICE_API_VERSION_1_3;
dev->device.common.module = const_cast
dev->device.common.close = sensors_close;
dev->device.activate = sensors_activate;//具体sensor activate
dev->device.setDelay = sensors_set_delay;
dev->device.poll = sensors_poll;
dev->device.batch = sensors_batch;
dev->device.flush = sensors_flush;
*device = &dev->device.common;
ret = 0;
return ret;
}
static int sensors_activate(struct sensors_poll_device_t *dev,
int handle, int en)
{
SensorsContext *ctx = (SensorsContext *)dev;
return ctx->activate(handle, en);//往下应该找到SensorsContext这个结构了
}
vendor/qcom/proprietary/sensors/dsps/libhalsensors/src/SensorsContext.cpp
int SensorsContext::activate(int handle, int en)
{
int err;
HAL_LOG_VERBOSE("%s: handle is %d, en is %d", __FUNCTION__, handle, en);
/* check all sensors and the time_service status */
pthread_mutex_lock(&active_mutex);
if (en) {
time_service->timeServiceStart();
active_sensors |= (1ULL << handle);
}
else {
active_sensors &= ~(1ULL << handle);
if(0 == active_sensors && 1 == time_service->getTimeSyncServiceStatus()) {
HAL_LOG_VERBOSE("All sensors stop, stop the time_service.");
time_service->timeServiceStop();
}
}
pthread_mutex_unlock(&active_mutex);
HAL_LOG_VERBOSE("activate sensors is %" PRIx64"", active_sensors);
if (true == mSensors[handle]->getAttribOK()) {
err = mSensors[handle]->enable(en);//这里才是真正执行enable的地方
if (err) {
HAL_LOG_ERROR("Activate the handle %d is not successful", handle);
active_sensors &= ~(1ULL << handle);
}
}
else {
HAL_LOG_ERROR("The handle %d is not available!", handle);
err = -EINVAL;
}
return err;
}
在头文件SensorsContext.h里可以找到mSensors就是一个子sensor类:
class SensorsContext {
...
/* sub sensor class */
Sensor *mSensors[MAX_NUM_SENSORS];//子sensor类的指针数组,
...
}
它的定义在:frameworks/native/include/gui/Sensor.h
class Sensor : public ASensor, public LightFlattenable
{
...
}
看下这个结构具体被填充赋值的地方:
void SensorsContext::addSensor(int handle)
{
...
case HANDLE_MAGNETIC_FIELD://H10血氧是用地磁sensor来改的
case HANDLE_MAGNETIC_FIELD_WAKE_UP:
if (is_mag_available) {
HAL_LOG_DEBUG("%s: SMGR MAG enabled handle:%d",
__FUNCTION__, handle);
mSensors[handle] = new Magnetic(handle);//所以是new了一个Magnetic Sensor对象来填充
} else {
HAL_LOG_DEBUG("%s: SMGR MAG disabled!", __FUNCTION__);
}
break;
...
}
vendor/qcom/proprietary/sensors/dsps/libhalsensors/src/Magnetic.cpp
Magnetic::Magnetic(int handle)
:SMGRSensor(handle)
{
trigger_mode = SENSOR_MODE_CONT;
(handle == HANDLE_MAGNETIC_FIELD_WAKE_UP)?(bWakeUp = true):(bWakeUp = false);
}
这条SENSORS_HARDWARE_MODULE_ID关联到sensors_hal.cpp的路貌似走不通,没看到具体Hal层和驱动通信的地方!
2)看一下另外两个SENSORS_HARDWARE_MODULE_ID关联的HAL层代码,其他都不是:
Multihal.cpp (z:\home\wanghl\code\msm8909w_android\hardware\libhardware\modules\sensors): .id = SENSORS_HARDWARE_MODULE_ID,
Sensors.cpp (z:\home\wanghl\code\msm8909w_android\hardware\qcom\sensors): id: SENSORS_HARDWARE_MODULE_ID,
网上说是编译Multihal.cpp这个文件,但是实际上这个文件不会被编译,
因为Android.mk的文件里的开关:
hardware/libhardware/modules/sensors/Android.mk:19:ifeq ($(USE_SENSOR_MULTI_HAL),true)
并没有被打开:
device/qcom/msm8909/BoardConfig.mk:139:#USE_SENSOR_MULTI_HAL := true//被注释掉了
通过Sensors.cpp添加log发现走的是Sensors.cpp,开机过程中会打印log:
05-12 14:03:04.484 1938 3008 E Sensors : [wanghl]2.open_sensors in sensors.cpp...
05-12 14:03:04.484 1938 3008 E Sensors : [wanghl]2.activate in sensors.cpp...
sensors_open_1函数:
hardware/libhardware/include/hardware/sensors.h//这个文件是google为sensor提供了统一的HAL接口,不同的硬件厂商需要根据该接口来实现并完成具体的硬件抽象层,Android中的Sensor的HAL接口定义,传感器类型定义都在这里
static inline int sensors_open_1(const struct hw_module_t* module,
sensors_poll_device_1_t** device) {
return module->methods->open(module,
SENSORS_HARDWARE_POLL, (struct hw_device_t**)device);
}
传感器模块的定义结构如下:
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
struct sensors_module_t {
struct hw_module_t common;
/**
* Enumerate all available sensors. The list is returned in "list".
* @return number of sensors in the list
*/
int (*get_sensors_list)(struct sensors_module_t* module,
struct sensor_t const** list);
/**
* Place the module in a specific mode. The following modes are defined
*
* 0 - Normal operation. Default state of the module.
* 1 - Loopback mode. Data is injected for the supported
* sensors by the sensor service in this mode.
* @return 0 on success
* -EINVAL if requested mode is not supported
* -EPERM if operation is not allowed
*/
int (*set_operation_mode)(unsigned int mode);
};
该接口定义实际上是对标准的硬件模块hw_module_t的一个拓展,增加了一个get_sensors_list函数,用于获取传感器列表,以及一个set_operation_mode来设置硬件模块的特殊模式
然后我们来看sensors_open_1里的open到底是哪个函数:
hardware/qcom/sensors/sensors.cpp
typedef struct hw_module_methods_t {//hw_module_methods_t结构只有一个成员,那就是一个函数指针
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
static struct hw_module_methods_t sensors_module_methods = {
open: open_sensors//为函数指针赋值,所以实际上指向的函数就是open_sensors
};
/** Open a new instance of a sensor device using name */
static int open_sensors(const struct hw_module_t* module, const char*,
struct hw_device_t** device)
{
int status = -EINVAL;
sensors_poll_context_t *dev = new sensors_poll_context_t();
NativeSensorManager& sm(NativeSensorManager::getInstance());//其实这里已经获取了一个NativeSensorManager的实例
ALOGE("[wanghl]2.open_sensors in sensors.cpp...");//这条会打印出来
memset(&dev->device, 0, sizeof(sensors_poll_device_1_ext_t));
dev->device.common.tag = HARDWARE_DEVICE_TAG;
#if defined(SENSORS_DEVICE_API_VERSION_1_3)
ALOGI("Sensors device API version 1.3 supported\n");
dev->device.common.version = SENSORS_DEVICE_API_VERSION_1_3;
#else
dev->device.common.version = SENSORS_DEVICE_API_VERSION_0_1;
#endif
dev->device.common.module = const_cast
dev->device.common.close = poll__close;
dev->device.activate = poll__activate;//真正的activate
dev->device.setDelay = poll__setDelay;
dev->device.poll = poll__poll;
dev->device.calibrate = poll_calibrate;
#if defined(SENSORS_DEVICE_API_VERSION_1_3)
dev->device.batch = poll__batch;
dev->device.flush = poll__flush;
#endif
*device = &dev->device.common;
status = 0;
return status;
}
static int poll__activate(struct sensors_poll_device_t *dev,
int handle, int enabled) {
sensors_poll_context_t *ctx = (sensors_poll_context_t *)dev;
return ctx->activate(handle, enabled);//activate就是sensors_poll_context_t这个结构的一个方法,也在本文件里:
}
int sensors_poll_context_t::activate(int handle, int enabled) {
int err = -1;
NativeSensorManager& sm(NativeSensorManager::getInstance());//获取了一个NativeSensorManager的实例
Mutex::Autolock _l(mLock);
ALOGE("[wanghl]2.activate in sensors.cpp...");//这个log也确实打印出来了
err = sm.activate(handle, enabled);
if (enabled && !err) {
const char wakeMessage(WAKE_MESSAGE);
int result = write(mWritePipeFd, &wakeMessage, 1);
ALOGE_IF(result<0, "error sending wake message (%s)", strerror(errno));
}
return err;
}
既然这里获取了一个NativeSensorManager实例,并且调用了这个实例的activate方法,那必然要执行NativeSensorManager类的构造方法!
我们来看这个NativeSensorManager类在一开始构造的时候所做的事情:
hardware/qcom/sensors/NativeSensorManager.cpp
NativeSensorManager::NativeSensorManager():
mSensorCount(0), mScanned(false), mEventCount(0), type_map(NULL), handle_map(NULL), fd_map(NULL)
{
int i;
memset(sensor_list, 0, sizeof(sensor_list));
memset(context, 0, sizeof(context));
type_map.setCapacity(MAX_SENSORS);
handle_map.setCapacity(MAX_SENSORS);
fd_map.setCapacity(MAX_SENSORS);
for (i = 0; i < MAX_SENSORS; i++) {
context[i].sensor = &sensor_list[i];
sensor_list[i].name = context[i].name;
sensor_list[i].vendor = context[i].vendor;
list_init(&context[i].listener);
list_init(&context[i].dep_list);
}
ALOGW("[wanghl]NativeSensorManager and getDataInfo");//05-12 14:53:30.016 1929 3024 W Sensors : [wanghl]NativeSensorManager and getDataInfo
if(getDataInfo()) {//最后跑到了这里
ALOGE("Get data info failed\n");
}
dump();
}
//int NativeSensorManager::activate(int handle, int enable)这个函数并没有执行到真正有和驱动联系的地方,忽略不计
int NativeSensorManager::activate(int handle, int enable)
{
SensorContext *list;
int i;
int number = getSensorCount();
int err = 0;
struct listnode *node;
struct SensorContext *ctx;
struct SensorRefMap *item;
ALOGD("activate called handle:%d enable:%d", handle, enable);
list = getInfoByHandle(handle);
if (list == NULL) {
ALOGE("Invalid handle(%d)", handle);
return -EINVAL;
}
list->enable = enable;
/* one shot sensors don't act as base sensors */
if (list->sensor->flags & SENSOR_FLAG_ONE_SHOT_MODE)
return list->driver->enable(handle, enable);
/* Search for the background sensor for the sensor specified by handle. */
list_for_each(node, &list->dep_list) {
item = node_to_item(node, struct SensorRefMap, list);
if (enable) {
registerListener(item->ctx, list);
#if defined(SENSORS_DEVICE_API_VERSION_1_3)
/* HAL 1.3 already set listener's delay and latency
* Sync it right now to make it take effect.
*/
syncLatency(item->ctx->sensor->handle);
#endif
/* Enable the background sensor and register a listener on it. */
ALOGD("%s calling driver enable", item->ctx->sensor->name);
item->ctx->driver->enable(item->ctx->sensor->handle, 1);//这个enable并没有做什么操作:virtual int enable(int32_t handle, int enabled) = 0;
syncDelay(item->ctx->sensor->handle);
} else {
/* The background sensor has other listeners, we need
* to unregister the current sensor from it and sync the
* poll delay settings.
*/
if (!list_empty(&item->ctx->listener)) {
unregisterListener(item->ctx, list);
/* restore delay settings */
syncDelay(item->ctx->sensor->handle);
#if defined(SENSORS_DEVICE_API_VERSION_1_3)
/* restore latency settings */
syncLatency(item->ctx->sensor->handle);
#endif
}
/* Disable the background sensor if it doesn't have any listeners. */
if (list_empty(&item->ctx->listener)) {
ALOGD("%s calling driver disable", item->ctx->sensor->name);
item->ctx->driver->enable(item->ctx->sensor->handle, 0);这个enable并没有做什么操作
}
}
}
/* Settings change notification */
if (list->is_virtual) {
ALOGD("%s calling driver %s", list->sensor->name, enable ? "enable" : "disable");
list->driver->enable(handle, enable);
}
return err;
}
int NativeSensorManager::getDataInfo() {
int i, j;
struct SensorContext *list;
int has_acc = 0;
int has_compass = 0;
int has_gyro = 0;
int has_light = 0;
int has_proximity = 0;
struct sensor_t sensor_mag;
struct sensor_t sensor_acc;
struct sensor_t sensor_light;
struct sensor_t sensor_proximity;
struct sensor_t sensor_gyro;
mSensorCount = getSensorListInner();
for (i = 0; i < mSensorCount; i++) {
struct SensorRefMap *item;
list = &context[i];
list->is_virtual = false;
item = new struct SensorRefMap;
item->ctx = list;
/* hardware sensor depend on itself */
list_add_tail(&list->dep_list, &item->list);
if (strlen(list->data_path) != 0)
list->data_fd = open(list->data_path, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
else
list->data_fd = -1;
if (list->data_fd > 0) {
fd_map.add(list->data_fd, list);
} else {
ALOGE("open %s failed, continue anyway.(%s)\n", list->data_path, strerror(errno));
}
type_map.add(list->sensor->type, list);
handle_map.add(list->sensor->handle, list);
switch (list->sensor->type) {
case SENSOR_TYPE_ACCELEROMETER:
has_acc = 1;
list->driver = new AccelSensor(list);
sensor_acc = *(list->sensor);
break;
case SENSOR_TYPE_MAGNETIC_FIELD://以血氧为例,最关键就是跑到这里了
has_compass = 1;
ALOGE("[wanghl]new CompassSensor(list)");//这个log能够打印出来
list->driver = new CompassSensor(list);//new一个CompassSensor
sensor_mag = *(list->sensor);
break;
case SENSOR_TYPE_PROXIMITY:
has_proximity = 1;
#if defined(SENSORS_DEVICE_API_VERSION_1_3)
/* reporting mode fix up */
list->sensor->flags |= SENSOR_FLAG_ON_CHANGE_MODE;
#endif
list->driver = new ProximitySensor(list);
sensor_proximity = *(list->sensor);
break;
case SENSOR_TYPE_LIGHT:
has_light = 1;
#if defined(SENSORS_DEVICE_API_VERSION_1_3)
/* reporting mode fix up */
list->sensor->flags |= SENSOR_FLAG_ON_CHANGE_MODE;
#endif
list->driver = new LightSensor(list);
sensor_light = *(list->sensor);
break;
case SENSOR_TYPE_GYROSCOPE:
has_gyro = 1;
list->driver = new GyroSensor(list);
sensor_gyro = *(list->sensor);
break;
case SENSOR_TYPE_PRESSURE:
list->driver = new PressureSensor(list);
break;
case SENSOR_TYPE_SIGNIFICANT_MOTION:
list->driver = new SmdSensor(list);
break;
default:
list->driver = NULL;
ALOGE("No handle %d for this type sensor!", i);
break;
}
initCalibrate(list);
}
/* Some vendor or the reference design implements some virtual sensors
* or pseudo sensors. These sensors are required by some of the applications.
* Here we check the CalibratoinManager to decide whether to enable them.
*/
CalibrationManager &cm(CalibrationManager::getInstance());
struct SensorRefMap *ref;
char *chip;
if (has_light && has_proximity) {
compositeVirtualSensorName(sensor_proximity.name, virtualSensorName[POCKET], SENSOR_TYPE_POCKET);
ALOGD("pocket virtual sensor name changed to %s\n", virtualSensorName[POCKET]);
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
virtualSensorList[POCKET])) {
addDependency(&context[mSensorCount], sensor_proximity.handle);
addDependency(&context[mSensorCount], sensor_light.handle);
mSensorCount++;
}
}
if (has_acc && has_compass) {
compositeVirtualSensorName(sensor_mag.name, virtualSensorName[ORIENTATION], SENSOR_TYPE_ORIENTATION);
ALOGD("orientation virtual sensor name changed to %s\n", virtualSensorName[ORIENTATION]);
/* HAL implemented orientation. Android will replace it for
* platform with Gyro with SensorFusion.
* The calibration manager will first match "oem-orientation" and
* then match "orientation" to select the algorithms. */
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
virtualSensorList[ORIENTATION])) {
addDependency(&context[mSensorCount], sensor_acc.handle);
addDependency(&context[mSensorCount], sensor_mag.handle);
mSensorCount++;
}
if (!has_gyro) {
compositeVirtualSensorName(sensor_mag.name, virtualSensorName[ORIENTATION], SENSOR_TYPE_ORIENTATION);
ALOGD("orientation virtual sensor name changed to %s\n", virtualSensorName[ORIENTATION]);
/* Pseudo gyroscope is a pseudo sensor which implements by accelerometer and
* magnetometer. Some sensor vendors provide such implementations. The pseudo
* gyroscope sensor is low cost but the performance is worse than the actual
* gyroscope. So disable it for the system with actual gyroscope. */
#ifdef ENABLE_DEPRECATED_VITRUAL_SENSOR
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
virtualSensorList[PSEUDO_GYROSCOPE])) {
addDependency(&context[mSensorCount], sensor_acc.handle);
addDependency(&context[mSensorCount], sensor_mag.handle);
mSensorCount++;
}
#endif
compositeVirtualSensorName(sensor_mag.name, virtualSensorName[LINEAR_ACCELERATION], SENSOR_TYPE_LINEAR_ACCELERATION);
ALOGD("liear acceleration virtual sensor name changed to %s\n", virtualSensorName[LINEAR_ACCELERATION]);
/* For linear acceleration */
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
virtualSensorList[LINEAR_ACCELERATION])) {
addDependency(&context[mSensorCount], sensor_acc.handle);
addDependency(&context[mSensorCount], sensor_mag.handle);
mSensorCount++;
}
compositeVirtualSensorName(sensor_mag.name, virtualSensorName[ROTATION_VECTOR], SENSOR_TYPE_ROTATION_VECTOR);
ALOGD("rotation vector virtual sensor name changed to %s\n", virtualSensorName[ROTATION_VECTOR]);
/* For rotation vector */
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
virtualSensorList[ROTATION_VECTOR])) {
addDependency(&context[mSensorCount], sensor_acc.handle);
addDependency(&context[mSensorCount], sensor_mag.handle);
mSensorCount++;
}
compositeVirtualSensorName(sensor_mag.name, virtualSensorName[GRAVITY], SENSOR_TYPE_GRAVITY);
ALOGD("gravity virtual sensor name changed to %s\n", virtualSensorName[GRAVITY]);
/* For gravity */
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
virtualSensorList[GRAVITY])) {
addDependency(&context[mSensorCount], sensor_acc.handle);
addDependency(&context[mSensorCount], sensor_mag.handle);
mSensorCount++;
}
}
}
if (has_compass) {
/* The uncalibrated magnetic field sensor shares the same vendor/name as the
* calibrated one. */
sensor_mag.type = SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED;
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
sensor_mag)) {
addDependency(&context[mSensorCount], sensor_mag.handle);
mSensorCount++;
}
}
if (has_gyro) {
sensor_gyro.type = SENSOR_TYPE_GYROSCOPE_UNCALIBRATED;
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
sensor_gyro)) {
addDependency(&context[mSensorCount], sensor_gyro.handle);
mSensorCount++;
}
}
if (has_acc && has_gyro) {
/* For game rotation vector */
if (!initVirtualSensor(&context[mSensorCount], SENSORS_HANDLE(mSensorCount),
virtualSensorList[GAME_ROTATION_VECTOR])) {
addDependency(&context[mSensorCount], sensor_acc.handle);
addDependency(&context[mSensorCount], sensor_gyro.handle);
mSensorCount++;
}
}
return 0;
}
hardware/qcom/sensors/CompassSensor.cpp//这才真正跑到指南针hal层驱动的地方
CompassSensor::CompassSensor(struct SensorContext *context)
: SensorBase(NULL, NULL, context),
mInputReader(4),
mHasPendingEvent(false),
mEnabledTime(0),
res(CONVERT_MAG)
{
int handle = -1;
res = context->sensor->resolution;
memset(mPendingEvent.data, 0, sizeof(mPendingEvent.data));
mPendingEvent.version = sizeof(sensors_event_t);
mPendingEvent.sensor = context->sensor->handle;
mPendingEvent.type = SENSOR_TYPE_MAGNETIC_FIELD;
mPendingEvent.magnetic.status = SENSOR_STATUS_UNRELIABLE;
data_fd = context->data_fd;
strlcpy(input_sysfs_path, context->enable_path, sizeof(input_sysfs_path));
ALOGE("[wanghl]CompassSensor input_sysfs_path = %s", input_sysfs_path);//05-12 14:54:25.809 3202 3369 E Sensors : [wanghl]CompassSensor input_sysfs_path = /sys/class/sensors/afe4400-Spo2/
input_sysfs_path_len = strlen(input_sysfs_path);
enable(0, 1);//这里调用了本地的enable方法
}
int CompassSensor::enable(int32_t, int en) {//本地enable方法
int flags = en ? 1 : 0;
compass_algo_args arg;
arg.common.enable = flags;
char propBuf[PROPERTY_VALUE_MAX];
property_get("sensors.compass.loopback", propBuf, "0");
if (strcmp(propBuf, "1") == 0) {
ALOGE("sensors.compass.loopback is set");
mEnabled = flags;
mEnabledTime = 0;
return 0;
}
if (flags != mEnabled) {
int fd;
if ((algo != NULL) && (algo->methods->config != NULL)) {
if (algo->methods->config(CMD_ENABLE, (sensor_algo_args*)&arg)) {
ALOGW("Calling enable config failed for compass");
}
}
strlcpy(&input_sysfs_path[input_sysfs_path_len],
SYSFS_ENABLE, SYSFS_MAXLEN);
ALOGE("[wanghl]open input_sysfs_path:%s", input_sysfs_path);//这个log就打印出来了:05-12 14:03:58.998 3191 3352 E Sensors : [wanghl]open input_sysfs_path:/sys/class/sensors/afe4400-Spo2/enable
fd = open(input_sysfs_path, O_RDWR);
if (fd >= 0) {
char buf[2];
int err;
buf[1] = 0;
if (flags) {
buf[0] = '1';//所以真正的使能就是往/sys/class/sensors/afe4400-Spo2/enable 这个文件节点写1
mEnabledTime = getTimestamp() + IGNORE_EVENT_TIME;
} else {
buf[0] = '0';//所以真正的disable就是往/sys/class/sensors/afe4400-Spo2/enable 这个文件节点写0
}
err = write(fd, buf, sizeof(buf));
close(fd);
mEnabled = flags;
return 0;
}
ALOGE("CompassSensor: failed to open %s", input_sysfs_path);
return -1;
}
return 0;
}
int CompassSensor::readEvents(sensors_event_t* data, int count)//sensors_event_t这个结构在hardware/libhardware/include/hardware/sensors.h定义
{
if (count < 1)
return -EINVAL;
if (mHasPendingEvent) {
mHasPendingEvent = false;
mPendingEvent.timestamp = getTimestamp();
*data = mPendingEvent;
return mEnabled ? 1 : 0;
}
if (mHasPendingMetadata) {
mHasPendingMetadata--;
meta_data.timestamp = getTimestamp();
*data = meta_data;
return mEnabled ? 1 : 0;
}
ssize_t n = mInputReader.fill(data_fd);
if (n < 0)
return n;
int numEventReceived = 0;
/*
input_event那就是内核定义的结构体了,内核驱动afe4400就是通过input子系统上报sensor数据的,这就对上了
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
*/
input_event const* event;
sensors_event_t raw, result;
#if FETCH_FULL_EVENT_BEFORE_RETURN
again:
#endif
while (count && mInputReader.readEvent(&event)) {
int type = event->type;
if (type == EV_ABS) {
/*
对比下驱动里的上报操作:
input_report_abs(afe->input, ABS_X, SpO2_high);
input_report_abs(afe->input, ABS_Y, SpO2_low);
input_report_abs(afe->input, ABS_Z, SpO2_high);//no use,but rm it will cause err
input_event(afe->input,
EV_SYN, SYN_TIME_SEC,
ktime_to_timespec(timestamp).tv_sec);
input_event(afe->input,
EV_SYN, SYN_TIME_NSEC,
ktime_to_timespec(timestamp).tv_nsec);
input_sync(afe->input);
*/
float value = event->value;//value值
if (event->code == EVENT_TYPE_MAG_X) {
mPendingEvent.magnetic.x = value * res;
} else if (event->code == EVENT_TYPE_MAG_Y) {
mPendingEvent.magnetic.y = value * res;
} else if (event->code == EVENT_TYPE_MAG_Z) {
mPendingEvent.magnetic.z = value * res;
}
} else if (type == EV_SYN) {
switch (event->code) {
case SYN_TIME_SEC:
mUseAbsTimeStamp = true;
report_time = event->value*1000000000LL;
break;
case SYN_TIME_NSEC:
mUseAbsTimeStamp = true;
mPendingEvent.timestamp = report_time+event->value;
break;
case SYN_REPORT:
if (mUseAbsTimeStamp != true) {
mPendingEvent.timestamp = timevalToNano(event->time);
}
if (mEnabled) {
raw = mPendingEvent;
if (algo != NULL) {
if (algo->methods->convert(&raw, &result, NULL)) {
ALOGE("Calibration failed.");
result.magnetic.x = CALIBRATE_ERROR_MAGIC;
result.magnetic.y = CALIBRATE_ERROR_MAGIC;
result.magnetic.z = CALIBRATE_ERROR_MAGIC;
result.magnetic.status = 0;
}
} else {
result = raw;
}
*data = result;
data->version = sizeof(sensors_event_t);
data->sensor = mPendingEvent.sensor;
data->type = SENSOR_TYPE_MAGNETIC_FIELD;
data->timestamp = mPendingEvent.timestamp;
/* The raw data is stored inside sensors_event_t.data after
* sensors_event_t.magnetic. Notice that the raw data is
* required to composite the virtual sensor uncalibrated
* magnetic field sensor.
*
* data[0~2]: calibrated magnetic field data.
* data[3]: magnetic field data accuracy.
* data[4~6]: uncalibrated magnetic field data.
*/
data->data[4] = mPendingEvent.data[0];
data->data[5] = mPendingEvent.data[1];
data->data[6] = mPendingEvent.data[2];
data++;
numEventReceived++;
count--;
}
break;
}
} else {
ALOGE("CompassSensor: unknown event (type=%d, code=%d)",
type, event->code);
}
mInputReader.next();
}
#if FETCH_FULL_EVENT_BEFORE_RETURN
/* if we didn't read a complete event, see if we can fill and
try again instead of returning with nothing and redoing poll. */
if (numEventReceived == 0 && mEnabled == 1) {
n = mInputReader.fill(data_fd);
if (n)
goto again;
}
#endif
return numEventReceived;
}
5.kernel设备驱动程序
写/sys/class/sensors/afe4400-Spo2/enable的时候触发的是什么流程?
kernel/drivers/sensors/sensors_class.c
static ssize_t sensors_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct sensors_classdev *sensors_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
unsigned long data = 0;
dev_err(dev, "[wanghl]sensors_enable_store buf = %s", buf);//log会打印出来:[ 1202.396651] sensors afe4400-Spo2: [wanghl]sensors_enable_store buf = 1
ret = kstrtoul(buf, 10, &data);
if (ret)
return ret;
if (data > 1) {
dev_err(dev, "Invalid value of input, input=%ld\n", data);
return -EINVAL;
}
if (sensors_cdev->sensors_enable == NULL) {//这里就是从sensor_cdev调用到具体sensor驱动enable的地方:
dev_err(dev, "Invalid sensor class enable handle\n");
return -EINVAL;
}
ret = sensors_cdev->sensors_enable(sensors_cdev, data);
if (ret)
return ret;
sensors_cdev->enabled = data;
return size;
}
kernel/drivers/input/misc/afe4400.c
int afe4400_spo2_probe(struct spi_device *spi)
{
...
s_afe->cdev.sensors_enable = afe_enable_set;//函数指针赋值
...
}
struct sensors_classdev cdev;//cdev就是一个sensors_classdev
struct sensors_classdev {
struct device *dev;
struct list_head node;
const char *name;
const char *vendor;
int version;
int handle;
int type;
const char *max_range;
const char *resolution;
const char *sensor_power;
int min_delay;
int fifo_reserved_event_count;
int fifo_max_event_count;
int32_t max_delay;
uint32_t flags;
unsigned int enabled;
unsigned int delay_msec;
unsigned int wakeup;
unsigned int max_latency;
char *params;
struct cal_result_t cal_result;
/* enable and disable the sensor handle*/
int (*sensors_enable)(struct sensors_classdev *sensors_cdev,
unsigned int enabled);
int (*sensors_poll_delay)(struct sensors_classdev *sensors_cdev,
unsigned int delay_msec);
int (*sensors_self_test)(struct sensors_classdev *sensors_cdev);
int (*sensors_set_latency)(struct sensors_classdev *sensor_cdev,
unsigned int max_latency);
int (*sensors_enable_wakeup)(struct sensors_classdev *sensor_cdev,
unsigned int enable);
int (*sensors_flush)(struct sensors_classdev *sensors_cdev);
int (*sensors_calibrate)(struct sensors_classdev *sensor_cdev,
int axis, int apply_now);
int (*sensors_write_cal_params)(struct sensors_classdev
*sensor_cdev, struct cal_result_t *cal_result);
};
static int afe_enable_set(struct sensors_classdev *sensors_cdev,
unsigned int enable)
{
int ret = 0;
ktime_t ktime;
struct afe4400_SpO2_data *afe = container_of(sensors_cdev,
struct afe4400_SpO2_data, cdev);
dev_warn(&afe->spi->dev, "afe_enable_set enable = %d\n", enable);
sample_cnt = 0;//clear the count
mutex_lock(&afe->val_mutex);
afe->enable_flag &= ~(1<
afe_spo2_sysfs_update_status(afe);
mutex_lock(&afe->op_mutex);
if (enable) {
dev_err(&afe->spi->dev, "afe4400_power_set true\n");
afe4400_power_set(afe, true);
afe4400_reset_config(afe, false);
}
else
{
dev_err(&afe->spi->dev, "afe4400_power_set false\n");
afe4400_power_set(afe, false);
}
if (afe->use_poll && afe->pdata->auto_report) {
if (enable) {
ktime = ktime_set(0,
afe->delay[SPO2_DATA_FLAG] * NSEC_PER_MSEC);
hrtimer_start(&afe->spo2_timer, ktime, HRTIMER_MODE_REL);
} else {
ret = hrtimer_try_to_cancel(&afe->spo2_timer);
}
} else {
if (enable)
enable_irq(afe->spi->irq);
else
disable_irq(afe->spi->irq);
}
mutex_unlock(&afe->op_mutex);
return ret;
}
msm8909:/ # echo 1 > /sys/class/sensors/afe4400-Spo2/enable
[ 1202.396651] sensors afe4400-Spo2: [wanghl]sensors_enable_store buf = 1//sensors_class.c打印
[ 1202.402332] AFE4400 spi3.0: afe_enable_set enable = 1//afe4400.c打印
[ 1202.407483] AFE4400 spi3.0: afe4400_power_set true
kernel/include/linux/sensors.h
struct sensors_classdev {
struct device *dev;
struct list_head node;
const char *name;
const char *vendor;
int version;
int handle;
int type;
const char *max_range;
const char *resolution;
const char *sensor_power;
int min_delay;
int fifo_reserved_event_count;
int fifo_max_event_count;
int32_t max_delay;
uint32_t flags;
unsigned int enabled;
unsigned int delay_msec;
unsigned int wakeup;
unsigned int max_latency;
char *params;
struct cal_result_t cal_result;
/* enable and disable the sensor handle*/
int (*sensors_enable)(struct sensors_classdev *sensors_cdev,
unsigned int enabled);//enable的函数指针
int (*sensors_poll_delay)(struct sensors_classdev *sensors_cdev,
unsigned int delay_msec);
int (*sensors_self_test)(struct sensors_classdev *sensors_cdev);
int (*sensors_set_latency)(struct sensors_classdev *sensor_cdev,
unsigned int max_latency);
int (*sensors_enable_wakeup)(struct sensors_classdev *sensor_cdev,
unsigned int enable);
int (*sensors_flush)(struct sensors_classdev *sensors_cdev);
int (*sensors_calibrate)(struct sensors_classdev *sensor_cdev,
int axis, int apply_now);
int (*sensors_write_cal_params)(struct sensors_classdev
*sensor_cdev, struct cal_result_t *cal_result);
};
----------------------------------
高通的HAL层其实分为2种,一种是直接从kernel这边报数据上来,由sensorhal层来监听,另外一种是走ADSP的模式,HAL层通过QMI的形式进行监听.
H10项目使用以input设备为基础的sensor_class抽象类在AP侧挂载sensor的方法。(使用高通MSM8909平台,Android 7.1)
1)首先基线代码要有对sensors_class的支持,要存在文件kernel/drivers/sensors/sensors_class.c以及头文件kernel/include/linux/sensors.h
sensor_class是google对ap侧sensor设备的抽象,并不是内核自带的。某些android版本可能没有kernel/drivers/sensors/sensors_class.c文件,SDM429/SDM439基线走的是ADSP架构,他就没有这两个文件的支持.
2)注册input设备
int afe4400_spo2_probe(struct spi_device *spi)
{
...
err = afe_spo2_input_init(&s_afe->input);
...
}
//首先需要注册标准的input设备,使用标准方法注册即可,注册后可以在/dev/input中获得相应节点dev/input/event1,读取key event
static int afe_spo2_input_init(
struct input_dev **input)
{
int err = 0;
/* Declare input device */
*input = input_allocate_device();
if (!*input)
return -ENOMEM;
/* Setup input device */
set_bit(EV_ABS, (*input)->evbit);
/* Magnetic field (limited to 16bit) */
input_set_abs_params(*input, ABS_X,
-32768, 32767, 0, 0);
input_set_abs_params(*input, ABS_Y,
-32768, 32767, 0, 0);
input_set_abs_params(*input, ABS_Z,
-32768, 32767, 0, 0);
/* Set name */
(*input)->name = AFE_INPUT_DEVICE_NAME;//#define AFE_INPUT_DEVICE_NAME "afe4400"
/* Register */
err = input_register_device(*input);//input_register_device函数将帮助driver注册到input子系统中去,此时可以到input相关节点去找到节点
if (err) {
input_free_device(*input);
return err;
}
return err;
}
adb shell getevent直接就能知道你的driver注册上没,当底层数据发生变化,是否有数值在event上.
127|msm8909:/ # getevent
add device 1: /dev/input/event9
name: "msm8909-snd-card Headset Jack"
add device 2: /dev/input/event8
name: "msm8909-snd-card Button Jack"
add device 3: /dev/input/event6
name: "virtual_touch"
add device 4: /dev/input/event5
name: "qpnp_pon"
add device 5: /dev/input/event4
name: "mmr901xa"
add device 6: /dev/input/event1//血氧
name: "afe4400"
could not get driver version for /dev/input/mice, Not a typewriter
add device 7: /dev/input/event0
name: "ft5x06_ts"
add device 8: /dev/input/event7
name: "gpio-keys"
add device 9: /dev/input/event3
name: "bma_interrupt"
add device 10: /dev/input/event2
name: "bma2x2-accel"
3)注册sensors_class设备
a,定义新sensor
static struct sensors_classdev sensors_cdev = {
.name = "afe4400-SpO2",
.vendor = "Texas Instruments semiconductor",
.version = 1,
.handle = SENSORS_MAGNETIC_FIELD_HANDLE,
.type = SENSOR_TYPE_MAGNETIC_FIELD,
.max_range = /*"1228.8"*/"4194303",
.resolution = /*"0.15"*/"1",
.sensor_power = "0.35",
.min_delay = /*10000*/2000,//2000us->set the FASTEST data rate
.max_delay = 10000,//10000ms
.fifo_reserved_event_count = 0,
.fifo_max_event_count = 0,
.enabled = 0,
.delay_msec = /*10*/2,//only used at the fiist time probe
.sensors_enable = NULL,
.sensors_poll_delay = NULL,
};
b,使用sensor_class中的注册函数注册sensor设备
int afe4400_spo2_probe(struct spi_device *spi)
{
...
s_afe->cdev = sensors_cdev;
s_afe->cdev.sensors_enable = afe_enable_set;
s_afe->cdev.sensors_poll_delay = afe_poll_delay_set;
dev_info(&spi->dev, "sensors_cdev.delay_msec: %d\n", sensors_cdev.delay_msec);
s_afe->delay[SPO2_DATA_FLAG] = sensors_cdev.delay_msec;
dev_info(&spi->dev, "s_afe->delay[1]: %d\n", s_afe->delay[SPO2_DATA_FLAG]);
err = sensors_classdev_register(&s_afe->input->dev, &s_afe->cdev);//注册函数注册,原型:int sensors_classdev_register(struct device *parent,struct sensors_classdev *sensors_cdev)
...
}
sensor device是基于input devie的,因此父设备为注册的input device。注册后的表现为/sys/class/sensor中出现了我们添加的设备,hal层将通过对/sys/class/sensor中节点的读取来获取设备,向android framwork提供sensor信息,将key event转换成android framwork层的sensor event
======================================================================================
在内核里经常可以看到__init, __exit这样的语句,这都是在init.h中定义的宏,gcc在编译时会将被修饰的内容放到这些宏所代表的section。被修饰的函数将会被link到.init.text段。
以SDM439代码为例:
kernel/msm-4.9/include/linux/init.h
#define __init __section(.init.text) __cold notrace __latent_entropy __noinitretpoline __nocfi//,标记内核启动时所用的初始化代码,内核启动完成后就不再使用。其所修饰的内容被放到.init.text section中。
#define __exit __section(.exit.text) __exitused __cold notrace//标记模块退出代码,对非模块无效
之所以加入这样的宏,原因有2:
1,一部分内核初始化机制依赖于它。如kernel将初始化要执行的init函数,分为7个级别,core_initcall, postcore_initcall, arch_initcall, subsys_initcall, fs_initcall, device_initcall, late_initcall。这7个级别优先级递减,即先执行core_initcall, 最后执行late_initcall。__init修饰的函数最先执行,比这7个还要早!
2,提高系统效率
初始化代码的特点是,在系统启动时运行,且一旦运行后马上退出内存,不再占用内存。
常用的宏:
__init,标记内核启动时所用的初始化代码,内核启动完成后就不再使用。其所修饰的内容被放到.init.text section中。
__exit,标记模块退出代码,对非模块无效
__initdata,标记内核启动时所用的初始化数据结构,内核启动完成后不再使用。其所修饰的内容被放到.init.data section中。
__devinit,标记设备初始化所用的代码
__devinitdata,标记设备初始化所用的数据结构
__devexit,标记设备移除时所用的代码
xxx_initcall,7个级别的初始化函数
These are only macros to locate some parts of the linux code into special areas in the final executing binary. __init, for example (or better the __attribute__ ((__section__(".init.text"))) this macro expands to) instructs the compiler to mark this function in a special way. At the end the linker collects all functions with this mark at the end (or begin) of the binary file.
When the kernel starts, this code runs only once (initialization). After it runs, the kernel can free this memory to reuse it and you will see the kernel message:
"Freeing unused kernel memory: 108k freed"
To use this feature, you need a special linker script file, that tells the linker where to locate all the marked functions.
==============================================================================================================================
创建设备文件节点(使用device_create)
Linux的各种设备都以文件的形式存放在/dev 目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。
1)adb命令:cat /proc/devices //可以查看到主设备号(第1列)和对应的设备名(第2列)主设备号是与驱动对应的概念,同一类设备一般使用相同的主设备号,不同类的设备一般使用不同的主设备号,但是同一驱动可支持多个同类但有一定差异的设备,所以用次设备号来区分,序号一般从0开始
以SDM429手机为例子:
1|V5:/ # cat proc/devices
Character devices:
1 mem
256 msm_rng
5 /dev/tty
5 /dev/console
5 /dev/ptmx
10 misc
13 input
21 sg
29 fb
81 video4linux
86 ch
89 i2c
108 ppp
116 alsa
128 ptm
136 pts
153 spi
166 ttyACM
180 usb
188 ttyUSB
189 usb_device
219 usb_rndis
220 msm_usb_bridge
221 avtimer
222 adsprpc-smd
223 6002000.stm
224 byte-cntr
225 qg
226 qg_battery
227 sensors
228 bt
229 jpeg0
230 cs_spi
231 rmnet_ctrl
232 ttyGS
233 usbmon
234 uio
235 icesdcc
236 qseecom
237 kgsl
238 dia
239 smdpkt
240 smd
241 ttyHS
242 ttyMSM
243 wcnss_wlan
244 wcnss_ctrl
245 dcc_sram
246 subsys
247 ramdump
248 mdss_rotator
249 iio
250 battery_data
251 media
252 rtc
253 msm_sps
254 gpiochip
Block devices:
1 ramdisk
259 blkext
7 loop
8 sd
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
253 device-mapper
254 zram
2)adb命令:cat /dev/ 可以查看到所有的设备节点
1|V5:/ # ls /dev/
6002000.stm gpiochip5 msm_sps subsys_adsp
__properties__ gpiochip6 msm_wma subsys_modem
adsprpc-smd gpiochip7 msm_wmapro subsys_venus
adsprpc-smd-secure gpiochip8 mtp_usb subsys_wcnss
apr_apps2 gpiochip9 network_latency sunwave_fp
ashmem graphics network_throughput tty
at_mdm0 hw_random null ttyMSM0
at_usb0 hwbinder ppp tun
at_usb1 i2c-2 psaux uhid
at_usb2 i2c-3 ptmx uinput
avtimer i2c-5 pts uio0
battery_data icesdcc qce urandom
binder input qg usb-ffs
block ion qg_battery usb_accessory
btpower ipa qseecom usf1
byte-cntr jpeg0 radio0 v4l-subdev0
ccid_bulk kgsl-3d0 ramdump_adsp v4l-subdev1
ccid_ctrl kmsg ramdump_md_modem v4l-subdev10
cg2_bpf kmsg_debug ramdump_memshare_DIAG v4l-subdev11
console loop-control ramdump_memshare_FTM v4l-subdev12
coresight-stm mdss_rotator ramdump_memshare_GPS v4l-subdev13
coresight-tmc-etf media0 ramdump_microdump_modem v4l-subdev14
coresight-tmc-etr media1 ramdump_modem v4l-subdev15
cpu_dma_latency media2 ramdump_smem v4l-subdev16
cpuctl media3 ramdump_venus v4l-subdev17
cpuset memory_bandwidth ramdump_wcnss v4l-subdev18
cs_spi msm-rng random v4l-subdev19
dcc_sram msm_aac rfkill v4l-subdev2
device-mapper msm_aac_in rmnet_ctrl v4l-subdev3
diag msm_alac rtc0 v4l-subdev4
dpl_ctrl msm_amrnb sensors v4l-subdev5
event-log-tags msm_amrnb_in smd1 v4l-subdev6
fd msm_amrwb smd11 v4l-subdev7
fscklogs msm_amrwb_in smd2 v4l-subdev8
full msm_amrwbplus smd21 v4l-subdev9
fuse msm_ape smd22 video0
gpiochip0 msm_audio_cal smd3 video1
gpiochip1 msm_evrc smd36 video2
gpiochip10 msm_evrc_in smd4 video3
gpiochip11 msm_g711alaw smd5 video32
gpiochip12 msm_g711alaw_in smd6 video33
gpiochip13 msm_g711mlaw smd7 vndbinder
gpiochip14 msm_g711mlaw_in smd8 wcnss_ctrl
gpiochip15 msm_hweffects smd_pkt_loopback wcnss_wlan
gpiochip16 msm_mp3 smdcntl0 xt_qtaguid
gpiochip17 msm_multi_aac smdcntl8 zero
gpiochip2 msm_qcelp snd
gpiochip3 msm_qcelp_in socket
gpiochip4 msm_rtac stune
创建设备节点可以手动创建也可以自动创建。
(一),手动创建:可以通过mknod命令手动创建。
mknod命令的格式是:
mknod /dev/设备名 设备类型(字符:c,块:b) 主设备号 从设备号
其中,主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。
因此,想要创建设备节点,需要知道设备类型,及其主从设备号。
例如:mknod /dev/icn6211 c 200 10 就创建了一个icn6211的字符设备节点,主设备号是200,次设备号是10
手动创建设备节点的缺点是容易导致设备号冲突,并且重启后设备是会消失的.
V5:/ # mknod /dev/icn6211 c 200 10//创建设备节点
130|V5:/ # ls /dev/icn6211
/dev/icn6211
V5:/ # rm -f /dev/icn6211//删除设备节点
V5:/ # ls /dev/icn6211
ls: /dev/icn6211: No such file or directory
(二),自动创建:
利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class设备类别,再为每个设备调用device_create创建对应的设备。
调用test_class = class_create(THIS_MODULE, “icn6211_class”);//会在sys/class目录下生成icn6211_class文件夹,icn6211_class可以随意命名;
V5:/ # ls sys/class/icn6211_class/
调用test_class_dev = device_create(test_class, NULL, MKDEV(major, 0), NULL, “icn6211”);//在/dev/目录和/sys/class/icn6211_class目录下分别创建设备文件icn6211和icn6211文件夹
V5:/ # ls dev/icn6211
dev/icn6211
V5:/ # ls sys/class/icn6211_class/icn6211/
dev power subsystem uevent
//具体文件和目录内容比如:
1|V5:/ # cat sys/class/icn6211_class/icn6211/dev
247:0//自动分配的主设备号和次设备号
V5:/ # cat sys/class/icn6211_class/icn6211/uevent
MAJOR=247
MINOR=0
DEVNAME=icn6211
驱动加载时(module_init)完成如下工作:分配设备号-------------注册字符设备------------动态创建设备节点。
驱动卸载时(module_exit)完成如下工作:删除设备节点-------------取消字符设备的注册-----------删除设备号。
kernel/msm-4.9/include/linux/device.h
/**
* device_create - creates a device a
* @class: pointer to the struct class t
* @parent: pointer to the parent struc
* @devt: the dev_t for the char devic
* @drvdata: the data to be added to t
* @fmt: string for the device's name
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
EXPORT_SYMBOL_GPL(device_create);
但是device_create(test_class, NULL, MKDEV(major, 0), NULL, “icn6211”);里的major是哪里来的呢?
还需要通过另外一个接口alloc_chrdev_region得到自动分配的设备号(设备号dev_t类型本身就包含16位的主设备号+16位的次设备号,或者12位的主设备号+20位的次设备号,与内核版本有关)
err = alloc_chrdev_region(&dev, 0, number_of_devices, "icn6211");//次设备号从0开始,总共个数number_of_devices为1,驱动的名字是icn6211
if(err < 0) {
ICN6211_ERROR("Err: Failed to alloc char dev region.\n");
}
test_major = MAJOR(dev);//通过宏定义根据设备号得到主设备号
alloc_chrdev_region函数,来让内核自动给我们分配设备号(类似的函数还有事先指定主、次设备号的register_chrdev_region()和老内核的register_chrdev())
(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
1:这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看是自动分配的是多少,方便我们在mknod创建设备文件时用到主设备号和次设备号。 mknod /dev/xxx c 主设备号 次设备号
2:第二个参数:次设备号的基准,从第几个次设备号开始分配。
3:第三个参数:次设备号的个数。
4:第四个参数:驱动的名字。
5:返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。
设备号的宏操作:
(2)MKDEV、MAJOR、MINOR三个宏
MKDEV:是用来将主设备号和次设备号,转换成一个dev_t类型设备号的
MAJOR:从设备号里面提取出来主设备号的。
MINOR:从设备号中提取出来次设备号的。
=====================================================
函数devm_kzalloc()和kzalloc()一样都是内核内存分配函数,但是devm_kzalloc()是跟设备(装置)有关的,当设备(装置)被拆卸或者驱动(驱动程序)卸载(空载)时,内存会被自动释放。另外,当内存不在使用时,可以使用函数devm_kfree()释放。而kzalloc()则需要手动释放(使用kfree()),但如果工程师检查不仔细,则有可能造成内存泄漏。
注:也就是在驱动的探针函数中调用devm_kzalloc(),在除去函数中调用devm_kfree()函数
=================================================================================
EPOLL接口的系统调用流程分析:
I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作,select、poll 和 epoll 都是 Linux API 提供的 IO 复用方式.与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
android目录说明:
bionic (bionic C库)
external (android使用的一些开源的模组)
现在我们来研究一下linux系统调用的流程,起源是因为我在H10项目的HAL层文件hardware/libhardware/modules/health/health.c里看到EPOLL机制的一个宏定义EPOLLIN在kernel的整个代码目录里都找不到定义的地方,后来才在kernel以外的目录中找到,也就是bionic(C库)里找到:
health.c代码里使用EPOLL机制的地方:
#include
...
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
do {
ret = epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &ev );
} while (ret < 0 && errno == EINTR);
在用户空间和内核空间之间,有一个叫做syscall(系统调用, system call)的中间层,是连接用户态和内核态的桥梁。这样即提高了内核的安全型,也便于移植,只需实现同一套接口即可。Linux系统,用户空间通过向内核空间发出syscall,产生软中断,从而让程序陷入内核态,执行相应的操作。对于每个系统调用都会有一个对应的系统调用号,比很多操作系统要少很多。
wanghl@wanghl-HP:~/code/msm8909w_android$ find bionic/ -type f | xargs grep "EPOLLIN" -n --color 2>/dev/null
bionic/libc/include/sys/epoll.h:39:#define EPOLLIN 0x00000001//这个头文件就和我们hal层的头文件对应起来了,我们展开看下这个头文件:
#ifndef _SYS_EPOLL_H_
#define _SYS_EPOLL_H_
#include
#include
#include
#include
__BEGIN_DECLS
#define EPOLLIN 0x00000001
#define EPOLLPRI 0x00000002
#define EPOLLOUT 0x00000004
#define EPOLLERR 0x00000008
#define EPOLLHUP 0x00000010
#define EPOLLRDNORM 0x00000040
#define EPOLLRDBAND 0x00000080
#define EPOLLWRNORM 0x00000100
#define EPOLLWRBAND 0x00000200
#define EPOLLMSG 0x00000400
#define EPOLLRDHUP 0x00002000
#define EPOLLWAKEUP 0x20000000
#define EPOLLONESHOT 0x40000000
#define EPOLLET 0x80000000
#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3
#define EPOLL_CLOEXEC O_CLOEXEC
typedef union epoll_data {
void* ptr;
int fd;//fd
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events;//对应ev.events = EPOLLIN;
epoll_data_t data;//ev.data.fd = fd;
}
#ifdef __x86_64__
__packed
#endif
;
//5个epoll开放给linux应用层的接口
int epoll_create(int);//创建一个epoll对象,一般epollfd = epoll_create()
int epoll_create1(int);
int epoll_ctl(int, int, int, struct epoll_event*);//(epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件
int epoll_wait(int, struct epoll_event*, int, int);//)等待直到注册的事件发生
int epoll_pwait(int, struct epoll_event*, int, int, const sigset_t*);
__END_DECLS
#endif /* _SYS_EPOLL_H_ */
现在我们来分析下epoll_wait的系统调用流程:
在bionic目录下搜索epoll_wait:
1)
bionic/libc/include/sys/glibc-syscalls.h:370:#define SYS_epoll_wait __NR_epoll_wait
...
#elif defined(__arm__)
...
#define SYS_epoll_wait __NR_epoll_wait
...
2)
跳转到__NR_epoll_wait的定义:
bionic/libc/kernel/uapi/asm-arm/asm/unistd.h:300:#define __NR_epoll_wait (__NR_SYSCALL_BASE + 252)
bionic/libc/kernel/uapi/asm-generic/unistd.h:472:#define __NR_epoll_wait 1069
具体是哪个呢?
内核中也有一份unistd.h与bionic/libc/kernel/uapi/asm-generic/unistd.h对应来描述系统调用号:
kernel/include/uapi/asm-generic/unistd.h
/*
* This file contains the system call numbers, based on the
* layout of the x86-64 architecture, which embeds the
* pointer to the syscall in the table.
*/
...
/* fs/eventpoll.c */根据这个能得到一丝线索,那就是epoll对应的方法sys_epoll_wait位于fs/eventpoll.c文件。
#define __NR_epoll_create1 20
__SYSCALL(__NR_epoll_create1, sys_epoll_create1)
#define __NR_epoll_ctl 21
__SYSCALL(__NR_epoll_ctl, sys_epoll_ctl)
...
#define __NR_epoll_wait 1069
__SYSCALL(__NR_epoll_wait, sys_epoll_wait)
但是注释说明这个应该是属于x86-64位架构的,不属于我们H10(MSM8909)arm32位的架构,所以我们的系统调用号文件就不是这份文件而是
bionic/libc/kernel/uapi/asm-arm/asm/unistd.h
另外:
根据网上查询的资料:
1.内核空间和用户空间通过什么来联系?那就是系统调用号的宏定义:
位于文件/bionic/libc/kernel/uapi/asm-arm/asm/unistd.h,记录着用户空间的系统调用号,格式为
#define __NR_xxx (__NR_SYSCALL_BASE + [num])//这个文件就是由内核空间同名的头文件自动生成的,所以该文件与内核空间的系统调用号是完全一致。
比如我们这里的:
#define __NR_epoll_wait (__NR_SYSCALL_BASE + 252)
内核空间头文件:kernel/arch/arm/include/uapi/asm/unistd.h
#define __NR_epoll_wait (__NR_SYSCALL_BASE+252)
这个内核空间和用户空间的系统调用号就对应上了
2.关于内核态怎么调用软中断进入内核态,我们可以查看汇编定义相关函数的中断调用过程:位于文件/bionic/libc/arch-arm/syscalls/xxx.S,比如epoll_ctl()对应到:
bionic/libc/arch-arm/syscalls/epoll_ctl.S,但是epoll_wait()经过了一定的封装调用,不是直接对应到
epoll_wait.S,而是在用户空间有着这样的调用关系:epoll_wait->epoll_pwait->__epoll_pwait->__epoll_pwait.S
/* Generated by gensyscalls.py. Do not edit. */
#include
ENTRY(__epoll_pwait)
mov ip, sp
stmfd sp!, {r4, r5, r6, r7}
.cfi_def_cfa_offset 16
.cfi_rel_offset r4, 0
.cfi_rel_offset r5, 4
.cfi_rel_offset r6, 8
.cfi_rel_offset r7, 12
ldmfd ip, {r4, r5, r6}
ldr r7, =__NR_epoll_pwait//当调用epoll_pwait时, 系统先保存r7内容, 然后将__NR_epoll_pwait值放入r7, 再执行swi软中断指令切换进内核态。
swi #0//通过写入SWI指令来切换到特权模式,当CPU执行到SWI指令时会从用户模式切换到管理模式下,执行软件中断处理
ldmfd sp!, {r4, r5, r6, r7}
.cfi_def_cfa_offset 0
cmn r0, #(MAX_ERRNO + 1)
bxls lr
neg r0, r0
b __set_errno_internal
END(__epoll_pwait)
3.在内核中有与系统调用号对应的系统调用表,定义在文件/kernel/arch/arm/kernel/calls.S,如下:
/* 250 */ CALL(sys_epoll_create)//250
CALL(ABI(sys_epoll_ctl, sys_oabi_epoll_ctl))//251
CALL(ABI(sys_epoll_wait, sys_oabi_epoll_wait))//此处是252
sys_epoll_wait对应的252就和第一点提到的#define __NR_epoll_wait (__NR_SYSCALL_BASE + 252)对应上了,其中__NR_SYSCALL_BASE=0,也就是__NR_epoll_wait系统调用号=252。
到这里可知252号系统调用对应sys_epoll_wait(),该方法所对应的函数声明在syscalls.h文件
sys_epoll_wait在kernel中的声明是在kernel/include/linux/syscalls.h
asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events,
int maxevents, int timeout);
asmlinkage是gcc标签,代表函数读取的参数来自于栈中,而非寄存器。
4.sys_epoll_wait如何跳到SYSCALL_DEFINE
sys_epoll_wait()定义在内核源码找不到直接定义,而是通过syscalls.h文件中的SYSCALL_DEFINE宏定义。根据前面线索:sys_epoll_wait()定义应该是在kernel/fs/eventpoll.c文件中:
/*
* Implement the event wait interface for the eventpoll file. It is the kernel
* part of the user space epoll_wait(2).
*/
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
int error;
struct fd f;
struct eventpoll *ep;
/* The maximum number of event must be greater than zero */
if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
return -EINVAL;
/* Verify that the area passed by the user is writeable */
if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event)))
return -EFAULT;
/* Get the "struct file *" for the eventpoll file */
f = fdget(epfd);
if (!f.file)
return -EBADF;
/*
* We have to check that the file structure underneath the fd
* the user passed to us _is_ an eventpoll file.
*/
error = -EINVAL;
if (!is_file_epoll(f.file))
goto error_fput;
/*
* At this point it is safe to assume that the "private_data" contains
* our own data structure.
*/
ep = f.file->private_data;
/* Time to fish for events ... */
error = ep_poll(ep, events, maxevents, timeout);
error_fput:
fdput(f);
return error;
}
sys_epoll_wait是通过语句SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)来定义
注意: 宏定义SYSCALL_DEFINEx(xxx,…),展开后对应的方法则是sys_xxx;
注意: 方法参数的个数x,对应于SYSCALL_DEFINEx。
syscalls.h里面的sys_epoll_wait声明总共有4个参数:
asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events,
int maxevents, int timeout);
就基本等价于SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)来定义的四个参数,这里数据类型和参数变量名分开写了
至此,我们就追踪到了epoll_wait的上层应用接口具体到内核的实现接口的具体函数实现是怎么样的了!
补充一下:
能够触发内核响应的有三类
1,系统调用,是基于软件中断实现的,应用层向内核层发起请求的方式--本次我们分析的就是这种情况
2,异常,如缺页异常,使虚拟地址被分配物理空间
3,中断,一般为硬件状态改变发起中断引起内核相应。如usb设备插入等。
Linux 下三种发生系统调用的方法:
1,通过 glibc 提供的库函数
2,使用 syscall 直接调用(比如自己在内核实现的一些系统调用)
3,通过 int 指令陷入(软中断)
android系统下没有使用glibc(glibc 是 Linux 下使用的开源的标准 C 库,它是 GNU 发布的 libc 库,即运行时库。glibc 为程序员提供丰富的 API(Application Programming Interface),除了例如字符串处理、数学运算等用户态服务之外,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。),而是使用一套更为轻量的,与glibc十分相似的bionic
bionic中实现了标准api的c库如stdio,math,string等,其中syscall相关的封装在
bionic/libc/arch-arm/syscalls/
其中每个系统调用为一个汇编文件:
wanghl@wanghl-HP:~/code/sdm439_android$ ls bionic/libc/arch-arm/syscalls/
__accept4.S _exit.S getitimer.S listxattr.S __preadv64.S sched_get_priority_max.S setrlimit.S timerfd_create.S
acct.S ___faccessat.S getpeername.S llistxattr.S prlimit64.S sched_get_priority_min.S setsid.S timerfd_gettime.S
adjtimex.S fallocate64.S getpgid.S __llseek.S process_vm_readv.S sched_getscheduler.S setsockopt.S timerfd_settime.S
__arm_fadvise64_64.S fchdir.S __getpid.S lremovexattr.S process_vm_writev.S sched_rr_get_interval.S __set_tid_address.S __timer_getoverrun.S
bind.S ___fchmodat.S getppid.S lseek.S __pselect6.S sched_setaffinity.S settimeofday.S __timer_gettime.S
__brk.S ___fchmod.S __getpriority.S lsetxattr.S __ptrace.S sched_setparam.S __set_tls.S __timer_settime.S
cacheflush.S fchownat.S getrandom.S madvise.S pwrite64.S sched_setscheduler.S setuid.S times.S
capget.S fchown.S getresgid.S mincore.S __pwritev64.S sched_yield.S setxattr.S truncate64.S
capset.S __fcntl64.S getresuid.S mkdirat.S quotactl.S sendfile64.S shutdown.S truncate.S
chdir.S fdatasync.S getrlimit.S mknodat.S readahead.S sendfile.S __sigaction.S umask.S
chroot.S ___fgetxattr.S getrusage.S mlockall.S readlinkat.S sendmmsg.S sigaltstack.S umount2.S
clock_adjtime.S ___flistxattr.S getsid.S mlock.S read.S sendmsg.S __signalfd4.S uname.S
__clock_getres.S flock.S getsockname.S __mmap2.S readv.S sendto.S socketpair.S unlinkat.S
__clock_gettime.S fremovexattr.S getsockopt.S mount.S __reboot.S setdomainname.S __socket.S unshare.S
___clock_nanosleep.S ___fsetxattr.S __gettimeofday.S mprotect.S recvfrom.S setfsgid.S splice.S utimensat.S
clock_settime.S fstat64.S getuid.S ___mremap.S recvmmsg.S setfsuid.S __statfs64.S vmsplice.S
___close.S fstatat64.S getxattr.S msync.S recvmsg.S setgid.S swapoff.S wait4.S
__connect.S __fstatfs64.S init_module.S munlockall.S removexattr.S setgroups.S swapon.S __waitid.S
delete_module.S fsync.S inotify_add_watch.S munlock.S renameat.S sethostname.S symlinkat.S write.S
dup3.S ftruncate64.S inotify_init1.S munmap.S __rt_sigaction.S setitimer.S __sync_file_range2.S writev.S
dup.S __getcpu.S inotify_rm_watch.S nanosleep.S __rt_sigpending.S setns.S syncfs.S
epoll_create1.S __getcwd.S __ioctl.S __openat.S __rt_sigprocmask.S setpgid.S sync.S
epoll_ctl.S __getdents64.S kill.S personality.S ___rt_sigqueueinfo.S setpriority.S sysinfo.S
__epoll_pwait.S getegid.S klogctl.S pipe2.S __rt_sigsuspend.S setregid.S tee.S
eventfd.S geteuid.S lgetxattr.S __ppoll.S __rt_sigtimedwait.S setresgid.S tgkill.S
execve.S getgid.S linkat.S prctl.S __sched_getaffinity.S setresuid.S __timer_create.S
__exit.S getgroups.S listen.S pread64.S sched_getparam.S setreuid.S __timer_delete.S
=========================================================================================
uevent浅谈:以SDM429代码为例分析:
uevent是一种linux设备模型中的一个组成部分。kset中包含的uevent_ops结构体拥有uevent的操作函数。
kernel/msm-4.9/include/linux/kobject.h
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
struct kset_uevent_ops {
int (* const filter)(struct kset *kset, struct kobject *kobj);
const char *(* const name)(struct kset *kset, struct kobject *kobj);
int (* const uevent)(struct kset *kset, struct kobject *kobj,//uevent的操作函数。
struct kobj_uevent_env *env);
};
uevent可以在设备发生变化时主动通知应用层。是对普通先注册设备后注册驱动模式的一种补充。一般用作usb设备的自动驱动加载、电池电量上报等。
uevent主动通知应用层:有两种方式,第一种是设置环境变量后使用call_usermodehelper_setup函数直接调用应用层程序;第二种是通过netlink向应用层发送消息,在应用层的守护进程收到消息后完成相关操作。其中第一种方式较少使用,以第二种为主。ps:netlink是一种基于socket的内核空间与用户空间的双向通信机制,十分灵活好用。
SDM429平台下,sys目录下有许多uevent节点
./sys/devices/platform/soc/200f000.qcom,spmi/spmi-0/spmi0-02/200f000.qcom,spmi:qcom,pmi632@2:qcom,qpnp-smb5/power_supply/battery/uevent
./sys/devices/platform/soc/200f000.qcom,spmi/spmi-0/spmi0-02/200f000.qcom,spmi:qcom,pmi632@2:qpnp,qg/power_supply/bms/uevent
./sys/class/power_supply/battery/uevent
好像uevent就像其他sysfs中的节点一样仅仅供上层读取。然而这并不是uevent的全部,在上面解释的uevent机制中是完全不需要这些节点存在的,事实上上层也并不会去使用这些节点。这些节点的存在仅仅是为了调试目的,可以提供一种简单方式检测到uevent的中间信息。但在android的电源管理结构中电池的相关信息是通过读取/sys/.../uevent节点获取的,netlink消息只发出KOBJ_CHANGE的action,kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE),去触发上层读取sys节点。
简单记录下电池电量上报的流程:
smbchg_charging_status_change->set_property_on_fg->power_supply_set_property->set_property->
chip->batt_psy_d.set_property = qpnp_batt_power_set_property;(qpnp_lbc_main_probe函数里赋值)
->power_supply_changed//调用power_supply_changed发送netlink消息,通知应用层重新读取
void power_supply_changed(struct power_supply *psy)
{
...
schedule_work(&psy->changed_work);
}
INIT_WORK(&psy->changed_work, power_supply_changed_work);
->power_supply_changed_work//kernel/msm-4.9/drivers/power/supply/power_supply_core.c
static void power_supply_changed_work(struct work_struct *work)
{
...
kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE);//kobject_uevent即发送netlink消息到上层
...
}
->kobject_uevent_env->netlink_broadcast_filtered发送netlink消息
//Kobject:Sysfs文件系统中最基本的结构就是kobject,kobject可以代表一个设备,一条总线等。
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;//uevent在这个结构里面
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
uevent是kobject的一部分,用于在kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。该机制通常是用来支持热拔插设备的,例如U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态地支持该设备。uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发uevent提供的接口。uevent模块准备好上报事件的格式后,
可以通过两个途径把事件上报到用户空间:一种是通过kmod(内核模块工具)模块,直接调用用户空间的可执行文件;另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。
至于android上层:android提供了UEventObserver这个类来使java可以监听uevent事件,这个类是一个抽象类,使用这个类必须实现onUEvent函数。
UEventObserver是android Java层利用uevent与获取Kernel层状态变化的机制。
通过grep发现framework有如下模块使用UEventObserver的功能来提供服务:
电池状态:frameworks/base/services/core/java/com/android/server/BatteryService.java:235: UEventObserver invalidChargerObserver = new UEventObserver() {
耳机状态:没找到
DOCK状态:frameworks/base/services/core/java/com/android/server/DockObserver.java:237: private final UEventObserver mObserver = new UEventObserver() {
USB状态: frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java:220: private final class UsbUEventObserver extends UEventObserver {
它们全部继承自UEventObserver,先看看这个类的构造和原理:
frameworks/base/core/java/android/os/UEventObserver.java
|
[ nativeSetup(), nativeWaitForNextEvent() ]
\|/
frameworks/base/core/jni/android_os_UEventObserver.cpp
|
[ uevent_init(),uevent_next_event() ]
\|/
hardware/libhardware_legacy/uevent.c// 在uevent.c中用poll调用来获取event,会阻塞
| [userspace]
---------------------[socket]-----------------------------------------------------------------------------
|
\|/ [kernel]
socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)
我们看下HAL层读取驱动的实现:
hardware/libhardware_legacy/uevent.c
int uevent_next_event(char* buffer, int buffer_length)
{
while (1) {
struct pollfd fds;
int nr;
fds.fd = fd;
fds.events = POLLIN;
fds.revents = 0;
nr = poll(&fds, 1, -1);
if(nr > 0 && (fds.revents & POLLIN)) {
int count = recv(fd, buffer, buffer_length, 0);
if (count > 0) {
struct uevent_handler *h;
pthread_mutex_lock(&uevent_handler_list_lock);
LIST_FOREACH(h, &uevent_handler_list, list)
h->handler(h->handler_data, buffer, buffer_length);
pthread_mutex_unlock(&uevent_handler_list_lock);
return count;
}
}
}
// won't get here
return 0;
}
========================================================================
分析一下显示子系统:以SDM429为例子!
核心函数:
kernel/msm-4.9/drivers/video/fbdev/core/fbmem.c
/**
* register_framebuffer - registers a frame buffer device
* @fb_info: frame buffer info structure
*
* Registers a frame buffer device @fb_info.
*
* Returns negative errno on error, or zero for success.
*
*/
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
EXPORT_SYMBOL(register_framebuffer);//导出函数,以供实际设备驱动中调用
核心结构体:
kernel/msm-4.9/include/linux/fb.h
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue */
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode */
struct file *file; /* current file node */
#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev;
/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops;
struct device *device; /* This is the parent */
struct device *dev; /* This is this fb device */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
union {
char __iomem *screen_base; /* Virtual address */
char *screen_buffer;
};
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
/* we need the PCI or similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch; /* no VT switch on suspend/resume required */
};
实际设备驱动部分,以高通平台SDM429为例
Mdss_fb.c (kernel/msm-4.9/drivers/video/fbdev/msm/mdss_fb.c): if (register_framebuffer(fbi) < 0) {
MMIO(Memory mapping I/O)即内存映射I/O,它是PCI规范的一部分,I/O设备被放置在内存空间而不是I/O空间。从处理器的角度看,内存映射I/O后系统设备访问起来和内存一样。
static int mdss_fb_register(struct msm_fb_data_type *mfd)
{
int ret = -ENODEV;
int bpp;
char panel_name[20];
struct mdss_panel_info *panel_info = mfd->panel_info;
struct fb_info *fbi = mfd->fbi;
struct fb_fix_screeninfo *fix;
struct fb_var_screeninfo *var;
int *id;
/*
* fb info initialization
*/
fix = &fbi->fix;
var = &fbi->var;
fix->type_aux = 0; /* if type == FB_TYPE_INTERLEAVED_PLANES */
fix->visual = FB_VISUAL_TRUECOLOR; /* True Color */
fix->ywrapstep = 0; /* No support */
fix->mmio_start = 0; /* No MMIO Address */
fix->mmio_len = 0; /* No MMIO Address */
fix->accel = FB_ACCEL_NONE;/* FB_ACCEL_MSM needes to be added in fb.h */
var->xoffset = 0, /* Offset from virtual to visible */
var->yoffset = 0, /* resolution */
var->grayscale = 0, /* No graylevels */
var->nonstd = 0, /* standard pixel format */
var->activate = FB_ACTIVATE_VBL, /* activate it at vsync */
var->height = -1, /* height of picture in mm */
var->width = -1, /* width of picture in mm */
var->accel_flags = 0, /* acceleration flags */
var->sync = 0, /* see FB_SYNC_* */
var->rotate = 0, /* angle we rotate counter clockwise */
mfd->op_enable = false;
switch (mfd->fb_imgType) {
case MDP_RGB_565:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 0;
var->green.offset = 5;
var->red.offset = 11;
var->blue.length = 5;
var->green.length = 6;
var->red.length = 5;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 0;
var->transp.length = 0;
bpp = 2;
break;
case MDP_RGB_888:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 0;
var->green.offset = 8;
var->red.offset = 16;
var->blue.length = 8;
var->green.length = 8;
var->red.length = 8;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 0;
var->transp.length = 0;
bpp = 3;
break;
case MDP_ARGB_8888:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 24;
var->green.offset = 16;
var->red.offset = 8;
var->blue.length = 8;
var->green.length = 8;
var->red.length = 8;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 0;
var->transp.length = 8;
bpp = 4;
break;
case MDP_RGBA_8888:
fix->type = FB_TYPE_PACKED_PIXELS;
fix->xpanstep = 1;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
var->blue.offset = 16;
var->green.offset = 8;
var->red.offset = 0;
var->blue.length = 8;
var->green.length = 8;
var->red.length = 8;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 24;
var->transp.length = 8;
bpp = 4;
break;
case MDP_YCRYCB_H2V1:
fix->type = FB_TYPE_INTERLEAVED_PLANES;
fix->xpanstep = 2;
fix->ypanstep = 1;
var->vmode = FB_VMODE_NONINTERLACED;
/* how about R/G/B offset? */
var->blue.offset = 0;
var->green.offset = 5;
var->red.offset = 11;
var->blue.length = 5;
var->green.length = 6;
var->red.length = 5;
var->blue.msb_right = 0;
var->green.msb_right = 0;
var->red.msb_right = 0;
var->transp.offset = 0;
var->transp.length = 0;
bpp = 2;
break;
default:
pr_err("msm_fb_init: fb %d unknown image type!\n",
mfd->index);
return ret;
}
mdss_panelinfo_to_fb_var(panel_info, var);
fix->type = panel_info->is_3d_panel;
if (mfd->mdp.fb_stride)
fix->line_length = mfd->mdp.fb_stride(mfd->index, var->xres,
bpp);
else
fix->line_length = var->xres * bpp;
var->xres_virtual = var->xres;
var->yres_virtual = panel_info->yres * mfd->fb_page;
var->bits_per_pixel = bpp * 8; /* FrameBuffer color depth */
/*
* Populate smem length here for uspace to get the
* Framebuffer size when FBIO_FSCREENINFO ioctl is called.
*/
fix->smem_len = PAGE_ALIGN(fix->line_length * var->yres) * mfd->fb_page;//设置fbi->fix->smem_len的长度,即分配framebuffer的长度
/* id field for fb app */
id = (int *)&mfd->panel;
snprintf(fix->id, sizeof(fix->id), "mdssfb_%x", (u32) *id);
fbi->fbops = &mdss_fb_ops;
fbi->flags = FBINFO_FLAG_DEFAULT;
fbi->pseudo_palette = mdss_fb_pseudo_palette;
mfd->ref_cnt = 0;
mfd->panel_power_state = MDSS_PANEL_POWER_OFF;
mfd->dcm_state = DCM_UNINIT;
if (mdss_fb_alloc_fbmem(mfd))//分配framebuffer的内存空间
pr_warn("unable to allocate fb memory in fb register\n");
mfd->op_enable = true;
mutex_init(&mfd->update.lock);
mutex_init(&mfd->no_update.lock);
mutex_init(&mfd->mdp_sync_pt_data.sync_mutex);
atomic_set(&mfd->mdp_sync_pt_data.commit_cnt, 0);
atomic_set(&mfd->commits_pending, 0);
atomic_set(&mfd->ioctl_ref_cnt, 0);
atomic_set(&mfd->kickoff_pending, 0);
init_timer(&mfd->no_update.timer);
mfd->no_update.timer.function = mdss_fb_no_update_notify_timer_cb;
mfd->no_update.timer.data = (unsigned long)mfd;
mfd->update.ref_count = 0;
mfd->no_update.ref_count = 0;
mfd->update.init_done = false;
init_completion(&mfd->update.comp);
init_completion(&mfd->no_update.comp);
init_completion(&mfd->power_off_comp);
init_completion(&mfd->power_set_comp);
init_waitqueue_head(&mfd->commit_wait_q);
init_waitqueue_head(&mfd->idle_wait_q);
init_waitqueue_head(&mfd->ioctl_q);
init_waitqueue_head(&mfd->kickoff_wait_q);
ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
if (ret)
pr_err("fb_alloc_cmap() failed!\n");
if (register_framebuffer(fbi) < 0) {//调用fbmem.c中导出的函数注册framebuffer设备
fb_dealloc_cmap(&fbi->cmap);
mfd->op_enable = false;
return -EPERM;
}
snprintf(panel_name, ARRAY_SIZE(panel_name), "mdss_panel_fb%d",
mfd->index);
mdss_panel_debugfs_init(panel_info, panel_name);
pr_info("FrameBuffer[%d] %dx%d registered successfully!\n", mfd->index,
fbi->var.xres, fbi->var.yres);
return 0;
}
static int mdss_fb_alloc_fbmem_iommu(struct msm_fb_data_type *mfd, int dom)
{
void *virt = NULL;
phys_addr_t phys = 0;
size_t size = 0;
struct platform_device *pdev = mfd->pdev;
int rc = 0;
struct device_node *fbmem_pnode = NULL;
if (!pdev || !pdev->dev.of_node) {
pr_err("Invalid device node\n");
return -ENODEV;
}
fbmem_pnode = of_parse_phandle(pdev->dev.of_node,
"linux,contiguous-region", 0);
if (!fbmem_pnode) {
pr_debug("fbmem is not reserved for %s\n", pdev->name);
mfd->fbi->screen_base = NULL;//fb_info->screen_base 是framebuffer起始虚拟地址,也就是mmap后程序写入fb的地址,该地址会直接写入到fb_info->fix.smem_start指向的物理地址。在用户空间的Surfaceflinger把数据写入screen_base时候,该数据通过DMA直接映射到smem_start,LCD控制器就能读到数据。
mfd->fbi->fix.smem_start = 0;
return 0;
}
{
const u32 *addr;
u64 len;
addr = of_get_address(fbmem_pnode, 0, &len, NULL);
if (!addr) {
pr_err("fbmem size is not specified\n");
of_node_put(fbmem_pnode);
return -EINVAL;
}
size = (size_t)len;
of_node_put(fbmem_pnode);
}
pr_debug("%s frame buffer reserve_size=0x%zx\n", __func__, size);
if (size < PAGE_ALIGN(mfd->fbi->fix.line_length *
mfd->fbi->var.yres_virtual))
pr_warn("reserve size is smaller than framebuffer size\n");
rc = mdss_smmu_dma_alloc_coherent(&pdev->dev, size, &phys, &mfd->iova,
&virt, GFP_KERNEL, dom);
if (rc) {
pr_err("unable to alloc fbmem size=%zx\n", size);
return -ENOMEM;
}
if (MDSS_LPAE_CHECK(phys)) {
pr_warn("fb mem phys %pa > 4GB is not supported.\n", &phys);
mdss_smmu_dma_free_coherent(&pdev->dev, size, &virt,
phys, mfd->iova, dom);
return -ERANGE;
}
pr_debug("alloc 0x%zxB @ (%pa phys) (0x%pK virt) (%pa iova) for fb%d\n",
size, &phys, virt, &mfd->iova, mfd->index);
mfd->fbi->screen_base = virt;
mfd->fbi->fix.smem_start = phys;
mfd->fbi->fix.smem_len = size;
return 0;
}
lcd驱动的probe函数中还会在sys文件系统中建立相关节点
/sys/class/graphics/fb0
关于显示子系统,HAL层的几个模块说明:
1)SurfaceFlinger(frameworks/native/services/surfaceflinger/)
SurfaceFlinger 是一个独立的android Service, 它接收所有Window的Surface作为输入,根据ZOrder, 透明度,大小,位置等参数,计算出每个Surface在最终合成图像中的位置,然后交由
HWComposer或OpenGL生成最终的显示Buffer, 然后显示到特定的显示设备上。
2)HWComposer
HWComposer 是 Andrid 4.0后推出的新特性,它定义一套HAL层接口,然后各个芯片厂商根据各种硬件特点来实现。它的主要工作是将SurfaceFlinger计算好的Layer的显示参数最终合成到一个显示Buffer上。注意的是,Surface Flinger 并非是HWComposer的唯一输入,有的Surface 不由Android的WindowManager 管理,比如说摄像头的预览输入Buffer, 可以由硬件直接写入,然后作为HWComposer的输入之一与SurfaceFlinger的输出做最后的合成。
3)gralloc(hardware/qcom/display/libgralloc/)
Android系统在硬件抽象层中提供了一个Gralloc模块,封装了对帧缓冲区的所有访问操作。用户空间的应用程序在使用帧缓冲区之间,首先要加载Gralloc模块,并且获得一个gralloc设备和一个fb设备。有了gralloc设备之后,用户空间中的应用程序就可以申请分配一块图形缓冲区,并且将这块图形缓冲区映射到应用程序的地址空间来,以便可以向里面写入要绘制的画面的内容。最后,用户空间中的应用程序就通过fb设备来将已经准备好了的图形缓冲区渲染到帧缓冲区中去,即将图形缓冲区的内容绘制到显示屏中去。相应地,当用户空间中的应用程序不再需要使用一块图形缓冲区的时候,就可以通过gralloc设备来释放它,并且将它从地址空间中解除映射。
4)ION driver
ION是google在Android4.0 ICS为了解决内存碎片管理而引入的通用内存管理器
=======================================================================
ICN6211一开始读取ID读取不成功是因为IIC地址配错,IIC地址配错的时候,主控还是能发出IIC波形,只是没有得到从机的ACK信号,修正IIC地址之后,能到通信成功读到ID.
ICN6211当验证完彩条测试信号正常之后,配置好DTS相关参数之后,MIPI信号还是不正常,HSYNC和VSYNC信号没有出来最关键的问题是:
qcom,mdss-dsi-force-clock-lane-hs;//DSI Continuous High Speed Mode!
要配置MIPI成强制高速模式输出才会正常输出图像信号,横屏显示正常,竖屏显示不正常(原因未知)
=======================================================================
fb旋转参数配置
1)方法1
fb_info->var->rotate//是否旋转
2)方法2
修改dts属性
kernel/msm-4.9/arch/arm64/boot/dts/qcom/dsi-panel-icn6211-video.dts
+ qcom,mdss-dsi-panel-orientation = "180";
kernel/msm-4.9/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt
- qcom,mdss-dsi-panel-orientation: String used to indicate orientation of panel
"180" = panel is flipped in both horizontal and vertical directions//M560项目实测有效
"hflip" = panel is flipped in horizontal direction//M560项目实测无效
"vflip" = panel is flipped in vertical direction//M560项目实测无效
kernel/msm-4.9/drivers/video/fbdev/msm/mdss_dsi_panel.c
static int mdss_panel_parse_dt(struct device_node *np,
struct mdss_dsi_ctrl_pdata *ctrl_pdata)
{
...
data = of_get_property(np, "qcom,mdss-dsi-panel-orientation", NULL);
if (data) {
pr_debug("panel orientation is %s\n", data);
if (!strcmp(data, "180"))
pinfo->panel_orientation = MDP_ROT_180;
else if (!strcmp(data, "hflip"))
pinfo->panel_orientation = MDP_FLIP_LR;
else if (!strcmp(data, "vflip"))
pinfo->panel_orientation = MDP_FLIP_UD;
}
...
}
3)方法3(M560项目未验证)
device/qcom/M560A/system.prop
+vendor.display.panel_mountflip=3
frameworks/native/services/surfaceflinger/DisplayDevice.cpp
DisplayDevice::DisplayDevice(
...
char property[PROPERTY_VALUE_MAX];
mPanelMountFlip = 0;
// 1: H-Flip, 2: V-Flip, 3: 180 (HV Flip)
property_get("vendor.display.panel_mountflip", property, "0");
mPanelMountFlip = atoi(property);
...
==============================================================
C2395摄像头效果调优:
Luma Target:目标亮度
3A模块:调颜色AWB,LSC,AF
CPP:锐度相关
ISP:噪点相关
Black Level:暗电流(C2395原厂工程师的说法),会影响到亮度!(common文件夹),黑电平(black level)指在经过一定校准的显示装置上,没有一行光亮输出的视频信号电平。定义图像数据为0时对应的信号电平,调节黑电平不影响信号的放大倍数,而仅仅是对信号进行上下平移。如果向上调节黑电平,图像将变暗,如果向下调节黑电平图像将变亮。黑电平为0时,对应0V以下的电平都转换为图像数据0,0V以上的电平则按照增益定义的放大倍数转换,最大数值为255。
AEC是打开预览之后会一直写Gain值到寄存器
帧率:寄存器设置的是30fps
ASF:清晰度
ABF2:Adaptive Bayer Filter 2(ABF2,去噪,如果有硬件小波,这部分参数调试较少,且值都放的比较小一些)
BPC2:Bad Pixel Correction 2 (Defect Pixel Correction).
AEC收敛速度
==================
ICN6211 LK阶段显示调试,LK阶段配置BLSP1_QUP3为I2C功能:
首先第一步要先排查硬件是否就绪了,比如ICN6211的VDD1(1.8V,由L5给出来的,L5默认就有拉高,用示波器量了波形确认了),VDD2(3.3V,硬件一上电就分压得到3.3V,示波器确认了),
I2C的上拉电阻电压由L6控制,这个也是一开始就有去拉了(target_panel_reset函数里面加的),接着是ICN6211的使能脚(GPIO23)拉高之后再去进行读取,不要在拉高之前去读取和写入,所以读取和写入操作的时间点就要选择在ICN6211 EN拉高之后!
bootable/bootloader/lk/target/msm8952/target_display.c
int target_ldo_ctrl(uint8_t enable, struct msm_panel_info *pinfo)
{
int rc = 0;
uint32_t ldo_num = REG_LDO6 | REG_LDO17;
uint32_t pmic_type = target_get_pmic();
if (platform_is_msm8956())
ldo_num |= REG_LDO1;
else if (platform_is_sdm439() || platform_is_sdm429())
ldo_num |= REG_LDO5; /* LDO23 is enable by default */
else
ldo_num |= REG_LDO2;
dprintf(INFO, "[ICN6211]%s: enable L5\n", __func__);
...
}
从log上看上电的先后顺序:
[700] [ICN6211]target_ldo_ctrl: enable L5//LDO上电
[700] Received SUCCESS CMD ACK
[700] Received SUCCESS CMD ACK
[710] Received SUCCESS CMD ACK
[720] wled_init: 0 5500000 5500000 5500000 5500000 4600000 1400000 1 0 1
[770] target_panel_reset: gpio=4 enable=1
[780] LCD_EN set high
[780] ICN6211_EN set high//ICN6211 EN 上电
往下采取读取ID和写初始化寄存器
再来检查寄存器配置,对比kernel的文件来检查CLK相关的寄存器设置,将其配置成与kernel配置一样
kernel参考文件:
kernel/msm-4.9/drivers/clk/msm/clock-gcc-8952.c
bootable/bootloader/lk/platform/msm8952/include/platform/iomap.h
@@ -102,6 +102,33 @@
#define GCC_CRYPTO_AXI_CBCR (CLK_CTL_BASE + 0x16020)
#define GCC_CRYPTO_AHB_CBCR (CLK_CTL_BASE + 0x16024)
+/*
+refer to kernel:kernel/msm-4.9/include/dt-bindings/clock/msm-clocks-hwio-8952.h
+#define BLSP1_QUP1_I2C_APPS_CMD_RCGR 0x0200C
+#define BLSP1_QUP1_I2C_APPS_CBCR 0x02008
+
+#define BLSP1_QUP2_I2C_APPS_CMD_RCGR 0x03000
+#define BLSP1_QUP2_I2C_APPS_CBCR 0x03010
+
+#define BLSP1_QUP3_I2C_APPS_CMD_RCGR 0x04000
+#define BLSP1_QUP3_I2C_APPS_CBCR 0x04020
+
+#define BLSP1_QUP4_I2C_APPS_CMD_RCGR 0x05000
+#define BLSP1_QUP4_I2C_APPS_CBCR 0x05020
+ */
+/* I2C add by wanghl*/
+#define BLSP_QUP_BASE(blsp_id, qup_id) (PERIPH_SS_BASE + 0xB5000 + 0x1000 * qup_id)
+#define GCC_BLSP1_QUP2_APPS_CBCR (CLK_CTL_BASE + 0x3010)
+#define GCC_BLSP1_QUP2_CFG_RCGR (CLK_CTL_BASE + 0x3004)
+#define GCC_BLSP1_QUP2_CMD_RCGR (CLK_CTL_BASE + 0x3000)
+
+#define GCC_BLSP1_QUP3_APPS_CBCR (CLK_CTL_BASE + 0x4020)
+#define GCC_BLSP1_QUP3_CFG_RCGR (CLK_CTL_BASE + 0x4004)
+#define GCC_BLSP1_QUP3_CMD_RCGR (CLK_CTL_BASE + 0x4000)
+
+#define GCC_BLSP1_QUP4_APPS_CBCR (CLK_CTL_BASE + 0x5020)
+#define GCC_BLSP1_QUP4_CFG_RCGR (CLK_CTL_BASE + 0x5004)
+#define GCC_BLSP1_QUP4_CMD_RCGR (CLK_CTL_BASE + 0x5000)
模拟IIC都能通信成功,但是高通自带的IIC读写驱动却连波形都出不来原因在于配置GPIO口功能错误!另外:IIC地址和DTS地址的格式一样都是读/写地址右移一位之后的地址.
bootable/bootloader/lk/platform/msm8952/gpio.c
+void gpio_config_blsp_i2c(uint8_t blsp_id, uint8_t qup_id)
+{
+ if(blsp_id == BLSP_ID_1) {
+ switch (qup_id) {
+ case QUP_ID_1:
+ /* configure I2C SDA gpio */
+ gpio_tlmm_config(6, 3, GPIO_OUTPUT, GPIO_NO_PULL,
+ GPIO_8MA, GPIO_DISABLE);
+
+ /* configure I2C SCL gpio */
+ gpio_tlmm_config(7, 3, GPIO_OUTPUT, GPIO_NO_PULL,
+ GPIO_8MA, GPIO_DISABLE);
+ break;
+ case QUP_ID_2:
+ /* configure I2C SDA gpio */
+ gpio_tlmm_config(10, 3, GPIO_OUTPUT, GPIO_NO_PULL,//一开始参照gpio excel表来配置功能为2,发现连波形都出不来,后来高通参照寄存器描述文档发现应该配置成3,就能输出波形并且读到ID
+ GPIO_8MA, GPIO_DISABLE);
+
+ /* configure I2C SCL gpio */
+ gpio_tlmm_config(11, 3, GPIO_OUTPUT, GPIO_NO_PULL,//参照的文档:80-pf886-2x_b_sdm429_sdm439_hardware_register_description.pdf
+ GPIO_8MA, GPIO_DISABLE);
+ break;
+ case QUP_ID_3:
+ /* configure I2C SDA gpio */
+ gpio_tlmm_config(14, 2, GPIO_OUTPUT, GPIO_NO_PULL,
+ GPIO_8MA, GPIO_DISABLE);
+
+ /* configure I2C SCL gpio */
+ gpio_tlmm_config(15, 2, GPIO_OUTPUT, GPIO_NO_PULL,
+ GPIO_8MA, GPIO_DISABLE);
+ break;
+ default:
+ dprintf(CRITICAL, "Incorrect QUP id %d\n",qup_id);
+ ASSERT(0);
+ };
+ } else {
+ dprintf(CRITICAL, "Incorrect BLSP id %d\n",blsp_id);
+ ASSERT(0);
+ }
+}
/bootable/bootloader/lk/platform/msm_shared/i2c_qup.c
+#define DEBUG_QUP 1//可以打开相应的QUP读写的时候的log
bootable/bootloader/lk/platform/msm_shared/mipi_dsi_i2c.c
int mipi_dsi_i2c_device_init(uint8_t blsp_id, uint8_t qup_id)
{
i2c_dev = qup_blsp_i2c_init(blsp_id, qup_id,
/*I2C_CLK_FREQ*/400000, /*I2C_SRC_CLK_FREQ*/19200000);
#define I2C_CLK_FREQ 100000
#define I2C_SRC_CLK_FREQ 50000000
为什么要该I2C的通讯速率以及I2C的CLK源的速率呢?
第一,ICN6211 datasheet说明可以支持400K的速率
第二,根据80-nu767-1_j_bam_low-speed_peripherals_(blsp)_user_guide.pdf文档的描述:
I2C CLK. should be 100KHZ, or 400KHz
Source clock, should be set @ 19.2 MHz for V1 and 50MHz for V2
我们在bootable/bootloader/lk/platform/msm8952/platform.c里头添加打印:
@@ -366,10 +366,10 @@ uint32_t platform_is_msm8976_v_1_1()
{
uint32_t soc_ver = board_soc_version();
uint32_t ret = 0;
-
+ dprintf(INFO, "[wanghl]soc_ver = %x\n", soc_ver);
if(soc_ver == MSM8976_SOC_V11)
ret = 1;
-
+ dprintf(INFO, "[wanghl]platform_is_msm8976_v_1_1 RET = %d\n", ret);
return ret;
}
[2580] [wanghl]soc_ver = 10000
[2580] [wanghl]platform_is_msm8976_v_1_1 RET = 0
说明不是V2也不是什么V1.1,还是老老实实走V1:19.2MHz
IIC通信正常之后,自检模式的彩条正常,但是logo显示不正常,擦除splash分区之后,linux小企鹅显示正常,烧录一张制作的开机logo,splash.img也正常显示
#if ENABLE_MIPI2RGB_ICN6211
宏开关控制所有添加的代码
====================================