远程处理器框架 【ChatGPT】

发布时间 2023-12-11 19:56:07作者: 摩斯电码

远程处理器框架

简介

现代SoC通常具有异构的远程处理器设备,采用非对称多处理(AMP)配置,可以运行不同实例的操作系统,无论是Linux还是任何其他实时操作系统的变种。

例如,OMAP4具有双核Cortex-A9、双核Cortex-M3和一个C64x+ DSP。在典型配置中,双核Cortex-A9以SMP配置运行Linux,而其他三个核(两个M3核和一个DSP)则以AMP配置运行各自的实时操作系统。

远程处理器框架允许不同平台/架构控制(开机、加载固件、关机)这些远程处理器,同时抽象硬件差异,因此整个驱动程序不需要重复。此外,该框架还为支持此类通信的远程处理器添加了rpmsg virtio设备。这样,特定于平台的远程处理器驱动程序只需要提供一些低级处理程序,然后所有rpmsg驱动程序就可以正常工作(有关基于virtio的rpmsg总线及其驱动程序的更多信息,请阅读“远程处理器消息(rpmsg)框架”)。现在还可以注册其他类型的virtio设备。固件只需发布其支持的virtio设备类型,然后远程处理器将添加这些设备。这使得可以以最小的开发成本重用现有的virtio驱动程序与远程处理器后端。

用户API

int rproc_boot(struct rproc *rproc)

启动远程处理器(即加载其固件,开机等)。

如果远程处理器已经开机,则此函数立即返回(成功)。

成功返回0,否则返回适当的错误值。注意:要使用此函数,您应该已经有一个有效的rproc句柄。有几种方法可以清晰地实现这一点(devres、pdata、remoteproc_rpmsg.c的方式,或者如果这变得普遍,我们也可以考虑使用dev_archdata)。

int rproc_shutdown(struct rproc *rproc)

关闭远程处理器(之前使用rproc_boot()启动)。如果@rproc仍然被其他用户使用,则此函数只会递减电源引用计数并退出,而不会真正关闭设备。

成功返回0,否则返回适当的错误值。每次调用rproc_boot()必须(最终)伴随着对rproc_shutdown()的调用。多次调用rproc_shutdown()是一个错误。

注意:我们不会递减rproc的引用计数,只会递减电源引用计数,这意味着即使rproc_shutdown()返回后,@rproc句柄仍然有效,用户仍然可以在需要时使用它进行后续的rproc_boot()。

struct rproc *rproc_get_by_phandle(phandle phandle)

使用设备树phandle查找rproc句柄。成功时返回rproc句柄,失败时返回NULL。此函数会增加远程处理器的引用计数,因此一旦不再需要rproc,始终使用rproc_put()将其递减。

典型用法

#include <linux/remoteproc.h>

/* 如果我们有一个有效的'rproc'句柄 */
int dummy_rproc_example(struct rproc *my_rproc)
{
      int ret;

      /* 让我们开机并启动我们的远程处理器 */
      ret = rproc_boot(my_rproc);
      if (ret) {
              /*
               * 出了些问题。处理它并离开。
               */
      }

      /*
       * 我们的远程处理器现在已经开机... 让它做一些工作
       */

      /* 现在让我们关闭它 */
      rproc_shutdown(my_rproc);
}

实现者API

struct rproc *rproc_alloc(struct device *dev, const char *name,
                              const struct rproc_ops *ops,
                              const char *firmware, int len)

分配一个新的远程处理器句柄,但尚未注册。必需的参数是底层设备、此远程处理器的名称、特定于平台的ops处理程序、要使用的固件的名称以及分配rproc驱动程序所需的私有数据的长度(以字节为单位)。

此函数应该由rproc实现在初始化远程处理器时使用。

成功时返回新的rproc,失败时返回NULL。

注意:永远不要直接释放@rproc,即使它尚未注册。当需要取消rproc_alloc()时,请使用rproc_free()。

void rproc_free(struct rproc *rproc)

释放由rproc_alloc分配的rproc句柄。

此函数本质上是取消rproc_alloc(),通过递减rproc的引用计数。只有当没有其他引用rproc且其引用计数现在降至零时,才会直接释放rproc。

int rproc_add(struct rproc *rproc)

在使用rproc_alloc()分配后,使用远程处理器框架注册@rproc。

每当探测到新的远程处理器设备时,特定于平台的rproc实现将调用此函数。

成功返回0,否则返回适当的错误代码。注意:此函数启动一个异步固件加载上下文,该上下文将查找rproc的固件支持的virtio设备。如果找到,将创建并添加这些virtio设备,因此注册此远程处理器可能会导致其他virtio驱动程序被探测。

int rproc_del(struct rproc *rproc)

取消rproc_add()。

当特定于平台的rproc实现决定移除rproc设备时,应调用此函数。只有在先前成功完成rproc_add()的调用后才应调用此函数。

rproc_del()返回后,@rproc仍然有效,其最后的引用计数应通过调用rproc_free()递减。

成功返回0,如果@rproc无效则返回-EINVAL。

void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type)

报告远程处理器的崩溃。

每当特定于平台的rproc实现检测到崩溃时,必须调用此函数。非远程处理器驱动程序不应调用此函数。此函数可以从原子/中断上下文中调用。

实现回调

这些回调应该由特定平台的remoteproc驱动程序提供:

/**
 * struct rproc_ops - 平台特定的设备处理程序
 * @start:    启动设备并引导它
 * @stop:     关闭设备
 * @kick:     激活一个virtqueue(以参数形式给出virtqueue id)
 */
