35、PCI设备驱动与NVMEM框架技术解析

PCI设备驱动与NVMEM框架技术解析

1. PCI与直接内存访问(DMA)

在PCI设备驱动中,为了加速数据传输并减轻CPU负担,可配置控制器和设备进行直接内存访问(DMA),即数据在设备和主机间交换时无需CPU参与。PCI地址空间可能是32位或64位,系统中用于DMA传输的源或目标内存区域称为DMA缓冲区,其内存范围与总线地址大小有关。

经典PCI总线支持32位寻址,PCIe则扩展到64位,因此有32位和64位两种地址格式。要使用DMA API,驱动需包含 #include <linux/dma-mapping.h> 。为告知内核DMA缓冲区的特殊需求(指定总线宽度),可使用 dma_set_mask() 函数:

dma_set_mask(struct device *dev, u64 mask);

以下是32位(或64位)系统的示例:

int err = 0;
err = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
/*
* OR the below on a 64 bits system:
* err = pci_set_dma_mask(dev, DMA_BIT_MASK(64));
*/
if (err) {
    dev_err(&pci_dev->dev,
            " Required dma mask not supported, \
              failed to initialize device\n" );
    goto err_disable_pci_dev;
}

DMA传输需要合适的内存映射,包括分配DMA缓冲区并为每个缓冲区生成总线地址( dma_addr_t 类型)。I/O设备通过总线控制器和I/O内存管理单元(IOMMU)查看DMA缓冲区,生成的总线地址会告知设备DMA缓冲区的位置。同时,内存映射还会生成虚拟地址,DMA服务例程会将DMA缓冲区的内核虚拟地址映射到总线地址。

PCI DMA映射有两种类型:连贯映射和流式映射,内核为这两种映射提供了完善的API,屏蔽了许多与DMA控制器交互的内部细节。

1.1 PCI连贯(一致)映射

连贯映射会为设备分配无缓存(连贯)和无缓冲的内存以进行DMA操作。由于设备或CPU的写入操作可立即被对方读取,无需担心缓存一致性问题,因此这种映射是同步的。虽然大多数设备需要这种映射,但它对系统来说成本较高,不过在代码实现上较为简单。

设置连贯映射的函数如下:

void * pci_alloc_consistent(struct pci_dev *hwdev, size_t size, dma_addr_t *dma_handle)

该函数保证分配的内存物理上连续, size 是需要分配的区域长度,返回两个值:CPU可用于访问的虚拟地址和 dma_handle (输出参数,对应分配区域的总线地址)。

需要注意的是, pci_alloc_consistent() 实际上是 dma_alloc_coherent() 的简单封装,设置了 GFP_ATOMIC 标志,意味着分配操作不会睡眠,可在原子上下文中安全调用。如果想更改分配标志,例如使用 GFP_KERNEL 代替 GFP_ATOMIC ,建议直接使用 dma_alloc_coherent()

映射成本较高,最小分配单位是一个页面,实际分配的页面数是2的幂次方,页面顺序可通过 int order = get_order(size) 获取。这种映射适用于设备生命周期内持续使用的缓冲区。

要解除映射并释放DMA区域,可调用 pci_free_consistent()

pci_free_consistent(dev, size, cpu_addr, dma_handle);

这里的 cpu_addr dma_handle 对应 pci_alloc_consistent() 返回的内核虚拟地址和总线地址。虽然映射函数可在原子上下文中调用,但此函数不能在原子上下文中调用。实际上, pci_free_consistent() dma_free_coherent() 的简单封装。

以下是一个进行DMA映射并将总线地址发送给设备的示例代码:

#define DMA_ADDR_OFFSET 0x14
#define DMA_REG_SIZE_OFFSET 0x32
[...]
int do_pci_dma (struct pci_dev *pci_dev, int direction, size_t count)
{
    dma_addr_t dma_pa;
    char *dma_va;
    void iomem *dma_io;
    /* should check errors */
    dma_io = pci_iomap(dev, 2, 0);
    dma_va = pci_alloc_consistent(&pci_dev->dev, count, &dma_pa);
    if (!dma_va)
        return -ENOMEM;
    /* may need to clear allocated region */
    memset(dma_va, 0, count);
    /* set up the device */
    iowrite8(CMD_DISABLE_DMA, dma_io + REG_CMD_OFFSET);
    iowrite8(direction ?  CMD_WR : CMD_RD);
    /* Send bus address to the device */
    iowrite32(dma_pa, dma_io + DMA_ADDR_OFFSET);
    /* Send size to the device */
    iowrite32(count, dma_io + DMA_REG_SIZE_OFFSET);
    /* Start the operation */
    iowrite8(CMD_ENABLE_DMA, dma_io + REG_CMD_OFFSET);
    return 0;
}
1.2 流式DMA映射

