遠端處理器框架

簡介

現代 SoC 通常在非對稱多處理 (AMP) 配置中具有異構遠端處理器裝置,這些裝置可能執行作業系統的不同例項,無論是 Linux 還是任何其他型別的即時作業系統。

例如,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() 是一個錯誤。

注意

我們沒有遞減 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>

/* 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 處理程式。

The ->start() 處理程式接收 rproc 控制代碼,然後應開啟裝置電源並啟動它(使用 rproc->priv 訪問特定於平臺的私有資料)。啟動地址(如果需要)可以在 rproc->bootaddr 中找到(remoteproc 核心將 ELF 入口點放在那裡)。成功時,應返回 0,失敗時,應返回相應的錯誤程式碼。

The ->stop() 處理程式接收 rproc 控制代碼並關閉裝置電源。成功時,返回 0,失敗時,返回相應的錯誤程式碼。

The ->kick() 處理程式接收 rproc 控制代碼和 virtqueue 的索引,其中放置了新訊息。實現應該中斷遠端處理器,並告知它有待處理的訊息。通知遠端處理器要查詢的確切 virtqueue 索引是可選的:遍歷現有 virtqueue 並查詢已使用環中的新緩衝區很容易(且成本不高)。

二進位制韌體結構

目前,remoteproc 支援 ELF32 和 ELF64 韌體二進位制檔案。然而,非常有可能的是,我們希望使用此框架支援的其他平臺/裝置將基於不同的二進位制格式。

當這些用例出現時,我們將不得不將二進位制格式與框架核心分離,以便我們可以支援多種二進位制格式,而無需複製通用程式碼。

解析韌體時,會根據指定的裝置地址將各個段載入到記憶體中(如果遠端處理器直接訪問記憶體,則可能是物理地址)。

除了標準的 ELF 段之外,大多數遠端處理器還將包含一個特殊的 section,我們稱之為“資源表”。

資源表包含遠端處理器在啟動之前所需的系統資源,例如物理連續記憶體的分配,或某些片上外圍裝置的 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 配置空間、vrings 資訊等。

註冊新的遠端處理器時,remoteproc 框架將查詢其資源表,並註冊其支援的 virtio 裝置。韌體可以支援任意數量的 virtio 裝置,以及任何型別(如果需要,單個遠端處理器也可以輕鬆地以這種方式支援多個 rpmsg virtio 裝置)。

當然,RSC_VDEV 資源條目僅適用於 virtio 裝置的靜態分配。還將可以使用 rpmsg 匯流排進行動態分配(類似於我們已經如何動態分配 rpmsg 通道;請在遠端處理器訊息傳遞 (rpmsg) 框架中閱讀更多相關資訊)。