HID-BPF

HID 是輸入裝置的標準協議,但某些裝置可能需要自定義調整,傳統上使用核心驅動程式修復來完成。 使用 eBPF 功能可以加快開發速度,併為現有的 HID 介面新增新的功能。

何時(以及為什麼)使用 HID-BPF

在某些情況下,使用 HID-BPF 比標準的核心驅動程式修復更好

操縱桿的死區

假設你有一箇舊的操縱桿,通常會看到它在中性點附近晃動。 這通常在應用程式級別透過為此特定軸新增死區來過濾。

使用 HID-BPF,我們可以直接在核心中應用此過濾,因此當輸入控制器上沒有其他事情發生時,使用者空間不會被喚醒。

當然,鑑於此死區特定於單個裝置,我們無法為所有相同的操縱桿建立通用修復程式。 為此新增自定義核心 API(例如,透過新增 sysfs 條目)並不能保證此新核心 API 將被廣泛採用和維護。

HID-BPF 允許使用者空間程式載入程式本身,確保我們只在有使用者時才載入自定義 API。

報告描述符的簡單修復

在 HID 樹中,一半的驅動程式僅修復報告描述符中的一個鍵或一個位元組。 這些修復都需要核心補丁以及後續的釋出管理,這對使用者來說是一個漫長而痛苦的過程。

我們可以透過提供 eBPF 程式來減輕這種負擔。 一旦使用者驗證了這樣的程式,我們可以將原始碼嵌入到核心樹中,並直接運送和載入 eBPF 程式,而不是載入特定的核心模組。

注意:eBPF 程式的釋出及其包含在核心中尚未完全實現

新增需要新的核心 API 的新功能

此類功能的一個示例是通用手寫筆介面 (USI) 筆。 基本上,USI 筆需要一個新的核心 API,因為我們的 HID 和輸入堆疊不支援新的通訊通道。 我們可以依靠 eBPF 來讓消費者控制核心 API,並且每次有事件發生時都不會喚醒使用者空間,從而不會影響效能,而不是使用 hidraw 或建立新的 sysfs 條目或 ioctl。

將裝置變形為其他裝置並從使用者空間控制它

核心具有 HID 專案到 evdev 位的相對靜態的對映。 它無法決定動態地將給定裝置轉換為其他裝置,因為它沒有所需的上下文,並且使用者空間無法撤消(甚至無法發現)任何此類轉換。

但是,某些裝置在這種靜態定義裝置的方式下是無用的。 例如,Microsoft Surface Dial 是一個帶有觸覺反饋的按鈕,到目前為止幾乎無法使用。

使用 eBPF,使用者空間可以將該裝置變形為滑鼠,並將撥號事件轉換為滾輪事件。 此外,使用者空間程式可以根據上下文設定/取消設定觸覺反饋。 例如,如果螢幕上可見一個選單,我們可能需要每 15 度進行一次觸覺點選。 但是,當在網頁中滾動時,裝置以最高解析度發出事件時,使用者體驗會更好。

防火牆

如果我們想阻止其他使用者訪問裝置的特定功能怎麼辦? (考慮一個可能已損壞的韌體更新入口點)

使用 eBPF,我們可以攔截髮送到裝置的任何 HID 命令並驗證它。

這也允許在使用者空間和核心/bpf 程式之間同步狀態,因為我們可以攔截任何傳入的命令。

跟蹤

最後一個用途是跟蹤事件以及我們可以使用 BPF 來彙總和分析事件的所有樂趣。

現在,跟蹤依賴於 hidraw。 除了幾個問題外,它工作正常

  1. 如果驅動程式不匯出 hidraw 節點,我們將無法跟蹤任何內容(eBPF 在這裡將是一個“上帝模式”,因此這可能會引起一些人的注意)

  2. hidraw 不捕獲其他程序對裝置的請求,這意味著我們需要向核心新增 printk 才能瞭解發生了什麼。

HID-BPF 的高階檢視

HID-BPF 背後的主要思想是它在位元組陣列級別上工作。 因此,HID 報告和 HID 報告描述符的所有解析都必須在載入 eBPF 程式的使用者的元件中實現。

例如,在上面的死區操縱桿中,知道資料流中的哪些欄位需要設定為 0 需要由使用者空間計算。

