文章目录

  • 关机充电服务
    • 关机充电启动
    • 关机充电main函数
    • 控制充电时背光和动画效果
    • 确定问题所在
    • 跟踪问题
    • 解决问题
    • 结束
      • 编译小技巧

关机充电服务

  最近公司在预研Android P的项目,在第一轮测试后有一个关机充电背光未关闭的问题,在这之前从未接触过充电和背光部分,开启漫漫源码之路。

关机充电启动

  

路径:vendor/mediatek/proprietary/external/charger/kpoc_charger.rc

  
如下所示,在此.rc文件里面,会去开启kpoc_charger服务,若想手动的运行此服务,可通过adb命令./system/bin/kpoc_charger执行

on chargerstart kpoc_chargerservice kpoc_charger /system/bin/kpoc_chargerclass 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 insidereturn 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 controlif (!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 surfaceflingerif(firstTime){// make sure charging source online when in KPOC mode// add 2s toleranceif(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 valuebc = 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 drawingif (!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);elseKPOC_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; //2case 0:return Status::SUCCESS; //0default: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.rcPRODUCT_COPY_FILES += device/mediatek/mt6580/init.mt6580.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/hw/init.mt8321.rcPRODUCT_COPY_FILES += device/mediatek/mt6580/init.recovery.mt6580.rc:recovery/root/init.recovery.mt8321.rcPRODUCT_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 := android.hardware.light@2.0-impl-mediatek

若是有LOCAL_MODULE这个选项,表示可以使用模块编译,最终会生成android.hardware.light@2.0-impl-mediatek这个库,存在于linux的“out/target/product/[project]]/vendor/lib/hw/”和Android的“vendor/lib/hw/”目录下

MTK--Android P 调试关机充电背光问题相关推荐

  1. MTK 驱动(72)---mtk Android如何调试recovery模块

    mtk Android如何调试recovery模块 通用的调试程序的步骤: 找到调试模块在系统代码中的路径: 知道代码在哪里之后就是编译代码了,即如何编译调试模块: 编译出目标文件后就要将目标文件放到 ...

  2. camera(21)---MTK android AF调试总结

    MTK android AF调试总结 如何读懂马达曲线来调试AF功能 MTK Android平台,我们是有一些手段可以自行调试Camera AF的功能的. 首先,需要从供应商处得到AF马达的震动曲线. ...

  3. MTK Android 11.0:充电低电指示灯会被未读消息信号灯灭掉,无优先级控制。

    MTK Android 11.0:充电低电指示灯会被未读消息信号灯灭掉,无优先级控制: 1.电池充电.低电状态提示(红绿双色).(LIGHT_ID_BATTERY) 小于15%时: A 正在充电 亮红 ...

  4. android 9.0关机充电流程,充电图标和电量显示百分比修改

    android 9.0关机充电图标和字体修改 相关源文件 电量显示百分比字体替换 充电图标替换 相关源文件 system/core/healthd/healthd_draw.cppsystem/cor ...

  5. Android系统自定义关机充电图标

    需求描述 关机充电图标的修改地址在哪里?替换照片有哪些格式要求? 实现方案 图片路径: system/core/healthd/images/ 关机充电: system/core/healthd/he ...

  6. mtk android lcm调试

    参考MTK 文档LCM_Customer_document_MT6575.pdf The following shows the steps to add a new LCM driver: (1)  ...

  7. mtk android 关机充电,MTK 关机充电 - yueqiulijun的个人空间 - OSCHINA - 中文开源技术交流社区...

    充电相关文件目录 alps\vendor\mediatek\proprietary\external\charger\ 充电控制函数:charging_control.cpp // total_tim ...

  8. mtk使用android开关机动画,Android9.0 MTK 平板横屏方案修改(强制app横屏 + 开机logo/动画+关机充电横屏 + RecoveryUI 横屏)...

    文章较长建议先收藏再看 拆解步骤 1.app 强制横屏显示,无视 android:screenOrientation="portrait" 属性 2.屏幕触摸坐标修改为横屏 3.开 ...

  9. MTK 关机充电时充电IC正常,电池正常充电,但是充电动画一直显示0%

    MTK 关机充电时充电IC正常,电池正常充电,但是充电动画一直显示0% 平台 mt8168+mt6357+chargerIC 问题 MTK 关机充电时充电IC正常,电池正常充电,但是充电动画一直显示0 ...

最新文章

  1. JAVA中的Font
  2. 分摊、分配、定期重过账
  3. Gitee ssh 公钥配置好后,仍然 permission denied 的排查过程及解决方法
  4. (43)VHDL实现译码器与解码器
  5. “简易四则运算生成程序——第一次改进后的单元测试”链接
  6. 《Android和PHP开发最佳实践》一1.3 如何学习Android和PHP
  7. JDBC+Servlet+JSP实现基本的增删改查(简易通讯录)
  8. 2021-09-09321. 拼接最大数 单调栈
  9. C#利用word2007插件实现word转pdf
  10. ts540服务器安装win7系统,解决本机不支持Win7系统安装问题[图文教程]
  11. python 断言方法
  12. 网络共享中心的计算机名,网络和共享中心在哪?教你打开Windows电脑系统网络和共享中心5大方法...
  13. win7休眠 计算机管理,Win7休眠功能怎么关闭?
  14. 笔记本电脑没有WiFi选项 解决办法
  15. preg_replace() 正则替换所有符合条件的字符串
  16. python爬虫爬取豆瓣电影排行榜并通过pandas保存到Excel文件当中
  17. 没事的时候一个人静静的想着往事
  18. surfacepro3运行C语言,终于等来USB-C接口!微软 发布 Surface Pro 7 与 Surface Laptop 3 笔记本电脑...
  19. 三星苹果鏖战:苹果未衰败 研发能力决胜千里
  20. shell删除文件的最后一个字符

热门文章

  1. Python模拟登陆淘宝并统计淘宝消费情况的代码实例分享
  2. 【Offer】2020华夏银行深圳分行管培生 | 笔试+一面+二面(2019.9.27-28)
  3. 天使童装微信小程序支付
  4. 注意,爱思唯尔(Elsevier)影响因子正式公布(速看20分以上期刊,最高超700分)
  5. 除了共享单车,你还知道有什么共享应用场景?
  6. 你和“Excel高手”之间,只差一款神级电子表格
  7. facebook防封软件?facebook防封插件facebook防封技巧?facebook主页怎么防封
  8. Mysql 【MVCC】
  9. 用代码道歉java_c语言道歉小程序
  10. Material Design(Android6.0)