流式映射在代码实现上有更多限制。首先,这种映射需要使用已分配的缓冲区,且映射后的缓冲区归设备所有,CPU在使用前需先解除映射以解决可能的缓存问题。

若要发起写事务(CPU到设备),驱动应在映射前将数据放入缓冲区,并指定数据移动方向,且数据只能按此方向使用。

缓冲区在被CPU访问前必须解除映射,原因在于CPU映射是可缓存的。用于流式映射的 dma_map_*() 系列函数(实际由 pci_map_*() 函数封装)会先清理/使与缓冲区相关的缓存无效,并依赖CPU在对应的 dma_unmap_*() (由 pci_unmap_*() 函数封装)操作前不访问这些缓冲区。解除映射时,若有必要会再次使缓存无效,以处理期间的推测性预取,之后CPU才能读取设备写入内存的数据。

流式映射有两种形式:
- 单缓冲区映射 :允许单页映射,适用于偶尔的映射操作。可使用 pci_map_single() 函数设置单缓冲区映射:

dma_addr_t pci_map_single(struct pci_dev *hwdev, void *ptr, size_t size, int direction)

direction 可以是 PCI_DMA_BIDIRECTION PCI_DMA_TODEVICE PCI_DMA_FROMDEVICE PCI_DMA_NONE ptr 是缓冲区的内核虚拟地址,返回的总线地址可发送给设备。使用时应确保 direction 与数据实际移动方向匹配,而不是总是使用 DMA_BIDIRECTIONAL pci_map_single() dma_map_single() 的简单封装。

使用以下函数释放映射:

Void pci_unmap_single(struct pci_dev *hwdev, dma_addr_t dma_addr, size_t size, int direction)

这是 dma_unmap_single() 的封装, dma_addr 应与 pci_map_single() 返回的地址相同, direction size 应与映射时指定的一致。

以下是简化的流式映射(单缓冲区)示例:

int do_pci_dma (struct pci_dev *pci_dev, int direction, void *buffer, size_t count)
{
    dma_addr_t dma_pa;
    /* bus address */
    void iomem *dma_io;
    /* should check errors */
    dma_io = pci_iomap(dev, 2, 0);
    dma_dir = (write ?  DMA_TO_DEVICE : DMA_FROM_DEVICE);
    dma_pa = pci_map_single(pci_dev, buffer, count, dma_dir);
    if (!dma_va)
        return -ENOMEM;
    /* may need to clear allocated region */
    memset(dma_va, 0, count);
    /* set up the device */
    iowrite8(CMD_DISABLE_DMA, dma_io + REG_CMD_OFFSET);
    iowrite8(direction ?  CMD_WR : CMD_RD);
    /* Send bus address to the device */
    iowrite32(dma_pa, dma_io + DMA_ADDR_OFFSET);
    /* Send size to the device */
    iowrite32(count, dma_io + DMA_REG_SIZE_OFFSET);
    /* Start the operation */
    iowrite8(CMD_ENABLE_DMA, dma_io + REG_CMD_OFFSET);
    return 0;
}

以下是作为DMA事务中断处理程序的示例,展示了如何从CPU端处理缓冲区:

void pci_dma_interrupt(int irq, void *dev_id)
{
    struct private_struct *priv = (struct private_struct *) dev_id;
    /* Unmap the DMA buffer */
    pci_unmap_single(priv->pci_dev, priv->dma_addr,
                     priv->dma_size, priv->dma_dir);
    /* now it is safe to access the buffer */
    [...]
}
  • 分散/聚集映射 :可一次性传输多个(不必物理连续)的缓冲区区域,而无需逐个映射和传输每个缓冲区。设置分散列表映射的步骤如下:
    1. 分配分散的缓冲区,除最后一个缓冲区外,其他缓冲区必须是页面大小。
    2. 分配一个 scatterlist 数组,并使用 sg_set_buf() 函数将之前分配的缓冲区填充到数组中。
    3. 调用 dma_map_sg() 函数对 scatterlist 数组进行映射。
    4. DMA操作完成后,调用 dma_unmap_sg() 函数解除映射。