由此得出,HID-BPF 不瞭解核心中可用的其他子系統。 您不能直接從 eBPF 透過輸入 API 發出輸入事件

當 BPF 程式需要發出輸入事件時,它需要與 HID 協議通訊,並依靠 HID 核心處理將 HID 資料轉換為輸入事件。

樹內 HID-BPF 程式和 udev-hid-bpf

官方裝置修復程式以原始碼的形式在核心樹中的 drivers/hid/bpf/progs 目錄中釋出。 這允許在 tools/testing/selftests/hid 中向它們新增自測。

但是,鑑於它們需要外部工具才能載入,因此這些物件的編譯不是常規核心編譯的一部分。 此工具當前是 udev-hid-bpf

為方便起見,該外部儲存庫將其自己的 src/bpf/stable 目錄中 drivers/hid/bpf/progs 中的檔案重複。 這允許發行版不必提取整個核心原始碼樹來發布和打包這些 HID-BPF 修復程式。 udev-hid-bpf 還具有根據使用者執行的核心處理多個物件檔案的能力。

可用程式型別

HID-BPF 構建在 BPF 之上,這意味著我們使用 bpf struct_ops 方法來宣告我們的程式。

HID-BPF 具有以下可用的附件型別

  1. 使用 libbpf 中的 SEC("struct_ops/hid_device_event") 進行事件處理/過濾

  2. 來自使用者空間的操作,在 libbpf 中使用 SEC("syscall")

  3. 在 libbpf 中使用 SEC("struct_ops/hid_rdesc_fixup")SEC("struct_ops.s/hid_rdesc_fixup") 更改報告描述符

當從裝置收到事件時,hid_device_event 正在呼叫 BPF 程式。 因此,我們在 IRQ 上下文中,可以對資料執行操作或通知使用者空間。 鑑於我們在 IRQ 上下文中,我們無法與裝置對話。

syscall 意味著使用者空間呼叫了 syscall BPF_PROG_RUN 工具。 這一次,我們可以執行 HID-BPF 允許的任何操作,並且允許與裝置對話。

最後,hid_rdesc_fixup 與其他程式不同,因為此型別的 BPF 程式只能有一個。 當驅動程式從 probe 呼叫它時,允許從 BPF 程式更改報告描述符。 一旦載入了 hid_rdesc_fixup 程式,除非插入它的程式透過固定程式並關閉指向它的所有 fd 來允許我們,否則無法覆蓋它。

請注意,hid_rdesc_fixup 可以宣告為可睡眠 (SEC("struct_ops.s/hid_rdesc_fixup"))。

開發者 API:

HID-BPF 的可用 struct_ops

struct hid_bpf_ops

一個 BPF struct_ops 回撥,允許將 HID-BPF 程式附加到 HID 裝置

定義:

struct hid_bpf_ops {
    int hid_id;
    u32 flags;
    int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type, u64 source);
    int (*hid_rdesc_fixup)(struct hid_bpf_ctx *ctx);
    int (*hid_hw_request)(struct hid_bpf_ctx *ctx, unsigned char reportnum,enum hid_report_type rtype, enum hid_class_request reqtype, u64 source);
    int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, u64 source);
};

成員

hid_id

要附加到的 HID 唯一 ID。 這在 load() 之前是可寫的,之後無法更改

標誌

將 struct_ops 附加到裝置時使用的標誌。 當前唯一可用的值是 0BPF_F_BEFORE。 僅在 load() 之前可寫

hid_device_event

每當裝置傳入事件時呼叫

它具有以下引數

ctx:作為 struct hid_bpf_ctx 的 HID-BPF 上下文

返回:成功時 0 並保持處理;一個正值以更改傳入的大小緩衝區;一個負錯誤程式碼以中斷此事件的處理

上下文:中斷上下文。

hid_rdesc_fixup

當 probe 函式解析 HID 裝置的報告描述符時呼叫

它具有以下引數

ctx:作為 struct hid_bpf_ctx 的 HID-BPF 上下文

返回:成功時 0 並保持處理;一個正值以更改傳入的大小緩衝區;一個負錯誤程式碼以中斷此裝置的處理

hid_hw_request

每當在 HID 裝置上發出 hid_hw_raw_request() 呼叫時呼叫

它具有以下引數

