【STM32MP157 异核通信框架学习篇】(10)Linux下Remoteproc相关API (上)
文章目录
- 1 概要
- 2 Linux下Remoteproc相关API
- 2.1 rproc 结构体
- 2.2 初始化 Remoteproc 实例
- 2.3 退出 Remoteproc 实例
- 2.4 启动远程处理器
- 2.5 关闭远程处理器
- 2.6 分配远程处理器句柄
- 2.7 注册远程处理器
- 3 总结
1 概要
使用正点原子的stm32mp157mini进行RPMSG多核通信试验。
硬件:STM32MP157
软件:STM32CubeIDE / STM32CubeMX
远程处理器(Remoteproc)框架负责根据固件资源表中可用的信息激活 Linux 端的进程间通信(IPC),本节我们来分析 Linux 下的 Remoteproc 相关驱动,了解 Remoteproc 是怎样控制远程处理器的。本小节的内容会涉及到分析代码,会比较枯燥乏味,如果只想了解 Remotepro使用方法,可以直接看本章的最后一小节。
本章分为如下几部分(文章末尾会附上链接):
- 资源表
- 存储和系统资源分配
- Linux 下 Remoteproc 相关 API
- 链接脚本
- Remoteproc 的使用
2 Linux下Remoteproc相关API
内核源码根目录的Documentation/remoteproc.txt文本有对Remoteproc的说明,可以查阅此帮助文档获得一些信息。在Linux内核源码的drivers/remoteproc目录下就是Remoteproc驱动文件,如下图4.3.1所示,在这些文件中,一般比较关心的是remoteproc_core.c、remoteproc_elf_loader.c、remoteproc_core.c、remoteproc_sysfs.c、remoteproc_virtio.c和stm32_rproc.c这几个文件。stm32_rproc.c文件是ST官方编写的部分驱动程序,而其它文件主要就是注册设备、初始化调试目录、为stm32_rproc.c文件驱动提供接口等。

在 M4 工程的 openampMiddlewares\Third_Party\OpenAMP 目录下也可以看到 Remoteproc驱动相关的文件:remoteproc_virtio.c,大家感兴趣的也可以分析这部分的代码。如下图 4.3.2所示。

下面我们来了解几个和Remoteproc框架相关的函数,首先打开Linux内核源码drivers/remoteproc/remoteproc_core.c 文件,分别查看如下表 4.3.1.1 所示函数:


Remoteproc 框架通过remoteproc_init()函数初始化远程处理器环境,其根据固件资源表的配置信息来建立远程固件所需的运行环境,如配置固件所需的物理存储地址、注册所支持的Virtio设备等。
rproc_alloc()函数根据远程处理器(即 M4)的名称和固件来分配一个远程处理器句柄,并完成远程设备的部分初始化,此时远程处理器的初始状态处于离线状态。stm32_rproc_parse_dt()函数用于获取设备树中的属性,目的是完成远程处理器的配置。接下来调用rproc_add()函数来注册远程处理器,rproc_add()函数内部会调用rproc_boot()函数来启动远程处理器,此时远程处理器为在线状态。stm32_rproc_request_mbox()函数用于为远程处理器申请邮箱,A7和M4的核间通信,通过邮箱机制来通知已经有数据在共享内存中了,可以进行读取操作了。以上API的关系,我们可以使用以下图4.3.3来表示:

