遠端處理器訊息傳遞 (rpmsg) 框架¶
注意
本文件描述了 rpmsg 匯流排以及如何編寫 rpmsg 驅動程式。要了解如何為新平臺新增 rpmsg 支援,請檢視 遠端處理器框架(也在 Documentation/ 中)。
介紹¶
現代 SoC 通常在非對稱多處理 (AMP) 配置中使用異構遠端處理器裝置,這些裝置可能執行不同作業系統的例項,無論是 Linux 還是任何其他型別的即時作業系統。
例如,OMAP4 具有雙核 Cortex-A9、雙核 Cortex-M3 和一個 C64x+ DSP。通常,雙核 Cortex-A9 在 SMP 配置中執行 Linux,而其他三個核心(兩個 M3 核心和一個 DSP)在 AMP 配置中執行各自的 RTOS 例項。
通常,AMP 遠端處理器採用專用的 DSP 編解碼器和多媒體硬體加速器,因此通常用於從主應用程式處理器解除安裝 CPU 密集型多媒體任務。
這些遠端處理器也可以用於控制延遲敏感的感測器、驅動隨機硬體塊或在主 CPU 空閒時執行後臺任務。
這些遠端處理器的使用者可以是使用者空間應用程式(例如,與遠端 OMX 元件通訊的多媒體框架)或核心驅動程式(控制僅遠端處理器可訪問的硬體、代表遠端處理器保留核心控制的資源等)。
Rpmsg 是一種基於 virtio 的訊息傳遞匯流排,允許核心驅動程式與系統上可用的遠端處理器通訊。反過來,如果需要,驅動程式可以公開適當的使用者空間介面。
在編寫將 rpmsg 通訊公開給使用者空間的驅動程式時,請記住,遠端處理器可能可以直接訪問系統的物理記憶體和其他敏感硬體資源(例如,在 OMAP4 上,遠端核心和硬體加速器可能可以直接訪問物理記憶體、gpio bank、dma 控制器、i2c 匯流排、gptimer、郵箱裝置、hwspinlock 等)。此外,這些遠端處理器可能正在執行 RTOS,其中每個任務都可以訪問暴露給處理器的所有記憶體/裝置。為了最大限度地降低惡意(或有缺陷的)使用者空間程式碼利用遠端錯誤並由此接管系統的風險,通常希望將使用者空間限制在特定的 rpmsg 通道(參見下面的定義)上,它可以在這些通道上傳送訊息,並且如果可能,儘量減少它對訊息內容有多少控制權。
每個 rpmsg 裝置都是與遠端處理器的通訊通道(因此 rpmsg 裝置被稱為通道)。通道由文字名稱標識,並具有本地(“源”)rpmsg 地址和遠端(“目標”)rpmsg 地址。
當驅動程式開始在通道上偵聽時,其 rx 回撥會繫結到唯一的 rpmsg 本地地址(一個 32 位整數)。 這樣,當入站訊息到達時,rpmsg 核心會根據其目標地址將它們分派給適當的驅動程式(這是透過使用入站訊息的有效負載呼叫驅動程式的 rx 處理程式來完成的)。
使用者 API¶
int rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len);
從給定的端點向遠端處理器傳送訊息。呼叫者應指定端點、要傳送的資料及其長度(以位元組為單位)。訊息將透過指定的端點的通道傳送,即其源地址和目標地址欄位將分別設定為端點的 src 地址及其父通道 dst 地址。
如果不存在可用的 TX 緩衝區,該函式將阻塞,直到有一個緩衝區變為可用(即,直到遠端處理器使用一個 tx 緩衝區並將其放回 virtio 的已用描述符環上),或者經過 15 秒的超時時間。當後者發生時,將返回 -ERESTARTSYS。
該函式只能從程序上下文中呼叫(目前)。成功時返回 0,失敗時返回適當的錯誤值。
int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst);
從給定端點向遠端處理器傳送訊息,傳送到呼叫者提供的目標地址。
呼叫者應指定端點、要傳送的資料及其長度(以位元組為單位)以及顯式目標地址。
然後,訊息將被髮送到端點的通道所屬的遠端處理器,使用端點的 src 地址和使用者提供的 dst 地址(因此通道的 dst 地址將被忽略)。
如果不存在可用的 TX 緩衝區,該函式將阻塞,直到有一個緩衝區變為可用(即,直到遠端處理器使用一個 tx 緩衝區並將其放回 virtio 的已用描述符環上),或者經過 15 秒的超時時間。當後者發生時,將返回 -ERESTARTSYS。
該函式只能從程序上下文中呼叫(目前)。成功時返回 0,失敗時返回適當的錯誤值。
int rpmsg_trysend(struct rpmsg_endpoint *ept, void *data, int len);
從給定端點向遠端處理器傳送訊息。呼叫者應指定端點、要傳送的資料及其長度(以位元組為單位)。訊息將透過指定的端點的通道傳送,即其源地址和目標地址欄位將分別設定為端點的 src 地址及其父通道 dst 地址。
如果不存在可用的 TX 緩衝區,該函式將立即返回 -ENOMEM,而不會等到有一個緩衝區變為可用。
該函式只能從程序上下文中呼叫(目前)。成功時返回 0,失敗時返回適當的錯誤值。
int rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)
從給定端點向遠端處理器傳送訊息,傳送到使用者提供的目標地址。
使用者應指定通道、要傳送的資料及其長度(以位元組為單位)以及顯式目標地址。
然後,訊息將被髮送到通道所屬的遠端處理器,使用通道的 src 地址和使用者提供的 dst 地址(因此通道的 dst 地址將被忽略)。
如果不存在可用的 TX 緩衝區,該函式將立即返回 -ENOMEM,而不會等到有一個緩衝區變為可用。
該函式只能從程序上下文中呼叫(目前)。成功時返回 0,失敗時返回適當的錯誤值。
struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,
rpmsg_rx_cb_t cb, void *priv,
struct rpmsg_channel_info chinfo);
系統中的每個 rpmsg 地址都透過 rpmsg_endpoint 結構繫結到 rx 回撥(因此,當入站訊息到達時,它們由 rpmsg 匯流排使用適當的回撥處理程式分派)。
此函式允許驅動程式建立這樣一個端點,並透過該端點將回調以及一些私有資料繫結到 rpmsg 地址(無論是預先已知的地址,還是將為其動態分配的地址)。
簡單的 rpmsg 驅動程式無需呼叫 rpmsg_create_ept,因為在它們被 rpmsg 匯流排探測時(使用它們在註冊到 rpmsg 匯流排時提供的 rx 回撥)已經為它們建立了一個端點。
所以對於簡單的驅動程式來說,一切都應該正常工作:它們已經有一個端點,它們的 rx 回撥繫結到它們的 rpmsg 地址,並且當相關的入站訊息到達時(即,其 dst 地址等於其 rpmsg 通道的 src 地址的訊息),驅動程式的處理程式被呼叫來處理它。
也就是說,更復雜的驅動程式可能確實需要分配額外的 rpmsg 地址,並將它們繫結到不同的 rx 回撥。為了完成這個任務,這些驅動程式需要呼叫這個函式。 驅動程式應提供它們的通道(因此新的端點將繫結到它們的通道所屬的同一個遠端處理器)、一個 rx 回撥函式、一個可選的私有資料(當呼叫 rx 回撥時會返回),以及它們想要繫結到回撥的地址。如果 addr 是 RPMSG_ADDR_ANY,則 rpmsg_create_ept 將動態地為它們分配一個可用的 rpmsg 地址(驅動程式應該有一個非常好的理由不總是在這裡使用 RPMSG_ADDR_ANY)。
成功時返回指向端點的指標,出錯時返回 NULL。
void rpmsg_destroy_ept(struct rpmsg_endpoint *ept);
銷燬現有的 rpmsg 端點。 使用者應提供指向先前使用 rpmsg_create_ept() 建立的 rpmsg 端點的指標。
int register_rpmsg_driver(struct rpmsg_driver *rpdrv);
向 rpmsg 匯流排註冊 rpmsg 驅動程式。 使用者應提供指向 rpmsg_driver 結構的指標,該結構包含驅動程式的 ->probe() 和 ->remove() 函式、rx 回撥以及一個 id_table,指定該驅動程式有興趣探測的通道的名稱。
void unregister_rpmsg_driver(struct rpmsg_driver *rpdrv);
從 rpmsg 匯流排登出 rpmsg 驅動程式。 使用者應提供指向先前註冊的 rpmsg_driver 結構的指標。 成功時返回 0,失敗時返回適當的錯誤值。
典型用法¶
以下是一個簡單的 rpmsg 驅動程式,它在 probe() 上傳送一條“hello!”訊息,並且每當它收到一條傳入訊息時,它都會將其內容轉儲到控制檯。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
static void rpmsg_sample_cb(struct rpmsg_channel *rpdev, void *data, int len,
void *priv, u32 src)
{
print_hex_dump(KERN_INFO, "incoming message:", DUMP_PREFIX_NONE,
16, 1, data, len, true);
}
static int rpmsg_sample_probe(struct rpmsg_channel *rpdev)
{
int err;
dev_info(&rpdev->dev, "chnl: 0x%x -> 0x%x\n", rpdev->src, rpdev->dst);
/* send a message on our channel */
err = rpmsg_send(rpdev->ept, "hello!", 6);
if (err) {
pr_err("rpmsg_send failed: %d\n", err);
return err;
}
return 0;
}
static void rpmsg_sample_remove(struct rpmsg_channel *rpdev)
{
dev_info(&rpdev->dev, "rpmsg sample client driver is removed\n");
}
static struct rpmsg_device_id rpmsg_driver_sample_id_table[] = {
{ .name = "rpmsg-client-sample" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sample_id_table);
static struct rpmsg_driver rpmsg_sample_client = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_sample_id_table,
.probe = rpmsg_sample_probe,
.callback = rpmsg_sample_cb,
.remove = rpmsg_sample_remove,
};
module_rpmsg_driver(rpmsg_sample_client);
注意
可以在 samples/rpmsg/ 中找到一個可以構建和載入的類似示例。
rpmsg 通道的分配¶
目前我們只支援 rpmsg 通道的動態分配。
這隻有在具有 VIRTIO_RPMSG_F_NS virtio 裝置功能集的遠端處理器上才有可能。 此功能位意味著遠端處理器支援動態名稱服務公告訊息。
啟用此功能後,rpmsg 裝置(即通道)的建立是完全動態的:遠端處理器透過傳送名稱服務訊息(其中包含遠端服務的名稱和 rpmsg 地址,請參閱 struct rpmsg_ns_msg)來宣佈遠端 rpmsg 服務存在。
然後,此訊息由 rpmsg 匯流排處理,rpmsg 匯流排反過來動態地建立和註冊一個 rpmsg 通道(表示遠端服務)。 如果/當註冊了相關的 rpmsg 驅動程式時,它將立即被匯流排探測,然後可以開始向遠端服務傳送訊息。
該計劃還包括透過 virtio 配置空間新增 rpmsg 通道的靜態建立,但尚未實現。