ctx:作為 struct hid_bpf_ctx 的 HID-BPF 上下文

reportnum:報告編號,如 hid_hw_raw_request() 中

rtype:報告型別 (HID_INPUT_REPORTHID_FEATURE_REPORTHID_OUTPUT_REPORT)

reqtype:請求

source:引用唯一但可識別源的 u64。 如果 0,則核心本身發出了該呼叫。 對於 hidraw,source 設定為相關的 struct file *

返回:0 以保持 hid-core 處理請求;任何其他值都會阻止 hid-core 處理該事件。 應該返回一個正值,其中包含在傳入緩衝區中返回的位元組數;一個負錯誤程式碼會中斷此呼叫的處理。

hid_hw_output_report

每當在 HID 裝置上發出 hid_hw_output_report() 呼叫時呼叫

它具有以下引數

ctx:作為 struct hid_bpf_ctx 的 HID-BPF 上下文

source:引用唯一但可識別源的 u64。 如果 0,則核心本身發出了該呼叫。 對於 hidraw,source 設定為相關的 struct file *

返回:0 以保持 hid-core 處理請求;任何其他值都會阻止 hid-core 處理該事件。 應該返回一個正值,其中包含寫入裝置的位元組數;一個負錯誤程式碼會中斷此呼叫的處理。

程式中可用的使用者 API 資料結構:

struct hid_bpf_ctx

所有 HID 程式的使用者可訪問資料

定義:

struct hid_bpf_ctx {
    struct hid_device *hid;
    __u32 allocated_size;
    union {
        __s32 retval;
        __s32 size;
    };
};

成員

hid

表示裝置本身的 struct hid_device

allocated_size

分配的資料大小。

這是可用的記憶體量,可以由 HID 程式請求。 請注意,對於 HID_BPF_RDESC_FIXUP,該記憶體設定為 4096 (4 KB)

{unnamed_union}

匿名

retval

先前程式的返回值。

大小

資料欄位中的有效資料。

程式可以透過獲取此欄位來獲取資料中可用的有效大小。 程式還可以透過在程式中返回一個正數來更改此值。 要丟棄該事件,請返回一個負錯誤程式碼。

size 必須始終小於或等於 allocated_size(一旦所有 BPF 程式都已執行,它就會強制執行)。

描述

data 不能直接從上下文中訪問。 我們需要呼叫 hid_bpf_get_data() 才能獲取指向該欄位的指標。

hidallocated_size 是隻讀的,sizeretval 是可讀寫的。

可以在所有 HID-BPF struct_ops 程式中使用的可用 API:

__bpf_kfunc __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)

獲取與上下文 ctx 關聯的核心記憶體指標

引數

struct hid_bpf_ctx *ctx

HID-BPF 上下文

unsigned int offset

記憶體中的偏移量

const size_t rdwr_buf_size

緩衝區的 const 大小

描述

返回 錯誤時 NULL,成功時 __u8 記憶體指標

可以在 syscall HID-BPF 程式或可睡眠 HID-BPF struct_ops 程式中使用的可用 API:

__bpf_kfunc struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id)

為給定的 HID 裝置分配一個上下文

引數

unsigned int hid_id

HID 裝置的系統唯一識別符號

描述

返回 成功時指向 struct hid_bpf_ctx 的指標,錯誤時 NULL

__bpf_kfunc void hid_bpf_release_context(struct hid_bpf_ctx *ctx)

釋放先前分配的上下文 ctx

引數

struct hid_bpf_ctx *ctx

要釋放的 HID-BPF 上下文

__bpf_kfunc int hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, enum hid_report_type rtype, enum hid_class_request reqtype)

與 HID 裝置通訊

引數

struct hid_bpf_ctx *ctx

先前在 hid_bpf_allocate_context() 中分配的 HID-BPF 上下文

__u8 *buf

一個 PTR_TO_MEM 緩衝區

size_t buf__sz

要傳輸的資料的大小

enum hid_report_type rtype

報告的型別 (HID_INPUT_REPORT, HID_FEATURE_REPORT, HID_OUTPUT_REPORT)

enum hid_class_request reqtype

請求的型別 (HID_REQ_GET_REPORT, HID_REQ_SET_REPORT, ...)

描述

返回值:成功時返回 0,否則返回負的錯誤程式碼。

__bpf_kfunc int hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)

