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_table 结构定义和注册 nvmem 单元
static struct nvmem_cell_info foo_nvmem_cells[] = {
{
.name = "macaddr",
.offset = 0x7f00,
.bytes = ETH_ALEN,
}
};
static struct nvmem_cell_table foo_nvmem_cell_table = {
.nvmem_name = "i2c-eeprom",
.cells = foo_nvmem_cells,
.ncells = ARRAY_SIZE(foo_nvmem_cells),
};
nvmem_add_cell_table(&foo_nvmem_cell_table);
此外,还可以创建 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()
api 将获取给定 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 布局是另一种创建单元的机制。使用设备树绑定,可以通过使用偏移量和长度来指定简单的单元。有时,这些单元没有静态偏移量,但内容仍然定义明确,例如标签长度值。在这种情况下,必须首先解析 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 属性的单个单元),则为 NULL。
返回
如果发生错误,将是一个 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。
返回
如果发生错误,则返回 ERR_PTR()
,否则返回指向成功读取的缓冲区的有效指针。缓冲区应由使用者使用 kfree()
释放。
-
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_table(struct nvmem_cell_table *table)¶
注册一个单元信息条目表。
参数
struct nvmem_cell_table *table
单元信息条目表。
-
void nvmem_del_cell_table(struct nvmem_cell_table *table)¶
删除之前注册的单元信息表。
参数
struct nvmem_cell_table *table
单元信息条目表。
-
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 设备的大小。