远程处理器框架

简介

现代 SoC 通常在非对称多处理(AMP)配置中包含异构的远程处理器设备,它们可能运行不同实例的操作系统,无论是 Linux 还是其他任何类型的实时操作系统(RTOS)。

例如,OMAP4 具有双 Cortex-A9、双 Cortex-M3 和一个 C64x+ DSP。在典型配置中,双 Cortex-A9 以 SMP 配置运行 Linux,而其他三个核心(两个 M3 核心和一个 DSP)中的每一个都在 AMP 配置中运行其自己的 RTOS 实例。

remoteproc 框架允许不同的平台/架构控制(上电、加载固件、下电)这些远程处理器,同时抽象出硬件差异,因此不需要复制整个驱动程序。此外,该框架还为支持这种通信的远程处理器添加了 rpmsg virtio 设备。这样,特定于平台的 remoteproc 驱动程序只需要提供几个底层处理程序,然后所有的 rpmsg 驱动程序就可以直接工作了(有关基于 virtio 的 rpmsg 总线及其驱动程序的更多信息,请阅读 远程处理器消息传递 (rpmsg) 框架)。现在还可以注册其他类型的 virtio 设备。固件只需要发布它们支持的 virtio 设备类型,然后 remoteproc 就会添加这些设备。这使得以最小的开发成本重用带有远程处理器后端的现有 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() 是一个 bug。

注意

我们没有递减 rproc 的引用计数,只递减了电源引用计数。这意味着 @rproc 句柄在 rproc_shutdown() 返回后仍然有效,如果需要,用户仍然可以在后续的 rproc_boot() 中使用它。

struct rproc *rproc_get_by_phandle(phandle phandle)

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

典型用法

#include <linux/remoteproc.h>

/* in case we were given a valid 'rproc' handle */
int dummy_rproc_example(struct rproc *my_rproc)
{
      int ret;

      /* let's power on and boot our remote processor */
      ret = rproc_boot(my_rproc);
      if (ret) {
              /*
               * something went wrong. handle it and leave.
               */
      }

      /*
       * our remote processor is now powered on... give it some work
       */

      /* let's shut it down now */
      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 实现应在远程处理器的初始化期间使用此函数。

在使用此函数创建 rproc 句柄并且准备就绪后,实现应调用 rproc_add() 以完成远程处理器的注册。

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

注意

切勿直接释放 @rproc,即使它尚未注册。相反,当您需要撤销 rproc_alloc() 时,请使用 rproc_free()

void rproc_free(struct rproc *rproc)

释放由 rproc_alloc 分配的 rproc 句柄。

此函数本质上通过递减 rproc 的引用计数来撤销 rproc_alloc()。它不会直接释放 rproc;只有在没有其他对 rproc 的引用且其引用计数降至零时才会释放。

int rproc_add(struct rproc *rproc)

在通过 rproc_alloc() 分配 @rproc 后,将其注册到 remoteproc 框架中。

每当探测到新的远程处理器设备时,特定于平台的 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)

报告 remoteproc 中的崩溃

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

实现回调

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

/**
 * struct rproc_ops - platform-specific device handlers
 * @start:    power on the device and boot it
 * @stop:     power off the device
 * @kick:     kick a virtqueue (virtqueue id given as a parameter)
 */
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 索引是可选的:遍历现有的 virtqueue 并在 used ring 中寻找新缓冲区非常简单(且开销不大)。

二进制固件结构

目前 remoteproc 支持 ELF32 和 ELF64 固件二进制文件。然而,可以预料的是,我们希望通过此框架支持的其他平台/设备将基于不同的二进制格式。

当这些用例出现时,我们将不得不把二进制格式从框架核心中解耦,以便我们能够支持多种二进制格式而无需重复编写公共代码。

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

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

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

除了系统资源外,资源表还可能包含一些资源条目,用于公布远程处理器所支持的功能或配置的存在,例如跟踪缓冲区和支持的 virtio 设备(及其配置)。

资源表以该头部开头

/**
 * struct resource_table - firmware resource table header
 * @ver: version number
 * @num: number of resource entries
 * @reserved: reserved (must be zero)
 * @offset: array of offsets pointing at the various resource entries
 *
 * The header of the resource table, as expressed by this structure,
 * contains a version number (should we need to change this format in the
 * future), the number of available resource entries, and their offsets
 * in the table.
 */
struct resource_table {
      u32 ver;
      u32 num;
      u32 reserved[2];
      u32 offset[0];
} __packed;

紧跟在该头部后面的是资源条目本身,每个条目都以以下资源条目头部开头

/**
 * struct fw_rsc_hdr - firmware resource entry header
 * @type: resource type
 * @data: resource data
 *
 * Every resource entry begins with a 'struct fw_rsc_hdr' header providing
 * its @type. The content of the entry itself will immediately follow
 * this header, and it should be parsed according to the resource type.
 */
struct fw_rsc_hdr {
      u32 type;
      u8 data[0];
} __packed;

一些资源条目仅仅是公告,用于将特定的 remoteproc 配置告知主机。其他条目则需要主机执行某些操作(例如分配系统资源)。有时需要进行协商,即固件请求一个资源,一旦分配完成,主机应将该资源的详细信息反馈给固件(例如已分配内存区域的地址)。

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

/**
 * enum fw_resource_type - types of resource entries
 *
 * @RSC_CARVEOUT:   request for allocation of a physically contiguous
 *                memory region.
 * @RSC_DEVMEM:     request to iommu_map a memory-based peripheral.
 * @RSC_TRACE:            announces the availability of a trace buffer into which
 *                the remote processor will be writing logs.
 * @RSC_VDEV:       declare support for a virtio device, and serve as its
 *                virtio header.
 * @RSC_LAST:       just keep this one at the end
 * @RSC_VENDOR_START: start of the vendor specific resource types range
 * @RSC_VENDOR_END:   end of the vendor specific resource types range
 *
 * Please note that these values are used as indices to the rproc_handle_rsc
 * lookup table, so please keep them sane. Moreover, @RSC_LAST is used to
 * check the validity of an index before the lookup table is accessed, so
 * please update it as needed.
 */
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 中所示)、virtio 特性、virtio 配置空间、vring 信息等。

当注册一个新的远程处理器时,remoteproc 框架会寻找其资源表并注册它所支持的 virtio 设备。一个固件可以支持任意数量和任意类型的 virtio 设备(如果需要的话,单个远程处理器也可以通过这种方式轻松支持多个 rpmsg virtio 设备)。

当然,RSC_VDEV 资源条目仅适用于 virtio 设备的静态分配。使用 rpmsg 总线也将使动态分配成为可能(类似于我们已经实现的 rpmsg 通道动态分配方式;在 远程处理器消息传递 (rpmsg) 框架 中了解更多信息)。