向 HID 裝置傳送輸出報告

引數

struct hid_bpf_ctx *ctx

先前在 hid_bpf_allocate_context() 中分配的 HID-BPF 上下文

__u8 *buf

一個 PTR_TO_MEM 緩衝區

size_t buf__sz

要傳輸的資料的大小

描述

成功時返回傳輸的位元組數,否則返回負的錯誤程式碼。

__bpf_kfunc int hid_bpf_try_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf, const size_t buf__sz)

從 HID 裝置向核心注入 HID 報告

引數

struct hid_bpf_ctx *ctx

先前在 hid_bpf_allocate_context() 中分配的 HID-BPF 上下文

enum hid_report_type type

報告的型別 (HID_INPUT_REPORT, HID_FEATURE_REPORT, HID_OUTPUT_REPORT)

u8 *buf

一個 PTR_TO_MEM 緩衝區

const size_t buf__sz

要傳輸的資料的大小

描述

成功時返回 0,否則返回負的錯誤程式碼。 如果裝置不可用,此函式將立即失敗,因此可以安全地在 IRQ 上下文中使用。

__bpf_kfunc int hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf, const size_t buf__sz)

從 HID 裝置向核心注入 HID 報告

引數

struct hid_bpf_ctx *ctx

先前在 hid_bpf_allocate_context() 中分配的 HID-BPF 上下文

enum hid_report_type type

報告的型別 (HID_INPUT_REPORT, HID_FEATURE_REPORT, HID_OUTPUT_REPORT)

u8 *buf

一個 PTR_TO_MEM 緩衝區

const size_t buf__sz

要傳輸的資料的大小

描述

成功時返回 0,否則返回負的錯誤程式碼。 此函式將等待裝置可用,然後再注入事件,因此需要在可睡眠的上下文中呼叫。

HID-BPF 程式的一般概述

訪問附加到上下文的資料

struct hid_bpf_ctx 不直接匯出 data 欄位,要訪問它,bpf 程式需要首先呼叫 hid_bpf_get_data()

offset 可以是任何整數,但 size 需要是常量,在編譯時已知。

這允許以下操作

  1. 對於給定的裝置,如果我們知道報告長度始終為某個值,我們可以請求 data 指標指向完整的報告長度。

    核心將確保我們使用正確的尺寸和偏移量,而 eBPF 將確保程式碼不會嘗試在邊界之外讀取或寫入

    __u8 *data = hid_bpf_get_data(ctx, 0 /* offset */, 256 /* size */);
    
    if (!data)
        return 0; /* ensure data is correct, now the verifier knows we
                   * have 256 bytes available */
    
    bpf_printk("hello world: %02x %02x %02x", data[0], data[128], data[255]);
    
  2. 如果報告長度是可變的,但我們知道 X 的值始終是 16 位整數,那麼我們可以擁有一個指向該值的指標

    __u16 *x = hid_bpf_get_data(ctx, offset, sizeof(*x));
    
    if (!x)
        return 0; /* something went wrong */
    
    *x += 1; /* increment X by one */
    

HID-BPF 程式的效果

對於除 hid_rdesc_fixup() 之外的所有 HID-BPF 附加型別,可以將多個 eBPF 程式附加到同一裝置。 如果 HID-BPF struct_ops 具有 hid_rdesc_fixup(),而另一個已經附加到裝置,則核心在附加 struct_ops 時將返回 -EINVAL

除非在附加程式時將 BPF_F_BEFORE 新增到標誌,否則新程式將附加到列表的末尾。 BPF_F_BEFORE 會將新程式插入到列表的開頭,這對於例如跟蹤非常有用,我們需要從裝置獲取未經處理的事件。

請注意,如果有多個程式使用 BPF_F_BEFORE 標誌,則只有最近載入的程式實際上是列表中的第一個。

SEC("struct_ops/hid_device_event")

每當引發匹配的事件時,eBPF 程式會一個接一個地被呼叫,並且作用於相同的資料緩衝區。

如果程式更改了與上下文關聯的資料,則下一個程式將看到修改後的資料,但它不知道原始資料是什麼。

一旦所有程式執行完畢並返回 0 或正值,HID 堆疊的其餘部分將作用於修改後的資料,其中最後一個 hid_bpf_ctx 的 size 欄位是資料輸入流的新大小。

