Linux内核中的NVMEM框架与看门狗设备驱动解析
1. NVMEM框架概述
NVMEM(Non-Volatile Memory)框架在Linux内核中扮演着重要角色,它涉及到生产者和消费者两个方面。以下是一段使用NVMEM框架读取校准值和温度等级值的代码示例:
int ret;
u32 val;
ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
if (ret)
return ret;
ret = imx_init_calib(pdev, val);
if (ret)
return ret;
ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
if (ret)
return ret;
imx_init_temp_grade(pdev, val);
return 0;
此代码首先读取校准值,调用 imx_init_calib 函数进行校准初始化;接着读取温度等级值,调用 imx_init_temp_grade 函数进行温度等级初始化。
1.1 NVMEM在用户空间的使用
NVMEM用户空间接口依赖于sysfs,与大多数内核框架类似。每个注册到系统的NVMEM设备都会在 /sys/bus/nvmem/devices 下创建一个目录项,并在该目录下创建一个 nvmem 二进制文件,代表设备的内存。其完整路径模式为 /sys/bus/nvmem/devices/<dev-name>X/nvmem ,其中 <dev-name> 由生产者驱动提供的 nvmem_config.name 指定。以下代码展示了NVMEM核心如何构建 <dev-name>X 模式:
int rval;
rval = ida_simple_get(&nvmem_ida, 0, 0, GFP_KERNEL);
nvmem->id = rval;
if (config->id == -1 && config->name) {
dev_set_name(&nvmem->dev, "%s", config->name);
} else {
dev_set_name(&nvmem->dev, "%s%d", config->name ? : "nvmem",
config->name ? config->id : nvmem->id);
}
不同情况下的命名规则如下:
| 条件 | 命名规则 |
| ---- | ---- |
| nvmem_config->id == -1 | 仅使用 nvmem_config->name 命名sysfs目录项 |
| nvmem_config->id != -1 且 nvmem_config->name 已设置 | 使用 nvmem_config->name 和 nvmem_config->id 命名 |
| nvmem_config->name 未设置 | 使用 nvmem 和生成的ID命名 |
需要注意的是,无论定义了哪些单元,NVMEM框架通过NVMEM二进制文件暴露完整的寄存器空间,而非单元。从用户空间访问单元需要事先知道其偏移量和大小。用户可以使用 hexdump 或 cat 命令读取NVMEM内容,例如:
cat /sys/bus/nvmem/devices/2-00550/nvmem
echo "foo" > /sys/bus/nvmem/devices/2-00550/nvmem
cat /sys/bus/nvmem/devices/2-00550/nvmem
2. 看门狗设备驱动简介
看门狗是一种硬件(有时由软件模拟)设备,旨在确保给定系统的可用性。它通常是一个具有合理超时时间的定时器,运行在受监控系统上的软件应定期刷新该定时器。如果软件在定时器到期前停止或未能刷新它(且未明确关闭它),将触发整个系统的(硬件)复位。这种机制甚至有助于从内核崩溃中恢复。
2.1 技术要求
在学习看门狗设备驱动之前,需要具备以下条件:
- C编程技能
- 基本电子知识
- Linux内核v4.19.X源码,可从 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/refs/tags 获取
2.2 看门狗数据结构和API
2.2.1 struct watchdog_device
这是Linux内核中看门狗设备的表示,包含了关于该设备的所有信息,定义在 include/linux/watchdog.h 中:
struct watchdog_device {
int id;
struct device *parent;
const struct watchdog_info *info;
const struct watchdog_ops *ops;
const struct watchdog_governor *gov;
unsigned int bootstatus;
unsigned int timeout;
unsigned int pretimeout;
unsigned int min_timeout;
struct watchdog_core_data *wd_data;
unsigned long status;
[...]
};
各字段含义如下:
| 字段 | 含义 |
| ---- | ---- |
| id | 内核在设备注册期间分配的看门狗ID |
| parent | 该设备的父设备 |
| info | 指向 struct watchdog_info 结构的指针,提供看门狗定时器的额外信息 |
| ops | 指向看门狗操作列表的指针 |
| gov | 指向看门狗预超时调节器的指针 |
| bootstatus | 看门狗设备在启动时的状态,是触发系统复位原因的位掩码 |
| timeout | 看门狗设备的超时值(秒) |
| pretimeout | 预超时事件触发的时间间隔(秒) |
| min_timeout 和 max_timeout | 看门狗设备的最小和最大超时值(秒) |
| wd_data | 指向看门狗核心内部数据的指针 |
| status | 包含设备内部状态位的字段 |
2.2.2 struct watchdog_info
定义在 include/uapi/linux/watchdog.h 中,是用户空间API的一部分,也是 WDIOC_GETSUPPORT ioctl成功时返回给用户空间的结构:
struct watchdog_info {
u32 options;
u32 firmware_version;
u8 identity[32];
};
各字段含义如下:
| 字段 | 含义 |
| ---- | ---- |
| options | 卡/驱动支持的功能,是看门狗设备/驱动支持功能的位掩码 |
| firmware_version | 卡的固件版本 |
| identity | 描述设备的字符串 |
2.2.3 struct watchdog_ops
包含了允许对看门狗设备执行的操作列表:
struct watchdog_ops {
struct module *owner;
/* mandatory operations */
int (*start)(struct watchdog_device *);
int (*stop)(struct watchdog_device *);
/* optional operations */
int (*ping)(struct watchdog_device *);
unsigned int (*status)(struct watchdog_device *);
int (*set_timeout)(struct watchdog_device *, unsigned int);
int (*set_pretimeout)(struct watchdog_device *, unsigned int);
unsigned int (*get_timeleft)(struct watchdog_device *);
int (*restart)(struct watchdog_device *, unsigned long, void *);
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
};
各操作含义如下:
| 操作 | 含义 |
| ---- | ---- |
| start 和 stop | 分别启动和停止看门狗的强制操作 |
| ping | 向看门狗发送保持活动信号的回调函数,可选 |
| status | 返回看门狗设备状态的可选例程 |
| set_timeout | 设置看门狗超时值(秒)的回调函数 |
| set_pretimeout | 设置预超时的回调函数 |
| get_timeleft | 返回复位前剩余秒数的可选操作 |
| restart | 重启机器的例程 |
| ioctl | 处理额外/非标准ioctl命令的回调函数,可选 |
2.3 看门狗设备的注册与注销
看门狗框架提供了两个基本函数来注册和注销看门狗设备:
int watchdog_register_device(struct watchdog_device *wdd)
void watchdog_unregister_device(struct watchdog_device *wdd)
watchdog_register_device 在成功时返回零,失败时返回负错误码; watchdog_unregister_device 执行相反的操作。为了避免手动注销,可以使用托管版本的函数 devm_watchdog_register_device :
int devm_watchdog_register_device(struct device *dev, struct watchdog_device *wdd)
注册方法会检查 wdd->ops->restart 函数是否提供,并将其注册为重启处理程序。因此,在向系统注册看门狗设备之前,驱动程序应使用 watchdog_set_restart_priority 设置重启优先级,优先级值的指导原则如下:
| 优先级值 | 含义 |
| ---- | ---- |
| 0 | 最低优先级,将看门狗的重启函数作为最后手段 |
| 128 | 默认优先级,若没有其他处理程序可用,则默认使用该重启处理程序 |
| 255 | 最高优先级,抢占所有其他处理程序 |
以下是一个将 watchdog_device 结构嵌入更大结构的示例:
struct imx2_wdt_device {
struct clk *clk;
struct regmap *regmap;
struct watchdog_device wdog;
bool ext_reset;
};
以下是初始化和注册看门狗设备的示例代码:
static int init imx2_wdt_probe(struct platform_device *pdev)
{
struct imx2_wdt_device *wdev;
struct watchdog_device *wdog;
int ret;
wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
if (!wdev)
return -ENOMEM;
wdog = &wdev->wdog;
if (imx2_wdt_is_running(wdev)) {
imx2_wdt_set_timeout(wdog, wdog->timeout);
set_bit(WDOG_HW_RUNNING, &wdog->status);
}
ret = watchdog_register_device(wdog);
if (ret) {
dev_err(&pdev->dev, "cannot register watchdog device\n");
}
return 0;
}
以下是注销看门狗设备的示例代码:
static int exit imx2_wdt_remove(struct platform_device *pdev)
{
struct watchdog_device *wdog = platform_get_drvdata(pdev);
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
watchdog_unregister_device(wdog);
if (imx2_wdt_is_running(wdev)) {
imx2_wdt_ping(wdog);
dev_crit(&pdev->dev, "Device removed: Expect reboot!\n");
}
return 0;
}
通过以上内容,我们对NVMEM框架和看门狗设备驱动有了较为全面的了解。NVMEM框架方便了非易失性内存的管理和使用,而看门狗设备驱动则为系统的可靠性提供了保障。在实际应用中,我们可以根据具体需求合理运用这些技术。
3. 看门狗设备驱动的深入理解
3.1 看门狗状态位详解
struct watchdog_device 中的 status 字段包含了设备的内部状态位,以下是各状态位的详细解释:
| 状态位 | 含义 |
| ---- | ---- |
| WDOG_ACTIVE | 表示看门狗是否正在运行/激活 |
| WDOG_NO_WAY_OUT | 指示是否设置了“无退出”功能,可以使用 watchdog_set_nowayout 函数设置 |
| WDOG_STOP_ON_REBOOT | 表示在重启时应停止看门狗 |
| WDOG_HW_RUNNING | 表示硬件看门狗正在运行,可以使用 watchdog_hw_running 函数检查该标志是否设置 |
| WDOG_STOP_ON_UNREGISTER | 表示在注销时应停止看门狗,可以使用 watchdog_stop_on_unregister 函数设置该标志 |
3.2 看门狗功能标志分析
struct watchdog_info 中的 options 字段是一个位掩码,代表了看门狗设备/驱动支持的功能。以下是各标志的详细说明:
| 标志 | 含义 |
| ---- | ---- |
| WDIOF_SETTIMEOUT | 表示看门狗设备可以设置超时值,若设置该标志,则必须定义 set_timeout 回调函数 |
| WDIOF_MAGICCLOSE | 表示驱动支持“魔法关闭”功能,即向看门狗字符设备文件写入 V 字符序列可允许下一次关闭操作关闭看门狗 |
| WDIOF_POWERUNDER | 表示设备可以监测/检测电源故障,在 watchdog_device.bootstatus 中设置该标志表示系统因欠压而复位 |
| WDIOF_POWEROVER | 表示设备可以监测工作电压,在 watchdog_device.bootstatus 中设置该标志表示系统因过压而复位 |
| WDIOF_OVERHEAT | 表示看门狗设备可以监测芯片/SoC 温度,在 watchdog_device.bootstatus 中设置该标志表示系统因过热而复位 |
| WDIOF_FANFAULT | 表示该看门狗设备可以监测风扇,设置该标志表示风扇故障 |
| WDIOF_EXTERN1 和 WDIOF_EXTERN2 | 表示设备有外部事件输入,在 watchdog_device.bootstatus 中设置该标志表示系统因外部继电器/源 1 或 2 而复位 |
| WDIOF_PRETIMEOUT | 表示该看门狗设备支持预超时功能 |
| WDIOF_KEEPALIVEPING | 表示驱动支持 WDIOC_KEEPALIVE ioctl,在 watchdog_device.bootstatus 中设置该标志表示看门狗自上次查询以来收到了保持活动信号 |
| WDIOF_CARDRESET | 是一个特殊标志,仅出现在 watchdog_device.bootstatus 中,表示系统最后一次重启是由看门狗本身(超时)引起的 |
3.3 看门狗设备驱动开发流程
下面通过 mermaid 流程图展示看门狗设备驱动的开发流程:
graph TD
A[分配内存空间] --> B[初始化 watchdog_device 结构]
B --> C[设置 info、ops 和超时相关字段]
C --> D[设置重启优先级]
D --> E[注册看门狗设备]
E --> F[设备正常运行]
F --> G{是否需要注销?}
G -- 是 --> H[注销看门狗设备]
G -- 否 --> F
具体步骤如下:
1. 分配内存空间 :为 watchdog_device 结构分配内存,可以将其封装在更大的驱动特定数据结构中。
2. 初始化 watchdog_device 结构 :设置 id 、 parent 、 info 、 ops 等字段。
3. 设置 info 、 ops 和超时相关字段 :提供有效的 watchdog_info 和 watchdog_ops 数据结构,设置 timeout 、 pretimeout 等字段。
4. 设置重启优先级 :使用 watchdog_set_restart_priority 函数设置重启处理程序的优先级。
5. 注册看门狗设备 :使用 watchdog_register_device 或 devm_watchdog_register_device 函数将设备注册到系统中。
6. 设备正常运行 :在设备运行过程中,根据需要调用 start 、 stop 、 ping 等操作。
7. 注销看门狗设备 :当不再需要设备时,使用 watchdog_unregister_device 函数注销设备。
4. 总结
本文详细介绍了 Linux 内核中的 NVMEM 框架和看门狗设备驱动。NVMEM 框架通过 sysfs 接口方便了用户空间对非易失性内存的访问,用户可以使用 hexdump 或 cat 命令读取和写入 NVMEM 内容。看门狗设备驱动则为系统的可靠性提供了保障,通过定期刷新定时器来防止系统因软件故障而死机。
在开发看门狗设备驱动时,需要熟悉 struct watchdog_device 、 struct watchdog_info 和 struct watchdog_ops 等数据结构,掌握看门狗设备的注册和注销方法,设置合适的重启优先级。同时,要理解看门狗状态位和功能标志的含义,根据设备的实际需求进行配置。
通过合理运用 NVMEM 框架和看门狗设备驱动,可以提高系统的稳定性和可靠性,满足不同应用场景的需求。在实际开发中,还可以根据具体情况进行优化和扩展,以实现更加高效和灵活的系统设计。
超级会员免费看

1708

被折叠的 条评论
为什么被折叠?