以下是示例代码:

u32 *wbuf1, *wbuf2, *wbuf3;
struct scatterlist sgl[3];
int num_mapped;
wbuf1 = kzalloc(PAGE_SIZE, GFP_DMA);
wbuf2 = kzalloc(PAGE_SIZE, GFP_DMA);
/* size may be different for the last entry */
wbuf3 = kzalloc(CUSTOM_SIZE, GFP_DMA);
sg_init_table(sg, 3);
sg_set_buf(&sgl[0], wbuf1, PAGE_SIZE);
sg_set_buf(&sgl[1], wbuf2, PAGE_SIZE);
sg_set_buf(&sgl[2], wbuf3, CUSTOM_SIZE);
num_mapped = pci_map_sg(NULL, sgl, 3, PCI_DMA_BIDIRECTIONAL);

pci_map_sg() dma_map_sg() 的简单封装。为使代码具有可移植性,可使用以下两个宏:

dma_addr_t sg_dma_address(struct scatterlist *sg);
unsigned int sg_dma_len(struct scatterlist *sg);

dma_map_sg() dma_unmap_sg() 会处理缓存一致性问题。若在DMA传输之间需要访问(读写)数据,应使用 dma_sync_sg_for_cpu() dma_sync_sg_for_device() 函数在每次传输之间以适当方式同步缓冲区,单区域映射的类似函数是 dma_sync_single_for_cpu() dma_sync_single_for_device()

2. NVMEM框架概述

NVMEM(非易失性内存)框架是内核层用于处理非易失性存储(如EEPROM、eFuse等)的机制。过去,这些设备的驱动程序存储在 drivers/misc/ 目录下,每个驱动程序通常需要实现自己的API来处理相同的功能,缺乏抽象代码,且内核中对这些设备的支持增加导致了大量代码重复。

引入NVMEM框架旨在解决上述问题,并引入设备树(DT)表示法,使消费设备能够从NVMEM获取所需数据(如MAC地址、SoC/修订ID、部件编号等)。

2.1 NVMEM数据结构和API

NVMEM基于生产者/消费者模式,NVMEM设备驱动程序必须包含 <linux/nvmem-provider.h> ,消费者则需包含 <linux/nvmem-consumer.h> 。该框架有几个重要的数据结构:

  • struct nvmem_device :抽象了实际的NVMEM硬件,在设备注册时由框架创建和填充,其字段实际上是 struct nvmem_config 字段的完整副本。
struct nvmem_device {
    const char  *name;
    struct module *owner;
    struct device dev;
    int stride;
    int word_size;
    int id;
    int users;
    size_t size;
    bool read_only;
    int flags;
    nvmem_reg_read_t reg_read;
    nvmem_reg_write_t reg_write; 
    void *priv;
    [...]
};
  • struct nvmem_config :是NVMEM设备的运行时配置,提供设备信息或访问其数据单元的辅助函数。设备注册时,其大部分字段用于填充新创建的 nvmem_device 结构。
struct nvmem_config {
    struct device *dev;
    const char *name;
    int id;
    struct module *owner;
    const struct nvmem_cell_info *cells;
    int ncells;
    bool read_only;
    bool root_only;
    nvmem_reg_read_t reg_read;
    nvmem_reg_write_t reg_write;
    int size;
    int word_size;
    int stride;
    void *priv;
    [...]
};

