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 的有效指针。

void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem)

放置已获取的 nvmem 设备

参数

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 nvmem_cell *nvmem_cell_get(struct device *dev, const char *id)

根据给定的单元名称从设备获取 nvmem 单元。

参数

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 单元的缓冲区长度。

返回值

写入的字节长度,失败时为负值。

int nvmem_cell_read_u8(struct device *dev, const char *cell_id, u8 *val)

将单元值读取为 u8。

参数

struct device *dev

请求 nvmem 单元的设备。

const char *cell_id

要读取的 nvmem 单元的名称。

u8 *val

指向输出值的指针。

返回值

成功时返回 0,失败时返回负 errno。

int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val)

将单元值读取为 u16。

参数

struct device *dev

请求 nvmem 单元的设备。

const char *cell_id

要读取的 nvmem 单元的名称。

u16 *val

指向输出值的指针。

返回值

成功时返回 0,失败时返回负 errno。

int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val)

将单元值读取为 u32。

参数

struct device *dev

请求 nvmem 单元的设备。

const char *cell_id

要读取的 nvmem 单元的名称。

u32 *val

指向输出值的指针。

返回值

成功时返回 0,失败时返回负 errno。

int nvmem_cell_read_u64(struct device *dev, const char *cell_id, u64 *val)

将单元值读取为 u64。

参数

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 设备的大小。