diff --git a/scripts/generate_toc.py b/scripts/generate_toc.py index 6314a6b..2fb7ac9 100644 --- a/scripts/generate_toc.py +++ b/scripts/generate_toc.py @@ -15,13 +15,13 @@ def generate_toc(base_dir, project_root): "GPU": "\n\nGPU:\n\n", "Scheduler": "\n\nScheduler:\n\n", "Networking": "\n\nNetworking:\n\n", - "tracing": "\n\nTracing:\n\n", + "Tracing": "\n\nTracing:\n\n", "Security": "\n\nSecurity:\n\n", "Features": "\n\nFeatures:\n\n", - "Other": "\n\nFeatures:\n\n" + "Other": "\nOther:\n\n" } - subsection_order = ['GPU', 'Scheduler', 'Networking', 'tracing', 'Security', 'Features', 'Other', 'Android'] + subsection_order = ['GPU', 'Scheduler', 'Networking', 'Tracing', 'Security', 'Features', 'Other', 'Android'] # To ensure numeric sorting of directories def sort_key(directory_name): diff --git a/src/10-hardirqs/README.md b/src/10-hardirqs/README.md index cf40fff..c7fef37 100644 --- a/src/10-hardirqs/README.md +++ b/src/10-hardirqs/README.md @@ -5,6 +5,8 @@ eBPF (Extended Berkeley Packet Filter) is a powerful network and performance ana This article is the tenth part of the eBPF Tutorial by Example, focusing on capturing interrupt events using hardirqs or softirqs in eBPF. hardirqs and softirqs are two different types of interrupt handlers in the Linux kernel. They are used to handle interrupt requests generated by hardware devices, as well as asynchronous events in the kernel. In eBPF, we can use the eBPF tools hardirqs and softirqs to capture and analyze information related to interrupt handling in the kernel. +> The complete source code: + ## What are hardirqs and softirqs? hardirqs are hardware interrupt handlers. When a hardware device generates an interrupt request, the kernel maps it to a specific interrupt vector and executes the associated hardware interrupt handler. Hardware interrupt handlers are commonly used to handle events in device drivers, such as completion of device data transfer or device errors. diff --git a/src/11-bootstrap/README.md b/src/11-bootstrap/README.md index c7bbd07..71489c4 100644 --- a/src/11-bootstrap/README.md +++ b/src/11-bootstrap/README.md @@ -4,6 +4,8 @@ eBPF (Extended Berkeley Packet Filter) is a powerful network and performance ana In this tutorial, we will learn how kernel-space and user-space eBPF programs work together. We will also learn how to use the native libbpf to develop user-space programs, package eBPF applications into executable files, and distribute them across different kernel versions. +> The complete source code: + ## The libbpf Library and Why We Need to Use It libbpf is a C language library that is distributed with the kernel version to assist in loading and running eBPF programs. It provides a set of C APIs for interacting with the eBPF system, allowing developers to write user-space programs more easily to load and manage eBPF programs. These user-space programs are typically used for system performance analysis, monitoring, or optimization. diff --git a/src/12-profile/README.md b/src/12-profile/README.md index 5c632e1..93606c5 100644 --- a/src/12-profile/README.md +++ b/src/12-profile/README.md @@ -4,6 +4,8 @@ This tutorial will guide you on using eBPF programs for performance analysis wit This implementation uses libbpf-rs, a Rust wrapper around libbpf, along with blazesym for symbol resolution. Perf is a performance analysis tool in the Linux kernel that allows users to measure and analyze the performance of kernel and user space programs, as well as obtain corresponding call stacks. It collects performance data using hardware counters and software events in the kernel. +> The complete source code: + ## eBPF Tool: profile Performance Analysis Example The `profile` tool is implemented based on eBPF and utilizes the perf events in the Linux kernel for performance analysis. The `profile` tool periodically samples each processor to capture the execution of kernel and user space functions. diff --git a/src/13-tcpconnlat/README.md b/src/13-tcpconnlat/README.md index 01a2243..3313148 100644 --- a/src/13-tcpconnlat/README.md +++ b/src/13-tcpconnlat/README.md @@ -4,6 +4,8 @@ eBPF (Extended Berkeley Packet Filter) is a powerful network and performance ana This article is the thirteenth installment of the eBPF Tutorial by Example, mainly about how to use eBPF to statistics TCP connection delay and process data in user space using libbpf. +> The complete source code: + ## Background When developing backends, regardless of the programming language used, we often need to call databases such as MySQL and Redis, perform RPC remote calls, or call other RESTful APIs. The underlying implementation of these calls is usually based on the TCP protocol. This is because TCP protocol has advantages such as reliable connection, error retransmission, congestion control, etc., so TCP is more widely used in network transport layer protocols than UDP. However, TCP also has some drawbacks, such as longer connection establishment delay. Therefore, some alternative solutions have emerged, such as QUIC (Quick UDP Internet Connections). diff --git a/src/14-tcpstates/README.md b/src/14-tcpstates/README.md index 189474d..6e30f9d 100644 --- a/src/14-tcpstates/README.md +++ b/src/14-tcpstates/README.md @@ -4,6 +4,8 @@ eBPF (Extended Berkeley Packet Filter) is a powerful network and performance ana In this article of our eBPF Tutorial by Example series, we will introduce two sample programs: `tcpstates` and `tcprtt`. `tcpstates` is used to record the state changes of TCP connections, while `tcprtt` is used to record the Round-Trip Time (RTT) of TCP. +> The complete source code: + ## `tcprtt` and `tcpstates` Network quality is crucial in the current Internet environment. There are many factors that affect network quality, including hardware, network environment, and the quality of software programming. To help users better locate network issues, we introduce the tool `tcprtt`. `tcprtt` can monitor the Round-Trip Time of TCP connections, evaluate network quality, and help users identify potential problems. diff --git a/src/15-javagc/README.md b/src/15-javagc/README.md index 1b76e18..e6e7381 100644 --- a/src/15-javagc/README.md +++ b/src/15-javagc/README.md @@ -4,6 +4,8 @@ eBPF (Extended Berkeley Packet Filter) is a powerful network and performance ana In this article of our eBPF Tutorial by Example series, we will explore how to use eBPF and USDT to capture and analyze the duration of Java garbage collection (GC) events. +> The complete source code: + ## Introduction to USDT USDT is a mechanism for inserting static tracepoints into applications, allowing developers to insert probes at critical points in the program for debugging and performance analysis purposes. These probes can be dynamically activated at runtime by tools such as DTrace, SystemTap, or eBPF, allowing access to the program's internal state and performance metrics without the need to restart the application or modify the program code. USDT is widely used in many open-source software applications such as MySQL, PostgreSQL, Ruby, Python, and Node.js. diff --git a/src/16-memleak/README.md b/src/16-memleak/README.md index 5530e6a..6b36c2e 100644 --- a/src/16-memleak/README.md +++ b/src/16-memleak/README.md @@ -4,6 +4,8 @@ eBPF (extended Berkeley Packet Filter) is a powerful network and performance ana In this tutorial, we will explore how to write a Memleak program using eBPF to monitor memory leaks in programs. +> The complete source code: + ## Background and Importance Memory leaks are a common problem in computer programming and should not be underestimated. When memory leaks occur, programs gradually consume more memory resources without properly releasing them. Over time, this behavior can lead to a gradual depletion of system memory, significantly reducing the overall performance of the program and system. diff --git a/src/17-biopattern/README.md b/src/17-biopattern/README.md index 2a7cb9c..6ab9f67 100644 --- a/src/17-biopattern/README.md +++ b/src/17-biopattern/README.md @@ -4,6 +4,8 @@ eBPF (Extended Berkeley Packet Filter) is a new technology in the Linux kernel t In this tutorial, we will explore how to use eBPF to write programs to count random and sequential disk I/O. Disk I/O is one of the key metrics of computer performance, especially in data-intensive applications. +> The complete source code: + ## Random/Sequential Disk I/O As technology advances and data volumes explode, disk I/O becomes a critical bottleneck in system performance. The performance of an application depends heavily on how it interacts with the storage tier. Therefore, it becomes especially important to deeply understand and optimise disk I/O, especially random and sequential I/O. diff --git a/src/18-further-reading/README.md b/src/18-further-reading/README.md index 8fc0c39..e6d8cce 100644 --- a/src/18-further-reading/README.md +++ b/src/18-further-reading/README.md @@ -5,6 +5,8 @@ You may find more about eBPF in these places: - A curated list of awesome projects related to eBPF: - A website of eBPF projects and tutorials: +> The complete source code: + This is also list of eBPF related papers I read in recent years, might be helpful for people who are interested in eBPF related research. eBPF (extended Berkeley Packet Filter) is an emerging technology that allows safe execution of user-provided programs in the Linux kernel. It has gained widespread adoption in recent years for accelerating network processing, enhancing observability, and enabling programmable packet processing. diff --git a/src/19-lsm-connect/README.md b/src/19-lsm-connect/README.md index 67f89c8..86e65c5 100644 --- a/src/19-lsm-connect/README.md +++ b/src/19-lsm-connect/README.md @@ -2,6 +2,8 @@ eBPF (Extended Berkeley Packet Filter) is a powerful network and performance analysis tool widely used in the Linux kernel. eBPF allows developers to dynamically load, update, and run user-defined code without restarting the kernel or modifying the kernel source code. This feature enables eBPF to provide high flexibility and performance, making it widely applicable in network and system performance analysis. The same applies to eBPF applications in security, and this article will introduce how to use the eBPF LSM (Linux Security Modules) mechanism to implement a simple security check program. +> The complete source code: + ## Background LSM has been an official security framework in the Linux kernel since Linux 2.6, and security implementations based on it include SELinux and AppArmor. With the introduction of BPF LSM in Linux 5.7, system developers have been able to freely implement function-level security checks. This article provides an example of limiting access to a specific IPv4 address through the socket connect function using a BPF LSM program. (This demonstrates its high control precision.) diff --git a/src/40-mysql/README.md b/src/40-mysql/README.md index 6506ee5..a587035 100644 --- a/src/40-mysql/README.md +++ b/src/40-mysql/README.md @@ -4,6 +4,8 @@ MySQL is one of the most widely used relational database management systems in t This is where eBPF (Extended Berkeley Packet Filter) comes into play. eBPF is a powerful technology that allows you to write programs that can run in the Linux kernel, enabling you to trace, monitor, and analyze various aspects of system behavior, including the performance of applications like MySQL. In this blog, we'll explore how to use eBPF to trace MySQL queries, measure their execution time, and gain valuable insights into your database's performance. +> The complete source code: + ## Background: MySQL and eBPF ### MySQL diff --git a/src/41-xdp-tcpdump/README.md b/src/41-xdp-tcpdump/README.md index b9a0764..0a3896d 100644 --- a/src/41-xdp-tcpdump/README.md +++ b/src/41-xdp-tcpdump/README.md @@ -2,6 +2,8 @@ Extended Berkeley Packet Filter (eBPF) is a revolutionary technology in the Linux kernel that allows developers to run sandboxed programs within the kernel space. It enables powerful networking, security, and tracing capabilities without the need to modify the kernel source code or load kernel modules. This tutorial focuses on using eBPF with the Express Data Path (XDP) to capture TCP header information directly from network packets at the earliest point of ingress. +> The complete source code: + ## Capturing TCP Headers with XDP Capturing network packets is essential for monitoring, debugging, and securing network communications. Traditional tools like `tcpdump` operate in user space and can incur significant overhead. By leveraging eBPF and XDP, we can capture TCP header information directly within the kernel, minimizing overhead and improving performance. diff --git a/src/42-xdp-loadbalancer/README.md b/src/42-xdp-loadbalancer/README.md index 8260d86..12dc686 100644 --- a/src/42-xdp-loadbalancer/README.md +++ b/src/42-xdp-loadbalancer/README.md @@ -3,6 +3,8 @@ In this tutorial, we will guide you through the process of implementing a simple XDP (eXpress Data Path) load balancer using eBPF (Extended Berkeley Packet Filter). With just C, libbpf, and no external dependencies, this hands-on guide is perfect for developers interested in harnessing the full power of the Linux kernel to build highly efficient network applications. +> The complete source code: + ## Why XDP? `XDP` (eXpress Data Path) is a fast, in-kernel networking framework in Linux that allows packet processing at the earliest point in the network stack, right in the network interface card (NIC). This enables ultra-low-latency and high-throughput packet handling, making XDP ideal for tasks like load balancing, DDoS protection, and traffic filtering. diff --git a/src/43-kfuncs/README.md b/src/43-kfuncs/README.md index 5880fce..76c1ef3 100644 --- a/src/43-kfuncs/README.md +++ b/src/43-kfuncs/README.md @@ -2,6 +2,8 @@ Have you ever felt constrained by eBPF's capabilities? Maybe you've run into situations where the existing eBPF features just aren't enough to accomplish your goals. Perhaps you need deeper interactions with the kernel, or you're facing performance issues that the standard eBPF runtime can't solve. If you've ever wished for more flexibility and power in your eBPF programs, this tutorial is for you. +> The complete source code: + ## Introduction: Adding a `strstr` kfunc to Break Free from eBPF Runtime Limitations **eBPF (extended Berkeley Packet Filter)** has revolutionized Linux system programming by allowing developers to run sandboxed programs inside the kernel. It's a game-changer for networking, security, and observability, enabling powerful functionalities without the need to modify kernel source code or load traditional kernel modules. diff --git a/src/44-scx-simple/README.md b/src/44-scx-simple/README.md index 0e37c1a..fa3f101 100644 --- a/src/44-scx-simple/README.md +++ b/src/44-scx-simple/README.md @@ -1,9 +1,11 @@ # eBPF Tutorial: Introduction to the BPF Scheduler -Welcome to our deep dive into the world of eBPF with a focus on the BPF scheduler! If you're looking to extend your eBPF knowledge beyond the basics, you're in the right place. +Welcome to our deep dive into the world of eBPF with a focus on the BPF scheduler! If you're looking to extend your eBPF knowledge beyond the basics, you're in the right place. In this tutorial, we'll explore the **scx_simple scheduler**, a minimal example of the sched_ext scheduler class introduced in Linux kernel version `6.12`. We'll walk you through its architecture, how it leverages BPF programs to define scheduling behavior, and guide you through compiling and running the example. By the end, you'll have a solid understanding of how to create and manage advanced scheduling policies using eBPF. +> The complete source code: + ## Understanding the Extensible BPF Scheduler At the heart of this tutorial is the **sched_ext** scheduler class. Unlike traditional schedulers, sched_ext allows its behavior to be defined dynamically through a set of BPF programs, making it highly flexible and customizable. This means you can implement any scheduling algorithm on top of sched_ext, tailored to your specific needs. diff --git a/src/45-scx-nest/README.md b/src/45-scx-nest/README.md index 61e4bd2..6f662b4 100644 --- a/src/45-scx-nest/README.md +++ b/src/45-scx-nest/README.md @@ -2,6 +2,8 @@ In the ever-evolving landscape of system performance optimization, the ability to customize and extend kernel behavior is invaluable. One of the most powerful tools for achieving this is eBPF (extended Berkeley Packet Filter). In this tutorial, we'll explore the implementation of the `scx_nest` scheduler, an advanced eBPF program that leverages the `sched_ext` scheduler class introduced in Linux kernel version `6.12`. By the end of this guide, you'll understand how to build a sophisticated scheduler that dynamically adjusts task placement based on CPU core frequencies and utilization. +> The complete source code: + ## Introduction to `sched_ext` The `sched_ext` scheduler class marks a significant advancement in Linux kernel scheduling capabilities. Unlike traditional schedulers, `sched_ext` allows its behavior to be defined dynamically through a set of BPF (Berkeley Packet Filter) programs. This flexibility enables developers to implement custom scheduling algorithms tailored to specific workloads and system requirements. diff --git a/src/46-xdp-test/README.md b/src/46-xdp-test/README.md index 9f4ec29..68c538b 100644 --- a/src/46-xdp-test/README.md +++ b/src/46-xdp-test/README.md @@ -4,6 +4,8 @@ Need to stress-test your network stack or measure XDP program performance? Tradi In this tutorial, we'll build an XDP-based packet generator that leverages the kernel's BPF_PROG_RUN test infrastructure. We'll explore how XDP's `XDP_TX` action creates a packet reflection loop, understand the live frames mode that enables real packet injection, and measure the performance characteristics of XDP programs under load. By the end, you'll have a production-ready tool for network testing and XDP benchmarking. +> The complete source code: + ## Understanding XDP Packet Generation XDP (eXpress Data Path) provides the fastest programmable packet processing in Linux by hooking into network drivers before the kernel's networking stack allocates socket buffers. Normally, XDP programs process packets arriving from network interfaces. But what if you want to test an XDP program's performance without real network traffic? Or inject synthetic packets to stress-test your network infrastructure? diff --git a/src/47-cuda-events/README.md b/src/47-cuda-events/README.md index fc490fe..e259f30 100644 --- a/src/47-cuda-events/README.md +++ b/src/47-cuda-events/README.md @@ -2,6 +2,8 @@ Have you ever wondered what's happening under the hood when your CUDA application is running? GPU operations can be challenging to debug and profile because they happen in a separate device with its own memory space. In this tutorial, we'll build a powerful eBPF-based tracing tool that lets you peek into CUDA API calls in real time. +> The complete source code: + ## Introduction to CUDA and GPU Tracing CUDA (Compute Unified Device Architecture) is NVIDIA's parallel computing platform and programming model that enables developers to use NVIDIA GPUs for general-purpose processing. When you run a CUDA application, a typical workflow begins with the host (CPU) allocating memory on the device (GPU), followed by data transfer from host memory to device memory, then GPU kernels (functions) are launched to process the data, after which results are transferred back from device to host, and finally device memory is freed. diff --git a/src/48-energy/.config b/src/48-energy/.config index feddf49..d50f199 100644 --- a/src/48-energy/.config +++ b/src/48-energy/.config @@ -1,2 +1,2 @@ level=Depth -type=tracing +type=Tracing diff --git a/src/48-energy/README.md b/src/48-energy/README.md index 83a299b..96d5e19 100644 --- a/src/48-energy/README.md +++ b/src/48-energy/README.md @@ -2,6 +2,8 @@ Have you ever wondered how much energy your applications are consuming? As energy efficiency becomes increasingly critical in both data centers and edge devices, understanding power consumption at the process level is essential for optimization. In this tutorial, we'll build an eBPF-based energy monitoring tool that provides real-time insights into process-level power consumption with minimal overhead. +> The complete source code: + ## Introduction to Energy Monitoring and Power Analysis Energy monitoring in computing systems has traditionally been challenging due to the lack of fine-grained measurement capabilities. While hardware counters like Intel RAPL (Running Average Power Limit) can measure total system or CPU package power, they don't tell you which processes are consuming that power. This is where software-based energy attribution comes into play. diff --git a/src/49-hid/.config b/src/49-hid/.config new file mode 100644 index 0000000..89157d4 --- /dev/null +++ b/src/49-hid/.config @@ -0,0 +1,2 @@ +level=Depth +type=Other diff --git a/src/49-hid/.gitignore b/src/49-hid/.gitignore new file mode 100644 index 0000000..dfc91cb --- /dev/null +++ b/src/49-hid/.gitignore @@ -0,0 +1,16 @@ +# Build outputs +.output/ +*.o +*.skel.h +hid-input-modifier + +# Editor files +*.swp +*.swo +*~ +.vscode/ +.idea/ + +# Temporary files +*.tmp +*.log diff --git a/src/49-hid/Makefile b/src/49-hid/Makefile new file mode 100644 index 0000000..267623b --- /dev/null +++ b/src/49-hid/Makefile @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +LIBBPF_SRC := $(abspath ../third_party/libbpf/src) +BPFTOOL_SRC := $(abspath ../third_party/bpftool/src) +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../third_party/vmlinux/$(ARCH)/vmlinux.h +INCLUDES := -I$(OUTPUT) -I../third_party/libbpf/include/uapi -I$(dir $(VMLINUX)) +CFLAGS := -g -Wall +ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) + +APPS = hid-input-modifier + +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all +all: $(APPS) + +.PHONY: clean +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(APPS) + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# Build libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIB,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ + install + +# Build bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + +# Build BPF code +$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,BPF,$@) + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + $(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + +# Generate BPF skeletons +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# Build user-space code +$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h + +$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +# Build application binary +$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) + $(call msg,BINARY,$@) + $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/src/49-hid/README.md b/src/49-hid/README.md new file mode 100644 index 0000000..9d357f2 --- /dev/null +++ b/src/49-hid/README.md @@ -0,0 +1,433 @@ +# eBPF Tutorial: Fixing Broken HID Devices Without Kernel Patches + +Ever plugged in a new mouse or drawing tablet only to find it doesn't work quite right on Linux? Maybe the Y-axis is inverted, buttons are mapped wrong, or the device just feels broken. Traditionally, fixing these quirks meant writing a kernel driver, waiting weeks for review, and hoping your distro ships the fix sometime next year. By then, you've probably bought a different device. + +This tutorial shows you a better way. We'll use HID-BPF to create a virtual mouse device and modify its input on the fly using eBPF. In minutes, not months, you'll see how to fix device quirks without touching the kernel. This is the same technology now shipping with 14+ device fixes in the mainline Linux kernel. + +> The complete source code: + +## The HID Device Problem + +HID (Human Interface Device) is a standard protocol for input devices like mice, keyboards, game controllers, and drawing tablets. The protocol is well-defined, but hardware vendors often implement it incorrectly or add quirks that don't follow the spec. When this happens on Linux, users suffer. + +Let's say you buy a drawing tablet with an inverted Y-axis. When you move the stylus up, the cursor goes down. Or you get a mouse where buttons 4 and 5 report themselves as buttons 6 and 7, breaking your browser's back/forward navigation. These bugs are incredibly frustrating because the hardware works perfectly on other operating systems, but Linux sees the raw incorrect data. + +The traditional fix requires writing a kernel driver or patching an existing one. You'd need to understand kernel development, submit patches to LKML, get them reviewed, wait for the next kernel release, and then wait again for your distribution to ship that kernel. For a user with broken hardware, this could take six months or more. Most users just return the device or dual-boot to another OS. + +## Enter HID-BPF + +HID-BPF changes everything by letting you fix devices in userspace with eBPF programs loaded into the kernel. The programs hook into the HID subsystem using BPF struct_ops, intercepting HID reports before applications see them. You can modify the report data, fix descriptor issues, or even block certain operations entirely. + +This approach gives you the safety of kernel code (the BPF verifier ensures no crashes) with the flexibility of userspace development. Write your fix, load it, test it immediately. If it works, package it and ship to users the same day. The Linux kernel already includes HID-BPF fixes for 14 different devices, including: + +- Microsoft Xbox Elite 2 controller +- Huion drawing tablets (Kamvas Pro 19, Inspiroy 2-S) +- XPPen tablets (Artist24, ArtistPro16Gen2, DecoMini4) +- Wacom ArtPen +- Thrustmaster TCA Yoke Boeing +- IOGEAR Kaliber MMOmentum mouse +- Various other mice and gaming peripherals + +Each fix is typically 100-300 lines of BPF code instead of a full kernel driver. The ecosystem has grown rapidly, with the udev-hid-bpf project providing scaffolding to make writing these fixes even easier. + +## Why Virtual Devices for Learning? + +This tutorial uses a virtual HID device created through uhid (userspace HID). You might wonder why we don't just attach to your real mouse. Virtual devices are perfect for learning because they give you: + +- **Complete control**: We send exactly the events we want, when we want +- **Repeatability**: Same test events produce same results every time +- **Safety**: Can't accidentally break your real input devices +- **No hardware required**: Works on any Linux system with kernel 6.3+ + +The virtual mouse we create reports movement events just like a real USB mouse. Our BPF program intercepts these events and modifies them before the input subsystem sees them. In our example, we'll double all movement, but the same technique applies to fixing inverted axes, remapping buttons, or any other transformation. + +## Implementation: The Virtual HID Device + +Let's look at the complete implementation, starting with the userspace code that creates our virtual mouse. This uses the uhid interface, which allows userspace programs to create kernel HID devices. + +### Creating the Virtual Mouse + +```c +// SPDX-License-Identifier: GPL-2.0 +/* Create virtual HID mouse and modify its input with BPF */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hid-input-modifier.skel.h" + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +/* Simple mouse report descriptor */ +static unsigned char rdesc[] = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x02, /* USAGE (Mouse) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x09, 0x01, /* USAGE (Pointer) */ + 0xa1, 0x00, /* COLLECTION (Physical) */ + 0x05, 0x09, /* USAGE_PAGE (Button) */ + 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /* USAGE (X) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */ + 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */ + 0xc0, /* END_COLLECTION */ + 0xc0 /* END_COLLECTION */ +}; +``` + +This report descriptor defines a standard USB mouse with three buttons and relative X/Y movement. The descriptor uses HID descriptor language to tell the kernel what data the device will send. Each report will contain three bytes: button states in byte 0, X movement in byte 1, and Y movement in byte 2. + +The uhid interface requires us to write this descriptor when creating the device: + +```c +static int create_uhid_device(void) +{ + struct uhid_event ev; + int fd; + + fd = open("/dev/uhid", O_RDWR | O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "Cannot open /dev/uhid: %m\n"); + return -errno; + } + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char*)ev.u.create.name, "BPF Virtual Mouse"); + ev.u.create.rd_data = rdesc; + ev.u.create.rd_size = sizeof(rdesc); + ev.u.create.bus = BUS_USB; + ev.u.create.vendor = 0x15d9; + ev.u.create.product = 0x0a37; + ev.u.create.version = 0; + ev.u.create.country = 0; + + if (uhid_write(fd, &ev)) { + close(fd); + return -1; + } + + printf("Created virtual HID device\n"); + return fd; +} +``` + +When this succeeds, the kernel creates a new HID device that appears in `/sys/bus/hid/devices/` just like a real USB mouse. We can then attach our BPF program to intercept its events. + +### Sending Synthetic Mouse Events + +With the virtual device created, we can inject mouse movement events: + +```c +static int send_mouse_event(int fd, __s8 x, __s8 y) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT; + ev.u.input.size = 3; + ev.u.input.data[0] = 0; /* Buttons */ + ev.u.input.data[1] = x; /* X movement */ + ev.u.input.data[2] = y; /* Y movement */ + + return uhid_write(fd, &ev); +} +``` + +Each event sends three bytes matching our report descriptor. Byte 0 contains button states (all zeros means no buttons pressed), byte 1 is X movement as a signed 8-bit value, and byte 2 is Y movement. The kernel processes this exactly like it would process events from a real USB mouse. + +## The BPF Program: Intercepting HID Events + +Now for the interesting part: the BPF program that modifies mouse input. This runs in the kernel, attached to the HID device via struct_ops. + +```c +// SPDX-License-Identifier: GPL-2.0 +/* HID-BPF example: Modify input data from virtual HID device + * + * This program doubles the X and Y movement of a mouse. + * Works with the virtual HID device created by the userspace program. + */ + +#include "vmlinux.h" +#include "hid_bpf_defs.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include + +SEC("struct_ops/hid_device_event") +int BPF_PROG(hid_double_movement, struct hid_bpf_ctx *hctx, enum hid_report_type type) +{ + __u8 *data = hid_bpf_get_data(hctx, 0, 9); + __s8 x, y; + + if (!data) + return 0; + + /* Mouse HID report format (simplified): + * Byte 0: Report ID + * Byte 1: Buttons + * Byte 2: X movement (signed byte) + * Byte 3: Y movement (signed byte) + */ + + x = (__s8)data[2]; + y = (__s8)data[3]; + + /* Double the movement */ + x *= 2; + y *= 2; + + data[2] = (__u8)x; + data[3] = (__u8)y; + + bpf_printk("Modified: X=%d Y=%d -> X=%d Y=%d", + (__s8)data[2]/2, (__s8)data[3]/2, + (__s8)data[2], (__s8)data[3]); + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops input_modifier = { + .hid_device_event = (void *)hid_double_movement, +}; + +char _license[] SEC("license") = "GPL"; +``` + +The program hooks into `hid_device_event`, which the kernel calls for every HID input report. The `hctx` parameter provides context about the device and report. We call `hid_bpf_get_data()` to get a pointer to the actual report data, which we can read and modify. + +The report data follows the format defined by our descriptor. For our simple mouse, byte 2 contains X movement and byte 3 contains Y movement, both as signed 8-bit integers. We read these values, double them, and write them back. The kernel will then pass the modified report to the input subsystem, where applications see the doubled movement. + +The `bpf_printk()` call logs our modifications to the kernel trace buffer. This is invaluable for debugging, letting you see exactly how the BPF program transforms each event. + +### Understanding struct_ops + +The `SEC(".struct_ops.link")` section creates a struct_ops map that connects our BPF program to the HID subsystem. Struct_ops is a BPF feature that lets you implement kernel interfaces in BPF code. For HID, this means providing callbacks that the kernel invokes during HID processing. + +The `hid_bpf_ops` structure defines which callbacks we're implementing. We only need `hid_device_event` to intercept reports, but HID-BPF also supports: + +- `hid_rdesc_fixup`: Modify the report descriptor itself +- `hid_hw_request`: Intercept requests to the device +- `hid_hw_output_report`: Intercept output reports + +The userspace code loads this BPF program and attaches it by setting the `hid_id` field to our virtual device's ID, then calling `bpf_map__attach_struct_ops()`. + +## Putting It All Together + +The main function orchestrates everything: + +```c +int main(int argc, char **argv) +{ + struct hid_input_modifier_bpf *skel = NULL; + struct bpf_link *link = NULL; + int err, hid_id; + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Create virtual HID device */ + uhid_fd = create_uhid_device(); + if (uhid_fd < 0) + return 1; + + /* Find the HID device ID */ + hid_id = find_hid_device(); + if (hid_id < 0) { + fprintf(stderr, "Cannot find virtual HID device\n"); + destroy_uhid_device(uhid_fd); + return 1; + } + + /* Open and load BPF program */ + skel = hid_input_modifier_bpf__open(); + if (!skel) { + fprintf(stderr, "Failed to open BPF skeleton\n"); + destroy_uhid_device(uhid_fd); + return 1; + } + + skel->struct_ops.input_modifier->hid_id = hid_id; + + err = hid_input_modifier_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load BPF skeleton: %d\n", err); + goto cleanup; + } + + /* Attach BPF program */ + link = bpf_map__attach_struct_ops(skel->maps.input_modifier); + if (!link) { + fprintf(stderr, "Failed to attach BPF program: %s\n", strerror(errno)); + err = -1; + goto cleanup; + } + + printf("BPF program attached successfully!\n"); + printf("The BPF program will DOUBLE all mouse movements\n\n"); + printf("Sending test mouse events:\n"); + + /* Send some test events */ + for (int i = 0; i < 5 && !exiting; i++) { + __s8 x = 10, y = 20; + printf("Sending: X=%d, Y=%d (BPF will double to X=%d, Y=%d)\n", + x, y, x*2, y*2); + send_mouse_event(uhid_fd, x, y); + sleep(1); + } + + printf("\nPress Ctrl-C to exit...\n"); + while (!exiting) + sleep(1); + +cleanup: + bpf_link__destroy(link); + hid_input_modifier_bpf__destroy(skel); + destroy_uhid_device(uhid_fd); + return err < 0 ? -err : 0; +} +``` + +The flow is straightforward: create virtual device, find its ID, load BPF program with that ID, attach the program, send test events. The BPF program runs in the kernel, intercepting and modifying each event before it reaches the input layer. + +## Understanding HID Report Format + +To modify HID data effectively, you need to understand the report format. Our simple mouse uses this structure: + +``` +Byte 0: Report ID (always 0 for our single report type) +Byte 1: Button states + bit 0: Left button + bit 1: Right button + bit 2: Middle button + bits 3-7: Unused +Byte 2: X movement (signed 8-bit, -127 to +127) +Byte 3: Y movement (signed 8-bit, -127 to +127) +``` + +Real devices often have more complex reports with multiple report IDs, more buttons, wheel data, and additional axes. You'd determine the format by examining the device's report descriptor, which you can read from sysfs or by looking at existing kernel drivers for similar devices. + +## Compilation and Execution + +Building the example is simple. Navigate to the tutorial directory and run make: + +```bash +cd src/49-hid +make +``` + +This compiles both the BPF program and userspace loader, producing the `hid-input-modifier` executable. Run it with sudo since HID-BPF requires CAP_BPF and CAP_SYS_ADMIN capabilities: + +```bash +sudo ./hid-input-modifier +``` + +You'll see output like: + +``` +Created virtual HID device +Found HID device ID: 8 +BPF program attached successfully! +The BPF program will DOUBLE all mouse movements + +Sending test mouse events: +Sending: X=10, Y=20 (BPF will double to X=20, Y=40) +Sending: X=10, Y=20 (BPF will double to X=20, Y=40) +Sending: X=10, Y=20 (BPF will double to X=20, Y=40) +Sending: X=10, Y=20 (BPF will double to X=20, Y=40) +Sending: X=10, Y=20 (BPF will double to X=20, Y=40) + +Press Ctrl-C to exit... +``` + +In another terminal, you can view the BPF trace output to see the modifications in action: + +```bash +sudo cat /sys/kernel/debug/tracing/trace_pipe +``` + +This shows the `bpf_printk()` messages from the BPF program, confirming that events are being intercepted and modified. + +## Experimenting with Modifications + +The beauty of this approach is how easy it is to experiment. Want to reverse mouse direction instead of doubling it? Just change the BPF code: + +```c +/* Reverse direction */ +x = -x; +y = -y; +``` + +Or maybe swap the axes so horizontal becomes vertical: + +```c +/* Swap axes */ +__s8 temp = x; +x = y; +y = temp; +``` + +You could implement dead zone filtering for aging joysticks: + +```c +/* Ignore small movements */ +if (x > -5 && x < 5) x = 0; +if (y > -5 && y < 5) y = 0; +``` + +Or fix the common inverted Y-axis problem on drawing tablets: + +```c +/* Invert Y axis only */ +y = -y; +``` + +After any change, just run `make` and execute again. No kernel rebuild, no module signing, no waiting. This is the power of HID-BPF. + + +## Summary + +HID-BPF transforms how we handle quirky input devices on Linux. Instead of kernel patches that take months to reach users, we can write small BPF programs that fix devices immediately. The programs run safely in the kernel thanks to the BPF verifier, and they can be packaged and distributed like any other software. + +This tutorial showed you the fundamentals by creating a virtual mouse and modifying its input. You saw how uhid lets userspace create HID devices, how BPF struct_ops connects programs to the HID subsystem, and how simple transformations can fix common device problems. The same techniques apply to real hardware, whether you're fixing an inverted tablet axis or implementing custom game controller mappings. + +The Linux kernel already ships with 14 HID-BPF device fixes, and that number grows with each release. Projects like udev-hid-bpf are making it even easier to write and distribute fixes. If you have a broken HID device, you now have the tools to fix it yourself, in hours instead of months. + +> If you'd like to dive deeper into eBPF, check out our tutorial repository at or visit our website at . + +## References + +- [Linux HID-BPF Documentation](https://docs.kernel.org/hid/hid-bpf.html) +- [udev-hid-bpf Project](https://gitlab.freedesktop.org/libevdev/udev-hid-bpf) +- [Kernel HID-BPF Device Fixes](https://github.com/torvalds/linux/tree/master/drivers/hid/bpf/progs) +- [UHID Kernel Documentation](https://www.kernel.org/doc/html/latest/hid/uhid.html) +- [HID Usage Tables](https://www.usb.org/document-library/device-class-definition-hid-111) +- [Who-T Blog: udev-hid-bpf Quickstart](https://who-t.blogspot.com/2024/04/udev-hid-bpf-quickstart-tooling-to-fix.html) diff --git a/src/49-hid/hid-input-modifier.bpf.c b/src/49-hid/hid-input-modifier.bpf.c new file mode 100644 index 0000000..68abb7f --- /dev/null +++ b/src/49-hid/hid-input-modifier.bpf.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* HID-BPF example: Modify input data from virtual HID device + * + * This program doubles the X and Y movement of a mouse. + * Works with the virtual HID device created by the userspace program. + */ + +#include "vmlinux.h" +#include "hid_bpf_defs.h" +#include "hid_bpf.h" +#include "hid_bpf_helpers.h" +#include + +SEC("struct_ops/hid_device_event") +int BPF_PROG(hid_double_movement, struct hid_bpf_ctx *hctx, enum hid_report_type type) +{ + __u8 *data = hid_bpf_get_data(hctx, 0, 9); + __s8 x, y; + + if (!data) + return 0; + + /* Mouse HID report format (simplified): + * Byte 0: Report ID + * Byte 1: Buttons + * Byte 2: X movement (signed byte) + * Byte 3: Y movement (signed byte) + */ + + x = (__s8)data[2]; + y = (__s8)data[3]; + + /* Double the movement */ + x *= 2; + y *= 2; + + data[2] = (__u8)x; + data[3] = (__u8)y; + + bpf_printk("Modified: X=%d Y=%d -> X=%d Y=%d", + (__s8)data[2]/2, (__s8)data[3]/2, + (__s8)data[2], (__s8)data[3]); + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops input_modifier = { + .hid_device_event = (void *)hid_double_movement, +}; + +char _license[] SEC("license") = "GPL"; diff --git a/src/49-hid/hid-input-modifier.c b/src/49-hid/hid-input-modifier.c new file mode 100644 index 0000000..4f300a7 --- /dev/null +++ b/src/49-hid/hid-input-modifier.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Create virtual HID mouse and modify its input with BPF + * + * This program: + * 1. Creates a virtual HID mouse using uhid + * 2. Attaches a BPF program that doubles movement + * 3. Sends synthetic mouse events + * 4. Shows the BPF modification in action + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hid-input-modifier.skel.h" + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +/* Simple mouse report descriptor */ +static unsigned char rdesc[] = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x02, /* USAGE (Mouse) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x09, 0x01, /* USAGE (Pointer) */ + 0xa1, 0x00, /* COLLECTION (Physical) */ + 0x05, 0x09, /* USAGE_PAGE (Button) */ + 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /* USAGE (X) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */ + 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */ + 0xc0, /* END_COLLECTION */ + 0xc0 /* END_COLLECTION */ +}; + +static int uhid_fd = -1; + +static int uhid_write(int fd, const struct uhid_event *ev) +{ + ssize_t ret; + ret = write(fd, ev, sizeof(*ev)); + if (ret < 0) { + fprintf(stderr, "Cannot write to uhid: %m\n"); + return -errno; + } else if (ret != sizeof(*ev)) { + fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n", + ret, sizeof(*ev)); + return -EFAULT; + } + return 0; +} + +static int create_uhid_device(void) +{ + struct uhid_event ev; + int fd; + + fd = open("/dev/uhid", O_RDWR | O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "Cannot open /dev/uhid: %m\n"); + return -errno; + } + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char*)ev.u.create.name, "BPF Virtual Mouse"); + ev.u.create.rd_data = rdesc; + ev.u.create.rd_size = sizeof(rdesc); + ev.u.create.bus = BUS_USB; + ev.u.create.vendor = 0x15d9; + ev.u.create.product = 0x0a37; + ev.u.create.version = 0; + ev.u.create.country = 0; + + if (uhid_write(fd, &ev)) { + close(fd); + return -1; + } + + printf("Created virtual HID device\n"); + return fd; +} + +static int destroy_uhid_device(int fd) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + uhid_write(fd, &ev); + close(fd); + printf("Destroyed virtual HID device\n"); + return 0; +} + +static int send_mouse_event(int fd, __s8 x, __s8 y) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT; + ev.u.input.size = 3; + ev.u.input.data[0] = 0; /* Buttons */ + ev.u.input.data[1] = x; /* X movement */ + ev.u.input.data[2] = y; /* Y movement */ + + return uhid_write(fd, &ev); +} + +/* Find our virtual HID device */ +static int find_hid_device(void) +{ + char path[256]; + FILE *fp; + int i; + + /* Wait a bit for device to appear */ + sleep(1); + + for (i = 0; i < 100; i++) { + snprintf(path, sizeof(path), "/sys/bus/hid/devices/0003:15D9:0A37.%04X/uevent", i); + fp = fopen(path, "r"); + if (fp) { + fclose(fp); + printf("Found HID device ID: %d\n", i); + return i; + } + } + + return -1; +} + +int main(int argc, char **argv) +{ + struct hid_input_modifier_bpf *skel = NULL; + struct bpf_link *link = NULL; + int err, hid_id; + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* Create virtual HID device */ + uhid_fd = create_uhid_device(); + if (uhid_fd < 0) + return 1; + + /* Find the HID device ID */ + hid_id = find_hid_device(); + if (hid_id < 0) { + fprintf(stderr, "Cannot find virtual HID device\n"); + destroy_uhid_device(uhid_fd); + return 1; + } + + /* Open and load BPF program */ + skel = hid_input_modifier_bpf__open(); + if (!skel) { + fprintf(stderr, "Failed to open BPF skeleton\n"); + destroy_uhid_device(uhid_fd); + return 1; + } + + skel->struct_ops.input_modifier->hid_id = hid_id; + + err = hid_input_modifier_bpf__load(skel); + if (err) { + fprintf(stderr, "Failed to load BPF skeleton: %d\n", err); + goto cleanup; + } + + /* Attach BPF program */ + link = bpf_map__attach_struct_ops(skel->maps.input_modifier); + if (!link) { + fprintf(stderr, "Failed to attach BPF program: %s\n", strerror(errno)); + err = -1; + goto cleanup; + } + + printf("BPF program attached successfully!\n"); + printf("The BPF program will DOUBLE all mouse movements\n\n"); + printf("Sending test mouse events:\n"); + printf("View trace with: sudo cat /sys/kernel/debug/tracing/trace_pipe\n\n"); + + /* Send some test events */ + for (int i = 0; i < 5 && !exiting; i++) { + __s8 x = 10, y = 20; + printf("Sending: X=%d, Y=%d (BPF will double to X=%d, Y=%d)\n", + x, y, x*2, y*2); + send_mouse_event(uhid_fd, x, y); + sleep(1); + } + + printf("\nPress Ctrl-C to exit...\n"); + while (!exiting) + sleep(1); + +cleanup: + bpf_link__destroy(link); + hid_input_modifier_bpf__destroy(skel); + destroy_uhid_device(uhid_fd); + return err < 0 ? -err : 0; +} diff --git a/src/49-hid/hid_bpf.h b/src/49-hid/hid_bpf.h new file mode 100644 index 0000000..7cabd1b --- /dev/null +++ b/src/49-hid/hid_bpf.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2022 Benjamin Tissoires + */ + +#ifndef ____HID_BPF__H +#define ____HID_BPF__H + +#define HID_BPF_DEVICE_EVENT "struct_ops/hid_device_event" +#define HID_BPF_RDESC_FIXUP "struct_ops/hid_rdesc_fixup" +#define HID_BPF_OPS(name) SEC(".struct_ops.link") \ + struct hid_bpf_ops name +#define hid_set_name(_hdev, _name) __builtin_memcpy(_hdev->name, _name, sizeof(_name)) + +struct hid_bpf_probe_args { + unsigned int hid; + unsigned int rdesc_size; + unsigned char rdesc[4096]; + int retval; +}; + +#endif /* ____HID_BPF__H */ diff --git a/src/49-hid/hid_bpf_defs.h b/src/49-hid/hid_bpf_defs.h new file mode 100644 index 0000000..c860df1 --- /dev/null +++ b/src/49-hid/hid_bpf_defs.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __HID_BPF_DEFS_H +#define __HID_BPF_DEFS_H + +/* HID BPF context structure */ +struct hid_bpf_ctx { + struct hid_device *hid; + __u32 allocated_size; + union { + __s32 retval; + __s32 size; + }; +}; + +/* HID BPF operations structure */ +struct hid_bpf_ops { + int hid_id; + __u32 flags; + int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type); + 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); +}; + +#endif /* __HID_BPF_DEFS_H */ diff --git a/src/49-hid/hid_bpf_helpers.h b/src/49-hid/hid_bpf_helpers.h new file mode 100644 index 0000000..3ba24d1 --- /dev/null +++ b/src/49-hid/hid_bpf_helpers.h @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2022 Benjamin Tissoires + */ + +#ifndef __HID_BPF_HELPERS_H +#define __HID_BPF_HELPERS_H + +#include "vmlinux.h" +#include +#include + +extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, + unsigned int offset, + const size_t __sz) __ksym; +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 buf__sz, + enum hid_report_type type, + enum hid_class_request reqtype) __ksym; + +#define HID_MAX_DESCRIPTOR_SIZE 4096 +#define HID_IGNORE_EVENT -1 + +/* extracted from */ +#define BUS_ANY 0x00 +#define BUS_PCI 0x01 +#define BUS_ISAPNP 0x02 +#define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 +#define BUS_VIRTUAL 0x06 +#define BUS_ISA 0x10 +#define BUS_I8042 0x11 +#define BUS_XTKBD 0x12 +#define BUS_RS232 0x13 +#define BUS_GAMEPORT 0x14 +#define BUS_PARPORT 0x15 +#define BUS_AMIGA 0x16 +#define BUS_ADB 0x17 +#define BUS_I2C 0x18 +#define BUS_HOST 0x19 +#define BUS_GSC 0x1A +#define BUS_ATARI 0x1B +#define BUS_SPI 0x1C +#define BUS_RMI 0x1D +#define BUS_CEC 0x1E +#define BUS_INTEL_ISHTP 0x1F +#define BUS_AMD_SFH 0x20 + +/* extracted from */ +#define HID_GROUP_ANY 0x0000 +#define HID_GROUP_GENERIC 0x0001 +#define HID_GROUP_MULTITOUCH 0x0002 +#define HID_GROUP_SENSOR_HUB 0x0003 +#define HID_GROUP_MULTITOUCH_WIN_8 0x0004 +#define HID_GROUP_RMI 0x0100 +#define HID_GROUP_WACOM 0x0101 +#define HID_GROUP_LOGITECH_DJ_DEVICE 0x0102 +#define HID_GROUP_STEAM 0x0103 +#define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104 +#define HID_GROUP_VIVALDI 0x0105 + +/* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */ +#define HID_VID_ANY 0x0000 +#define HID_PID_ANY 0x0000 + +#define BIT(n) (1UL << (n)) +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/* Helper macro to convert (foo, __LINE__) into foo134 so we can use __LINE__ for + * field/variable names + */ +#define COMBINE1(X, Y) X ## Y +#define COMBINE(X, Y) COMBINE1(X, Y) + +/* Macro magic: + * __uint(foo, 123) creates a int (*foo)[1234] + * + * We use that macro to declare an anonymous struct with several + * fields, each is the declaration of an pointer to an array of size + * bus/group/vid/pid. (Because it's a pointer to such an array, actual storage + * would be sizeof(pointer) rather than sizeof(array). Not that we ever + * instantiate it anyway). + * + * This is only used for BTF introspection, we can later check "what size + * is the bus array" in the introspection data and thus extract the bus ID + * again. + * + * And we use the __LINE__ to give each of our structs a unique name so the + * BPF program writer doesn't have to. + * + * $ bpftool btf dump file target/bpf/HP_Elite_Presenter.bpf.o + * shows the inspection data, start by searching for .hid_bpf_config + * and working backwards from that (each entry references the type_id of the + * content). + */ + +#define HID_DEVICE(b, g, ven, prod) \ + struct { \ + __uint(name, 0); \ + __uint(bus, (b)); \ + __uint(group, (g)); \ + __uint(vid, (ven)); \ + __uint(pid, (prod)); \ + } COMBINE(_entry, __LINE__) + +/* Macro magic below is to make HID_BPF_CONFIG() look like a function call that + * we can pass multiple HID_DEVICE() invocations in. + * + * For up to 16 arguments, HID_BPF_CONFIG(one, two) resolves to + * + * union { + * HID_DEVICE(...); + * HID_DEVICE(...); + * } _device_ids SEC(".hid_bpf_config") + * + */ + +/* Returns the number of macro arguments, this expands + * NARGS(a, b, c) to NTH_ARG(a, b, c, 15, 14, 13, .... 4, 3, 2, 1). + * NTH_ARG always returns the 16th argument which in our case is 3. + * + * If we want more than 16 values _COUNTDOWN and _NTH_ARG both need to be + * updated. + */ +#define _NARGS(...) _NARGS1(__VA_ARGS__, _COUNTDOWN) +#define _NARGS1(...) _NTH_ARG(__VA_ARGS__) + +/* Add to this if we need more than 16 args */ +#define _COUNTDOWN \ + 15, 14, 13, 12, 11, 10, 9, 8, \ + 7, 6, 5, 4, 3, 2, 1, 0 + +/* Return the 16 argument passed in. See _NARGS above for usage. Note this is + * 1-indexed. + */ +#define _NTH_ARG( \ + _1, _2, _3, _4, _5, _6, _7, _8, \ + _9, _10, _11, _12, _13, _14, _15,\ + N, ...) N + +/* Turns EXPAND(_ARG, a, b, c) into _ARG3(a, b, c) */ +#define _EXPAND(func, ...) COMBINE(func, _NARGS(__VA_ARGS__)) (__VA_ARGS__) + +/* And now define all the ARG macros for each number of args we want to accept */ +#define _ARG1(_1) _1; +#define _ARG2(_1, _2) _1; _2; +#define _ARG3(_1, _2, _3) _1; _2; _3; +#define _ARG4(_1, _2, _3, _4) _1; _2; _3; _4; +#define _ARG5(_1, _2, _3, _4, _5) _1; _2; _3; _4; _5; +#define _ARG6(_1, _2, _3, _4, _5, _6) _1; _2; _3; _4; _5; _6; +#define _ARG7(_1, _2, _3, _4, _5, _6, _7) _1; _2; _3; _4; _5; _6; _7; +#define _ARG8(_1, _2, _3, _4, _5, _6, _7, _8) _1; _2; _3; _4; _5; _6; _7; _8; +#define _ARG9(_1, _2, _3, _4, _5, _6, _7, _8, _9) _1; _2; _3; _4; _5; _6; _7; _8; _9; +#define _ARG10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; +#define _ARG11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; +#define _ARG12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; +#define _ARG13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; +#define _ARG14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; +#define _ARG15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, _f) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; _f; + + +#define HID_BPF_CONFIG(...) union { \ + _EXPAND(_ARG, __VA_ARGS__) \ +} _device_ids SEC(".hid_bpf_config") + +#endif /* __HID_BPF_HELPERS_H */ diff --git a/src/features/bpf_arena/README.md b/src/features/bpf_arena/README.md index 885c1b4..a59da75 100644 --- a/src/features/bpf_arena/README.md +++ b/src/features/bpf_arena/README.md @@ -4,6 +4,8 @@ Ever tried building a linked list in eBPF and got stuck using awkward integer in This is what **BPF Arena** solves. Created by Alexei Starovoitov in 2024, arena provides a sparse shared memory region where BPF programs can use real pointers to build complex data structures like linked lists, trees, and graphs, while userspace gets zero-copy direct access to the same memory. In this tutorial, we'll build a linked list in arena memory and show you how both kernel and userspace can manipulate it using standard pointer operations. +> The complete source code: + ## Introduction to BPF Arena: Breaking Free from Map Limitations ### The Problem: When BPF Maps Aren't Enough diff --git a/src/features/bpf_iters/README.md b/src/features/bpf_iters/README.md index d260ebb..1b1df6b 100644 --- a/src/features/bpf_iters/README.md +++ b/src/features/bpf_iters/README.md @@ -4,6 +4,8 @@ Ever tried monitoring hundreds of processes and ended up parsing thousands of `/ This is what **BPF Iterators** solve. Introduced in Linux kernel 5.8, iterators let you traverse kernel data structures directly from BPF programs, apply filters in-kernel, and output exactly the data you need in any format you want. In this tutorial, we'll build a dual-mode iterator that shows kernel stack traces and open file descriptors for processes, with in-kernel filtering by process name - dramatically faster than parsing `/proc`. +> The complete source code: + ## Introduction to BPF Iterators: The /proc Replacement ### The Problem: /proc is Slow and Rigid diff --git a/src/features/bpf_wq/README.md b/src/features/bpf_wq/README.md index 48a11b0..882f8b1 100644 --- a/src/features/bpf_wq/README.md +++ b/src/features/bpf_wq/README.md @@ -4,6 +4,8 @@ Ever needed your eBPF program to sleep, allocate memory, or wait for device I/O? This is what **BPF Workqueues** enable. Created by Benjamin Tissoires at Red Hat in 2024 for HID-BPF device handling, workqueues let you schedule asynchronous work that runs in process context where sleeping and blocking operations are allowed. In this tutorial, we'll explore why workqueues were created, how they differ from timers, and build a complete example demonstrating async callback execution. +> The complete source code: + ## Introduction to BPF Workqueues: Solving the Sleep Problem ### The Problem: When eBPF Can't Sleep diff --git a/src/xpu/gpu-kernel-driver/README.md b/src/xpu/gpu-kernel-driver/README.md index a27fdd5..1a12cec 100644 --- a/src/xpu/gpu-kernel-driver/README.md +++ b/src/xpu/gpu-kernel-driver/README.md @@ -4,6 +4,8 @@ When games stutter or ML training slows down, the answers lie inside the GPU ker This tutorial shows how to monitor GPU activity using eBPF and bpftrace. We'll track DRM scheduler jobs, measure latency, and diagnose bottlenecks using stable kernel tracepoints that work across Intel, AMD, and Nouveau drivers. +> The complete source code: + ## GPU Kernel Tracepoints: Zero-Overhead Observability GPU tracepoints are instrumentation points built into the kernel's Direct Rendering Manager (DRM) subsystem. When your GPU schedules a job, allocates memory, or signals a fence, these tracepoints fire with precise timing and driver state. diff --git a/src/xpu/npu-kernel-driver/README.md b/src/xpu/npu-kernel-driver/README.md index 0ef739f..0ddb311 100644 --- a/src/xpu/npu-kernel-driver/README.md +++ b/src/xpu/npu-kernel-driver/README.md @@ -4,6 +4,8 @@ Neural Processing Units (NPUs) are the next frontier in AI acceleration - built This tutorial shows you how to trace Intel NPU kernel driver operations using eBPF and bpftrace. We'll monitor the complete workflow from Level Zero API calls down to kernel functions, track IPC communication with NPU firmware, measure memory allocation patterns, and diagnose performance bottlenecks. By the end, you'll understand how NPU drivers work internally and have practical tools for debugging AI workload issues. +> The complete source code: + ## Intel NPU Driver Architecture Intel's NPU driver follows a two-layer architecture similar to GPU drivers. The kernel module (`intel_vpu`) lives in mainline Linux at `drivers/accel/ivpu/` and exposes `/dev/accel/accel0` as the device interface. This handles hardware communication, memory management through an MMU, and IPC (Inter-Processor Communication) with NPU firmware running on the accelerator itself.