当前位置: 首页 > news >正文

【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使用方法,可以直接看本章的最后一小节。

本章分为如下几部分(文章末尾会附上链接):

  1. 资源表
  2. 存储和系统资源分配
  3. Linux 下 Remoteproc 相关 API
  4. 链接脚本
  5. 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 总结

本章篇幅过多,剩余部分下半部分讲解。

http://www.dtcms.com/a/613678.html

相关文章:

  • 东莞企业建站平台中企动力 做网站 怎么样
  • 虚拟机的未来:从云计算到量子模拟
  • 前端响应式设计资源,框架+模板
  • 品牌网站建设服务网络品牌塑造
  • C语言编译系统 | 高效编译与优化技术分析
  • L2层差错控制与HARQ协议介绍
  • 4. Qt深入 线程和QObject
  • 印尼游戏出海合规指南:法律框架、税务政策与运营挑战
  • 【Java Web学习 | 第11篇】JavaScript(5)BOM
  • 打造您专属的高效DNS解析器:RethinkDNS
  • 网上书店网站建设方案策划如何建设好一个网站
  • Spring Framework 中文官方文档
  • 深度剖析 C++ vector的底层实现
  • USDe:站在稳定币、永续化与资产代币化三大趋势交汇点的新型美元
  • SpringBoot 2.x 升级到 3.x 时 Swagger 迁移完整指南
  • 网站首页浮动窗口代码忘记了wordpress登录密码忘记
  • springMVC(3)学习
  • 负载均衡API测试
  • 门户类网站费用淘宝网站边上的导航栏怎么做
  • oralce创建种子表,使用存储过程生成最大值sql,考虑并发,不考虑并发的脚本,plsql调试存储过程,java调用存储过程示例代码
  • 计算机网络技术三级知识点
  • 好用心 做网站送女友wordpress英文主题出现汉字
  • 建筑网站夜里几点维护个人网站名字大全
  • 18.HTTP协议(二)
  • 【科技补全76】新概念英语点读工具NCE-Flow、在线文件管理器copyparty 部署指北
  • 添加某些应用程序使其能够用win+r启动
  • 免费的个人网站北京工程建设监理协会网站
  • 34_FastMCP 2.x 中文文档之FastMCP客户端高级功能:处理服务端发起的用户引导详解
  • 算法公司技术面试经验总结
  • 公路建设管理办公室网站网站建设到维护