偶然看到别人的截图里有个阶梯式充电,于是我心生好奇,毕竟我已经好久没缝合这些功能了,看看这多出来的东西是个啥又何妨?

节点

其实自己从power_supply下面通过字面翻译也能找到,不过此处为了方便,我们可以直接取隔壁scene的源码进行分析
可以很轻松的找到判断功能是否存在的函数:

fun stepChargeSupport(): Boolean {
        return RootFile.itemExists("/sys/class/power_supply/battery/step_charging_enabled")
    }

(垃圾kt)
于是,节点也就被扯出来了
位于
/sys/class/power_supply/battery/step_charging_enabled

那么,它是个啥?系统默认给的设置情况呢?

历史

论这东西的历史,其实可以追溯到3.18内核了(更早的有没有不知道,懒得翻了)
但是在3.18内核上,仅仅只是power supply框架给了一个接口,但是并没有相关驱动去继承它
在4.4内核上,高通提供了继承这个接口的smb2充电驱动,而这个驱动在骁龙835处理器上是默认启用的
所以,概括一下

  • 3.18内核中出现了框架接口
  • 4.4内核在启用smb2驱动后,上述节点出现 (当然,骁龙820的移植4.4内核是不会有这个东西的)

源码分析

power supply sysfs框架的工作方式

我们从最末端的节点读写开始分析
首先,我们直接按名称找节点的定义
在这儿
power_supply_sysfs.c

static struct device_attribute power_supply_attrs[] = {
......
POWER_SUPPLY_ATTR(step_charging_enabled),
......
};

那么,这个POWER_SUPPLY_ATTR是个啥子呢?
我们反着去找