返回負錯誤的 BPF 程式會丟棄該事件,即 HID 堆疊不會處理該事件。 客戶端(hidraw、input、LED)將看不到此事件。

SEC("syscall")

syscall 未附加到給定的裝置。 為了區分我們正在使用的裝置,使用者空間需要透過其唯一的系統 ID 來引用該裝置(sysfs 路徑中的最後 4 個數字:/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000)。

要檢索與裝置關聯的上下文,程式必須呼叫 hid_bpf_allocate_context(),並且必須在返回之前使用 hid_bpf_release_context() 釋放它。 檢索到上下文後,還可以使用 hid_bpf_get_data() 請求指向核心記憶體的指標。 此記憶體足夠大,可以支援給定裝置的所有輸入/輸出/特徵報告。

SEC("struct_ops/hid_rdesc_fixup")

hid_rdesc_fixup 程式的運作方式類似於 struct hid_driver.report_fixup

當探測裝置時,核心使用報告描述符的內容設定上下文的資料緩衝區。 與該緩衝區關聯的記憶體是 HID_MAX_DESCRIPTOR_SIZE(當前為 4kB)。

eBPF 程式可以隨意修改資料緩衝區,核心使用修改後的內容和大小作為報告描述符。

每當附加包含 SEC("struct_ops/hid_rdesc_fixup") 程式的 struct_ops 時(如果之前未附加程式),核心會立即斷開 HID 裝置並進行重新探測。

同樣,當分離此 struct_ops 時,核心會在裝置上發出斷開連線。

HID-BPF 中沒有 detach 工具。 當指向 HID-BPF struct_ops 連結的所有使用者空間檔案描述符關閉時,會發生分離程式。 因此,如果我們需要替換報告描述符修復程式,則需要原始報告描述符修復程式的擁有者進行一些協作。 前一個擁有者可能會將 struct_ops 連結固定在 bpffs 中,然後我們可以透過正常的 bpf 操作來替換它。

將 bpf 程式附加到裝置

我們現在使用透過 bpf_map__attach_struct_ops() 的標準 struct_ops 附加。 但是鑑於我們需要將 struct_ops 附加到專用的 HID 裝置,因此呼叫者必須在將程式載入到核心之前在 struct_ops 對映中設定 hid_id

hid_id 是 HID 裝置的唯一系統 ID(sysfs 路徑中的最後 4 個數字:/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000

也可以設定 flags,它的型別是 enum hid_bpf_attach_flags

我們不能依靠 hidraw 將 BPF 程式繫結到 HID 裝置。 hidraw 是 HID 裝置處理的產物,並且不穩定。 某些驅動程式甚至停用它,因此消除了對這些裝置進行跟蹤的功能(在這些裝置上獲取非 hidraw 跟蹤很有趣)。

另一方面,對於 HID 裝置的整個生命週期,即使我們更改其報告描述符,hid_id 也是穩定的。

鑑於當裝置斷開/重新連線時 hidraw 不穩定,我們建議透過 sysfs 訪問裝置的當前報告描述符。 可以在 /sys/bus/hid/devices/BUS:VID:PID.000N/report_descriptor 中以二進位制流的形式訪問它。

解析報告描述符是 BPF 程式設計師或載入 eBPF 程式的使用者空間元件的責任。

一個(幾乎)完整的 BPF 增強型 HID 裝置示例

前言:在大多數情況下,這可以作為核心驅動程式來實現

讓我們想象一下,我們有一個新的平板電腦裝置,它具有一些觸覺功能來模擬使用者在其上刮擦的表面。 該裝置還將具有一個特定的 3 位開關,用於在紙上的鉛筆牆上的蠟筆繪畫畫布上的刷子之間切換。 為了使事情變得更好,我們可以透過特徵報告來控制開關的物理位置。

當然,該開關依賴於一些使用者空間元件來控制裝置本身的觸覺功能。

過濾事件

第一步是過濾來自裝置的事件。 鑑於開關位置實際上是在筆事件流中報告的,因此使用 hidraw 來實現該過濾意味著我們為每個事件喚醒使用者空間。

這對 libinput 來說是可以的,但是如果有一個外部庫只對報告中的一個位元組感興趣,那就不太理想了。

為此,我們可以為我們的 BPF 程式建立一個基本骨架

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

/* HID programs need to be GPL */
char _license[] SEC("license") = "GPL";

/* HID-BPF kfunc API definitions */
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
                            unsigned int offset,
                            const size_t __sz) __ksym;