下面我们来分析其中的几个API函数。
2.1 rproc 结构体
关于这部分驱动,可以做简单了解即可,下面我们先看和远程处理器相关的rproc结构体,打开include/linux/remoteproc.h 文件,找到如下示例代码:
struct rproc
{/* rproc 代表一个物理远程处理器设备 */struct list_head node;/* rproc 对象的节点 */struct iommu_domain *domain; /* iommu 域 */const char *name;/* rproc 的可读名称,即远程处理器的名称 */char *firmware;/* 要加载的固件文件名字 */void *priv;/* 芯片厂商保存自己私有数据的指针 *//* rproc_ops 结构体是芯片厂商做好的,用于加载/启动/停止固件 */struct rproc_ops *ops;struct device dev;/* 用于引用计数和常见Remoteproc行为的虚拟设备 */atomic_t power;/* 需要此rproc启动的用户的引用计数 */unsigned int state;/* 设备状态 */struct mutex lock; /* 保护rproc并发操作的锁 */struct dentry *dbg_dir; /* 这个rproc设备的 debugfs 目录 */struct list_head traces; /* 跟踪缓冲区列表 */int num_traces; /* 跟踪缓冲区的数量 */struct list_head carveouts; /* 物理连续内存分配列表 */struct list_head mappings; /* 我们启动的iommu映射列表,关闭时需要 */u32 bootaddr; /* 加载的首地址 */struct list_head rvdevs; /* 远程Virtio设备列表 */struct list_head subdevs; /* 子设备列表,跟踪运行状态 */struct idr notifyids; /* idr 用于动态分配rproc范围内的唯一通知 ID */int index; /* 这个 rproc 设备的索引 */struct work_struct crash_handler; /* 处理崩溃的工作队列 */unsigned int crash_cnt; /* 崩溃计数器 */bool recovery_disabled; /* 如果恢复被禁用,则标记该状态 */int max_notifyid; /* 最大分配的通知ID */struct resource_table *table_ptr; /* 指向有效资源表的指针 */struct resource_table *cached_table; /* 资源表的副本 */size_t table_sz; /* cached_table 的大小 */bool has_iommu; /* 指示远程处理器是否在 MMU 后面的标志 */bool auto_boot; /* 指示是否应自动启动远程处理器的标志 */struct list_head dump_segments; /* 固件中的段列表 */int nb_vdev; /* 当前由 rproc 处理的Virtio设备数量 */bool early_boot; /* 固件加载标志位(0表示加载,1表示没有加载)*/
};
对于写驱动的人来说,rproc是Remoteproc框架的一个重要的结构体,后面的函数只需要定义一个结构体指针,然后通过指针来配置这个结构体里面的成员变量,就可以达到一定的目的。
2.2 初始化 Remoteproc 实例
remoteproc_init()函数用于初始化Remoteproc实例,即初始化远程处理器环境,其定义如下:
static int __init remoteproc_init(void)
{ rproc_init_sysfs(); rproc_init_debugfs(); return 0;
}
subsys_initcall(remoteproc_init);
rproc_init_sysfs()函数在remoteproc_sysfs.c文件中定义,它为sysfs文件系统注册 Remoteproc 设备类,它提供了一个用于控制远程处理器的sysfs接口,sysfs其实也是个文件系统,挂载在/sys下,如对此文件系统感兴趣的可自行了解,这里就不占用过多篇幅介绍了。
rproc_init_debugfs()函数在remoteproc_debugfs.c文件中定义,用于创建调试(debugfs)目录,debugfs其实也是一个虚拟文件系统,它是内核空间与用户空间的接口,方便开发人员调试和向用户空间导出内核空间数据,一般发行版的内核都已经默认将debugfs和sysfs编译到了内核,并将debugfs自动挂载到文件系统的/sys/kernel/debug 目录下,我们进入到开发板Linux文件系统该目录下,如下图4.3.2.1所示:

可以看到有remoteproc文件夹,进入文件夹发现如下有一个remoteproc0目录,如下图4.3.2.2 所示。

进入到remoteproc0 目录下,可以看到有如下文件:carveout_memories、crash、name、recovery 和 resource_table。这几个文件说明如下:
1)name中的内容是处理器的名字,内容为m4,也就是M4协处理器;
2)resource_table 记录了 M4 的资源信息(资源表);
3)carveout_memories 记录了 M4 的内存分配情况(内存是在设备树下配置的);
4)recovery 文件的内容可以是 enabled、disabled 或 recovery 三者之一,用于控制恢复机制的行为。enabled 表示远程处理器将在崩溃时可以自动恢复,disabled表示远程处理器在崩溃时将保持崩溃状态,recovery 表示如果远程处理器处于崩溃状态,此功能将触发立即恢复,而不用手动更改或检查恢复状态(启用/禁用)。这三种模式在调试的时候很有用,默认为enabled,如果要修改为disabled,执行如下指令即可:
echo disabled >/sys/kernel/debug/remoteproc/remoteproc0/recovery
5)crash 是记录系统崩溃时候的有关信息。
如果A7没有使用Remoteproc加载固件,在carveout_memories 和 resource_table 文件中是没有什么内容的,如下操作所示,使用cat命令查看文件是没有什么内容的,如下图4.3.2.3所示:

