关机充电服务
最近公司在预研Android P的项目,在第一轮测试后有一个关机充电背光未关闭的问题,在这之前从未接触过充电和背光部分,开启漫漫源码之路。
关机充电启动
路径:vendor/mediatek/proprietary/external/charger/kpoc_charger.rc
如下所示,在此.rc文件里面,会去开启kpoc_charger服务,若想手动的运行此服务,可通过adb命令./system/bin/kpoc_charger执行
on charger
start kpoc_charger
service kpoc_charger /system/bin/kpoc_charger
class charger
关机充电main函数
在路径vendor/mediatek/proprietary/external/charger/下可以看到很多文件,服务相关的代码基本上都在这个目录下了,从main.cpp切入:
int main(__attribute__((unused))int argc, __attribute__((unused))char *argv[])
{
//设置充电动画的绘制模式
set_draw_anim_mode(1);
pthread_mutex_init(&lights_mutex, NULL);
setpriority(PRIO_PROCESS, 0, -20);
FILE *oom_adj = fopen("/proc/self/oom_score_adj", "w");
if (oom_adj) {
fputs("-17", oom_adj);
fclose(oom_adj);
}
//stop_backlight();
bootlogo_init(); //充电动画相关的的初始化工作
alarm_control();
charging_control(); //控制充电时led灯和动画效果(重要!)
unsigned int i;
for (i=0; i< ARRAY_SIZE(pwrkeys); i++)
KPOC_LOGI("pwrkeys[%d]:%d\n",i,pwrkeys[i]);
key_control(pwrkeys, ARRAY_SIZE(pwrkeys)); //will loop inside
return 0;
}
控制充电时背光和动画效果
charging_control定义在charging_control.cpp中,在此函数中会去初始化led灯,然后开启两个线程,第一个线程这里没研究,看网上有说法是操作led灯的,第二个线程控制充电动画效果和LCD背光;
void charging_control()
{
int ret = 0;
pthread_attr_t attr, attrd, attrl;
pthread_t uevent_thread, draw_thread, light_thread;
//charging led control
if (!is_charging_source_available()) {
lights_exit();
}
pthread_mutex_init(&mutexlstate, NULL);
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_attr_init(&attr);
pthread_attr_init(&attrd);
pthread_attr_init(&attrl);
inDraw = 0;
//
ret = pthread_create(&uevent_thread, &attr, uevent_thread_routine, NULL);
if (ret != 0)
{
KPOC_LOGI("create uevt pthread failed.\n");
exit_charger(EXIT_ERROR_SHUTDOWN);
}
firstTime = 1;
//在这篇文章中主要是看这一个线程
ret = pthread_create(&draw_thread, &attrd, draw_thread_routine, NULL);
if (ret != 0)
{
KPOC_LOGI("create draw pthread failed.\n");
exit_charger(EXIT_ERROR_SHUTDOWN);
}
}
draw_thread_routine函数的代码如下:
bc = get_capacity(); //获取电量的百分比;
draw_with_interval();//设置播放充电动画时长和动画图片间时间间隔的;
stop_backlight(); //关闭背光(重点!);
request_suspend(); //唤醒early_suspend机制;
static void* draw_thread_routine(__attribute__((unused))void *arg)
{
int bc;
int fd_fb = -1, err =0;
char filename[32] = {0};
do {
KPOC_LOGI("draw thread working2...\n");
// move here to avoid suspend when syncing with surfaceflinger
if(firstTime){
// make sure charging source online when in KPOC mode
// add 2s tolerance
if(wait_until(is_charging_source_available,
charging_source_waiting_duration_ms,
charging_source_waiting_interval_ms))
{
KPOC_LOGI("wait until charging source available\n");
}else{
KPOC_LOGI("charging source not available for %d ms at KPOC starup\n",
charging_source_waiting_duration_ms);
}
firstTime = 0;
}
inDraw = 1;
// check the bc offest value
bc = get_capacity();
draw_with_interval(bootlogo_show_charging, bc, nChgAnimDuration_msec, nCbInterval_msec);
stop_backlight();
/*我的理解是:确保先关闭背光再关闭lcd*/
// @@@ draw fb again to refresh ddp
bootlogo_show_charging(bc, 1);
/* make fb blank */
snprintf(filename, sizeof(filename), "/dev/graphics/fb0");
fd_fb = open(filename, O_RDWR);
if (fd_fb < 0) {
KPOC_LOGI("Failed to open fb0 device: %s", strerror(errno));
}
err = ioctl(fd_fb, FBIOBLANK, FB_BLANK_POWERDOWN);
if (err < 0) {
KPOC_LOGI("Failed to blank fb0 device: %s", strerror(errno));
}
if (fd_fb >= 0)
close(fd_fb);
//加载early_suspend机制,关闭相关模块
request_suspend(true);
inDraw = 0;
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
} while(1);
pthread_exit(NULL);
return NULL;
}
draw_with_interval()函数通过循环实现动画效果,若是规定的时间已经到了或者有按键事件发生便退出while循环,终止充电动画的显示。在while循环中第一次循环会开启背光(按照代码的逻辑应该是这样执行的,但是在log中没有看到在此while循环中去执行star_backlight()这个函数,这个现象很奇怪,现在还没找到合理的解释,有知道的大佬希望可以在评论区里告知一声,感谢!)。
draw_with_interval(bootlogo_show_charging, bc, nChgAnimDuration_msec, nCbInterval_msec);
nChgAnimDuration_msec:表示播放动画的总时长;
nCbInterval_msec:表示上一个图片和下一个图片之间的时间间隔;
bootlogo_show_charging:进行图片显示的函数,通过它循环调用它实现动画效果;
bc:表示电量的百分比;
static void draw_with_interval(void (*func)(int, int), int bc, int total_time_msec, int interval_msec)
{
struct timeval start;
int resume_started = 0, backlight_started = 0, cnt = 0;
int fd_fb = -1, err = 0;
char filename[32] = {0};
gettimeofday(&start, NULL);
/*通过循环实现充电动画的效果*/
while(!time_exceed(start, total_time_msec) && !key_trigger_suspend)
{
// check if need to draw animation before performing drawing
if (!is_charging_source_available())
return;
if (!resume_started) {
resume_started = 1;
request_suspend(false);
/* make fb unblank */
snprintf(filename, sizeof(filename), "/dev/graphics/fb0");
fd_fb = open(filename, O_RDWR);
if (fd_fb < 0) {
KPOC_LOGI("Failed to open fb0 device: %s", strerror(errno));
}
err = ioctl(fd_fb, FBIOBLANK, FB_BLANK_UNBLANK);
if (err < 0) {
KPOC_LOGI("Failed to unblank fb0 device: %s", strerror(errno));
}
if (fd_fb >= 0)
close(fd_fb);
}
/*显示充电图片*/
func(bc, ++cnt);
if (!backlight_started) {
backlight_started = 1;
usleep(1000);
start_backlight();
}
KPOC_LOGI("draw_with_interval... key_trigger_suspend = %d\n",key_trigger_suspend);
usleep(interval_msec*1000);
}
}
当充电动画显示结束后,调用stop_backlight()函数关闭背光,函数实现如下:
void stop_backlight()
{
LightState brightness = {
.color = 0u, .flashMode = Flash::NONE, .brightnessMode = Brightness::USER,
};
/*定义在lights.cpp文件*/
set_light_brightness(Type::BACKLIGHT, brightness);
backlight_on = 0;
}
void set_light_brightness(Type light_type, LightState level)
{
if (g_light == nullptr) {
KPOC_LOGI("No light service, cannot set brightness\n");
return;
}
/* 关键!重点!! */
Status ret = g_light->setLight(light_type, level);
if (ret != Status::SUCCESS)
KPOC_LOGI("Failed to set light to 0x%x for type %d, ret=%d\n",
level.color, light_type, ret);
else
KPOC_LOGI("set light to 0x%x for type %d successfully\n",
level.color, light_type);
}
从代码中可以看出来“Status ret = g_light->setLight(light_type, level);”才是真正起作用的,那么g_light是在哪里定义的呢?往下看:
void light_init(void)
{
g_light = ILight::getService();
if (g_light == nullptr) {
KPOC_LOGI("Could not retrieve light service\n");
return;
}
}
当看到“g_light = ILight::getService();” 这行代码的时候相信各位童鞋就知道接下来的工作肯定不是在内核层完成了,要向Hardword层进军了,这也是我第一次去看HAL层的代码,对于C++完全是陌生的,只能边学边看……
首先得知道ILight是个什么东西,是在哪里被定义的,在同一目录下main.h这个头文件里有这么一段:
……………………
// Use light HAL
#include <android/hardware/light/2.0/ILight.h>
……………………
从以上路径大概可以看出背光相关HAL层代码的路径:
vim vendor/mediatek/proprietary/hardware/liblights/2.0/default/Light.cpp
确定问题所在
在这里插个题外话,其实最开始我并不确定一定是stop_backlight()这个函数去执行了关闭背光的操作,最开始有点怀疑,但是不确定,Android 8.0和Android 9.0关于关闭背光这项操作在实现方法上不太一样,虽然最终的原理都是向brightness这个节点里面写值,所以即使这里跑fail掉了,我也没确定一定是这里,再加上后面的early_suspend机制,看网上有说法:会将屏幕和背光在early_suspend里面一起关掉,所以一直在看这个机制,但是始终没有看到和背光相关的代码。于是,开始考虑stop_backlight函数,至少从标题来看很符合我们想要的……
在决定跟踪这个函数之前,做了一个验证:将Android 8.0上关于这部分的代码放在Android 9.0上,若是背光成功关闭,则证明是这个函数的问题,于是将stop_backlight()函数换成如下所示:直接向braghtness节点里面写0;
void stop_backlight()
{
set_int_value(BKL_LCD_PATH, 0);
backlight_on = 0;
}
#define BKL_LCD_PATH “/sys/class/leds/lcd-backlight/brightness”
在做这个验证的时候也遇到了问题,因为谷歌的SElinux策略,在Android 9.0的kpoc_charger这个服务中,对"/sys/class/leds/lcd-backlight/brightness"没有dac_override的权限,尝试在kpoc_charger.te文件里面增加allow,但是谷歌做了neverallow机制,没有修改成功;最后直接将SElinux模式由enforcing改为了permissive(宽容模式),验证直接写0,背光正常关闭,确定了问题所在。
SElinux模式修改路径:
vendor/mediatek/proprietary/bootable/bootloader/lk/platform/mt6580/rules.mk
将SELINUX_STATUS改为2
# choose one of following value 2: permissive /3: enforcing
SELINUX_STATUS := 3
跟踪问题
回到Android 9.0的stop_backlight()函数,既然知道是这个函数的问题,那肯定是要去跟踪源码看看是什么原因挂掉了,上面已经提过HAL层代码的路径,让我们看看Light.cpp里面都干了什么,其实这个文件里面只有几个函数,在度娘的帮助下勉强能看懂,首先找到我们要的setLight()函数:
Return<Status> Light::setLight(Type type, const LightState& state) {
auto it = mLights.find(type);
if (it == mLights.end()) {
return Status::LIGHT_NOT_SUPPORTED; //1
}
light_device_t* hwLight = it->second;
light_state_t legacyState {
.color = state.color,
.flashMode = static_cast<int>(state.flashMode),
.flashOnMS = state.flashOnMs,
.flashOffMS = state.flashOffMs,
.brightnessMode = static_cast<int>(state.brightnessMode),
};
int ret = hwLight->set_light(hwLight, &legacyState);
switch (ret) {
case -ENOSYS:
return Status::BRIGHTNESS_NOT_SUPPORTED; //2
case 0:
return Status::SUCCESS; //0
default:
return Status::UNKNOWN; //3
}
}
1、mLights 的由来:
/*一个初始化map:kLogicalLights;第一个元素是Tpye:类型,实际上是int型;第二个元素是const char *型的字符串;*/
const static std::map<Type, const char*> kLogicalLights = {
{Type::BACKLIGHT, LIGHT_ID_BACKLIGHT},
{Type::KEYBOARD, LIGHT_ID_KEYBOARD},
{Type::BUTTONS, LIGHT_ID_BUTTONS},
{Type::BATTERY, LIGHT_ID_BATTERY},
{Type::NOTIFICATIONS, LIGHT_ID_NOTIFICATIONS},
{Type::ATTENTION, LIGHT_ID_ATTENTION},
{Type::BLUETOOTH, LIGHT_ID_BLUETOOTH},
{Type::WIFI, LIGHT_ID_WIFI}
};
kLogicalLights里面的内容只是初始化时赋值,真正使用时并不存在;
ILight* HIDL_FETCH_ILight(const char* /* name */) {
std::map<Type, light_device_t*> lights; //定义一个名为lights的map;
for(auto const &pair : kLogicalLights) {
Type type = pair.first;
const char* name = pair.second; //*将map里面的第二个元素赋值给name,一个const char*类型的变量*/
light_device_t* light = getLightDevice(name); //检测name这个设备是否可用
if (light != nullptr) { //若可用,放入lights这个map里面
lights[type] = light;
}
}
if (lights.size() == 0) {
// Log information, but still return new Light.
// Some devices may not have any lights.
ALOGI("Could not open any lights.");
}
return new Light(std::move(lights));
}
分析到这儿我们就知道lights这个map里面才是真正发挥作用的map,但是和mLight有什么关系呢?往下看:
Light::Light(std::map<Type, light_device_t*> &&lights)
: mLights(std::move(lights)) {}
std::move函数可以以非常简单的方式将左值引用转换为右值引用;通俗讲就是操作mLights实际上就是操作lights。
再回到setLight,type的值为Type::BACKLIGHT,从串口log中可以看出来,set_light_brightness()函数报错返回值为1,即LIGHT_NOT_SUPPORTED;报这个错说明是mlight这个map里面没有Type::BACKLIGHT;背光设备不存在吗?开什么玩笑,肯定是不可能的,于是去看getLightDevice()这个函数,前面提到过,会通过这个函数获取可用设备放在map里面,函数如下:
light_device_t* getLightDevice(const char* name) {
light_device_t* lightDevice;
const hw_module_t* hwModule = NULL;
int ret = hw_get_module (LIGHTS_HARDWARE_MODULE_ID, &hwModule);
if (ret == 0) {
ret = hwModule->methods->open(hwModule, name,
reinterpret_cast<hw_device_t**>(&lightDevice));
if (ret != 0) {
ALOGE("light_open %s %s failed: %d", LIGHTS_HARDWARE_MODULE_ID, name, ret);
}
} else {
ALOGE("hw_get_module %s %s failed: %d", LIGHTS_HARDWARE_MODULE_ID, name, ret);
}
if (ret == 0) {
return lightDevice;
} else {
ALOGE("Light passthrough failed to load legacy HAL.");
return nullptr;
}
}
通过上面的分析基本上能确定这个函数里面一定出错了,从代码来看要么是hw_get_module出错,要么是light_open出错;但是HAL层的log不属于串口log;
关于如何查看Android Log这里也做个记录,也是我第一次接触到:
1、开启一个cmd命令窗口,输入adb logcat -b all抓取log,当然,也可以只抓我们想要的log:adb logcat -b all | findstr " "
2、手动运行此服务,文章开篇提到过:./system/bin/kpoc_charger
解决问题
最后,通过log确定是“hw_get_module lights faild” ,hw_get_module()最终会调用到hardware/libhardware/hardware.c里面的hw_get_module_by_class()去下载对应的module,至于具体是怎么工作的,这里不做分析,反正最后下载module失败了,至于为什么失败我也不是特别明白,最后是MTK给的解决方案,如下:
diff --git a/device.mk b/device.mk
index 1d34514..453fe36 100644
--- a/device.mk
+++ b/device.mk
@@ -4,9 +4,9 @@
# PRODUCT_COPY_FILES += $(MTK_PROJECT_FOLDER)/egl.cfg:$(TARGET_COPY_OUT_VENDOR)/lib/egl/egl.cfg:mtk
# PRODUCT_COPY_FILES += $(MTK_PROJECT_FOLDER)/ueventd.mt6580.rc:root/ueventd.mt6580.rc
-PRODUCT_COPY_FILES += $(MTK_PROJECT_FOLDER)/factory_init.project.rc:root/factory_init.project.rc
-PRODUCT_COPY_FILES += $(MTK_PROJECT_FOLDER)/init.project.rc:root/init.project.rc
-PRODUCT_COPY_FILES += $(MTK_PROJECT_FOLDER)/meta_init.project.rc:root/meta_init.project.rc
+PRODUCT_COPY_FILES += $(MTK_PROJECT_FOLDER)/factory_init.project.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/factory_init.project.rc
+PRODUCT_COPY_FILES += $(MTK_PROJECT_FOLDER)/init.project.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.project.rc
+PRODUCT_COPY_FILES += $(MTK_PROJECT_FOLDER)/meta_init.project.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/meta_init.project.rc
PRODUCT_COPY_FILES += device/mediatek/mt6580/init.mt6580.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.mt8321.rc
PRODUCT_COPY_FILES += device/mediatek/mt6580/init.recovery.mt6580.rc:recovery/root/init.recovery.mt8321.rc
PRODUCT_COPY_FILES += $(OUT_DIR)/target/product/$(MTK_BASE_PROJECT)/vendor/etc/fstab.mt6580:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.mt8321
想要弄明白为什么要这么修改还需要时间,在此先做个记录……
结束
本次调试到此结束,虽然中间也有抓耳捞腮的时候,但是在公司前辈的带领下学习到很多新知识和调试技巧,也遗留下很多没有弄明白的地方,写下这篇文档,一是做个记录,以后遇到类似的问题方便查看;二是记下自己的不足,方便补拙;三是给遇到同样问题的童鞋们提供一个小小的参考,有理解不当的地方望包涵。
编译小技巧
mmm : 模块编译
查看同目录下的 Android.mk,如:
LOCAL_MODULE := [email protected]
若是有LOCAL_MODULE这个选项,表示可以使用模块编译,最终会生成[email protected]这个库,存在于linux的“out/target/product/[project]]/vendor/lib/hw/”和Android的“vendor/lib/hw/”目录下