struct rproc_ops {
      int (*start)(struct rproc *rproc);
      int (*stop)(struct rproc *rproc);
      void (*kick)(struct rproc *rproc, int vqid);
};

每个remoteproc实现至少应该提供->start和->stop处理程序。如果还希望使用rpmsg/virtio功能,则还应提供->kick处理程序。

->start()处理程序接受一个rproc句柄,然后应该启动设备并引导它(使用rproc->priv来访问平台特定的私有数据)。如果需要,引导地址可以在rproc->bootaddr中找到(remoteproc核心将ELF入口点放在那里)。成功时返回0,失败时返回适当的错误代码。

->stop()处理程序接受一个rproc句柄并关闭设备。成功时返回0,失败时返回适当的错误代码。

->kick()处理程序接受一个rproc句柄和一个新消息所在的virtqueue的索引。实现应该中断远程处理器并让其知道有待处理的消息。通知远程处理器要查找的确切virtqueue索引是可选的:遍历现有的virtqueues并查找已使用环中的新缓冲区既容易又不太昂贵。

二进制固件结构

目前remoteproc支持ELF32和ELF64固件二进制。然而,可以预期到我们希望使用该框架支持的其他平台/设备将基于不同的二进制格式。

当出现这些用例时,我们将不得不将二进制格式与框架核心解耦,以便我们可以支持多种二进制格式而不重复通用代码。

解析固件时,根据指定的设备地址(如果远程处理器直接访问内存,则可能是物理地址),将其各种段加载到内存中。

除了标准的ELF段外,大多数远程处理器还将包括一个我们称之为“资源表”的特殊部分。

资源表包含远程处理器在上电之前需要的系统资源,例如物理连续内存的分配或某些片上外设的iommu映射。只有在满足资源表的所有要求之后,远程处理器才会上电。

除了系统资源,资源表还可以包含发布远程处理器支持的特性或配置存在的资源条目,例如跟踪缓冲区和支持的virtio设备(及其配置)。

资源表以以下标题开头:

/**
 * struct resource_table - 固件资源表头
 * @ver: 版本号
 * @num: 资源条目数
 * @reserved: 保留(必须为零)
 * @offset: 指向各个资源条目的偏移量数组
 *
 * 通过此结构表达的资源表头包含版本号(如果将来需要更改此格式),可用资源条目的数量以及它们在表中的偏移量。
 */
struct resource_table {
      u32 ver;
      u32 num;
      u32 reserved[2];
      u32 offset[0];
} __packed;

紧随此标题之后的是资源条目本身,每个资源条目都以以下资源条目头开始:

/**
 * struct fw_rsc_hdr - 固件资源条目头
 * @type: 资源类型
 * @data: 资源数据
 *
 * 每个资源条目都以 'struct fw_rsc_hdr' 头开始,提供其 @type。条目本身的内容将紧随此标题,并应根据资源类型进行解析。
 */
struct fw_rsc_hdr {
      u32 type;
      u8 data[0];
} __packed;

一些资源条目只是通知,其中主机被告知特定的remoteproc配置。其他条目要求主机执行某些操作(例如分配系统资源)。有时期望进行协商,其中固件请求资源,一旦分配,主机应提供其详细信息(例如分配的内存区域的地址)。

以下是当前支持的各种资源类型:

/**
 * enum fw_resource_type - 资源条目类型
 *
 * @RSC_CARVEOUT:   请求分配物理连续内存区域。
 * @RSC_DEVMEM:     请求iommu_map内存型外设。
 * @RSC_TRACE:            宣布远程处理器将写入日志的跟踪缓冲区的可用性。
 * @RSC_VDEV:       声明对virtio设备的支持,并充当其virtio头。
 * @RSC_LAST:       保持在最后
 * @RSC_VENDOR_START: 供应商特定资源类型范围的开始
 * @RSC_VENDOR_END:   供应商特定资源类型范围的结束
 *
 * 请注意,这些值用作rproc_handle_rsc查找表的索引,因此请保持它们合理。此外,@RSC_LAST用于在访问查找表之前检查索引的有效性,因此请根据需要更新它。
 */
enum fw_resource_type {
      RSC_CARVEOUT            = 0,
      RSC_DEVMEM              = 1,
      RSC_TRACE               = 2,
      RSC_VDEV                = 3,
      RSC_LAST                = 4,
      RSC_VENDOR_START        = 128,
      RSC_VENDOR_END          = 512,
};

有关特定资源类型的更多详细信息,请参阅include/linux/remoteproc.h中的专用结构。

我们还期望在某个时候会出现特定于平台的资源条目。当发生这种情况时,我们可以轻松地添加一个新的RSC_PLATFORM类型,并将这些资源交给特定于平台的rproc驱动程序来处理。

Virtio和remoteproc

固件应向remoteproc提供有关其支持的virtio设备及其配置的信息:RSC_VDEV资源条目应指定virtio设备id(如virtio_ids.h中的id)、virtio特性、virtio配置空间、vrings信息等。

当注册新的远程处理器时,remoteproc框架将查找其资源表,并注册其支持的virtio设备。一个固件可以支持任意数量和任何类型的virtio设备(如果需要,单个远程处理器也可以轻松支持多个rpmsg virtio设备)。

当然,RSC_VDEV资源条目仅适用于virtio设备的静态分配。动态分配也将通过rpmsg总线变得可能(类似于我们已经如何动态分配rpmsg通道;请阅读有关远程处理器消息(rpmsg)框架的更多信息)。