fwctl 子系統

作者:

Jason Gunthorpe

概述

現代裝置包含大量的韌體,並且在許多情況下,很大程度上是軟體定義的硬體。這種方法的演變在很大程度上是對摩爾定律的反應,現在晶片流片非常昂貴,並且晶片設計非常龐大。用靈活且緊密整合的韌體/硬體組合替換固定的硬體邏輯,是降低晶片重置風險的有效方法。裝置韌體可以抵消硬體設計中的問題。對於向作業系統驅動程式呈現穩定且向後相容的介面(例如 NVMe)的裝置尤其如此。

裝置中的韌體層已經增長到令人難以置信的大小,裝置經常整合快速處理器的叢集來執行它。例如,mlx5 裝置具有超過 30MB 的韌體程式碼,並且大型配置以超過 1GB 的韌體管理的執行時狀態執行。

這種靈活層的可用性在行業中創造了相當多的多樣性,現在單個矽片是可配置的軟體定義裝置,並且可以根據需要以截然不同的方式執行。此外,我們經常看到特定站點希望以高度專業化的方式操作裝置,並且需要針對其獨特配置量身定製的應用程式的情況。

此外,裝置已經變得多功能和整合,以至於它們不再完全適合核心的子系統劃分。現代多功能裝置具有跨越多個子系統的驅動程式,例如 bnxt/ice/mlx5/pds,同時使用輔助裝置系統共享底層硬體。

總而言之,這給作業系統帶來了挑戰,裝置的韌體環境非常龐大,需要強大的裝置特定除錯支援,以及不適合“通用”介面的韌體驅動的功能。fwctl 旨在允許從使用者空間訪問裝置的全部功能,包括可除錯性、管理和首次啟動/第 N 次啟動配置。

fwctl 面向常見的裝置設計模式,其中作業系統和韌體透過使用佇列或郵箱方案構建的 RPC 訊息層進行通訊。在這種情況下,驅動程式通常會有一個層來傳遞 RPC 訊息並從裝置韌體收集 RPC 響應。為主要目的操作裝置的核心內子系統驅動程式將使用這些 RPC 來構建其驅動程式,但裝置通常也有一組不真正適合任何特定子系統的輔助 RPC。例如,硬體 RAID 控制器主要由塊層操作,但也帶有一組 RPC 來管理硬體 RAID 中的驅動器構建。

過去,當裝置功能更多時,各個子系統會發展出不同的方法來解決這些常見問題。例如,監視裝置執行狀況、操作其 FLASH、除錯韌體、配置等,在核心中都有各種獨特的介面。

fwctl 的目的是定義一組有限的通用規則(如下所述),允許使用者空間安全地在裝置韌體內部構建和執行 RPC。這些規則充當作業系統和韌體之間關於如何正確設計 RPC 介面的協議。作為 uAPI,該子系統提供了一個薄的發現層和一個通用的 uAPI 來傳遞 RPC 並收集響應。它支援一個使用者空間庫和工具系統,該系統將使用此介面來使用裝置本機協議控制裝置。

操作範圍

fwctl 驅動程式嚴格限制為操作裝置韌體的方式。它不是訪問隨機核心內部結構或其他作業系統軟體狀態的途徑。

fwctl 例項必須在定義明確的裝置功能上執行,並且裝置應該具有定義明確的安全模型,以確定允許該功能訪問物理裝置內的範圍。例如,當今最複雜的 PCIe 裝置可能大致具有多個功能級範圍

  1. 具有完全訪問裝置上全域性狀態和配置的特權功能

  2. 多個虛擬機器監控程式功能,可以控制自身以及與虛擬機器一起使用的子功能

  3. 多個虛擬機器功能,嚴格限定在虛擬機器內

裝置可以在這些範圍之間建立邏輯父/子關係。例如,子虛擬機器的韌體可能在虛擬機器監控程式韌體的範圍內。在 VFIO 世界中,虛擬機器監控程式環境通常負責為 VFIO 分配給虛擬機器的函式進行復雜的配置/分析/配置。