各字段含义如下:
| 字段 | 含义 |
| ---- | ---- |
| dev | 父设备 |
| name | NVMEM设备的可选名称,与 id 一起构成完整设备名,建议在名称中添加 - ,如 <name>-<id> ,若省略则默认使用 nvmem<id> |
| id | NVMEM设备的可选ID,若 name NULL 则忽略,若设置为 -1 ,内核会为设备提供唯一ID |
| owner | 拥有此NVMEM设备的模块 |
| cells | 预定义NVMEM单元的数组,可选 |
| ncells | cells 数组中的元素数量 |
| read_only | 将设备标记为只读 |
| root_only | 指示设备是否仅对root用户可访问 |
| reg_read reg_write | 框架用于读写数据的底层回调函数 |
| size | 设备的大小 |
| word_size | 设备的最小读写访问粒度 |
| stride | 最小读写访问步长 |
| priv | 传递给读写回调函数的上下文数据 |

数据单元(data cell)表示NVMEM设备中的内存区域,可分配给消费驱动程序。框架使用两种不同的数据结构来维护这些内存区域,分别用于提供者和消费者:
- struct nvmem_cell_info (提供者)

struct nvmem_cell_info {
    const char *name;
    unsigned int offset;
    unsigned int bytes;
    unsigned int bit_offset;
    unsigned int nbits;
};
  • struct nvmem_cell (消费者)
struct nvmem_cell {
    const char *name;
    int offset;
    int bytes;
    int bit_offset;
    int nbits;
    struct nvmem_device *nvmem;
    struct list_head node;
};

各字段含义如下:
| 字段 | 含义 |
| ---- | ---- |
| name | 单元的名称 |
| offset | 单元在整个硬件数据寄存器中的偏移量(起始位置) |
| bytes | 从 offset 开始的数据单元大小(字节) |
| bit_offset | 单元内的位偏移量,用于位级粒度的单元 |
| nbits | 感兴趣区域的位大小 |
| nvmem | 此单元所属的NVMEM设备 |
| node | 用于系统范围内跟踪单元,最终存储在 nvmem_cells 列表中 |

为了更清晰地说明,以下是一个单元配置示例:

static struct nvmem_cellinfo mycell = {
    .offset = 0xc,
    .bytes = 0x1,
    [...]
};

.nbits .bit_offset 都为0,表示我们对单元的整个数据区域感兴趣,在本例中为1字节大小。若只对第2到4位(实际为3位)感兴趣,结构如下:

staic struct nvmem_cellinfo mycell = {
    .offset = 0xc,
    .bytes = 0x1,
    .bit_offset = 2,
    [...]
};

综上所述,连贯映射代码实现简单但使用成本高,流式映射则相反。流式映射适用于I/O设备长时间拥有缓冲区的情况,如网络驱动程序中,每个 skbuf 数据都在运行时进行映射和解除映射。但具体使用哪种方法,设备可能有最终决定权。如果有选择,应尽可能使用流式映射,必要时使用连贯映射。

2.2 NVMEM提供者驱动

NVMEM提供者驱动的主要任务是将NVMEM内存区域暴露给消费者。以下是编写NVMEM提供者驱动的关键步骤:

  1. 定义 struct nvmem_config :根据设备的具体情况,填充 struct nvmem_config 结构,设置设备的相关信息和回调函数。
static struct nvmem_config my_nvmem_config = {
    .dev = &my_device->dev,
    .name = "my_nvmem_device",
    .id = 0,
    .owner = THIS_MODULE,
    .cells = my_nvmem_cells,
    .ncells = ARRAY_SIZE(my_nvmem_cells),
    .read_only = false,
    .root_only = false,
    .reg_read = my_nvmem_read,
    .reg_write = my_nvmem_write,
    .size = MY_NVMEM_SIZE,
    .word_size = MY_NVMEM_WORD_SIZE,
    .stride = MY_NVMEM_STRIDE,
    .priv = my_private_data,
};
  1. 实现读写回调函数 reg_read reg_write 是用于读写NVMEM数据的回调函数,需要根据实际硬件的读写操作进行实现。
static int my_nvmem_read(void *priv, unsigned int offset, void *val, size_t bytes) {
    // 实现读取NVMEM数据的逻辑
    // ...
    return 0;
}

static int my_nvmem_write(void *priv, unsigned int offset, void *val, size_t bytes) {
    // 实现写入NVMEM数据的逻辑
    // ...
    return 0;
}
  1. 注册NVMEM设备 :使用 nvmem_register 函数注册NVMEM设备。