#define POWER_SUPPLY_ATTR(_name)                    \
{                                    \
    .attr = { .name = #_name },                    \
    .show = power_supply_show_property,                \
    .store = power_supply_store_property,                \
}

众所周知(bushi),在C语言预处理中,#的含义是为后面的东西加上引号
所以把上面的两块东西整合,得到预处理之后的东西,那就是

static struct device_attribute power_supply_attrs[] = {
......
{                                    
    .attr = { .name = “step_charging_enabled” },                    
    .show = power_supply_show_property,                
    .store = power_supply_store_property,                
},
......
};

瞬间清晰了许多((

可以看到上面已经定义了,我们读写这个变量时的操作,那我们就去看看吧

就看读的方法吧 (写入我看了下,也是相同的道理)(从万普拉斯内核拿的 所以有一些奇怪的东西)

static ssize_t power_supply_show_property(struct device *dev,
                      struct device_attribute *attr,
                      char *buf) {
    ssize_t ret;
    struct power_supply *psy = dev_get_drvdata(dev);
    enum power_supply_property psp = attr - power_supply_attrs;
    union power_supply_propval value;

    if (psp == POWER_SUPPLY_PROP_TYPE) {
        value.intval = psy->desc->type;
    } else {
        ret = power_supply_get_property(psy, psp, &value);

        if (ret < 0) {
            if (ret == -ENODATA)
                dev_dbg(dev, "driver has no data for `%s' property\n",
                    attr->attr.name);
            else if (ret != -ENODEV && ret != -EAGAIN)
                dev_err_ratelimited(dev,
                    "driver failed to report `%s' property: %zd\n",
                    attr->attr.name, ret);
            return ret;
        }
    }

    switch (psp) {
    case POWER_SUPPLY_PROP_STATUS:
        ret = sprintf(buf, "%s\n",
                  power_supply_status_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CHARGE_TYPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_charge_type_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_HEALTH:
        ret = sprintf(buf, "%s\n",
                  power_supply_health_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TECHNOLOGY:
        ret = sprintf(buf, "%s\n",
                  power_supply_technology_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
        ret = sprintf(buf, "%s\n",
                  power_supply_capacity_level_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPE:
    case POWER_SUPPLY_PROP_REAL_TYPE:
    /* @bsp, 2019/08/30 Wireless Charging porting */
    case POWER_SUPPLY_PROP_WIRELESS_TYPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_type_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_USB_TYPE:
        ret = power_supply_show_usb_type(dev, psy->desc->usb_types,
                         psy->desc->num_usb_types,
                         &value, buf);
        break;
    case POWER_SUPPLY_PROP_SCOPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_scope_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_MODE:
        ret = sprintf(buf, "%s\n",
                  power_supply_usbc_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
        ret = sprintf(buf, "%s\n",
                  power_supply_usbc_pr_text[value.intval]);
        break;
/* @bsp, 2019/07/05 Battery & Charging porting */
    case POWER_SUPPLY_PROP_OEM_TYPEC_CC_ORIENTATION:
        ret = snprintf(buf, sizeof(buf), "%s\n",
                  cc_orientation_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_SRC_RP:
        ret = sprintf(buf, "%s\n",
                  power_supply_typec_src_rp_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_DIE_HEALTH:
    case POWER_SUPPLY_PROP_SKIN_HEALTH:
    case POWER_SUPPLY_PROP_CONNECTOR_HEALTH:
        ret = sprintf(buf, "%s\n",
                  power_supply_health_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT:
        ret = sprintf(buf, "%lld\n", value.int64val);
        break;
    /* @bsp, 2019/08/30 Wireless Charging porting */
    case POWER_SUPPLY_PROP_WIRELESS_MODE:
    case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
        ret = sprintf(buf, "%s\n", value.strval);
        break;
    default:
        ret = sprintf(buf, "%d\n", value.intval);
    }

    return ret;
}

嗯?是不是感觉啥也看不懂?别急,慢慢分析。
首先,我们明确,读,要读出的内容是个啥。。
众所周知(其实是约定俗成?),最后作为内容返回给用户空间的,是char *buf这个字符数组的内容
那么,我们再来看看它是怎么被赋值的

    switch (psp) {
    case POWER_SUPPLY_PROP_STATUS:
        ret = sprintf(buf, "%s\n",
                  power_supply_status_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CHARGE_TYPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_charge_type_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_HEALTH:
        ret = sprintf(buf, "%s\n",
                  power_supply_health_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TECHNOLOGY:
        ret = sprintf(buf, "%s\n",
                  power_supply_technology_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
        ret = sprintf(buf, "%s\n",
                  power_supply_capacity_level_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPE:
    case POWER_SUPPLY_PROP_REAL_TYPE:
    /* @bsp, 2019/08/30 Wireless Charging porting */
    case POWER_SUPPLY_PROP_WIRELESS_TYPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_type_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_USB_TYPE:
        ret = power_supply_show_usb_type(dev, psy->desc->usb_types,
                         psy->desc->num_usb_types,
                         &value, buf);
        break;
    case POWER_SUPPLY_PROP_SCOPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_scope_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_MODE:
        ret = sprintf(buf, "%s\n",
                  power_supply_usbc_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
        ret = sprintf(buf, "%s\n",
                  power_supply_usbc_pr_text[value.intval]);
        break;
/* @bsp, 2019/07/05 Battery & Charging porting */
    case POWER_SUPPLY_PROP_OEM_TYPEC_CC_ORIENTATION:
        ret = snprintf(buf, sizeof(buf), "%s\n",
                  cc_orientation_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_SRC_RP:
        ret = sprintf(buf, "%s\n",
                  power_supply_typec_src_rp_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_DIE_HEALTH:
    case POWER_SUPPLY_PROP_SKIN_HEALTH:
    case POWER_SUPPLY_PROP_CONNECTOR_HEALTH:
        ret = sprintf(buf, "%s\n",
                  power_supply_health_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT:
        ret = sprintf(buf, "%lld\n", value.int64val);
        break;
    /* @bsp, 2019/08/30 Wireless Charging porting */
    case POWER_SUPPLY_PROP_WIRELESS_MODE:
    case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
        ret = sprintf(buf, "%s\n", value.strval);
        break;
    default:
        ret = sprintf(buf, "%d\n", value.intval);
    }

可以看到,这个switch语句就是在花式给这个buf赋值
sprintf的作用就是把后面的值打印到这个字符数组之中(类似printf,只是不是控制台输出,而是输出到指定字符串里)。如果还是有疑问,建议看看 https://www.runoob.com/cprogramming/c-function-sprintf.html

这上面case了非常多的情况,而这些情况是在哪里定义的呢?
power_supply.h

enum power_supply_property {
    /* Properties of type `int' */
    POWER_SUPPLY_PROP_STATUS = 0,
    /* @bsp, 2019/07/05 Battery & Charging porting */
    POWER_SUPPLY_PROP_SET_ALLOW_READ_EXTERN_FG_IIC,
    POWER_SUPPLY_PROP_CC_TO_CV_POINT,
    POWER_SUPPLY_PROP_CHG_PROTECT_STATUS,
    POWER_SUPPLY_PROP_FASTCHG_IS_OK,
    POWER_SUPPLY_PROP_FASTCHG_STATUS,
    POWER_SUPPLY_PROP_FASTCHG_STARTING,
    ....................................................
    POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED,
    ....................................................
};

在这儿,可以看到这里定义的“情况”真的非常之多
大概带一下enum是个啥
你大概可以把上面的那一堆东西理解为

#define POWER_SUPPLY_PROP_STATUS 0
#define POWER_SUPPLY_PROP_SET_ALLOW_READ_EXTERN_FG_IIC 1
...............................

至于define是什么,你可以理解为替换,拿数字替换前面的一大串字符
也就是说,上面case的那一堆type,在编译器看来,其实就是数字123456789.....................

与此同时,我们还可以注意到定义中其实是有POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED这个选项的,而读取数据的那个函数(power_supply_show_property)中并没有用到它,而且我们也不知道这个数据是从哪里读取的,这是为什么呢?

于是,我们现在的首要问题就是弄清step_charging_enabled节点的返回值到底是哪里给出来的
case的每一个分支似乎都不是我们想要的情况,也就是POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED
那么,唯一的可能就是case那里走了default分支,也就是说
返回值是在这里赋值的:

default:
        ret = sprintf(buf, "%d\n", value.intval);

那么我们回去看看哪里会动value

    if (psp == POWER_SUPPLY_PROP_TYPE) {
        value.intval = psy->desc->type;
    } else {
        ret = power_supply_get_property(psy, psp, &value);
        ..................
                }

只有可能是这里了
这里的一个分支是直接给value.intval赋值,但是条件是一个无关的POWER_SUPPLY_PROP_TYPE
也就是说,它走的一定是else分支
也就是说,value的值是通过指针&value反着传回来的
我们接着去看看power_supply_get_property到底干了啥
power_supply_core.c

int power_supply_get_property(struct power_supply *psy,
                enum power_supply_property psp,
                union power_supply_propval *val)
{
    if (atomic_read(&psy->use_cnt) <= 0) {
        if (!psy->initialized)
            return -EAGAIN;
        return -ENODEV;
    }

    return psy->desc->get_property(psy, psp, val);
}
EXPORT_SYMBOL_GPL(power_supply_get_property);

可以看到,在return的位置,它调用了get_property函数

直接顺藤摸瓜已经不太可能了,我没法知道运行内存里存着些什么
直接按照函数名找应该比较简单(这有点类似于java的interface,直接去找所有接口)
于是,在
qpnp-smb5中,我们可以找到对应函数名的接口被使用了

static const struct power_supply_desc usb_psy_desc = {
    .name = "usb",
    .type = POWER_SUPPLY_TYPE_USB_PD,
    .properties = smb5_usb_props,
    .num_properties = ARRAY_SIZE(smb5_usb_props),
    .get_property = smb5_usb_get_prop,
    .set_property = smb5_usb_set_prop,
    .property_is_writeable = smb5_usb_prop_is_writeable,
};
static const struct power_supply_desc usb_port_psy_desc = {
    .name        = "pc_port",
    .type        = POWER_SUPPLY_TYPE_USB,
    .properties    = smb5_usb_port_props,
    .num_properties    = ARRAY_SIZE(smb5_usb_port_props),
    .get_property    = smb5_usb_port_get_prop,
    .set_property    = smb5_usb_port_set_prop,
};
static const struct power_supply_desc usb_main_psy_desc = {
    .name        = "main",
    .type        = POWER_SUPPLY_TYPE_MAIN,
    .properties    = smb5_usb_main_props,
    .num_properties    = ARRAY_SIZE(smb5_usb_main_props),
    .get_property    = smb5_usb_main_get_prop,
    .set_property    = smb5_usb_main_set_prop,
    .property_is_writeable = smb5_usb_main_prop_is_writeable,
};
static const struct power_supply_desc dc_psy_desc = {
    .name = "dc",
    .type = POWER_SUPPLY_TYPE_WIRELESS,
    .properties = smb5_dc_props,
    .num_properties = ARRAY_SIZE(smb5_dc_props),
    .get_property = smb5_dc_get_prop,
    .set_property = smb5_dc_set_prop,
    .property_is_writeable = smb5_dc_prop_is_writeable,
};
static const struct power_supply_desc batt_psy_desc = {
    .name = "battery",
    .type = POWER_SUPPLY_TYPE_BATTERY,
    .properties = smb5_batt_props,
    .num_properties = ARRAY_SIZE(smb5_batt_props),
    .get_property = smb5_batt_get_prop,
    .set_property = smb5_batt_set_prop,
    .property_is_writeable = smb5_batt_prop_is_writeable,
};

可以看到,在smb5中,存在4个power_supply_desc对象
而这几个对象,都会在smb5_probe(驱动初始化)时被注册

此时,我们可以发现一个特点,那就是这里注册的power_supply_desc最终都会被链接到/sys/class/power_supply下
它们四个的name正好对应
/sys/class/power_supply/usb
/sys/class/power_supply/pc_port
/sys/class/power_supply/main
/sys/class/power_supply/dc
/sys/class/power_supply/battery

也就是说,我们的节点 /sys/class/power_supply/battery/step_charging_enabled 大概率是在 smb5_batt_get_prop下面被处理的
我们直接来看这个函数

static int smb5_batt_get_prop(struct power_supply *psy,
        enum power_supply_property psp,
        union power_supply_propval *val)
{
    struct smb_charger *chg = power_supply_get_drvdata(psy);
    int rc = 0;

    switch (psp) {
    .........................................
    case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED:
        val->intval = chg->step_chg_enabled;
        break;
    case POWER_SUPPLY_PROP_SW_JEITA_ENABLED:
        val->intval = chg->sw_jeita_enabled;
        break;
    ............................................
    default:
        pr_err("batt power supply prop %d not supported\n", psp);
        return -EINVAL;
    }

    if (rc < 0) {
        pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
        return -ENODATA;
    }

    return 0;
}

果然,在这里val下面的intval在此处被赋值了,赋值的内容是chg下面的step_chg_enabled

我们现在可以梳理一下,这个值究竟是如何显示出来的

  • power_supply_show_property函数中调用power_supply_get_property并传入val的内存地址
  • power_supply_get_property函数中调用对应已注册power_supply设备下的get_property所对应的函数指针,这个指针指向smb5_batt_get_prop,并继续传入val的内存地址
  • smb5_batt_get_prop中判断请求的类型,然后将上面传入的val的内存地址中的intval改为step_chg_enabled的状态
  • 逐级返回
  • 回到顶层函数power_supply_show_property继续执行,直到default分支,将val下面的intval打印到buf中
  • 用户空间得到开关状态

相比于别的一些直接把节点绑定到内核空间变量的内核驱动,power_supply的这种做法真的很有意思,虽有些复杂,但是使用户空间的节点结构得到了规范。

现在我们已经知道了用户空间读取step_charging_enabled时,究竟把内存中的哪个变量传了出来
但是问题远还没结束
现在还有一个非常关键的问题

{                                    
    .attr = { .name = “step_charging_enabled” },                    
    .show = power_supply_show_property,                
    .store = power_supply_store_property,                
},

POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED

之间有啥关系

我们没有定义POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED.name = “step_charging_enabled“之间的绑定关系,那么内核做到:我们在访问“step_charging_enabled“节点时通过POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED标注我们访问的是哪个节点,然后正确的返回值呢?

其实答案就藏在一行注释里

/* Must be in the same order as POWER_SUPPLY_PROP_* */
static struct device_attribute power_supply_attrs[] = {

也就是说,power_supply_attrs的顺序必须和power_supply_property保持一致(至于这两个东西是啥,建议在本页内搜索,上面已经列出它们的代码了)
比如说,我访问power_supply_attrs中列出的第三个节点,那么switch语句中case到的值应该是power_supply_property中对应的第三个名称
这里内核只是用了一个障眼法罢了,实际上在这内部完全就是数字的传递,不过,内核只是给每一个数字命了个额外的名称,从而避免混淆😂

都说到这儿了,那就再说点别的吧。
为什么,只有/sys/class/power_supply/battery/step_charging_enabled而没有
/sys/class/power_supply/usb/step_charging_enabled或者是/sys/class/power_supply/dc/step_charging_enabled节点呢?

这里就直接说说这个框架大体的工作方式吧,再源码分析下去真的分析不动了((

我们可以暂时把上面所说的
usb
pc_port
main
dc
battery
理解为一个个power_supply设备

在上面,我们已经提到了,power_supply.h中定义了一个总的power_supply_property

enum power_supply_property {
    /* Properties of type `int' */
    POWER_SUPPLY_PROP_STATUS = 0,
/* @bsp, 2019/07/05 Battery & Charging porting */
    POWER_SUPPLY_PROP_SET_ALLOW_READ_EXTERN_FG_IIC,
    POWER_SUPPLY_PROP_CC_TO_CV_POINT,
    POWER_SUPPLY_PROP_CHG_PROTECT_STATUS,
    ..............................................

你可以把这个表理解为,所有power_supply设备的可用节点总和
表中的每一项都对应一个在power_supply_sysfs.c中声明的POWER_SUPPLY_ATTR(节点名称) (严格按顺序)

有一部分节点是所有设备都可以使用的(节点直接由power_supply框架处理,而非私有驱动)
这些东西也就是我们在上面看到的switch case的内容

    switch (psp) {
    case POWER_SUPPLY_PROP_STATUS:
        ret = sprintf(buf, "%s\n",
                  power_supply_status_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CHARGE_TYPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_charge_type_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_HEALTH:
        ret = sprintf(buf, "%s\n",
                  power_supply_health_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TECHNOLOGY:
        ret = sprintf(buf, "%s\n",
                  power_supply_technology_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
        ret = sprintf(buf, "%s\n",
                  power_supply_capacity_level_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPE:
    case POWER_SUPPLY_PROP_REAL_TYPE:
    /* @bsp, 2019/08/30 Wireless Charging porting */
    case POWER_SUPPLY_PROP_WIRELESS_TYPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_type_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_USB_TYPE:
        ret = power_supply_show_usb_type(dev, psy->desc->usb_types,
                         psy->desc->num_usb_types,
                         &value, buf);
        break;
    case POWER_SUPPLY_PROP_SCOPE:
        ret = sprintf(buf, "%s\n",
                  power_supply_scope_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_MODE:
        ret = sprintf(buf, "%s\n",
                  power_supply_usbc_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
        ret = sprintf(buf, "%s\n",
                  power_supply_usbc_pr_text[value.intval]);
        break;
/* @bsp, 2019/07/05 Battery & Charging porting */
    case POWER_SUPPLY_PROP_OEM_TYPEC_CC_ORIENTATION:
        ret = snprintf(buf, sizeof(buf), "%s\n",
                  cc_orientation_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_TYPEC_SRC_RP:
        ret = sprintf(buf, "%s\n",
                  power_supply_typec_src_rp_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_DIE_HEALTH:
    case POWER_SUPPLY_PROP_SKIN_HEALTH:
    case POWER_SUPPLY_PROP_CONNECTOR_HEALTH:
        ret = sprintf(buf, "%s\n",
                  power_supply_health_text[value.intval]);
        break;
    case POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT:
        ret = sprintf(buf, "%lld\n", value.int64val);
        break;
    /* @bsp, 2019/08/30 Wireless Charging porting */
    case POWER_SUPPLY_PROP_WIRELESS_MODE:
    case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
        ret = sprintf(buf, "%s\n", value.strval);
        break;
    default:
        ret = sprintf(buf, "%d\n", value.intval);
    }

(当然,在上面一加加入了一些肮脏的修改,这些修改不应该存在于power_supply框架的共有部分中,而应该被移动到专有驱动)

然而,还有一些节点并不是由power_supply框架直接处理的,而是转交给对应的驱动程序处理,比如我们这次研究的step_charging_enabled
这就是为什么这个节点没有出现在power_supply_show_property函数的switch case中

在我们注册battery设备时

static const struct power_supply_desc batt_psy_desc = {
    .name = "battery",
    .type = POWER_SUPPLY_TYPE_BATTERY,
    .properties = smb5_batt_props,
    .num_properties = ARRAY_SIZE(smb5_batt_props),
    .get_property = smb5_batt_get_prop,
    .set_property = smb5_batt_set_prop,
    .property_is_writeable = smb5_batt_prop_is_writeable,
};

我们就指定了.properties属性,并将其指向了一个数组
在这个数组中,我们可以定义battery设备的可用节点,也就是/sys/class/power_supply/*/下面除了uevent以外的所有内容

static enum power_supply_property smb5_batt_props[] = {
    POWER_SUPPLY_PROP_INPUT_SUSPEND,
    POWER_SUPPLY_PROP_STATUS,
    POWER_SUPPLY_PROP_HEALTH,
    POWER_SUPPLY_PROP_PRESENT,
    POWER_SUPPLY_PROP_CHARGE_TYPE,
    POWER_SUPPLY_PROP_CAPACITY,
/* @bsp, 2019/07/05 Battery & Charging porting */
    POWER_SUPPLY_PROP_APSD_NOT_DONE,
    POWER_SUPPLY_PROP_FASTCHG_IS_OK,
    POWER_SUPPLY_PROP_CHARGE_NOW,
    POWER_SUPPLY_PROP_CHG_PROTECT_STATUS,
    POWER_SUPPLY_PROP_FASTCHG_STATUS,
    POWER_SUPPLY_PROP_FASTCHG_STARTING,
    POWER_SUPPLY_CUTOFF_VOLT_WITH_CHARGER,
    POWER_SUPPLY_PROP_CHARGING_ENABLED,
    POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
    POWER_SUPPLY_PROP_IS_AGING_TEST,
    POWER_SUPPLY_PROP_CONNECTER_TEMP_ONE,
    POWER_SUPPLY_PROP_CONNECTER_TEMP_TWO,
    POWER_SUPPLY_PROP_CONNECT_DISABLE,
    POWER_SUPPLY_PROP_CHARGER_TEMP,
    POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
    POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
    POWER_SUPPLY_PROP_VOLTAGE_NOW,
    POWER_SUPPLY_PROP_VOLTAGE_MAX,
    POWER_SUPPLY_PROP_VOLTAGE_QNOVO,
    POWER_SUPPLY_PROP_CURRENT_NOW,
    POWER_SUPPLY_PROP_CURRENT_QNOVO,
    POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
    POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
    POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
    POWER_SUPPLY_PROP_TEMP,
    POWER_SUPPLY_PROP_TECHNOLOGY,
    POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED,
    POWER_SUPPLY_PROP_SW_JEITA_ENABLED,
    POWER_SUPPLY_PROP_CHARGE_DONE,
    POWER_SUPPLY_PROP_PARALLEL_DISABLE,
    POWER_SUPPLY_PROP_SET_SHIP_MODE,
    POWER_SUPPLY_PROP_DIE_HEALTH,
    POWER_SUPPLY_PROP_RERUN_AICL,
    POWER_SUPPLY_PROP_DP_DM,
    POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
    POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
    POWER_SUPPLY_PROP_CHARGE_COUNTER,
    POWER_SUPPLY_PROP_CYCLE_COUNT,
    POWER_SUPPLY_PROP_RECHARGE_SOC,
    POWER_SUPPLY_PROP_CHARGE_FULL,
    POWER_SUPPLY_PROP_FORCE_RECHARGE,
    POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE,
    POWER_SUPPLY_PROP_OP_DISABLE_CHARGE,
};

上面smb5_batt_props中的内容,必然为power_supply_property的子集(power_supply_property是所有设备所有节点的声明,而smb5_batt_props则是在其中选出了本设备应该显示的节点)

怎么样,你学废了吗?反正我是快写废了。。

大概总结一下,

  • 所有要用到的(或者用不到的节点)都会写死在power_supply.h与power_supply_sysfs.c中
  • 这些节点中,有一部分可以直接由内核的power_supply框架处理,这些节点的处理方式写死在power_supply_sysfs.c中
  • 还有一部分节点需要由专有驱动处理,那么这些专有驱动会通过向power_supply框架注册的方式,传递对应处理函数的指针
  • 私有驱动(power_supply设备)可以自定义要展示哪些节点

step_charging_enabled 是个啥

在完成上述分析之后,我们就可以轻易的寻找step_charging_enabled所对应的内核变量,在内核中的使用情况了。
在step-chg-jeita.c中,我们可以找到这个变量的读取和使用方式

static int handle_step_chg_config(struct step_chg_info *chip)
{
        .................................
    rc = power_supply_get_property(chip->batt_psy,
        POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, &pval);
    if (rc < 0)
        chip->step_chg_enable = false;
    else
        chip->step_chg_enable = pval.intval;
    ...................................
}

而函数handle_step_chg_config会被status_change_workstep_chg_notifier_call中调用,而这个call则被进行了注册

    chip->nb.notifier_call = step_chg_notifier_call;
    rc = power_supply_reg_notifier(&chip->nb);

这个call大概是会在power_supply配置改变时调用,也就是在用户写入了新的变量值后调用
也就是说,用户写入的值最终会被传递给变量step_chg_enable

那么,我们只需要回去追着变量step_chg_enable看,就知道它到底是干啥用的了
我们接着往下看

static int handle_step_chg_config(struct step_chg_info *chip)
{
        ................................................
    if (!chip->step_chg_enable || !chip->step_chg_cfg_valid) {
        if (chip->fcc_votable)
            vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
        goto update_time;
    }

    if (chip->step_chg_config->param.use_bms)
        rc = power_supply_get_property(chip->bms_psy,
                chip->step_chg_config->param.psy_prop, &pval);
    else
        rc = power_supply_get_property(chip->batt_psy,
                chip->step_chg_config->param.psy_prop, &pval);

    if (rc < 0) {
        pr_err("Couldn't read %s property rc=%d\n",
            chip->step_chg_config->param.prop_name, rc);
        return rc;
    }

    current_index = chip->step_index;
    rc = get_val(chip->step_chg_config->fcc_cfg,
            chip->step_chg_config->param.hysteresis,
            chip->step_index,
            pval.intval,
            &chip->step_index,
            &fcc_ua);
    if (rc < 0) {
        /* remove the vote if no step-based fcc is found */
        if (chip->fcc_votable)
            vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
        goto update_time;
    }

    /* Do not drop step-chg index, if input supply is present */
    if (is_input_present(chip)) {
        if (chip->step_index < current_index)
            chip->step_index = current_index;
    } else {
        chip->step_index = 0;
    }

    if (!chip->fcc_votable)
        chip->fcc_votable = find_votable("FCC");
    if (!chip->fcc_votable)
        return -EINVAL;

    if (chip->taper_fcc) {
        taper_fcc_step_chg(chip, chip->step_index, pval.intval);
    } else {
        fcc_ua = chip->step_chg_config->fcc_cfg[chip->step_index].value;
        vote(chip->fcc_votable, STEP_CHG_VOTER, true, fcc_ua);
    }

    pr_debug("%s = %d Step-FCC = %duA taper-fcc: %d\n",
        chip->step_chg_config->param.prop_name, pval.intval,
        get_client_vote(chip->fcc_votable, STEP_CHG_VOTER),
        chip->taper_fcc);

update_time:
    chip->step_last_update_time = ktime_get();
    return 0;
}

可以看到,在step_chg_enable被禁用后,一个电源管理的voter被禁用,并且跳过了下方的一个电流调节(投票)逻辑
至于vote(投票)是干啥的 可以来看看 https://www.akr-developers.com/d/132

    } else {
        fcc_ua = chip->step_chg_config->fcc_cfg[chip->step_index].value;
        vote(chip->fcc_votable, STEP_CHG_VOTER, true, fcc_ua);
    }

那个被跳过的电流调节逻辑是这样的
可以看到,这个逻辑将电流设置为了fcc_ua
而fcc_ua则来自配置文件数组fcc_cfg的value

我们接着去看这个数组是怎么被初始化的

static int get_step_chg_jeita_setting_from_profile(struct step_chg_info *chip)
{
        .................
    rc = read_range_data_from_node(profile_node,
            "qcom,step-chg-ranges",
            chip->step_chg_config->fcc_cfg,
            chip->soc_based_step_chg ? 100 : max_fv_uv,
            max_fcc_ma * 1000);
        .................
}

数组的赋值来自于这里,可以看到,这些数据读取自dt节点"qcom,step-chg-ranges"
那我们直接去寻这个节点
在某电池的dt配置中,我们找到了它读取的东西

    qcom,step-chg-ranges = <3600000  3800000  5400000
                3801000  4300000  3600000
                4301000  4350000  2500000>;

至于这些数据是何含义,我真的懒得源码分析了,直接去翻bsp不香吗?

- qcom,step-chg-ranges: Array of tuples in which a tuple describes a range
            data of step charging setting.
            A range contains following 3 integer elements:
            [0]: the low threshold of battery voltage in uV
                 or SoC (State of Charge) in percentage when
                 SoC based step charge is used;
            [1]: the high threshold of battery voltage in uV
                 or SoC in percentage when SoC based step charge
                 is used;
            [2]: the FCC (full charging current) in uA when battery
                 voltage or SoC falls between the low and high
                 thresholds.
            The threshold values in range should be in ascending
            and shouldn't overlap. It support 8 ranges at max.

人工翻译一下

这是一个数组的数组(没错,就是数组套娃数组)。也就是说,这个数组中三个数值作为一小组,其中第一个数字描述电池电压的下限,第二个数字描述电池电压的上线,第三个变量描述在这个上线和下线之间要使用的充电电流大小。

也就是说

电池电压
在3.8v到3.8v之间,使用5.4A的充电电流
在3.8v到4.3v之间,使用3.6A的充电电流
在4.3v到4.35v之间,使用2.5A的充电电流

(上方描述是基于某电池的配置,并非适用于所有电池)
好了,这一切的工作机制差不多明了了吧

默认使用情况

上面,我们已经分析了这个“阶梯式充电“到底是干啥用的
那么我们接下来说说为什么有的机子默认开启了这个选项
有的却没有。。

其实这个功能的开启状态和厂家默认使用的充电方案是有关的
使用qc方案的设备大都默认开启了这个功能
而使用了自家充电轮子的厂商则普遍会关闭这个来自qc的功能
比如一加使用了非常肮脏的修改驱动的方式默认关闭了它

+    /* disable step_chg */
+    chg->step_chg_enabled = false;

而隔壁摩托罗拉则是用了非常标准的方式在dt中关闭了这项功能

+    /delete-property/ qcom,step-charging-enable;

不得不说,一加工程师素质极低

而摩托罗拉也给出了关闭这功能的原因

arm/dts: remove JEITA and step charging properties

qcom,sw-jeita-enable and qcom,step-charging-enable properties will
enable the QC JEITA charging and step charging policy, which will
conflict with Moto battery charging profile.

Change-Id: I23642375241f1ed6303460ce29893612c671daab
Signed-off-by: yanyh2 <[email protected]>
Reviewed-on: https://gerrit.mot.com/1221216
SLTApproved: Slta Waiver
SME-Granted: SME Approvals Granted
Tested-by: Jira Key
Reviewed-by: Haijian Ma <[email protected]>
Reviewed-by: Huosheng Liao <[email protected]>
Submit-Approved: Jira Key

简而言之,和自家充电策略冲突

想必,现在应该可以明白在默认没有开启这功能的设备上强开它会发生什么了吧?

还有些好玩的

  • qcom,soc-based-step-chg 节点可以提供类似的”阶梯式充电“功能,但是不是基于电压,而是基于电量百分比的
    (在部分老设备上用的是这个)
  • qcom,jeita-fcc-ranges 节点提供了基于温度进行充电限流的配置
    它还有对应的用户空间节点
    /sys/class/power_supply/battery/sw_jeita_enabled
    不过这个节点的默认权限是只读的
    但是在step-chg-jeita驱动中,却是有基于配置更改事件的处理的

      rc = power_supply_get_property(chip->batt_psy,
          POWER_SUPPLY_PROP_SW_JEITA_ENABLED, &pval);
      if (rc < 0)
          chip->sw_jeita_enable = false;
      else
          chip->sw_jeita_enable = pval.intval;

    在smb5驱动中,却没有它的写入处理。。
    至于把这个节点chmod了之后往里写数据能不能用,你们自己试试吧(我觉得是不行的)
    稍微改一下smb5驱动也许能让这个节点变得可写

总结

  • 没啥好总结的,只是记录一下这次是怎么从sysfs节点挖穿驱动的 (主要是这power supply的sysfs框架真的有点意思)