此外,在功能範圍內,裝置通常具有屬於某些常規操作範圍內的 RPC 命令(請參閱enum fwctl_rpc_scope

  1. 訪問在功能重置時生效的功能和子配置、FLASH 等。 訪問對任何驅動程式或虛擬機器透明或非破壞性的功能和子執行時配置。

  2. 對功能除錯資訊的只讀訪問,該除錯資訊可以報告功能和子功能中的韌體物件,包括其他核心子系統擁有的韌體物件。

  3. 對與核心鎖定和核心完整性保護原則嚴格相容的功能和子除錯資訊進行寫入訪問。 觸發核心汙染。

  4. 完全除錯裝置訪問。 觸發核心汙染,需要 CAP_SYS_RAWIO。

使用者空間將在每個 RPC 上提供一個範圍標籤,並且核心必須根據該範圍強制執行上述 CAP 和汙染。核心和韌體的組合可以強制使用者空間將 RPC 放置在正確的範圍內。

拒絕的行為

此介面不能允許使用者空間做很多事情(沒有汙染或 CAP),這些事情大致源自核心鎖定原則。 一些例子

  1. DMA 到/從任意記憶體、掛起系統、使用不受信任的程式碼破壞韌體完整性,或以其他方式破壞裝置或系統安全性和完整性。

  2. 為核心驅動程式提供異常的“後門”。 不要操縱核心驅動程式擁有的核心物件。

  3. 直接配置或以其他方式控制核心驅動程式。 子系統核心驅動程式可以在功能重置/驅動程式載入時對裝置配置做出反應,但在其他情況下不得與 fwctl 耦合。

  4. 以與另一個主要核心子系統的核心目的重疊的方式操作硬體,例如讀/寫 LBA、傳送/接收網路資料包或操作加速器的資料平面。

fwctl 不能替代裝置直接訪問子系統,例如 uacce 或 VFIO。

透過 fwctl 的非汙染介面公開的操作應與其他裝置使用者完全共享。 例如,透過 fwctl 公開 RPC 永遠不應阻止核心子系統將來同時使用相同的 RPC 或硬體單元。 在這種情況下,fwctl 將不如最終出現的適當核心子系統重要。 在此區域中導致衝突的錯誤將優先考慮核心實現來解決。

fwctl 使用者 API

通用 ioctl 格式

ioctl 介面遵循通用格式以允許擴充套件。 每個 ioctl 都傳遞一個結構指標作為引數,在第一個 u32 中提供結構的大小。 核心檢查超出其理解範圍的任何結構空間是否為 0。 這允許使用者空間使用向後相容的部分,同時一致地使用更新的、更大的結構。

ioctls 對常見的錯誤程式碼使用標準含義

  • ENOTTY:完全不支援 IOCTL 號碼本身

  • E2BIG:支援 IOCTL 號碼,但提供的結構在核心不理解的部分中具有非零值。

  • EOPNOTSUPP:支援 IOCTL 號碼,並且理解結構,但是已知欄位具有核心不理解或不支援的值。

  • EINVAL:理解了關於 IOCTL 的所有內容,但是欄位不正確。

  • ENOMEM:記憶體不足。

  • ENODEV:底層裝置已熱插拔,並且 FD 為

    孤立。

以及特定 ioctl 中的其他錯誤程式碼。

struct fwctl_info

ioctl(FWCTL_INFO)

定義:

struct fwctl_info {
    __u32 size;
    __u32 flags;
    __u32 out_device_type;
    __u32 device_data_len;
    __aligned_u64 out_device_data;
};

成員

size

sizeof(struct fwctl_info)

flags

必須為 0

out_device_type

從 enum fwctl_device_type 返回裝置型別

device_data_len

在輸入時,out_device_data 記憶體的長度。 在輸出時,核心 device_data 的大小,可能大於或小於輸入。 在輸入時可能為 0。

out_device_data

指向 device_data_len 位元組記憶體的指標。 核心將填充整個記憶體,根據需要清零。

描述

返回關於此 fwctl 例項的基本資訊,特別是用於定義 device_data 格式的驅動程式。

enum fwctl_rpc_scope

RPC 的訪問範圍

常量

FWCTL_RPC_CONFIGURATION

裝置配置訪問範圍

對裝置配置的讀/寫訪問。 當配置寫入裝置時,它保持完全受支援的狀態。

FWCTL_RPC_DEBUG_READ_ONLY

對除錯資訊的只讀訪問

可讀的除錯資訊。 除錯資訊與核心鎖定相容,並且不洩露任何敏感資訊。 例如,禁止從此資訊中公開任何加密金鑰。

FWCTL_RPC_DEBUG_WRITE

對與鎖定相容的除錯資訊的可寫訪問

允許寫入裝置中的資料,這可能會導致裝置脫離完全受支援的狀態。 這旨在允許密集且可能具有侵入性的除錯。 此範圍將汙染核心。

FWCTL_RPC_DEBUG_WRITE_FULL

對所有除錯資訊的寫入訪問

允許讀/寫訪問所有內容。 需要 CAP_SYS_RAW_IO,因此不需要遵循鎖定原則。 如果有疑問,除錯應放置在此範圍內。 此範圍將汙染核心。

描述

有關這些範圍的更詳細討論,請參閱fwctl 子系統

struct fwctl_rpc

ioctl(FWCTL_RPC)

定義:

struct fwctl_rpc {
    __u32 size;
    __u32 scope;
    __u32 in_len;
    __u32 out_len;
    __aligned_u64 in;
    __aligned_u64 out;
};

成員

size

sizeof(struct fwctl_rpc)

scope

enum fwctl_rpc_scope 之一,RPC 的必需範圍

in_len

記憶體中的長度

out_len

記憶體中的長度

in

裝置特定格式的請求訊息

out

裝置特定格式的響應訊息

描述

將遠端過程呼叫傳遞到裝置韌體並返回響應。 呼叫的引數和返回值被編組到線性記憶體緩衝區中。 任何錯誤程式碼都指示將 RPC 傳遞到裝置失敗。 在成功傳遞期間裝置中產生的返回狀態必須編碼到 out 中。

緩衝區的格式與 FWCTL_INFO 中的 out_device_type 匹配。

struct fwctl_info_mlx5

ioctl(FWCTL_INFO) out_device_data

定義:

struct fwctl_info_mlx5 {
    __u32 uid;
    __u32 uctx_caps;
};

成員

uid

此 FD 繫結到的 FW UID。 每個命令標頭都將強制執行此值。

uctx_caps

為 uid 啟用的 FW 功能。

描述

返回關於可用 FW 介面的基本資訊。

struct fwctl_info_pds

定義:

struct fwctl_info_pds {
    __u32 uctx_caps;
};

成員

uctx_caps

韌體功能點陣圖

描述

返回關於可用 FW 介面的基本資訊。

enum pds_fwctl_capabilities

常量

PDS_FWCTL_QUERY_CAP

可以查詢韌體以獲取資訊

PDS_FWCTL_SEND_CAP

可以向韌體傳送命令

struct fwctl_rpc_pds

定義:

struct fwctl_rpc_pds {
    struct {
        __u32 op;
        __u32 ep;
        __u32 rsvd;
        __u32 len;
        __aligned_u64 payload;
    } in;
    struct {
        __u32 retval;
        __u32 rsvd[2];
        __u32 len;
        __aligned_u64 payload;
    } out;
};

成員

in

rpc in 引數

in.op

請求的操作碼

in.ep

要操作的韌體端點

in.rsvd

保留

in.len

有效負載資料的長度

in.payload

有效負載緩衝區的地址

out

rpc out 引數

out.retval

操作結果值

out.rsvd

保留

out.len

結果資料緩衝區的長度

out.payload

有效負載資料緩衝區的地址

sysfs 類

fwctl 具有一個 sysfs 類 (/sys/class/fwctl/fwctlNN/) 和字元裝置 (/dev/fwctl/fwctlNN),具有一個簡單的編號方案。 字元裝置操作上述 iotcl uAPI。

fwctl 裝置可以透過 sysfs 與其他子系統中的驅動程式元件相關聯

$ ls /sys/class/fwctl/fwctl0/device/infiniband/
ibp0s10f0

$ ls /sys/class/infiniband/ibp0s10f0/device/fwctl/
fwctl0/

$ ls /sys/devices/pci0000:00/0000:00:0a.0/fwctl/fwctl0
dev  device  power  subsystem  uevent

使用者空間社群

從 nvme-cli 中汲取靈感,參與核心端必須帶有一個通用的 TBD git 樹中的使用者空間,至少要有效地操作核心驅動程式。 提供此類實現是合併核心驅動程式的先決條件。

目標是圍繞我們所有人都遇到的一些共同問題建立使用者空間社群,理想情況下開發一些具有以下一些起始主題的常見使用者空間程式

  • 裝置現場除錯

  • 硬體配置

  • 虛擬機器啟動前的 VFIO 子裝置分析

  • 機密計算主題(證明、安全配置)

跨越核心中的所有子系統。 fwupd 是一個很好的例子,說明了如何從核心端的多樣性中湧現出優秀的使用者空間體驗。

fwctl 核心 API

int fwctl_register(struct fwctl_device *fwctl)

將新設備註冊到子系統

引數

struct fwctl_device *fwctl

先前分配的 fwctl_device

描述

返回時,該裝置透過 sysfs 和 /dev 可見,可能會呼叫驅動程式操作。

void fwctl_unregister(struct fwctl_device *fwctl)

從子系統登出裝置

引數

struct fwctl_device *fwctl

先前分配和註冊的 fwctl_device

描述

撤消 fwctl_register()。 返回時,不會呼叫任何驅動程式操作。 呼叫者仍然必須呼叫 fwctl_put() 來釋放 fwctl。

即使使用者空間仍然有開啟的檔案描述符,登出也會返回。 這將對任何開啟的 FD 呼叫 ops->close_uctx(),並且在返回後不會呼叫驅動程式操作。 FD 保持開啟狀態,但所有 fops 都將返回 -ENODEV。

fwctl 的設計允許驅動程式與子系統進行這種分離,這主要是透過使記憶體分配歸核心子系統所有來實現的。 可以釋放 fwctl_device 和 fwctl_uctx,而無需驅動程式回撥。 這允許在 FD 開啟時模組保持未鎖定狀態。

struct fwctl_ops

驅動程式提供的操作

定義:

struct fwctl_ops {
    enum fwctl_device_type device_type;
    size_t uctx_size;
    int (*open_uctx)(struct fwctl_uctx *uctx);
    void (*close_uctx)(struct fwctl_uctx *uctx);
    void *(*info)(struct fwctl_uctx *uctx, size_t *length);
    void *(*fw_rpc)(struct fwctl_uctx *uctx, enum fwctl_rpc_scope scope, void *rpc_in, size_t in_len, size_t *out_len);
};

成員

device_type

驅動程式分配的 device_type 號碼。 這是 uABI。

uctx_size

要分配的 fwctl_uctx 結構的大小。 此記憶體的第一個位元組將是一個 fwctl_uctx。 驅動程式可以使用剩餘的位元組作為其私有記憶體。

open_uctx

在首次使用 uctx 之前開啟檔案描述符時呼叫。

close_uctx

銷燬 uctx 時呼叫,通常在 FD 關閉時呼叫。

info

實現 FWCTL_INFO。 返回複製到 out_device_data 的 kmalloc() 記憶體。 在輸入時,length 指示使用者緩衝區的大小,在輸出時,它指示記憶體的大小。 驅動程式可以在輸入時忽略 length,核心程式碼將處理所有內容。

fw_rpc

實現 FWCTL_RPC。 將 rpc_in/in_len 傳遞到 FW 並返回響應並設定 out_len。 rpc_in 可以作為響應指標返回。 否則,返回的指標將使用 kvfree() 釋放。

描述

fwctl_unregister() 將等到所有正在執行的操作完成後才會返回。 驅動程式應注意不要讓他們的操作執行太長時間,因為它會阻止裝置熱插拔和模組解除安裝。

struct fwctl_device

每個驅動程式的註冊結構

定義:

struct fwctl_device {
    struct device dev;
};

成員

dev

sysfs (class/fwctl/fwctlXX) 裝置

描述

每個驅動程式例項都將具有其中一個結構,驅動程式私有資料緊隨其後。 此結構是引用計數的,它透過呼叫 fwctl_put() 釋放。

fwctl_alloc_device

fwctl_alloc_device (parent, ops, drv_struct, member)

分配一個 fwctl

引數

parent

提供 FW 介面的物理裝置

ops

要註冊的驅動程式操作

drv_struct

儲存 struct fwctl_device 的“struct driver_fwctl”

member

drv_structstruct fwctl_device 的名稱

描述

這會分配並初始化嵌入在 drv_struct 中的 fwctl_device。 成功後,必須透過 fwctl_put() 釋放指標。 成功時返回“drv_struct *”,錯誤時返回 NULL。

struct fwctl_uctx

每個使用者 FD 上下文

定義:

struct fwctl_uctx {
    struct fwctl_device *fwctl;
};

成員

fwctl

擁有上下文的 fwctl 例項

描述

使用者空間開啟的每個 FD 都將獲得唯一的上下文分配。 任何驅動程式私有資料都將緊隨其後。

fwctl 驅動程式設計

在許多情況下,fwctl 驅動程式將是更大的跨子系統裝置的一部分,可能使用輔助裝置機制。 在這種情況下,多個子系統將共享同一個裝置和 FW 介面層,因此裝置設計必須已經提供核心子系統之間的隔離和協作。 fwctl 應該適合相同的模型。

驅動程式的一部分應包括對其範圍限制和安全模型如何工作的描述。 驅動程式和韌體必須共同確保使用者空間提供的 RPC 對映到適當的範圍。 如果驗證是在驅動程式中完成的,則驗證可以從裝置讀取“命令效果”報告,或者硬連線執行。 如果驗證是在韌體中完成的,則驅動程式應將 fwctl_rpc_scope 與命令一起傳遞到韌體。

驅動程式和韌體必須協同工作以確保 fwctl 不能分配任何 FW 資源,或者它分配的任何資源都在 FD 關閉時釋放。 主要圍繞 FW RPC 構建的驅動程式可能會發現其核心 PCI 功能和 RPC 層屬於 fwctl,輔助裝置連線到其他子系統。

每種裝置型別都必須注意 Linux 的穩定 ABI 理念。 FW RPC 介面不必滿足嚴格的穩定 ABI,但確實需要滿足這樣一個期望:已部署並大量使用的使用者空間工具不會不必要地中斷。 FW 升級和核心升級應使廣泛部署的工具正常工作。

在更寬鬆的範圍下以開發和除錯為重點的 RPC 如果使用它們的工具僅在特殊情況下執行而不是用於裝置的日常使用,則可以具有較少的穩定性。 除錯工具甚至可能需要精確的版本匹配,因為它們可能需要類似於 FW 二進位制檔案中的 DWARF 除錯資訊。

安全響應

核心仍然是此介面的守門人。 如果發現違反範圍、安全或隔離原則的情況,我們可以選擇透過 FW 更新來修復這些問題,推送核心補丁來解析和阻止 RPC 命令,或推送核心補丁來阻止整個韌體版本/裝置。

雖然核心始終可以直接解析和限制 RPC,但預計允許驅動程式將驗證委託給 FW 的現有核心模式將是一個有用的設計。

現有類似示例

本文件中描述的方法並不是一個新想法。 幾十年來,核心一直在不同的領域提供直接或接近直接的裝置訪問。 隨著越來越多裝置想要遵循這種設計模式,很明顯它並沒有被完全理解,更重要的是,安全注意事項沒有得到很好的定義或一致同意。

一些例子

  • 硬體 RAID 控制器。 這包括執行諸如將驅動器組合到 RAID 卷中、配置 RAID 引數、監視硬體等操作的 RPC。

  • 基板管理器。 用於配置裝置中的設定等的 RPC

  • NVMe 供應商命令膠囊。 nvme-cli 提供了對不同產品定義的一些監視功能的訪問,但存在更多功能。

  • CXL 也有一個類似 NVMe 的供應商命令系統。

  • DRM 允許使用者空間驅動程式透過核心中介將命令傳送到裝置

  • RDMA 允許使用者空間驅動程式直接將命令推送到裝置,而無需核心參與

  • 各種“原始”API,原始 HID (SDL2)、原始 USB、NVMe 通用介面等。

前 4 個示例是 fwctl 旨在涵蓋的領域。 後三個示例是被拒絕的行為,因為它們與核心子系統的主要目的完全重疊。

從過去的這些努力中吸取的一些關鍵教訓是,必須有一個通用的使用者空間專案才能獲得核心驅動程式。 在使用者空間中圍繞有用的軟體開發良好的社群是公司資助參與以啟用其產品的關鍵。