static int __init my_nvmem_driver_init(void) {
    struct nvmem_device *nvmem;
    nvmem = nvmem_register(&my_nvmem_config);
    if (IS_ERR(nvmem)) {
        dev_err(&my_device->dev, "Failed to register NVMEM device\n");
        return PTR_ERR(nvmem);
    }
    return 0;
}
  1. 注销NVMEM设备 :在驱动退出时,使用 nvmem_unregister 函数注销NVMEM设备。
static void __exit my_nvmem_driver_exit(void) {
    nvmem_unregister(my_nvmem_device);
}
2.3 NVMEM消费者驱动API

NVMEM消费者驱动可以使用NVMEM提供者暴露的内存区域。以下是一些常用的NVMEM消费者驱动API:

  • nvmem_cell_get :获取指定名称的NVMEM单元。
struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *name);
  • nvmem_cell_read :从NVMEM单元中读取数据。
int nvmem_cell_read(struct nvmem_cell *cell, void *val, size_t bytes);
  • nvmem_cell_write :向NVMEM单元中写入数据。
int nvmem_cell_write(struct nvmem_cell *cell, const void *val, size_t bytes);
  • nvmem_cell_put :释放NVMEM单元。
void nvmem_cell_put(struct nvmem_cell *cell);

以下是一个简单的NVMEM消费者驱动示例:

static int my_nvmem_consumer_probe(struct platform_device *pdev) {
    struct nvmem_cell *cell;
    u8 data[MY_NVMEM_CELL_SIZE];
    int ret;

    cell = nvmem_cell_get(&pdev->dev, "my_nvmem_cell");
    if (IS_ERR(cell)) {
        dev_err(&pdev->dev, "Failed to get NVMEM cell\n");
        return PTR_ERR(cell);
    }

    ret = nvmem_cell_read(cell, data, sizeof(data));
    if (ret) {
        dev_err(&pdev->dev, "Failed to read NVMEM cell\n");
        goto err_put_cell;
    }

    // 处理读取到的数据
    // ...

    ret = nvmem_cell_write(cell, data, sizeof(data));
    if (ret) {
        dev_err(&pdev->dev, "Failed to write NVMEM cell\n");
        goto err_put_cell;
    }

err_put_cell:
    nvmem_cell_put(cell);
    return ret;
}

总结

本文详细介绍了PCI设备驱动中的直接内存访问(DMA)技术和NVMEM框架。在PCI设备驱动中,DMA技术通过直接在设备和主机间交换数据,减轻了CPU的负担,提高了数据传输效率。DMA映射分为连贯映射和流式映射,连贯映射代码实现简单但使用成本高,流式映射则适用于I/O设备长时间拥有缓冲区的情况。

NVMEM框架是内核层用于处理非易失性存储的机制,解决了传统驱动程序缺乏抽象代码和代码重复的问题。通过定义数据结构和实现读写回调函数,NVMEM提供者驱动可以将NVMEM内存区域暴露给消费者,消费者驱动则可以使用提供的API读取和写入NVMEM数据。

流程图

graph TD;
    A[PCI设备驱动] --> B[直接内存访问(DMA)];
    B --> C[连贯映射];
    B --> D[流式映射];
    D --> E[单缓冲区映射];
    D --> F[分散/聚集映射];
    A --> G[NVMEM框架];
    G --> H[NVMEM提供者驱动];
    G --> I[NVMEM消费者驱动];

关键知识点总结