如果A7使用Remoteproc加载固件后,固件资源被解析,关联的资源信息会被记录到以上文件中,后面我们在加载固件后再来查看这些文件的内容。
2.3 退出 Remoteproc 实例
remoteproc_exit()函数用于退出 Remoteproc 示例,即退出远程处理器环境,其定义如下:
static void __exit remoteproc_exit(void)
{ ida_destroy(&rproc_dev_index); rproc_exit_debugfs(); rproc_exit_sysfs();
}
module_exit(remoteproc_exit);
释放Remoteproc和初始化Remoteproc相反,也就是退出debugfs和sysfs目录,并删除关联的资源。
2.4 启动远程处理器
rproc_boot() 函数用于启动远程处理器,其定义如下:
1 /**
2 * @brief 启动远程处理器(即加载其固件,打开电源,...)
3 * @param rproc:指针变量,远程处理器的结构体
4 * @retval 成功返回 0,否则返回适当的错误值。
5 */
6 int rproc_boot(struct rproc *rproc)
7 {
8 const struct firmware *firmware_p = NULL;
9 struct device *dev;
10 int ret;
11
12 if (!rproc) {
13 pr_err("invalid rproc handle\n");
14 return -EINVAL;
15 }
16
17 dev = &rproc->dev;
18 /* 获取互斥锁,可中断 */
19 ret = mutex_lock_interruptible(&rproc->lock);
20 if (ret) {
21 dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, ret);
22 return ret;
23 }
24 /* 如果处理器的状态是已经删除,则解锁互斥锁 */
25 if (rproc->state == RPROC_DELETED) {
26 ret = -ENODEV;
27 dev_err(dev, "can't boot deleted rproc %s\n", rproc->name);
28 goto unlock_mutex;
29 }
30 /* 如果 rproc 已经通电,则跳过引导过程 */
31 if (atomic_inc_return(&rproc->power) > 1) {
32 ret = 0;
33 goto unlock_mutex;
34 }
35
36 dev_info(dev, "powering up %s\n", rproc->name);
37
38 if (!rproc->early_boot) {
39 /* 如果没有加载固件的话,则加载固件到内存 */
40 ret = request_firmware(&firmware_p, rproc->firmware, dev);
41 if (ret < 0) {
42 dev_err(dev, "request_firmware failed: %d\n", ret);
43 goto downref_rproc;
44 }
45 } else {
46 /* 如果已经加载了固件,则将固件名称设置为 null 作为未知 */
47 kfree(rproc->firmware);
48 rproc->firmware = NULL;
49 }
50 /* 获取固件并用固件启动远程处理器。 */
51 ret = rproc_fw_boot(rproc, firmware_p);
52 /* 释放固件 */
53 release_firmware(firmware_p);
54
55 downref_rproc:
56 if (ret)
57 atomic_dec(&rproc->power); /* 递减原子变量 */
58 unlock_mutex:
59 mutex_unlock(&rproc->lock); /* 释放互斥锁 */
60 return ret;
61 }
62 EXPORT_SYMBOL(rproc_boot);
2.5 关闭远程处理器
前面使用 rproc_boot()启动远程处理器,这里rproc_shutdown()函数用于关闭远程处理器,其定义如下:
1 /**
2 * @brief 关闭远程处理器
3 * @param rproc: 指针变量,远程处理器的结构体
4 * @retval 无。
5 * @note 如果rproc 仍在被其它用户使用,则此函数只会
6 * 减少电源引用计数并退出,无需真正关闭设备电源。
7 */
8 void rproc_shutdown(struct rproc *rproc)
9 {
10 struct device *dev = &rproc->dev;
11 int ret;
12 /* 获取互斥锁,可中断 */
13 ret = mutex_lock_interruptible(&rproc->lock);
14 if (ret) {
15 dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, ret);
16 return;
17 }
18
19 /* 如果仍然需要远程过程,请退出 */
20 if (!atomic_dec_and_test(&rproc->power))
21 goto out;
22 /* Virtio 设备被 rproc_stop() 销毁 */
23 ret = rproc_stop(rproc, false);
24 if (ret) {
25 atomic_inc(&rproc->power);
26 goto out;
27 }
28
29 /* 清理所有获得的资源 */
30 rproc_resource_cleanup(rproc);
31
32 rproc_disable_iommu(rproc);
33
34 /* 释放资源表 */
35 kfree(rproc->cached_table);
36 rproc->cached_table = NULL;
37 rproc->table_ptr = NULL;
38 out:
39 mutex_unlock(&rproc->lock); /* 释放互斥锁 */
40 }
41 EXPORT_SYMBOL(rproc_shutdown);
2.6 分配远程处理器句柄
在远程处理器初始化期间会调用rproc_alloc()函数,该函数会根据远程处理器的名称和固件来分配一个远程处理器句柄,并完成远程设备的部分初始化。在使用此函数分配新的远程处理器句柄后,远程处理器是还未注册的,后期应该调用rproc_add()函数以完成远程处理器的注册。 运行此函数后,远程处理器的初始状态处于离线状态。
/**
* @brief 分配一个远程处理器句柄
* @dev dev:底层设备
* name:此远程处理器的名称
* ops:处理程序(主要是启动/停止)
* firmware:要加载的固件文件的名称,可以为空
* len:rproc驱动程序所需的私有数据长度(字节)
* @retval 成功时返回新的rproc,失败时返回NULL。
* @note 注意:如果要释放rproc_add()函数分配的处理器,使用rproc_free()
*/ struct rproc *rproc_alloc(struct device *dev, const char *name, const struct rproc_ops *ops, const char *firmware, int len){ struct rproc *rproc; char *p, *template = "rproc-%s-fw"; int name_len; if (!dev || !name || !ops) return NULL; /* 如果调用方没有传入固件名称,则构造一个默认名称 */ if (!firmware) { name_len = strlen(name) + strlen(template) - 2 + 1; p = kmalloc(name_len, GFP_KERNEL); if (!p) return NULL; snprintf(p, name_len, template, name); } /* * 如果调用方有传入固件名称,则分配内存空间, * 并将该固件名称字符串拷贝到所分配的地址空间中 */ else { p = kstrdup(firmware, GFP_KERNEL); if (!p) return NULL; } /* 调用kzalloc分配一个内存空间 */ rproc = kzalloc(sizeof(struct rproc) + len, GFP_KERNEL); if (!rproc) { kfree(p); return NULL; } /* 根据给定的ops来分配一个内存空间 */ rproc->ops = kmemdup(ops, sizeof(*ops), GFP_KERNEL); if (!rproc->ops) { kfree(p); kfree(rproc); return NULL; } rproc->firmware = p; /* 要加载的固件文件名字 */ rproc->name = name; /* 远程处理器器的名称 */ rproc->priv = &rproc[1];/* 私有数据 */ rproc->auto_boot = true;/* 自动启动远程处理器 */ /* 对设备进行初始化,主要是设备引用计数器、信号量、设备访问锁等字段的初始化 */ device_initialize(&rproc->dev); rproc->dev.parent = dev; rproc->dev.type = &rproc_type; rproc->dev.class = &rproc_class; rproc->dev.driver_data = rproc; /* 分配唯一的设备索引和名称 */ rproc->index = ida_simple_get(&rproc_dev_index, 0, 0, GFP_KERNEL); if (rproc->index < 0) { dev_err(dev, "ida_simple_get failed: %d\n", rproc->index); put_device(&rproc->dev); /* 对设备引用次数减一 */ return NULL; } dev_set_name(&rproc->dev, "remoteproc%d", rproc->index); atomic_set(&rproc->power, 0); /* Default to ELF loader if no load function is specified */ /* 若未指定加载的文件名,则默认是ELF文件 */ if (!rproc->ops->load) { /* 加载ELF文件,即加载固件盗内存中 */ rproc->ops->load = rproc_elf_load_segments; /* 加载固件资源表 */ rproc->ops->parse_fw = rproc_elf_load_rsc_table; /* 查找已经加载的资源表 */ rproc->ops->find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table; /* 检查ELF固件映像 */ rproc->ops->sanity_check = rproc_elf_sanity_check; /* 获取处理器的启动地址 */ rproc->ops->get_boot_addr = rproc_elf_get_boot_addr; } mutex_init(&rproc->lock); idr_init(&rproc->notifyids); /* 初始化对应的双向链表 */ INIT_LIST_HEAD(&rproc->carveouts); INIT_LIST_HEAD(&rproc->mappings); INIT_LIST_HEAD(&rproc->traces); INIT_LIST_HEAD(&rproc->rvdevs); INIT_LIST_HEAD(&rproc->subdevs); INIT_LIST_HEAD(&rproc->dump_segments); /* 初始化一个工作队列 */ INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work); /* 远程处理器的初始状态是离线的状态 */ rproc->state = RPROC_OFFLINE; return rproc;
} EXPORT_SYMBOL(rproc_alloc);
2.7 注册远程处理器
rproc_add()函数用于注册一个远程处理器(rproc),rproc_add()函数把rproc结构体注册进Remoteproc框架,就能够为上一层提供接口去加载固件。使用rproc_add()函数注册rproc结构体后,当需要删除时可以使用rproc_del()函数。
1 /**
2 * @brief 注册一个远程处理器
3 * @param rproc:远程处理器的结构体
4 * @retval 成功返回 0,否则返回适当的错误代码。
5 * @note 注意:此函数会启动一个异步固件加载上下文,
6 * 它将寻找rproc的固件支持的virtio设备。
7 */
8 int rproc_add(struct rproc *rproc)
9 {
10 struct device *dev = &rproc->dev;
11 int ret;
12 /* 调用device_add增加一个设备对象 */
13 ret = device_add(dev);
14 if (ret < 0)
15 return ret;
16
17 dev_info(dev, "%s is available\n", rproc->name);
18
19 /* 创建debugfs条目 */
20 rproc_create_debug_dir(rproc);
21
22 /* 添加资源管理器设备 */
23 ret = devm_of_platform_populate(dev->parent);
24 if (ret < 0)
25 return ret;
26 if (rproc->early_boot) {
27 /* 如果已经加载固件,则无需等待固件,只需处理关联资源并启动子设备 */
28 ret = rproc_boot(rproc);
29 if (ret < 0)
30 return ret;
31 } else if (rproc->auto_boot) {
32 /* 如果rproc被标记为永远在线,则请求它启动 */
33 ret = rproc_trigger_auto_boot(rproc);
34 if (ret < 0)
35 return ret;
36 }
37
38 /* 暴露给 rproc_get_by_phandle 用户 */
39 mutex_lock(&rproc_list_mutex);
40 list_add(&rproc->node, &rproc_list);
41 mutex_unlock(&rproc_list_mutex);
42
43 return 0;
44 }
45 EXPORT_SYMBOL(rproc_add);
rproc_add()函数调用了rproc_boot()函数,我们分析其启动过程:
第12行,调用device_add()增加一个设备对象,注册一个远程处理器;
第23行,添加资源管理器设备,dev是从设备树请求的设备,devm_of_platform_populate()函数会调用of_platform_populate()函数,of_platform_populate()函数会遍历对应的设备树节点,并将设备树节点转化为一个platform device,因为在platform driver中,最终是platform device和platform driver匹配,最后可以完成probe过程;
第26~36行,如果已经加载了固件,则执行rproc_boot()函数,表示处理关联资源并启动处理器,如果远程处理器被标记为永远在线,则请求启动它;
第39~41行,先调用互斥锁锁定rproc_list_mutex互斥对象,再使用list_add将处理器设备节点添加到列表中,然后再解锁rproc_list_mutex互斥对象,这样操作后,rproc_get_by_phandle()可通过设备节点的phandle查找远程处理器。
前面的第20行,创建debugfs条目,rproc_create_debug_dir()函数在remoteproc_debugfs.c文件中定义,如下:
void rproc_create_debug_dir(struct rproc *rproc)
{ struct device *dev = &rproc->dev; if (!rproc_dbg) return; rproc->dbg_dir = debugfs_create_dir(dev_name(dev), rproc_dbg); if (!rproc->dbg_dir) return; debugfs_create_file("name", 0400, rproc->dbg_dir, rproc, &rproc_name_ops); debugfs_create_file("recovery", 0400, rproc->dbg_dir, rproc, &rproc_recovery_ops); debugfs_create_file("crash", 0200, rproc->dbg_dir, rproc, &rproc_crash_ops); debugfs_create_file("resource_table", 0400, rproc->dbg_dir, rproc, &rproc_rsc_table_ops); debugfs_create_file("carveout_memories", 0400, rproc->dbg_dir, rproc, &rproc_carveouts_ops);
}
可以看到,rproc_create_debug_dir()函数通过debugfs_create_file()创建了4个debugfs文件:name、recovery、crash、resource_table和carveout_memories,这4个文件我们在前面已经了解过了。看到的rproc_name_ops、rproc_recovery_ops等类似XXX_ops结构的其实是回调函数结构体,这些回调函数结构体中的函数会对文件进行打开、释放、读文件等操作。例如rproc_rsc _table_ops如下:
static const struct file_operations rproc_rsc_table_ops = { .open = rproc_rsc_table_open, .read = seq_read, .llseek = seq_lseek, .release = single_release,
};
open处理程序会执行rproc_rsc_table_open()函数:
static int rproc_rsc_table_open(struct inode *inode, struct file *file)
{ return single_open(file, rproc_rsc_table_show, inode->i_private);
}
rproc_rsc_table_open()函数会执行single_open()函数,single_open()调用rproc_rsc_table_show()函数,rproc_rsc_table_show()函数的内容,如下:
1 /**
2 * @brief 通过 debugfs 打印出资源表内容。
3 * @param seq:序列文件接口指针
4 * @retval 成功返回 0,否则返回适当的错误代码。
5 */
6 static int rproc_rsc_table_show(struct seq_file *seq, void *p)
7 {
8 static const char * const types[] = {"carveout","devmem","trace", "vdev"};
9 struct rproc *rproc = seq->private;
10 struct resource_table *table = rproc->table_ptr;
11 struct fw_rsc_carveout *c;
12 struct fw_rsc_devmem *d;
13 struct fw_rsc_trace *t;
14 struct fw_rsc_vdev *v;
15 int i, j;
16
17 if (!table) {
18 seq_puts(seq, "No resource table found\n");
19 return 0;
20 }
21
22 for (i = 0; i < table->num; i++) {
23 int offset = table->offset[i];
24 struct fw_rsc_hdr *hdr = (void *)table + offset;
25 void *rsc = (void *)hdr + sizeof(*hdr);
26
27 switch (hdr->type) {
28 case RSC_CARVEOUT:
29 /*省略部分代码 */
30 break;
31 case RSC_DEVMEM:
32 /*省略部分代码 */
33 break;
34 case RSC_TRACE:
35 /*省略部分代码 */
36 break;
37 case RSC_VDEV:
38 v = rsc;
39 seq_printf(seq, "Entry %d is of type %s\n", i, types[hdr->type]);
40
41 seq_printf(seq, " ID %d\n", v->id);
42 seq_printf(seq, " Notify ID %d\n", v->notifyid);
43 seq_printf(seq, " Device features 0x%x\n", v->dfeatures);
44 seq_printf(seq, " Guest features 0x%x\n", v->gfeatures);
45 seq_printf(seq, " Config length 0x%x\n", v->config_len);
46 seq_printf(seq, " Status 0x%x\n", v->status);
47 seq_printf(seq, " Number of vrings %d\n", v->num_of_vrings);
48 seq_printf(seq, " Reserved (should be zero) [%d][%d]\n\n",
49 v->reserved[0], v->reserved[1]);
50
51 for (j = 0; j < v->num_of_vrings; j++) {
52 seq_printf(seq, " Vring %d\n", j);
53 seq_printf(seq, " Device Address 0x%x\n", v->vring[j].da);
54 seq_printf(seq, " Alignment %d\n", v->vring[j].align);
55 seq_printf(seq, " Number of buffers %d\n", v->vring[j].num);
56 seq_printf(seq, " Notify ID %d\n", v->vring[j].notifyid);
seq_file 是序列文件接口,seq_file 可以将 Linux 内核里面常用的数据结构通过文件导出到用户空间,如果我们读取或者打开Linux文件系统/sys/kernel/debug/remoteproc/remoteproc0 下的resource_table 文件,那么就会执行第37 行~60行的代码,即打印RSC_VDEV 资源条目信息(包括Virtio 设备 ID、Virtio 功能、Virtio 配置空间、vrings 信息等)。 假设此时已经加载固件了,在Linux 文件系统的/sys/kernel/debug/remoteproc/remoteproc0 目录下执行如下指令:
cat resource_table
打印如下图4.3.7.1所示信息,可以看到Virtio RPMsg 设备 ID为7,vring个数是2,每个vring 有16个rpmsg buffer,这些信息我们在前面分析资源表的时候已经确定下来了,其中vring0和vring1 的地址和前面我们分析stm32mp157d-atk.dtsi设备树的时候看到的配置一样:

又比如rproc_carveouts_ops 如下:
static const struct file_operations rproc_carveouts_ops = { .open = rproc_carveouts_open, .read = seq_read, .llseek = seq_lseek, .release = single_release,
};
open处理程序会执行rproc_carveouts_open()函数,rproc_carveouts_open()函数如下:
static int rproc_carveouts_open(struct inode *inode, struct file *file)
{ return single_open(file, rproc_carveouts_show, inode->i_private);
}
我们再打开rproc_carveouts_show()函数如下:
/** * @brief 通过 debugfs 打印 carveout 内容。 * @param seq:序列文件接口指针 * @retval 成功返回 0,否则返回适当的错误代码。 */
static int rproc_carveouts_show(struct seq_file *seq, void *p)
{ struct rproc *rproc = seq->private; struct rproc_mem_entry *carveout; list_for_each_entry(carveout, &rproc->carveouts, node) { seq_puts(seq, "Carveout memory entry:\n"); seq_printf(seq, "\tName: %s\n", carveout->name); seq_printf(seq, "\tVirtual address: %pK\n", carveout->va); seq_printf(seq, "\tDMA address: %pad\n", &carveout->dma); seq_printf(seq, "\tDevice address: 0x%x\n", carveout->da); seq_printf(seq, "\tLength: 0x%x Bytes\n\n", carveout->len); } return 0;
}
可以看到,最终打印的是carveout_memories的信息,即M4内存相关配置信息,如名字Name 、虚拟地址Virtual address、DMA地址DMA address、设备地址Device address和地址长度Length等。假设此时已经加载了固件,在Linux文件系统的/sys/kernel/debug/remoteproc/remoteproc0目录下执行如下指令:
cat carveout_memories
如下图4.3.7.2操作所示,这是笔者加载固件后查看的信息,这些信息就是M4的内存分配信息,分别有retram、mcuram、mcuram2、vdev0vring0、vdev0vring1和vdev0buffer,这些地址范围和前面我们讲解stm32mp157d-atk.dtsi设备树下的存储配置一样:

大家在后面实验操作的时候可以多查看这些文件的信息。其它回调函数这里就不再一一分析了,感兴趣的小伙伴可以自行阅读代码。
3 总结
本章篇幅过多,剩余部分下半部分讲解。
