Linux Power Supply Class深度解析:从驱动注册到用户空间事件的全链路实现
在嵌入式系统开发中,电源管理始终是影响设备稳定性和用户体验的关键因素。Linux内核的Power Supply子系统(简称PSY)作为连接硬件驱动与用户空间的桥梁,其设计精妙程度往往决定了电源管理的灵活性和可靠性。本文将以fan54015充电芯片驱动为例,深入剖析PSY设备从内核注册到用户空间通知的完整生命周期。
1. Power Supply子系统的架构设计
Linux的Power Supply子系统采用典型的分层设计,完美体现了Unix"一切皆文件"的哲学。其核心架构可分为三个层次:
- 硬件抽象层:通过
struct power_supply和struct power_supply_desc等数据结构封装各类电源设备 - 核心服务层:处理属性管理、事件通知等公共逻辑
- 用户接口层:通过sysfs和uevent机制向用户空间暴露统一接口
这种设计使得不同厂商的电源芯片只需实现必要的硬件操作接口,就能无缝接入Linux电源管理体系。以fan54015驱动为例,其关键数据结构如下:
static const struct power_supply_desc fan54015_charger_desc = { .name = "fan54015_charger", .type = POWER_SUPPLY_TYPE_USB, .properties = fan54015_usb_props, .num_properties = ARRAY_SIZE(fan54015_usb_props), .get_property = fan54015_charger_usb_get_property, .set_property = fan54015_charger_usb_set_property, .property_is_writeable = fan54015_charger_property_is_writeable, .usb_types = fan54015_charger_usb_types, .num_usb_types = ARRAY_SIZE(fan54015_charger_usb_types), };该描述符定义了设备名称、类型、支持的属性列表以及对应的回调函数,是驱动与子系统交互的契约。值得注意的是,properties数组定义了该PSY设备对外暴露的接口能力,例如:
static enum power_supply_property fan54015_usb_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_USB_TYPE, };2. PSY设备的注册流程剖析
PSY设备的注册通常发生在驱动的probe阶段,这个过程涉及多个关键步骤:
- 资源分配与初始化:获取设备树配置,初始化硬件寄存器
- 工作队列准备:初始化
changed_work用于异步事件处理 - 描述符填充:如前一节所示的
power_supply_desc配置 - 正式注册:调用
power_supply_register()完成注册
fan54015驱动的注册代码片段如下:
info->psy = power_supply_register(dev, &fan54015_charger_desc, &cfg); if (IS_ERR(info->psy)) { dev_err(dev, "Failed to register power supply\n"); return PTR_ERR(info->psy); }注册过程中,内核会执行以下关键操作:
- 在/sys/class/power_supply/下创建对应目录
- 根据描述符中的属性列表生成sysfs文件节点
- 初始化内核对象关联关系
- 设置uevent回调机制
特别值得注意的是power_supply_config结构体,它允许驱动传递一些注册时的特定配置:
struct power_supply_config { struct device_node *of_node; /* Driver private data */ void *drv_data; char **supplied_to; size_t num_supplicants; char **supplied_from; size_t num_supplies; bool attach_extcon; };其中supplied_to和supplied_from字段用于建立PSY设备间的供耗关系,这在多电源系统中尤为重要。
3. 属性操作的实现机制
PSY子系统的核心价值在于它提供了统一的属性访问接口。驱动需要实现两个关键回调函数:
get_property:当用户空间读取sysfs属性时触发set_property:当用户空间写入sysfs属性时触发
fan54015的实现示例如下:
static int fan54015_charger_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct fan54015_charger_info *info = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: val->intval = fan54015_charger_get_status(info); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: val->intval = info->cur.cur; break; /* 其他属性处理 */ default: return -EINVAL; } return 0; }属性值的传递使用union power_supply_propval联合体,可以适应不同类型的属性值:
union power_supply_propval { int intval; const char *strval; };对于可写属性,驱动还需要实现property_is_writeable回调,告知内核哪些属性允许修改:
static int fan54015_charger_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: return 1; default: return 0; } }4. 状态变更与用户空间通知
当硬件状态发生变化时,驱动需要及时通知用户空间。这个过程通过power_supply_changed()函数触发:
void power_supply_changed(struct power_supply *psy) { unsigned long flags; dev_dbg(&psy->dev, "%s\n", __func__); spin_lock_irqsave(&psy->changed_lock, flags); psy->changed = true; pm_stay_awake(&psy->dev); spin_unlock_irqrestore(&psy->changed_lock, flags); schedule_work(&psy->changed_work); }该函数的核心工作流程是:
- 设置
changed标志位 - 防止系统进入深度睡眠
- 调度工作队列处理异步通知
在工作队列中,内核会完成以下操作:
- 更新sysfs节点值
- 发送uevent事件
- 调用notifier链通知其他内核模块
对于Android系统,这个uevent会被healthd服务捕获,进而触发电量更新广播。开发者可以通过以下命令观察PSY设备的uevent:
# 监控power_supply类的uevent udevadm monitor --property --subsystem-match=power_supply5. 多PSY设备的协同工作
在实际系统中,往往存在多个PSY设备协同工作的情况。例如典型的移动设备可能包含:
| PSY设备类型 | 功能描述 | 典型驱动文件 |
|---|---|---|
| Battery | 电池状态管理 | charger-manager.c |
| USB | USB充电管理 | fan54015-charger.c |
| AC | 交流充电管理 | sc2721-charger.c |
| Fuel Gauge | 电量计量 | sc27xx_fuel_gauge.c |
这些设备通过supplied_to/supplied_from建立关联关系。当某个PSY状态变化时,内核会自动通知依赖它的其他PSY设备。这种设计使得电源管理策略可以灵活配置,而不需要硬编码在驱动中。
6. 调试技巧与性能优化
开发PSY驱动时,以下几个调试技巧非常实用:
sysfs接口检查:
# 查看所有PSY设备 ls /sys/class/power_supply/ # 查看特定设备属性 cat /sys/class/power_supply/fan54015_charger/status内核动态调试:
# 启用PSY子系统的动态调试 echo 'file power_supply_* +p' > /sys/kernel/debug/dynamic_debug/control性能优化要点:
- 减少
power_supply_changed()的调用频率 - 对高频变化的属性使用
power_supply_changed_work()延迟通知 - 合理设置
psy_desc中的no_thermal标志避免不必要的温控检查
- 减少
电源状态跟踪:
# 跟踪PSY相关函数调用 perf probe --add 'power_supply_changed' perf probe --add 'power_supply_get_property'
7. 设备���配置与硬件抽象
良好的设备树配置可以使驱动更加灵活。fan54015的典型设备树配置如下:
fan54015_chg: charger@6a { compatible = "fairchild,fan54015_chg"; reg = <0x6a>; phys = <&hsphy>; monitored-battery = <&bat>; extcon = <&extcon_gpio>; vddvbus:otg-vbus { regulator-name = "vddvbus"; }; };关键配置项包括:
- reg:I2C设备地址
- monitored-battery:关联的电池节点
- extcon:USB连接器状态检测
对应的电池节点需要详细定义电池参数:
bat: battery { compatible = "simple-battery"; charge-full-design-microamp-hours = <3900000>; charge-term-current-microamp = <200000>; constant_charge_voltage_max_microvolt = <4400000>; voltage-min-design-microvolt = <3561000>; /* 温度补偿表等更多参数 */ };这些配置信息会被驱动通过power_supply_get_battery_info()接口读取,用于实现精确的充电控制。