深入探索NVMEM与看门狗设备驱动
1. NVMEM框架概述
NVMEM(Non-Volatile Memory)设备可从内核或用户空间进行访问。在内核中,存在生产者/消费者设计模式,其中提供者驱动作为生产者,其他驱动作为消费者。
2. 编写NVMEM提供者驱动
提供者驱动的主要任务包括:
- 依据设备的数据手册提供合适的NVMEM配置,并提供访问内存的例程。
- 向系统注册设备。
- 提供设备树绑定文档。
NVMEM设备的注册和注销可使用以下函数:
struct nvmem_device *nvmem_register(const struct nvmem_config *config)
struct nvmem_device *devm_nvmem_register(struct device *dev, const struct nvmem_config *config)
int nvmem_unregister(struct nvmem_device *nvmem)
int devm_nvmem_unregister(struct device *dev, struct nvmem_device *nvmem)
注册成功后,会在 /sys/bus/nvmem/devices/dev-name/nvmem 创建二进制条目。
3. RTC设备中的NVMEM存储
许多实时时钟(RTC)设备嵌入了非易失性存储,如EEPROM或电池备份RAM。RTC设备的数据结构包含NVMEM相关字段:
struct rtc_device {
[...]
struct nvmem_device *nvmem;
/* Old ABI support */
bool nvram_old_abi;
struct bin_attribute *nvram;
[...]
}
-
nvmem:抽象底层硬件内存。 -
nvram_old_abi:布尔值,指示是否使用旧的(现已弃用)NVRAM ABI注册RTC的NVMEM。 -
nvram:仅用于旧ABI支持的底层内存的二进制属性。
RTC相关的NVMEM框架API可通过 RTC_NVMEM 内核配置选项启用,定义在 drivers/rtc/nvmem.c 中,包括 rtc_nvmem_register() 和 rtc_nvmem_unregister() 。
以下是DS1307 RTC驱动的探测函数示例:
static int ds1307_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct ds1307 *ds1307;
int err = -ENODEV;
int tmp;
const struct chip_desc *chip;
[...]
ds1307->rtc->ops = chip->rtc_ops ? : &ds13xx_rtc_ops;
err = rtc_register_device(ds1307->rtc);
if (err)
return err;
if (chip->nvram_size) {
struct nvmem_config nvmem_cfg = {
.name = " ds1307_nvram" ,
.word_size = 1,
.stride = 1,
.size = chip->nvram_size,
.reg_read = ds1307_nvram_read,
.reg_write = ds1307_nvram_write,
.priv = ds1307,
};
ds1307->rtc->nvram_old_abi = true;
rtc_nvmem_register(ds1307->rtc, &nvmem_cfg);
}
[...]
}
4. 实现NVMEM读写回调
为使内核和其他框架能够读写NVMEM设备及其单元,每个NVMEM提供者必须暴露读写回调函数:
typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset, void *val, size_t bytes);
typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset, void *val, size_t bytes);
-
nvmem_reg_read_t:从NVMEM设备读取数据。 -
nvmem_reg_write_t:向NVMEM设备写入数据。
5. NVMEM提供者的设备树绑定
NVMEM数据提供者应根据其父总线的设备树绑定进行描述。例如,I2C设备应作为代表其所在I2C总线的节点的子节点进行描述。
以下是一个MMIO NVMEM设备及其子节点的示例:
ocotp: ocotp@21bc000 {
#address-cells = <1>;
#size-cells = <1>;
compatible = " fsl,imx6sx-ocotp" , " syscon" ;
reg = <0x021bc000 0x4000>;
[...]
tempmon_calib: calib@38 {
reg = <0x38 4>;
};
tempmon_temp_grade: temp-grade@20 {
reg = <0x20 4>;
};
foo: foo@6 {
reg = <0x6 0x2> bits = <7 2>
};
[...]
};
数据单元绑定的可能属性:
| 属性 | 描述 |
| ---- | ---- |
| reg | 必需属性,描述数据区域的偏移量和大小。 |
| bits | 可选属性,指定位偏移和位数。 |
6. NVMEM消费者驱动API
NVMEM消费者驱动可通过包含 <linux/nvmem-consumer.h> 来使用以下基于单元的API:
struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *name);
struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *name);
void nvmem_cell_put(struct nvmem_cell *cell);
void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell);
void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len);
int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len);
int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val);
以下是读取 tempmon 节点分配的单元内容的代码示例:
static int imx_init_from_nvmem_cells(struct platform_device *pdev)
{
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;
}
7. 用户空间的NVMEM
NVMEM用户空间接口依赖于sysfs。每个注册的NVMEM设备在 /sys/bus/nvmem/devices 下有一个目录条目,其中包含一个 nvmem 二进制文件。
以下是读写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
8. 看门狗设备驱动概述
看门狗是一种硬件(有时由软件模拟)设备,用于确保系统的可用性。它本质上是一个定时器,在系统出现故障时触发硬件复位。
9. 技术要求
在开始学习看门狗设备驱动之前,需要具备以下条件:
- C编程技能
- 基本电子知识
- Linux内核v4.19.X源代码,可从 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/refs/tags 获取
10. 看门狗数据结构和API
看门狗子系统的主要数据结构是 struct watchdog_device :
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 | 看门狗设备的最小超时值(秒)。 |
| wd_data | 指向看门狗核心内部数据的指针。 |
| status | 包含设备内部状态位的字段。 |
struct watchdog_info 结构定义如下:
struct watchdog_info {
u32 options;
u32 firmware_version;
u8 identity[32];
};
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 是启动和停止看门狗的必需操作。
通过以上内容,我们详细介绍了NVMEM框架和看门狗设备驱动的相关知识,包括数据结构、API以及使用方法。这些知识对于开发和维护嵌入式系统具有重要意义。
深入探索NVMEM与看门狗设备驱动
11. 看门狗设备的状态标志
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() 函数设置。 |
12. struct watchdog_info 结构详解
struct watchdog_info 结构是用户空间 API 的一部分,在 WDIOC_GETSUPPORT ioctl 成功时返回给用户空间。该结构各字段含义如下:
| 字段 | 描述 |
| ---- | ---- |
| options | 表示卡/驱动程序支持的功能,是一个位掩码。 |
| firmware_version | 卡的固件版本。 |
| identity | 描述设备的字符串。 |
options 字段的可能标志及含义:
| 标志 | 描述 |
| ---- | ---- |
| WDIOF_SETTIMEOUT | 表示看门狗设备可以设置超时值,若设置此标志,则必须定义 set_timeout 回调函数。 |
| WDIOF_MAGICCLOSE | 表示驱动程序支持 “魔术关闭” 功能,即写入 V 字符可在下次关闭时关闭看门狗(前提是未设置 nowayout )。 |
| 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 中设置时,表示自上次查询以来看门狗收到了保活 ping。 |
| WDIOF_CARDRESET | 仅在 watchdog_device.bootstatus 中出现,表示上次重启是由看门狗本身(实际上是其超时)引起的。 |
13. struct watchdog_ops 结构详解
struct watchdog_ops 结构包含了允许对看门狗设备进行的操作列表,各操作含义如下:
| 操作 | 描述 |
| ---- | ---- |
| start | 启动看门狗,必需操作。 |
| stop | 停止看门狗,必需操作。 |
| ping | 可选操作,用于向看门狗发送保活信号。 |
| status | 可选操作,用于获取看门狗的状态。 |
| set_timeout | 可选操作,用于设置看门狗的超时值。 |
| set_pretimeout | 可选操作,用于设置看门狗的预超时值。 |
| get_timeleft | 可选操作,用于获取看门狗剩余的时间。 |
| restart | 可选操作,用于重启看门狗。 |
| ioctl | 可选操作,用于处理自定义的 ioctl 命令。 |
14. 看门狗设备驱动开发流程
以下是开发看门狗设备驱动的基本流程:
graph TD;
A[初始化看门狗设备结构] --> B[设置看门狗操作函数];
B --> C[注册看门狗设备];
C --> D[处理看门狗事件];
D --> E[注销看门狗设备];
具体步骤如下:
1. 初始化看门狗设备结构 :创建并初始化 struct watchdog_device 结构,设置各字段的值。
2. 设置看门狗操作函数 :填充 struct watchdog_ops 结构,实现必要的操作函数,如 start 和 stop 。
3. 注册看门狗设备 :使用相应的函数将看门狗设备注册到系统中。
4. 处理看门狗事件 :在驱动程序中处理看门狗的各种事件,如超时、预超时等。
5. 注销看门狗设备 :在驱动程序卸载时,注销看门狗设备。
15. 总结
本文详细介绍了 NVMEM 框架和看门狗设备驱动的相关知识。在 NVMEM 方面,我们了解了提供者驱动和消费者驱动的开发,包括设备注册、读写回调实现、设备树绑定以及用户空间访问等内容。在看门狗设备驱动方面,我们学习了看门狗的基本原理、数据结构、API 以及开发流程。掌握这些知识对于开发和维护嵌入式系统具有重要意义,能够帮助我们更好地管理非易失性内存和确保系统的可靠性。
通过对 NVMEM 框架和看门狗设备驱动的深入学习,我们可以在实际项目中灵活运用这些技术,提高系统的性能和稳定性。例如,在嵌入式系统中使用 NVMEM 存储重要的配置信息,使用看门狗设备确保系统在出现故障时能够自动复位,从而保证系统的正常运行。
超级会员免费看
1549

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