技术领域 关键知识点
PCI DMA 连贯映射、流式映射(单缓冲区映射、分散/聚集映射)、缓存一致性处理
NVMEM框架 struct nvmem_device struct nvmem_config 、提供者驱动编写、消费者驱动API
基于遗传算法的新的异构分布式系统任务调度算法研究(Matlab代码实现)内容概要:本文档围绕基于遗传算法的异构分布式系统任务调度算法展开研究,重点介绍了一种结合遗传算法的新颖优化方法,并通过Matlab代码实现验证其在复杂调度问题中的有效性。文中还涵盖了多种智能优化算法在生产调度、经济调度、车间调度、无人机路径规划、微电网优化等领域的应用案例,展示了从理论建模到仿真实现的完整流程。此外,文档系统梳理了智能优化、机器学习、路径规划、电力系统管理等多个科研方向的技术体系实际应用场景,强调“借力”工具创新思维在科研中的重要性。; 适合人群:具备一定Matlab编程基础,从事智能优化、自动化、电力系统、控制工程等相关领域研究的研究生及科研人员,尤其适合正在开展调度优化、路径规划或算法改进类课题的研究者; 使用场景及目标:①学习遗传算法及其他智能优化算法(如粒子群、蜣螂优化、NSGA等)在任务调度中的设计实现;②掌握Matlab/Simulink在科研仿真中的综合应用;③获取多领域(如微电网、无人机、车间调度)的算法复现创新思路; 阅读建议:建议按目录顺序系统浏览,重点关注算法原理代码实现的对应关系,结合提供的网盘资源下载完整代码进行调试复现,同时注重从已有案例中提炼可迁移的科研方法创新路径。
【微电网】【创新点】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究(Matlab代码实现)内容概要:本文提出了一种基于非支配排序的蜣螂优化算法(NSDBO),用于求解微电网多目标优化调度问题。该方法结合非支配排序机制,提升了传统蜣螂优化算法在处理多目标问题时的收敛性和分布性,有效解决了微电网调度中经济成本、碳排放、能源利用率等多个相互冲突目标的优化难题。研究构建了包含风、光、储能等多种分布式能源的微电网模型,并通过Matlab代码实现算法仿真,验证了NSDBO在寻找帕累托最优解集方面的优越性能,相较于其他多目标优化算法表现出更强的搜索能力和稳定性。; 适合人群:具备一定电力系统或优化算法基础,从事新能源、微电网、智能优化等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于微电网能量管理系统的多目标优化调度设计;②作为新型智能优化算法的研究改进基础,用于解决复杂的多目标工程优化问题;③帮助理解非支配排序机制在进化算法中的集成方法及其在实际系统中的仿真实现。; 阅读建议:建议读者结合Matlab代码深入理解算法实现细节,重点关注非支配排序、拥挤度计算和蜣螂行为模拟的结合方式,并可通过替换目标函数或系统参数进行扩展实验,以掌握算法的适应性调参技巧。
本项目是一个以经典51系列单片机——STC89C52为核心,设计实现的一款高性价比数字频率计。它集成了信号输入处理、频率测量及直观显示的功能,专为电子爱好者、学生及工程师设计,旨在提供一种简单高效的频率测量解决方案。 系统组成 核心控制器:STC89C52单片机,负责整体的运算和控制。 信号输入:兼容多种波形(如正弦波、三角波、方波)的输入接口。 整形电路:采用74HC14施密特触发器,确保输入信号的稳定性和精确性。 分频电路:利用74HC390双十进制计数器/分频器,帮助进行频率的准确测量。 显示模块:LCD1602液晶显示屏,清晰展示当前测量的频率值(单位:Hz)。 电源:支持标准电源输入,保证系统的稳定运行。 功能特点 宽频率测量范围:1Hz至12MHz,覆盖了从低频到高频的广泛需求。 高灵敏度:能够识别并测量幅度小至1Vpp的信号,适合各类微弱信号的频率测试。 直观显示:通过LCD1602液晶屏实时显示频率值,最多显示8位数字,便于读取。 扩展性设计:基础版本提供了丰富的可能性,用户可根据需要添加更多功能,如数据记录、报警提示等。 资源包含 原理图:详细的电路连接示意图,帮助快速理解系统架构。 PCB设计文件:用于制作电路板。 单片机程序源码:用C语言编写,适用于Keil等开发环境。 使用说明:指导如何搭建系统,以及基本的操作方法。 设计报告:分析设计思路,性能评估和技术细节。
08-28
“**NVMEM**” 是一个常见的缩写,尤其在嵌入式系统、操作系统、存储管理和硬件编程中。它的含义通常 **非易失性内存(Non-Volatile Memory)** 有关。 --- ## 一、NVMEM 的常见含义 | 缩写 | 全称 | 含义 | |------|------|------| | **NVMEM** | Non-Volatile Memory | 非易失性内存,断电后数据不丢失 | | **NVMEM** | NV Memory | 简写形式,常用于变量、函数、模块命名 | | **NVMEM** | NVMEM API | 某些嵌入式系统中用于管理非易失性内存的接口 | | **NVMEM** | NVMEM Driver | 驱动程序,用于访问 Flash、EEPROM 等非易失性存储设备 | --- ## 二、NVMEM 在嵌入式系统中的使用 在嵌入式系统中,**NVMEM 通常用于存储配置参数、设备状态、用户设置等需要断电后保留的数据**。常见的存储介质包括: - **Flash Memory** - **EEPROM** - **FRAM(铁电存储器)** - **Battery-backed SRAM** ### 示例:使用 NVMEM 存储设备配置 ```c #include <stdint.h> #include <string.h> // 假设 NVMEM 基地址为 0x08010000 #define NVMEM_BASE_ADDR 0x08010000 #define CONFIG_SIZE 128 // 模拟的配置结构体 typedef struct { uint32_t baud_rate; uint8_t device_id; uint8_t reserved[123]; // 填充 } DeviceConfig; // 模拟写入 NVMEM 函数 void nvmem_write(void *data, size_t size) { memcpy((void*)NVMEM_BASE_ADDR, data, size); } // 模拟读取 NVMEM 函数 void nvmem_read(void *data, size_t size) { memcpy(data, (void*)NVMEM_BASE_ADDR, size); } int main() { DeviceConfig config = { .baud_rate = 9600, .device_id = 0x01 }; // 写入配置到 NVMEM nvmem_write(&config, sizeof(DeviceConfig)); // 读取配置 DeviceConfig loaded_config; nvmem_read(&loaded_config, sizeof(DeviceConfig)); printf("Baud Rate: %d\n", loaded_config.baud_rate); printf("Device ID: 0x%02X\n", loaded_config.device_id); return 0; } ``` > **解释**: > - 使用宏定义 NVMEM 的起始地址(例如 Flash 的某个扇区) > - 使用 `memcpy` 模拟写入和读取操作(实际中应使用 Flash 编程 API) > - 用于保存和恢复设备配置 --- ## 三、NVMEM 在操作系统中的使用(Linux) 在 Linux 系统中,`nvmem` 是一个内核子系统,用于访问非易失性存储设备(如 EEPROM、One-Time Programmable Memory 等)。 ### 1. 查看 NVMEM 设备 ```bash ls /sys/class/nvmem/ ``` 输出示例: ``` nvmem0 nvmem1 ``` ### 2. 读取 NVMEM 数据 ```bash cat /sys/class/nvmem/nvmem0 ``` ### 3. 写入 NVMEM 数据(需配置为可写) ```bash echo -n "ABCD" > /sys/class/nvmem/nvmem0 ``` --- ## 四、NVMEM 在 RTOS 中的使用(如 FreeRTOS、Zephyr) 在 RTOS 中,`nvmem` 模块常用于封装对非易失性存储的访问,例如: ### Zephyr OS 示例 ```c #include <zephyr/kernel.h> #include <zephyr/drivers/nvmem.h> const struct device *nvmem_dev = DEVICE_DT_GET(DT_NODELABEL(nvmem0)); int main() { uint8_t buffer[16]; int err; if (!device_is_ready(nvmem_dev)) { printk("NVMEM 设备未就绪\n"); return 0; } // 读取数据 err = nvmem_read(nvmem_dev, 0, buffer, sizeof(buffer)); if (err) { printk("读取失败: %d\n", err); } else { printk("读取成功: %02X %02X %02X %02X\n", buffer[0], buffer[1], buffer[2], buffer[3]); } return 0; } ``` > **解释**: > - 使用 Zephyr 的 `nvmem` 驱动访问 EEPROM > - 支持读写操作,适用于设备配置、日志记录等 --- ## 五、如何实现一个简单的 NVMEM 驱动? ### 1. 定义接口 ```c typedef struct { int (*read)(uint32_t offset, void *buf, size_t len); int (*write)(uint32_t offset, const void *buf, size_t len); } NvmemOps; ``` ### 2. 实现 Flash 操作(以 STM32 为例) ```c int flash_nvmem_read(uint32_t offset, void *buf, size_t len) { memcpy(buf, (void*)(FLASH_BASE_ADDR + offset), len); return 0; } int flash_nvmem_write(uint32_t offset, const void *buf, size_t len) { // 实际调用 HAL_FLASH_Program 或其他 Flash 写入 API return 0; } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值