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

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 属性的单个单元),则为 NULL。

返回

如果发生错误,将是一个 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_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 设备的大小。