struct {
      __uint(type, BPF_MAP_TYPE_RINGBUF);
      __uint(max_entries, 4096 * 64);
} ringbuf SEC(".maps");

__u8 current_value = 0;

SEC("struct_ops/hid_device_event")
int BPF_PROG(filter_switch, struct hid_bpf_ctx *hid_ctx)
{
      __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 192 /* size */);
      __u8 *buf;

      if (!data)
              return 0; /* EPERM check */

      if (current_value != data[152]) {
              buf = bpf_ringbuf_reserve(&ringbuf, 1, 0);
              if (!buf)
                      return 0;

              *buf = data[152];

              bpf_ringbuf_commit(buf, 0);

              current_value = data[152];
      }

      return 0;
}

SEC(".struct_ops.link")
struct hid_bpf_ops haptic_tablet = {
      .hid_device_event = (void *)filter_switch,
};

要附加 haptic_tablet,使用者空間需要首先設定 hid_id

static int attach_filter(struct hid *hid_skel, int hid_id)
{
      int err, link_fd;

      hid_skel->struct_ops.haptic_tablet->hid_id = hid_id;
      err = hid__load(skel);
      if (err)
              return err;

      link_fd = bpf_map__attach_struct_ops(hid_skel->maps.haptic_tablet);
      if (!link_fd) {
              fprintf(stderr, "can not attach HID-BPF program: %m\n");
              return -1;
      }

      return link_fd; /* the fd of the created bpf_link */
}

我們的使用者空間程式現在可以監聽環形緩衝區上的通知,並且僅當值更改時才會被喚醒。

當用戶空間程式不再需要監聽事件時,它只需關閉從 attach_filter() 返回的 bpf 連結,這將告訴核心從 HID 裝置分離程式。

當然,在其他用例中,使用者空間程式還可以像任何 bpf_link 一樣,透過呼叫 bpf_obj_pin() 將 fd 固定到 BPF 檔案系統。

控制裝置

為了能夠更改平板電腦的觸覺反饋,使用者空間程式需要在裝置本身上發出特徵報告。

我們可以建立一個與此相關的 SEC("syscall") 程式,而不是使用 hidraw

/* some more HID-BPF kfunc API definitions */
extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
                            __u8* data,
                            size_t len,
                            enum hid_report_type type,
                            enum hid_class_request reqtype) __ksym;


struct hid_send_haptics_args {
      /* data needs to come at offset 0 so we can do a memcpy into it */
      __u8 data[10];
      unsigned int hid;
};

SEC("syscall")
int send_haptic(struct hid_send_haptics_args *args)
{
      struct hid_bpf_ctx *ctx;
      int ret = 0;

      ctx = hid_bpf_allocate_context(args->hid);
      if (!ctx)
              return 0; /* EPERM check */

      ret = hid_bpf_hw_request(ctx,
                               args->data,
                               10,
                               HID_FEATURE_REPORT,
                               HID_REQ_SET_REPORT);

      hid_bpf_release_context(ctx);

      return ret;
}

然後,使用者空間需要直接呼叫該程式

static int set_haptic(struct hid *hid_skel, int hid_id, __u8 haptic_value)
{
      int err, prog_fd;
      int ret = -1;
      struct hid_send_haptics_args args = {
              .hid = hid_id,
      };
      DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
              .ctx_in = &args,
              .ctx_size_in = sizeof(args),
      );

      args.data[0] = 0x02; /* report ID of the feature on our device */
      args.data[1] = haptic_value;

      prog_fd = bpf_program__fd(hid_skel->progs.set_haptic);

      err = bpf_prog_test_run_opts(prog_fd, &tattrs);
      return err;
}

現在我們的使用者空間程式知道觸覺狀態,並且可以控制它。 該程式可以使該狀態進一步可用於其他使用者空間程式(例如,透過 DBus API)。

這裡有趣的是,我們沒有為此建立新的核心 API。 這意味著,如果我們的實現中存在錯誤,我們可以隨意更改與核心的介面,因為使用者空間應用程式負責其自身的使用。