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。 除了幾個問題外,它工作正常
如果驅動程式不匯出 hidraw 節點,我們將無法跟蹤任何內容(eBPF 在這裡將是一個“上帝模式”,因此這可能會引起一些人的注意)
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 具有以下可用的附件型別
使用 libbpf 中的
SEC("struct_ops/hid_device_event")進行事件處理/過濾來自使用者空間的操作,在 libbpf 中使用
SEC("syscall")在 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 附加到裝置時使用的標誌。 當前唯一可用的值是
0或BPF_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_REPORT、HID_FEATURE_REPORT、HID_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_deviceallocated_size分配的資料大小。
這是可用的記憶體量,可以由 HID 程式請求。 請注意,對於
HID_BPF_RDESC_FIXUP,該記憶體設定為4096(4 KB){unnamed_union}匿名
retval先前程式的返回值。
大小資料欄位中的有效資料。
程式可以透過獲取此欄位來獲取資料中可用的有效大小。 程式還可以透過在程式中返回一個正數來更改此值。 要丟棄該事件,請返回一個負錯誤程式碼。
size必須始終小於或等於allocated_size(一旦所有 BPF 程式都已執行,它就會強制執行)。
描述
data 不能直接從上下文中訪問。 我們需要呼叫 hid_bpf_get_data() 才能獲取指向該欄位的指標。
hid 和 allocated_size 是隻讀的,size 和 retval 是可讀寫的。
可以在所有 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 *ctxHID-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 裝置分配一個上下文
-
__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 需要是常量,在編譯時已知。
這允許以下操作
對於給定的裝置,如果我們知道報告長度始終為某個值,我們可以請求
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]);如果報告長度是可變的,但我們知道
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。 這意味著,如果我們的實現中存在錯誤,我們可以隨意更改與核心的介面,因為使用者空間應用程式負責其自身的使用。