NVMEM 子系统¶
Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
本文档解释了 NVMEM 框架以及提供的 API,以及如何使用它。
1. 简介¶
NVMEM 是非易失性存储器层的缩写。 它用于从非易失性存储器(如 eeprom、efuse 等)检索 SOC 或设备特定数据的配置。
在这个框架存在之前,像 eeprom 这样的 NVMEM 驱动程序存储在 drivers/misc 中,它们都必须复制几乎相同的代码来注册 sysfs 文件,允许内核用户访问他们正在驱动的设备的内容等等。
就其他内核用户而言,这也是一个问题,因为使用的解决方案几乎因驱动程序而异,存在相当大的抽象泄漏。
该框架旨在解决这些问题。 它还为消费设备引入了 DT 表示,以便从 NVMEM 获取他们需要的数据(MAC 地址、SoC/修订 ID、部件号等)。
NVMEM 提供者¶
NVMEM 提供者是指实现初始化、读取和写入非易失性存储器的方法的实体。
2. 注册/注销 NVMEM 提供者¶
NVMEM 提供者可以通过向 nvmem_register()
提供相关的 nvmem 配置来向 NVMEM 核心注册,成功后核心将返回一个有效的 nvmem_device 指针。
nvmem_unregister()
用于注销先前注册的提供者。
例如,一个简单的 nvram 案例
static int brcm_nvram_probe(struct platform_device *pdev)
{
struct nvmem_config config = {
.name = "brcm-nvram",
.reg_read = brcm_nvram_read,
};
...
config.dev = &pdev->dev;
config.priv = priv;
config.size = resource_size(res);
devm_nvmem_register(&config);
}
设备驱动程序可以使用 nvmem_cell_info 结构定义和注册 nvmem 单元
static const struct nvmem_cell_info foo_nvmem_cell = {
{
.name = "macaddr",
.offset = 0x7f00,
.bytes = ETH_ALEN,
}
};
int nvmem_add_one_cell(nvmem, &foo_nvmem_cell);
此外,还可以创建 nvmem 单元查找条目,并像下面的示例中那样从机器代码向 nvmem 框架注册它们
static struct nvmem_cell_lookup foo_nvmem_lookup = {
.nvmem_name = "i2c-eeprom",
.cell_name = "macaddr",
.dev_id = "foo_mac.0",
.con_id = "mac-address",
};
nvmem_add_cell_lookups(&foo_nvmem_lookup, 1);
NVMEM 消费者¶
NVMEM 消费者是利用 NVMEM 提供者来读取和写入 NVMEM 的实体。
3. 基于 NVMEM 单元的消费者 API¶
NVMEM 单元是 NVMEM 中的数据条目/字段。 NVMEM 框架提供 3 个 API 来读取/写入 NVMEM 单元
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, ssize_t *len);
int nvmem_cell_write(struct nvmem_cell *cell, void *buf, ssize_t len);
*nvmem_cell_get()
apis 将获取给定 id 的 nvmem 单元的引用,然后 nvmem_cell_read/write() 可以读取或写入该单元。 一旦完成单元的使用,消费者应调用 *nvmem_cell_put()
以释放单元的所有分配内存。
4. 基于直接 NVMEM 设备的消费者 API¶
在某些情况下,需要直接读取/写入 NVMEM。 为了方便这些消费者,NVMEM 框架提供以下 api
struct nvmem_device *nvmem_device_get(struct device *dev, const char *name);
struct nvmem_device *devm_nvmem_device_get(struct device *dev,
const char *name);
struct nvmem_device *nvmem_device_find(void *data,
int (*match)(struct device *dev, const void *data));
void nvmem_device_put(struct nvmem_device *nvmem);
int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset,
size_t bytes, void *buf);
int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset,
size_t bytes, void *buf);
int nvmem_device_cell_read(struct nvmem_device *nvmem,
struct nvmem_cell_info *info, void *buf);
int nvmem_device_cell_write(struct nvmem_device *nvmem,
struct nvmem_cell_info *info, void *buf);
在消费者可以直接读取/写入 NVMEM 之前,它应该从 *nvmem_device_get()
api 之一获取 nvmem_controller。
这些 api 和基于单元的 api 之间的区别在于,这些 api 始终将 nvmem_device 作为参数。
5. 释放对 NVMEM 的引用¶
当消费者不再需要 NVMEM 时,它必须释放它使用上面提到的 API 获得的对 NVMEM 的引用。 NVMEM 框架提供 2 个 API 来释放对 NVMEM 的引用
void nvmem_cell_put(struct nvmem_cell *cell);
void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell);
void nvmem_device_put(struct nvmem_device *nvmem);
void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem);
这两个 API 都用于释放对 NVMEM 的引用,并且 devm_nvmem_cell_put 和 devm_nvmem_device_put 会销毁与此 NVMEM 关联的 devres。
用户空间¶
6. 用户空间二进制接口¶
用户空间可以读取/写入位于以下位置的原始 NVMEM 文件
/sys/bus/nvmem/devices/*/nvmem
示例
hexdump /sys/bus/nvmem/devices/qfprom0/nvmem
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
00000a0 db10 2240 0000 e000 0c00 0c00 0000 0c00
0000000 0000 0000 0000 0000 0000 0000 0000 0000
...
*
0001000
7. 设备树绑定¶
请参阅 Documentation/devicetree/bindings/nvmem/nvmem.txt
8. NVMEM 布局¶
NVMEM 布局是另一种创建单元的机制。 通过设备树绑定,可以使用偏移量和长度来指定简单的单元。 有时,单元没有静态偏移量,但内容仍然定义明确,例如 tag-length-values。 在这种情况下,必须首先解析 NVMEM 设备内容,然后相应地添加单元。 布局允许您读取 NVMEM 设备的内容并允许您动态添加单元。
布局的另一个用例是单元的后处理。 通过布局,可以将自定义后处理钩子与单元相关联。 甚至可以将此钩子添加到不是由布局本身创建的单元。
9. 内部内核 API¶
-
int nvmem_add_one_cell(struct nvmem_device *nvmem, const struct nvmem_cell_info *info)¶
将一个单元信息添加到 nvmem 设备
参数
struct nvmem_device *nvmem
要添加单元的 nvmem 设备。
const struct nvmem_cell_info *info
要添加到设备的 nvmem 单元信息
返回值
0 或失败时的负错误代码。
-
int nvmem_register_notifier(struct notifier_block *nb)¶
为 nvmem 事件注册一个通知程序块。
参数
struct notifier_block *nb
nvmem 事件发生时要调用的通知程序块。
返回值
成功时为 0,失败时为负错误号。
-
int nvmem_unregister_notifier(struct notifier_block *nb)¶
为 nvmem 事件注销一个通知程序块。
参数
struct notifier_block *nb
要注销的通知程序块。
返回值
成功时为 0,失败时为负错误号。
-
struct nvmem_device *nvmem_register(const struct nvmem_config *config)¶
为给定的 nvmem_config 注册一个 nvmem 设备。 还在 /sys/bus/nvmem/devices/dev-name/nvmem 中创建一个二进制条目
参数
const struct nvmem_config *config
创建 nvmem 设备时使用的 nvmem 设备配置。
返回值
如果出错,将为 ERR_PTR()
,成功时将为指向 nvmem_device 的有效指针。
-
void nvmem_unregister(struct nvmem_device *nvmem)¶
注销先前注册的 nvmem 设备
参数
struct nvmem_device *nvmem
指向先前注册的 nvmem 设备的指针。
-
struct nvmem_device *devm_nvmem_register(struct device *dev, const struct nvmem_config *config)¶
为给定的 nvmem_config 注册一个托管的 nvmem 设备。 还在 /sys/bus/nvmem/devices/dev-name/nvmem 中创建一个二进制条目
参数
struct device *dev
使用 nvmem 设备的设备。
const struct nvmem_config *config
创建 nvmem 设备时使用的 nvmem 设备配置。
返回值
如果出错,将为 ERR_PTR()
,成功时将为指向 nvmem_device 的有效指针。
-
struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id)¶
从给定的 id 获取 nvmem 设备
参数
struct device_node *np
使用 nvmem 设备的设备树节点。
const char *id
来自 nvmem-names 属性的 nvmem 名称。
返回值
如果出错,将为 ERR_PTR()
,成功时将为指向 struct nvmem_device 的有效指针。
-
struct nvmem_device *nvmem_device_get(struct device *dev, const char *dev_name)¶
从给定的 id 获取 nvmem 设备
参数
struct device *dev
使用 nvmem 设备的设备。
const char *dev_name
请求的 nvmem 设备的名称。
返回值
如果出错,将为 ERR_PTR()
,成功时将为指向 struct nvmem_device 的有效指针。
-
struct nvmem_device *nvmem_device_find(void *data, int (*match)(struct device *dev, const void *data))¶
查找具有匹配函数的 nvmem 设备
参数
void *data
要传递给匹配函数的数据
int (*match)(struct device *dev, const void *data)
用于检查设备的回调函数
返回值
如果出错,将为 ERR_PTR()
,成功时将为指向 struct nvmem_device 的有效指针。
参数
struct device *dev
使用 nvmem 设备的设备。
struct nvmem_device *nvmem
指向由
devm_nvmem_cell_get()
分配的 nvmem 设备的指针,需要释放它。
-
void nvmem_device_put(struct nvmem_device *nvmem)¶
放置已获取的 nvmem 设备
参数
struct nvmem_device *nvmem
指向需要释放的 nvmem 设备的指针。
-
struct nvmem_device *devm_nvmem_device_get(struct device *dev, const char *id)¶
根据给定的 ID 从设备获取 nvmem 设备。
参数
struct device *dev
请求 nvmem 设备的设备。
const char *id
请求的 nvmem 设备的名称 ID。
返回值
出错时返回 ERR_PTR()
,成功时返回指向 struct nvmem_device 的有效指针。 nvmem_device 将在设备释放后自动释放。
-
struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)¶
从给定的设备节点和单元 ID 获取 nvmem 单元。
参数
struct device_node *np
使用 nvmem 单元的设备树节点。
const char *id
来自 nvmem-cell-names 属性的 nvmem 单元名称,如果为空,则表示索引 0 处的单元(没有附属 nvmem-cell-names 属性的单个单元)。
返回值
出错时返回 ERR_PTR()
,成功时返回指向 struct nvmem_cell 的有效指针。 nvmem_cell 将由 nvmem_cell_put()
释放。
参数
struct device *dev
请求 nvmem 单元的设备。
const char *id
要获取的 nvmem 单元名称(这与 DT 系统的 nvmem-cell-names 属性中的名称相对应,与非 DT 系统的查找条目的 con_id 相对应)。
返回值
出错时返回 ERR_PTR()
,成功时返回指向 struct nvmem_cell 的有效指针。 nvmem_cell 将由 nvmem_cell_put()
释放。
-
struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *id)¶
根据给定的 ID 从设备获取 nvmem 单元。
参数
struct device *dev
请求 nvmem 单元的设备。
const char *id
要获取的 nvmem 单元名称 ID。
返回值
出错时返回 ERR_PTR()
,成功时返回指向 struct nvmem_cell 的有效指针。 nvmem_cell 将在设备释放后自动释放。
-
void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell)¶
释放先前从 devm_nvmem_cell_get 分配的 nvmem 单元。
参数
struct device *dev
请求 nvmem 单元的设备。
struct nvmem_cell *cell
先前由
devm_nvmem_cell_get()
分配的 nvmem 单元。
-
void nvmem_cell_put(struct nvmem_cell *cell)¶
释放先前分配的 nvmem 单元。
参数
struct nvmem_cell *cell
先前由
nvmem_cell_get()
分配的 nvmem 单元。
-
void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)¶
读取给定的 nvmem 单元。
参数
struct nvmem_cell *cell
要读取的 nvmem 单元。
size_t *len
指向单元长度的指针,成功读取时将被填充;可以为 NULL。
返回值
-
int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)¶
写入给定的 nvmem 单元。
参数
struct nvmem_cell *cell
要写入的 nvmem 单元。
void *buf
要写入的缓冲区。
size_t len
要写入 nvmem 单元的缓冲区长度。
返回值
写入的字节长度,失败时为负值。
参数
struct device *dev
请求 nvmem 单元的设备。
const char *cell_id
要读取的 nvmem 单元的名称。
u8 *val
指向输出值的指针。
返回值
成功时返回 0,失败时返回负 errno。
参数
struct device *dev
请求 nvmem 单元的设备。
const char *cell_id
要读取的 nvmem 单元的名称。
u16 *val
指向输出值的指针。
返回值
成功时返回 0,失败时返回负 errno。
参数
struct device *dev
请求 nvmem 单元的设备。
const char *cell_id
要读取的 nvmem 单元的名称。
u32 *val
指向输出值的指针。
返回值
成功时返回 0,失败时返回负 errno。
参数
struct device *dev
请求 nvmem 单元的设备。
const char *cell_id
要读取的 nvmem 单元的名称。
u64 *val
指向输出值的指针。
返回值
成功时返回 0,失败时返回负 errno。
-
int nvmem_cell_read_variable_le_u32(struct device *dev, const char *cell_id, u32 *val)¶
将最多 32 位的数据读取为小端数字。
参数
struct device *dev
请求 nvmem 单元的设备。
const char *cell_id
要读取的 nvmem 单元的名称。
u32 *val
指向输出值的指针。
返回值
成功时返回 0,失败时返回负 errno。
-
int nvmem_cell_read_variable_le_u64(struct device *dev, const char *cell_id, u64 *val)¶
将最多 64 位的数据读取为小端数字。
参数
struct device *dev
请求 nvmem 单元的设备。
const char *cell_id
要读取的 nvmem 单元的名称。
u64 *val
指向输出值的指针。
返回值
成功时返回 0,失败时返回负 errno。
-
ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem, struct nvmem_cell_info *info, void *buf)¶
读取给定的 nvmem 设备和单元。
参数
struct nvmem_device *nvmem
要从中读取的 nvmem 设备。
struct nvmem_cell_info *info
要读取的 nvmem 单元信息。
void *buf
缓冲区指针,成功读取时将被填充。
返回值
成功时返回读取的字节长度,出错时返回负错误代码。
-
int nvmem_device_cell_write(struct nvmem_device *nvmem, struct nvmem_cell_info *info, void *buf)¶
将单元写入给定的 nvmem 设备。
参数
struct nvmem_device *nvmem
要写入的 nvmem 设备。
struct nvmem_cell_info *info
要写入的 nvmem 单元信息。
void *buf
要写入单元的缓冲区。
返回值
写入的字节长度,失败时返回负错误代码。
-
int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset, size_t bytes, void *buf)¶
从给定的 nvmem 设备读取。
参数
struct nvmem_device *nvmem
要从中读取的 nvmem 设备。
unsigned int offset
nvmem 设备中的偏移量。
size_t bytes
要读取的字节数。
void *buf
缓冲区指针,成功读取时将被填充。
返回值
成功时返回读取的字节长度,出错时返回负错误代码。
-
int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset, size_t bytes, void *buf)¶
将单元写入给定的 nvmem 设备。
参数
struct nvmem_device *nvmem
要写入的 nvmem 设备。
unsigned int offset
nvmem 设备中的偏移量。
size_t bytes
要写入的字节数。
void *buf
要写入的缓冲区。
返回值
写入的字节长度,失败时返回负错误代码。
-
void nvmem_add_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries)¶
注册单元查找条目列表。
参数
struct nvmem_cell_lookup *entries
单元查找条目数组。
size_t nentries
数组中单元查找条目的数量。
-
void nvmem_del_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries)¶
删除先前添加的单元查找条目列表。
参数
struct nvmem_cell_lookup *entries
单元查找条目数组。
size_t nentries
数组中单元查找条目的数量。
-
const char *nvmem_dev_name(struct nvmem_device *nvmem)¶
获取给定 nvmem 设备的名称。
参数
struct nvmem_device *nvmem
nvmem 设备。
返回值
nvmem 设备的名称。
-
size_t nvmem_dev_size(struct nvmem_device *nvmem)¶
获取给定 nvmem 设备的大小。
参数
struct nvmem_device *nvmem
nvmem 设备。
返回值
nvmem 设备的大小。