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 裝置的大小。