Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,7 @@
bpftool
*.skel.h
/host-tools
/tools
hid_bpf
hidraw
results
@@ -0,0 +1,237 @@
# SPDX-License-Identifier: GPL-2.0
# based on tools/testing/selftest/bpf/Makefile
include ../../../build/Build.include
include ../../../scripts/Makefile.arch
include ../../../scripts/Makefile.include
TEST_PROGS := hid-core.sh
TEST_PROGS += hid-apple.sh
TEST_PROGS += hid-gamepad.sh
TEST_PROGS += hid-ite.sh
TEST_PROGS += hid-keyboard.sh
TEST_PROGS += hid-mouse.sh
TEST_PROGS += hid-multitouch.sh
TEST_PROGS += hid-sony.sh
TEST_PROGS += hid-tablet.sh
TEST_PROGS += hid-usb_crash.sh
TEST_PROGS += hid-wacom.sh
TEST_FILES := run-hid-tools-tests.sh
TEST_FILES += tests
CXX ?= $(CROSS_COMPILE)g++
HOSTPKG_CONFIG := pkg-config
CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT) $(KHDR_INCLUDES)
CFLAGS += -I$(OUTPUT)/tools/include
LDLIBS += -lelf -lz -lrt -lpthread
# Silence some warnings when compiled with clang
ifneq ($(LLVM),)
CFLAGS += -Wno-unused-command-line-argument
endif
# Order correspond to 'make run_tests' order
TEST_GEN_PROGS = hid_bpf hidraw
# Emit succinct information message describing current building step
# $1 - generic step name (e.g., CC, LINK, etc);
# $2 - optional "flavor" specifier; if provided, will be emitted as [flavor];
# $3 - target (assumed to be file); only file name will be emitted;
# $4 - optional extra arg, emitted as-is, if provided.
ifeq ($(V),1)
msg =
else
msg = @printf ' %-8s%s %s%s\n' "$(1)" "$(if $(2), [$(2)])" "$(notdir $(3))" "$(if $(4), $(4))";
MAKEFLAGS += --no-print-directory
submake_extras := feature_display=0
endif
# override lib.mk's default rules
OVERRIDE_TARGETS := 1
override define CLEAN
$(call msg,CLEAN)
$(Q)$(RM) -r $(TEST_GEN_PROGS)
$(Q)$(RM) -r $(EXTRA_CLEAN)
endef
include ../lib.mk
TOOLSDIR := $(top_srcdir)/tools
LIBDIR := $(TOOLSDIR)/lib
BPFDIR := $(LIBDIR)/bpf
TOOLSINCDIR := $(TOOLSDIR)/include
BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool
SCRATCH_DIR := $(OUTPUT)/tools
BUILD_DIR := $(SCRATCH_DIR)/build
INCLUDE_DIR := $(SCRATCH_DIR)/include
BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a
ifneq ($(CROSS_COMPILE),)
HOST_BUILD_DIR := $(BUILD_DIR)/host
HOST_SCRATCH_DIR := $(OUTPUT)/host-tools
HOST_INCLUDE_DIR := $(HOST_SCRATCH_DIR)/include
else
HOST_BUILD_DIR := $(BUILD_DIR)
HOST_SCRATCH_DIR := $(SCRATCH_DIR)
HOST_INCLUDE_DIR := $(INCLUDE_DIR)
endif
HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a
RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \
$(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \
../../../../vmlinux \
/sys/kernel/btf/vmlinux \
/boot/vmlinux-$(shell uname -r)
VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
ifeq ($(VMLINUX_BTF),)
$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
endif
# Define simple and short `make test_progs`, `make test_sysctl`, etc targets
# to build individual tests.
# NOTE: Semicolon at the end is critical to override lib.mk's default static
# rule for binaries.
$(notdir $(TEST_GEN_PROGS)): %: $(OUTPUT)/% ;
# sort removes libbpf duplicates when not cross-building
MAKE_DIRS := $(sort $(BUILD_DIR)/libbpf $(HOST_BUILD_DIR)/libbpf \
$(HOST_BUILD_DIR)/bpftool $(HOST_BUILD_DIR)/resolve_btfids \
$(INCLUDE_DIR))
$(MAKE_DIRS):
$(call msg,MKDIR,,$@)
$(Q)mkdir -p $@
DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
TEST_GEN_PROGS_EXTENDED += $(DEFAULT_BPFTOOL)
$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(BPFOBJ)
BPFTOOL ?= $(DEFAULT_BPFTOOL)
$(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) \
$(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/bpftool
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOLDIR) \
ARCH= CROSS_COMPILE= CC=$(HOSTCC) LD=$(HOSTLD) \
EXTRA_CFLAGS='-g -O0' \
OUTPUT=$(HOST_BUILD_DIR)/bpftool/ \
LIBBPF_OUTPUT=$(HOST_BUILD_DIR)/libbpf/ \
LIBBPF_DESTDIR=$(HOST_SCRATCH_DIR)/ \
prefix= DESTDIR=$(HOST_SCRATCH_DIR)/ install-bin
$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
| $(BUILD_DIR)/libbpf
$(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \
EXTRA_CFLAGS='-g -O0' \
DESTDIR=$(SCRATCH_DIR) prefix= all install_headers
ifneq ($(BPFOBJ),$(HOST_BPFOBJ))
$(HOST_BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
| $(HOST_BUILD_DIR)/libbpf
$(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) \
EXTRA_CFLAGS='-g -O0' ARCH= CROSS_COMPILE= \
OUTPUT=$(HOST_BUILD_DIR)/libbpf/ CC=$(HOSTCC) LD=$(HOSTLD) \
DESTDIR=$(HOST_SCRATCH_DIR)/ prefix= all install_headers
endif
$(INCLUDE_DIR)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
ifeq ($(VMLINUX_H),)
$(call msg,GEN,,$@)
$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
else
$(call msg,CP,,$@)
$(Q)cp "$(VMLINUX_H)" $@
endif
$(RESOLVE_BTFIDS): $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/resolve_btfids \
$(TOOLSDIR)/bpf/resolve_btfids/main.c \
$(TOOLSDIR)/lib/rbtree.c \
$(TOOLSDIR)/lib/zalloc.c \
$(TOOLSDIR)/lib/string.c \
$(TOOLSDIR)/lib/ctype.c \
$(TOOLSDIR)/lib/str_error_r.c
$(Q)$(MAKE) $(submake_extras) -C $(TOOLSDIR)/bpf/resolve_btfids \
CC=$(HOSTCC) LD=$(HOSTLD) AR=$(HOSTAR) \
LIBBPF_INCLUDE=$(HOST_INCLUDE_DIR) \
OUTPUT=$(HOST_BUILD_DIR)/resolve_btfids/ BPFOBJ=$(HOST_BPFOBJ)
# Get Clang's default includes on this system, as opposed to those seen by
# '--target=bpf'. This fixes "missing" files on some architectures/distros,
# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc.
#
# Use '-idirafter': Don't interfere with include mechanics except where the
# build would have failed anyways.
define get_sys_includes
$(shell $(1) -v -E - </dev/null 2>&1 \
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \
$(shell $(1) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}')
endef
# Determine target endianness.
IS_LITTLE_ENDIAN = $(shell $(CC) -dM -E - </dev/null | \
grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__')
MENDIAN=$(if $(IS_LITTLE_ENDIAN),-mlittle-endian,-mbig-endian)
CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG))
BPF_CFLAGS = -g -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN) \
-Wno-microsoft-anon-tag \
-fms-extensions \
-I$(INCLUDE_DIR)
CLANG_CFLAGS = $(CLANG_SYS_INCLUDES) \
-Wno-compare-distinct-pointer-types
# Build BPF object using Clang
# $1 - input .c file
# $2 - output .o file
# $3 - CFLAGS
define CLANG_BPF_BUILD_RULE
$(call msg,CLNG-BPF,$(TRUNNER_BINARY),$2)
$(Q)$(CLANG) $3 -O2 --target=bpf -c $1 -mcpu=v3 -o $2
endef
# Similar to CLANG_BPF_BUILD_RULE, but with disabled alu32
define CLANG_NOALU32_BPF_BUILD_RULE
$(call msg,CLNG-BPF,$(TRUNNER_BINARY),$2)
$(Q)$(CLANG) $3 -O2 --target=bpf -c $1 -mcpu=v2 -o $2
endef
# Build BPF object using GCC
define GCC_BPF_BUILD_RULE
$(call msg,GCC-BPF,$(TRUNNER_BINARY),$2)
$(Q)$(BPF_GCC) $3 -O2 -c $1 -o $2
endef
BPF_PROGS_DIR := progs
BPF_BUILD_RULE := CLANG_BPF_BUILD_RULE
BPF_SRCS := $(notdir $(wildcard $(BPF_PROGS_DIR)/*.c))
BPF_OBJS := $(patsubst %.c,$(OUTPUT)/%.bpf.o, $(BPF_SRCS))
BPF_SKELS := $(patsubst %.c,$(OUTPUT)/%.skel.h, $(BPF_SRCS))
TEST_GEN_FILES += $(BPF_OBJS)
$(BPF_PROGS_DIR)-bpfobjs := y
$(BPF_OBJS): $(OUTPUT)/%.bpf.o: \
$(BPF_PROGS_DIR)/%.c \
$(wildcard $(BPF_PROGS_DIR)/*.h) \
$(INCLUDE_DIR)/vmlinux.h \
$(wildcard $(BPFDIR)/hid_bpf_*.h) \
$(wildcard $(BPFDIR)/*.bpf.h) \
| $(OUTPUT) $(BPFOBJ)
$(call $(BPF_BUILD_RULE),$<,$@, $(BPF_CFLAGS))
$(BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(OUTPUT)
$(call msg,GEN-SKEL,$(BINARY),$@)
$(Q)$(BPFTOOL) gen object $(<:.o=.linked1.o) $<
$(Q)$(BPFTOOL) gen skeleton $(<:.o=.linked1.o) name $(notdir $(<:.bpf.o=)) > $@
$(OUTPUT)/%.o: %.c $(BPF_SKELS) hid_common.h
$(call msg,CC,,$@)
$(Q)$(CC) $(CFLAGS) -c $(filter %.c,$^) $(LDLIBS) -o $@
$(OUTPUT)/%: $(OUTPUT)/%.o
$(call msg,BINARY,,$@)
$(Q)$(LINK.c) $^ $(LDLIBS) -o $@
EXTRA_CLEAN := $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) feature bpftool \
$(addprefix $(OUTPUT)/,*.o *.skel.h no_alu32)
@@ -0,0 +1,32 @@
CONFIG_BPF_EVENTS=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_BPF_LSM=y
CONFIG_BPF_PRELOAD_UMD=y
CONFIG_BPF_PRELOAD=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF=y
CONFIG_CGROUP_BPF=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
CONFIG_FPROBE=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_FUNCTION_TRACER=y
CONFIG_HIDRAW=y
CONFIG_HID=y
CONFIG_HID_BPF=y
CONFIG_INPUT_EVDEV=y
CONFIG_UHID=y
CONFIG_LEDS_CLASS_MULTICOLOR=y
CONFIG_USB=y
CONFIG_USB_HID=y
CONFIG_HID_APPLE=y
CONFIG_HID_ITE=y
CONFIG_HID_MULTITOUCH=y
CONFIG_HID_PLAYSTATION=y
CONFIG_PLAYSTATION_FF=y
CONFIG_HID_SONY=y
CONFIG_SONY_FF=y
CONFIG_HID_WACOM=y
@@ -0,0 +1,241 @@
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y
CONFIG_9P_FS=y
CONFIG_AUDIT=y
CONFIG_BINFMT_MISC=y
CONFIG_BLK_CGROUP_IOLATENCY=y
CONFIG_BLK_CGROUP=y
CONFIG_BLK_DEV_BSGLIB=y
CONFIG_BLK_DEV_IO_TRACE=y
CONFIG_BLK_DEV_RAM_SIZE=16384
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_THROTTLING=y
CONFIG_BONDING=y
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
CONFIG_BOOTTIME_TRACING=y
CONFIG_BSD_DISKLABEL=y
CONFIG_BSD_PROCESS_ACCT=y
CONFIG_CFS_BANDWIDTH=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_DEBUG=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_NET_CLASSID=y
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_RDMA=y
CONFIG_CGROUP_SCHED=y
CONFIG_CGROUPS=y
CONFIG_CGROUP_WRITEBACK=y
CONFIG_CMA_AREAS=7
CONFIG_CMA=y
CONFIG_COMPAT_32BIT_TIME=y
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
CONFIG_CPU_FREQ_GOV_ONDEMAND=y
CONFIG_CPU_FREQ_GOV_USERSPACE=y
CONFIG_CPU_FREQ_STAT=y
CONFIG_CPU_IDLE_GOV_LADDER=y
CONFIG_CPUSETS=y
CONFIG_CRYPTO_BLAKE2B=y
CONFIG_CRYPTO_DEV_VIRTIO=y
CONFIG_CRYPTO_SEQIV=y
CONFIG_CRYPTO_XXHASH=y
CONFIG_DCB=y
CONFIG_DEBUG_ATOMIC_SLEEP=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
CONFIG_DEBUG_MEMORY_INIT=y
CONFIG_DEFAULT_FQ_CODEL=y
CONFIG_DEFAULT_RENO=y
CONFIG_DEFAULT_SECURITY_DAC=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_DEVTMPFS=y
CONFIG_DMA_CMA=y
CONFIG_DNS_RESOLVER=y
CONFIG_EFI_STUB=y
CONFIG_EFI=y
CONFIG_EXPERT=y
CONFIG_EXT4_FS_POSIX_ACL=y
CONFIG_EXT4_FS_SECURITY=y
CONFIG_EXT4_FS=y
CONFIG_FAIL_FUNCTION=y
CONFIG_FAULT_INJECTION_DEBUG_FS=y
CONFIG_FAULT_INJECTION=y
CONFIG_FB_MODE_HELPERS=y
CONFIG_FB_TILEBLITTING=y
CONFIG_FB_VESA=y
CONFIG_FB=y
CONFIG_FONT_8x16=y
CONFIG_FONT_MINI_4x6=y
CONFIG_FONTS=y
CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y
CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_FUSE_FS=y
CONFIG_FW_LOADER_USER_HELPER=y
CONFIG_GART_IOMMU=y
CONFIG_GENERIC_PHY=y
CONFIG_HARDLOCKUP_DETECTOR=y
CONFIG_HIGH_RES_TIMERS=y
CONFIG_HPET=y
CONFIG_HUGETLBFS=y
CONFIG_HUGETLB_PAGE=y
CONFIG_HWPOISON_INJECT=y
CONFIG_HZ_1000=y
CONFIG_INET=y
CONFIG_INTEL_POWERCLAMP=y
CONFIG_IP6_NF_FILTER=y
CONFIG_IP6_NF_IPTABLES=y
CONFIG_IP6_NF_NAT=y
CONFIG_IP6_NF_TARGET_MASQUERADE=y
CONFIG_IP_ADVANCED_ROUTER=y
CONFIG_IP_MROUTE=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_MULTIPLE_TABLES=y
CONFIG_IP_NF_FILTER=y
CONFIG_IP_NF_IPTABLES=y
CONFIG_IP_NF_NAT=y
CONFIG_IP_NF_TARGET_MASQUERADE=y
CONFIG_IP_PIMSM_V1=y
CONFIG_IP_PIMSM_V2=y
CONFIG_IP_ROUTE_MULTIPATH=y
CONFIG_IP_ROUTE_VERBOSE=y
CONFIG_IPV6_MIP6=y
CONFIG_IPV6_ROUTE_INFO=y
CONFIG_IPV6_ROUTER_PREF=y
CONFIG_IPV6_SEG6_LWTUNNEL=y
CONFIG_IPV6_SUBTREES=y
CONFIG_IRQ_POLL=y
CONFIG_JUMP_LABEL=y
CONFIG_KARMA_PARTITION=y
CONFIG_KEXEC=y
CONFIG_KPROBES=y
CONFIG_KSM=y
CONFIG_LEGACY_VSYSCALL_NONE=y
CONFIG_LOG_BUF_SHIFT=21
CONFIG_LOG_CPU_MAX_BUF_SHIFT=0
CONFIG_LOGO=y
CONFIG_LSM="selinux,bpf,integrity"
CONFIG_MAC_PARTITION=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_MCORE2=y
CONFIG_MEMCG=y
CONFIG_MEMORY_FAILURE=y
CONFIG_MINIX_SUBPARTITION=y
CONFIG_MODULES=y
CONFIG_NAMESPACES=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_NET_9P=y
CONFIG_NET_ACT_BPF=y
CONFIG_NET_CLS_CGROUP=y
CONFIG_NETDEVICES=y
CONFIG_NET_EMATCH=y
CONFIG_NETFILTER_NETLINK_LOG=y
CONFIG_NETFILTER_NETLINK_QUEUE=y
CONFIG_NETFILTER_XTABLES=y
CONFIG_NETFILTER_XTABLES_LEGACY=y
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
CONFIG_NETFILTER_XT_MATCH_BPF=y
CONFIG_NETFILTER_XT_MATCH_COMMENT=y
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
CONFIG_NETFILTER_XT_MATCH_MARK=y
CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y
CONFIG_NETFILTER_XT_MATCH_STATISTIC=y
CONFIG_NETFILTER_XT_NAT=y
CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y
CONFIG_NET_IPGRE_BROADCAST=y
CONFIG_NET_L3_MASTER_DEV=y
CONFIG_NETLABEL=y
CONFIG_NET_SCH_DEFAULT=y
CONFIG_NET_SCHED=y
CONFIG_NET_SCH_FQ_CODEL=y
CONFIG_NET_TC_SKB_EXT=y
CONFIG_NET_VRF=y
CONFIG_NET=y
CONFIG_NF_CONNTRACK=y
CONFIG_NF_NAT_MASQUERADE=y
CONFIG_NF_NAT=y
CONFIG_NLS_ASCII=y
CONFIG_NLS_CODEPAGE_437=y
CONFIG_NLS_DEFAULT="utf8"
CONFIG_NO_HZ=y
CONFIG_NR_CPUS=128
CONFIG_NUMA_BALANCING=y
CONFIG_NUMA=y
CONFIG_NVMEM=y
CONFIG_OSF_PARTITION=y
CONFIG_OVERLAY_FS_INDEX=y
CONFIG_OVERLAY_FS_METACOPY=y
CONFIG_OVERLAY_FS_XINO_AUTO=y
CONFIG_OVERLAY_FS=y
CONFIG_PACKET=y
CONFIG_PANIC_ON_OOPS=y
CONFIG_PARTITION_ADVANCED=y
CONFIG_PCIEPORTBUS=y
CONFIG_PCI_IOV=y
CONFIG_PCI_MSI=y
CONFIG_PCI=y
CONFIG_PHYSICAL_ALIGN=0x1000000
CONFIG_POSIX_MQUEUE=y
CONFIG_POWER_SUPPLY=y
CONFIG_PREEMPT=y
CONFIG_PRINTK_TIME=y
CONFIG_PROC_KCORE=y
CONFIG_PROFILING=y
CONFIG_PROVE_LOCKING=y
CONFIG_PTP_1588_CLOCK=y
CONFIG_RC_DEVICES=y
CONFIG_RC_LOOPBACK=y
CONFIG_RCU_CPU_STALL_TIMEOUT=60
CONFIG_SCHED_STACK_END_CHECK=y
CONFIG_SCHEDSTATS=y
CONFIG_SECURITY_NETWORK=y
CONFIG_SECURITY_SELINUX=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_DETECT_IRQ=y
CONFIG_SERIAL_8250_EXTENDED=y
CONFIG_SERIAL_8250_MANY_PORTS=y
CONFIG_SERIAL_8250_NR_UARTS=32
CONFIG_SERIAL_8250_RSA=y
CONFIG_SERIAL_8250_SHARE_IRQ=y
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_NONSTANDARD=y
CONFIG_SERIO_LIBPS2=y
CONFIG_SGI_PARTITION=y
CONFIG_SMP=y
CONFIG_SOCK_CGROUP_DATA=y
CONFIG_SOLARIS_X86_PARTITION=y
CONFIG_SUN_PARTITION=y
CONFIG_SYNC_FILE=y
CONFIG_SYSVIPC=y
CONFIG_TASK_DELAY_ACCT=y
CONFIG_TASK_IO_ACCOUNTING=y
CONFIG_TASKSTATS=y
CONFIG_TASK_XACCT=y
CONFIG_TCP_CONG_ADVANCED=y
CONFIG_TCP_MD5SIG=y
CONFIG_TLS=y
CONFIG_TMPFS_POSIX_ACL=y
CONFIG_TMPFS=y
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_TUN=y
CONFIG_UNIXWARE_DISKLABEL=y
CONFIG_UNIX=y
CONFIG_USER_NS=y
CONFIG_VALIDATE_FS_PARSER=y
CONFIG_VETH=y
CONFIG_VIRT_DRIVERS=y
CONFIG_VIRTIO_BALLOON=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_CONSOLE=y
CONFIG_VIRTIO_FS=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_PCI=y
CONFIG_VLAN_8021Q=y
CONFIG_XFRM_SUB_POLICY=y
CONFIG_XFRM_USER=y
CONFIG_ZEROPLUS_FF=y
CONFIG_KASAN=y
@@ -0,0 +1,4 @@
CONFIG_X86_ACPI_CPUFREQ=y
CONFIG_X86_CPUID=y
CONFIG_X86_MSR=y
CONFIG_X86_POWERNOW_K8=y
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_apple_keyboard.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_hid_core.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_gamepad.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_ite_keyboard.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_keyboard.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_mouse.py
bash ./run-hid-tools-tests.sh
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_multitouch.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_sony.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_tablet.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_usb_crash.py
bash ./run-hid-tools-tests.sh
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
export TARGET=test_wacom_generic.py
bash ./run-hid-tools-tests.sh
@@ -0,0 +1,933 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022-2024 Red Hat */
#include "hid.skel.h"
#include "hid_common.h"
#include <bpf/bpf.h>
struct hid_hw_request_syscall_args {
__u8 data[10];
unsigned int hid;
int retval;
size_t size;
enum hid_report_type type;
__u8 request_type;
};
FIXTURE(hid_bpf) {
struct uhid_device hid;
int hidraw_fd;
struct hid *skel;
struct bpf_link *hid_links[3]; /* max number of programs loaded in a single test */
};
static void detach_bpf(FIXTURE_DATA(hid_bpf) * self)
{
int i;
if (self->hidraw_fd)
close(self->hidraw_fd);
self->hidraw_fd = 0;
if (!self->skel)
return;
hid__detach(self->skel);
for (i = 0; i < ARRAY_SIZE(self->hid_links); i++) {
if (self->hid_links[i])
bpf_link__destroy(self->hid_links[i]);
}
hid__destroy(self->skel);
self->skel = NULL;
}
FIXTURE_TEARDOWN(hid_bpf) {
void *uhid_err;
uhid_destroy(_metadata, &self->hid);
detach_bpf(self);
pthread_join(self->hid.tid, &uhid_err);
}
#define TEARDOWN_LOG(fmt, ...) do { \
TH_LOG(fmt, ##__VA_ARGS__); \
hid_bpf_teardown(_metadata, self, variant); \
} while (0)
FIXTURE_SETUP(hid_bpf)
{
int err;
err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, rdesc, sizeof(rdesc));
ASSERT_OK(err);
}
struct test_program {
const char *name;
int insert_head;
};
#define LOAD_PROGRAMS(progs) \
load_programs(progs, ARRAY_SIZE(progs), _metadata, self, variant)
#define LOAD_BPF \
load_programs(NULL, 0, _metadata, self, variant)
static void load_programs(const struct test_program programs[],
const size_t progs_count,
struct __test_metadata *_metadata,
FIXTURE_DATA(hid_bpf) * self,
const FIXTURE_VARIANT(hid_bpf) * variant)
{
struct bpf_map *iter_map;
int err = -EINVAL;
ASSERT_LE(progs_count, ARRAY_SIZE(self->hid_links))
TH_LOG("too many programs are to be loaded");
/* open the bpf file */
self->skel = hid__open();
ASSERT_OK_PTR(self->skel) TEARDOWN_LOG("Error while calling hid__open");
/*
* Disable all struct_ops maps by default so libbpf does not autoload
* programs referenced by maps that are unrelated to the current test.
*/
bpf_object__for_each_map(iter_map, *self->skel->skeleton->obj) {
if (bpf_map__type(iter_map) == BPF_MAP_TYPE_STRUCT_OPS) {
err = bpf_map__set_autocreate(iter_map, false);
ASSERT_OK(err) TH_LOG("can not disable struct_ops map '%s'",
bpf_map__name(iter_map));
}
bpf_map__set_autoattach(iter_map, false);
}
for (int i = 0; i < progs_count; i++) {
struct bpf_program *prog;
struct bpf_map *map;
int *ops_hid_id;
prog = bpf_object__find_program_by_name(*self->skel->skeleton->obj,
programs[i].name);
ASSERT_OK_PTR(prog) TH_LOG("can not find program by name '%s'", programs[i].name);
bpf_program__set_autoload(prog, true);
map = bpf_object__find_map_by_name(*self->skel->skeleton->obj,
programs[i].name + 4);
ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'",
programs[i].name + 4);
err = bpf_map__set_autocreate(map, true);
ASSERT_OK(err) TH_LOG("can not enable struct_ops map '%s'",
programs[i].name + 4);
/* hid_id is the first field of struct hid_bpf_ops */
ops_hid_id = bpf_map__initial_value(map, NULL);
ASSERT_OK_PTR(ops_hid_id) TH_LOG("unable to retrieve struct_ops data");
*ops_hid_id = self->hid.hid_id;
}
err = hid__load(self->skel);
ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err);
for (int i = 0; i < progs_count; i++) {
struct bpf_map *map;
map = bpf_object__find_map_by_name(*self->skel->skeleton->obj,
programs[i].name + 4);
ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'",
programs[i].name + 4);
self->hid_links[i] = bpf_map__attach_struct_ops(map);
ASSERT_OK_PTR(self->hid_links[i]) TH_LOG("failed to attach struct ops '%s'",
programs[i].name + 4);
}
hid__attach(self->skel);
self->hidraw_fd = open_hidraw(&self->hid);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
}
/*
* A simple test to see if the fixture is working fine.
* If this fails, none of the other tests will pass.
*/
TEST_F(hid_bpf, test_create_uhid)
{
}
/*
* Attach hid_first_event to the given uhid device,
* retrieve and open the matching hidraw node,
* inject one event in the uhid device,
* check that the program sees it and can change the data
*/
TEST_F(hid_bpf, raw_event)
{
const struct test_program progs[] = {
{ .name = "hid_first_event" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* check that the program is correctly loaded */
ASSERT_EQ(self->skel->data->callback_check, 52) TH_LOG("callback_check1");
ASSERT_EQ(self->skel->data->callback2_check, 52) TH_LOG("callback2_check1");
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* check that hid_first_event() was executed */
ASSERT_EQ(self->skel->data->callback_check, 42) TH_LOG("callback_check1");
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[2], 47);
/* inject another event */
memset(buf, 0, sizeof(buf));
buf[0] = 1;
buf[1] = 47;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* check that hid_first_event() was executed */
ASSERT_EQ(self->skel->data->callback_check, 47) TH_LOG("callback_check1");
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[2], 52);
}
/*
* Attach hid_first_event to the given uhid device,
* retrieve and open the matching hidraw node,
* inject one event in the uhid device,
* check that the program sees it and can change the data
*/
TEST_F(hid_bpf, subprog_raw_event)
{
const struct test_program progs[] = {
{ .name = "hid_subprog_first_event" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[2], 47);
/* inject another event */
memset(buf, 0, sizeof(buf));
buf[0] = 1;
buf[1] = 47;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[2], 52);
}
/*
* Attach hid_first_event to the given uhid device,
* attempt at re-attaching it, we should not lock and
* return an invalid struct bpf_link
*/
TEST_F(hid_bpf, multiple_attach)
{
const struct test_program progs[] = {
{ .name = "hid_first_event" },
};
struct bpf_link *link;
LOAD_PROGRAMS(progs);
link = bpf_map__attach_struct_ops(self->skel->maps.first_event);
ASSERT_NULL(link) TH_LOG("unexpected return value when re-attaching the struct_ops");
}
/*
* Ensures that we can attach/detach programs
*/
TEST_F(hid_bpf, test_attach_detach)
{
const struct test_program progs[] = {
{ .name = "hid_first_event" },
{ .name = "hid_second_event" },
};
struct bpf_link *link;
__u8 buf[10] = {0};
int err, link_fd;
LOAD_PROGRAMS(progs);
link = self->hid_links[0];
ASSERT_OK_PTR(link) TH_LOG("HID-BPF link not created");
link_fd = bpf_link__fd(link);
ASSERT_GE(link_fd, 0) TH_LOG("HID-BPF link FD not valid");
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[2], 47);
/* make sure both programs are run */
ASSERT_EQ(buf[3], 52);
/* pin the first program and immediately unpin it */
#define PIN_PATH "/sys/fs/bpf/hid_first_event"
err = bpf_obj_pin(link_fd, PIN_PATH);
ASSERT_OK(err) TH_LOG("error while calling bpf_obj_pin");
remove(PIN_PATH);
#undef PIN_PATH
usleep(100000);
/* detach the program */
detach_bpf(self);
self->hidraw_fd = open_hidraw(&self->hid);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
/* inject another event */
memset(buf, 0, sizeof(buf));
buf[0] = 1;
buf[1] = 47;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw_no_bpf");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 47);
ASSERT_EQ(buf[2], 0);
ASSERT_EQ(buf[3], 0);
/* re-attach our program */
LOAD_PROGRAMS(progs);
/* inject one event */
memset(buf, 0, sizeof(buf));
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[2], 47);
ASSERT_EQ(buf[3], 52);
}
/*
* Attach hid_change_report_id to the given uhid device,
* retrieve and open the matching hidraw node,
* inject one event in the uhid device,
* check that the program sees it and can change the data
*/
TEST_F(hid_bpf, test_hid_change_report)
{
const struct test_program progs[] = {
{ .name = "hid_change_report_id" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 9) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 42);
ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test");
}
/*
* Call hid_bpf_input_report against the given uhid device,
* check that the program is called and does the expected.
*/
TEST_F(hid_bpf, test_hid_user_input_report_call)
{
struct hid_hw_request_syscall_args args = {
.retval = -1,
.size = 10,
};
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
.ctx_in = &args,
.ctx_size_in = sizeof(args),
);
__u8 buf[10] = {0};
int err, prog_fd;
LOAD_BPF;
args.hid = self->hid.hid_id;
args.data[0] = 1; /* report ID */
args.data[1] = 2; /* report ID */
args.data[2] = 42; /* report ID */
prog_fd = bpf_program__fd(self->skel->progs.hid_user_input_report);
/* check that there is no data to read from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, -1) TH_LOG("read_hidraw");
err = bpf_prog_test_run_opts(prog_fd, &tattrs);
ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts");
ASSERT_EQ(args.retval, 0);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 2);
ASSERT_EQ(buf[2], 42);
}
/*
* Call hid_bpf_hw_output_report against the given uhid device,
* check that the program is called and does the expected.
*/
TEST_F(hid_bpf, test_hid_user_output_report_call)
{
struct hid_hw_request_syscall_args args = {
.retval = -1,
.size = 10,
};
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
.ctx_in = &args,
.ctx_size_in = sizeof(args),
);
int err, cond_err, prog_fd;
struct timespec time_to_wait;
LOAD_BPF;
args.hid = self->hid.hid_id;
args.data[0] = 1; /* report ID */
args.data[1] = 2; /* report ID */
args.data[2] = 42; /* report ID */
prog_fd = bpf_program__fd(self->skel->progs.hid_user_output_report);
pthread_mutex_lock(&uhid_output_mtx);
memset(output_report, 0, sizeof(output_report));
clock_gettime(CLOCK_REALTIME, &time_to_wait);
time_to_wait.tv_sec += 2;
err = bpf_prog_test_run_opts(prog_fd, &tattrs);
cond_err = pthread_cond_timedwait(&uhid_output_cond, &uhid_output_mtx, &time_to_wait);
ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts");
ASSERT_OK(cond_err) TH_LOG("error while calling waiting for the condition");
ASSERT_EQ(args.retval, 3);
ASSERT_EQ(output_report[0], 1);
ASSERT_EQ(output_report[1], 2);
ASSERT_EQ(output_report[2], 42);
pthread_mutex_unlock(&uhid_output_mtx);
}
/*
* Call hid_hw_raw_request against the given uhid device,
* check that the program is called and does the expected.
*/
TEST_F(hid_bpf, test_hid_user_raw_request_call)
{
struct hid_hw_request_syscall_args args = {
.retval = -1,
.type = HID_FEATURE_REPORT,
.request_type = HID_REQ_GET_REPORT,
.size = 10,
};
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
.ctx_in = &args,
.ctx_size_in = sizeof(args),
);
int err, prog_fd;
LOAD_BPF;
args.hid = self->hid.hid_id;
args.data[0] = 1; /* report ID */
prog_fd = bpf_program__fd(self->skel->progs.hid_user_raw_request);
err = bpf_prog_test_run_opts(prog_fd, &tattrs);
ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts");
ASSERT_EQ(args.retval, 2);
ASSERT_EQ(args.data[1], 2);
}
/*
* Call hid_hw_raw_request against the given uhid device,
* check that the program is called and prevents the
* call to uhid.
*/
TEST_F(hid_bpf, test_hid_filter_raw_request_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_filter_raw_request" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* first check that we did not attach to device_event */
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 42);
ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test");
/* now check that our program is preventing hid_hw_raw_request() */
/* emit hid_hw_raw_request from hidraw */
/* Get Feature */
memset(buf, 0, sizeof(buf));
buf[0] = 0x1; /* Report Number */
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_LT(err, 0) TH_LOG("unexpected success while reading HIDIOCGFEATURE: %d", err);
ASSERT_EQ(errno, 20) TH_LOG("unexpected error code while reading HIDIOCGFEATURE: %d",
errno);
/* remove our bpf program and check that we can now emit commands */
/* detach the program */
detach_bpf(self);
self->hidraw_fd = open_hidraw(&self->hid);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGFEATURE: %d", err);
}
/*
* Call hid_hw_raw_request against the given uhid device,
* check that the program is called and can issue the call
* to uhid and transform the answer.
*/
TEST_F(hid_bpf, test_hid_change_raw_request_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_hidraw_raw_request" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_raw_request from hidraw */
/* Get Feature */
memset(buf, 0, sizeof(buf));
buf[0] = 0x1; /* Report Number */
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_EQ(err, 3) TH_LOG("unexpected returned size while reading HIDIOCGFEATURE: %d", err);
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 3);
ASSERT_EQ(buf[2], 4);
}
/*
* Call hid_hw_raw_request against the given uhid device,
* check that the program is not making infinite loops.
*/
TEST_F(hid_bpf, test_hid_infinite_loop_raw_request_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_infinite_loop_raw_request" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_raw_request from hidraw */
/* Get Feature */
memset(buf, 0, sizeof(buf));
buf[0] = 0x1; /* Report Number */
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_EQ(err, 3) TH_LOG("unexpected returned size while reading HIDIOCGFEATURE: %d", err);
}
/*
* Call hid_hw_output_report against the given uhid device,
* check that the program is called and prevents the
* call to uhid.
*/
TEST_F(hid_bpf, test_hid_filter_output_report_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_filter_output_report" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* first check that we did not attach to device_event */
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 42);
ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test");
/* now check that our program is preventing hid_hw_output_report() */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
err = write(self->hidraw_fd, buf, 3);
ASSERT_LT(err, 0) TH_LOG("unexpected success while sending hid_hw_output_report: %d", err);
ASSERT_EQ(errno, 25) TH_LOG("unexpected error code while sending hid_hw_output_report: %d",
errno);
/* remove our bpf program and check that we can now emit commands */
/* detach the program */
detach_bpf(self);
self->hidraw_fd = open_hidraw(&self->hid);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
err = write(self->hidraw_fd, buf, 3);
ASSERT_GE(err, 0) TH_LOG("error while sending hid_hw_output_report: %d", err);
}
/*
* Call hid_hw_output_report against the given uhid device,
* check that the program is called and can issue the call
* to uhid and transform the answer.
*/
TEST_F(hid_bpf, test_hid_change_output_report_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_hidraw_output_report" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_output_report from hidraw */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
err = write(self->hidraw_fd, buf, 10);
ASSERT_EQ(err, 2) TH_LOG("unexpected returned size while sending hid_hw_output_report: %d",
err);
}
/*
* Call hid_hw_output_report against the given uhid device,
* check that the program is not making infinite loops.
*/
TEST_F(hid_bpf, test_hid_infinite_loop_output_report_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_infinite_loop_output_report" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_output_report from hidraw */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
err = write(self->hidraw_fd, buf, 8);
ASSERT_EQ(err, 2) TH_LOG("unexpected returned size while sending hid_hw_output_report: %d",
err);
}
/*
* Attach hid_multiply_event_wq to the given uhid device,
* retrieve and open the matching hidraw node,
* inject one event in the uhid device,
* check that the program sees it and can add extra data
*/
TEST_F(hid_bpf, test_multiply_events_wq)
{
const struct test_program progs[] = {
{ .name = "hid_test_multiply_events_wq" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 47);
usleep(100000);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 9) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 3);
}
/*
* Attach hid_multiply_event to the given uhid device,
* retrieve and open the matching hidraw node,
* inject one event in the uhid device,
* check that the program sees it and can add extra data
*/
TEST_F(hid_bpf, test_multiply_events)
{
const struct test_program progs[] = {
{ .name = "hid_test_multiply_events" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 9) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 47);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 9) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 52);
}
/*
* Call hid_bpf_input_report against the given uhid device,
* check that the program is not making infinite loops.
*/
TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_infinite_loop_input_report" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_output_report from hidraw */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 3);
/* read the data from hidraw: hid_bpf_try_input_report should work exactly one time */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 4);
/* read the data from hidraw: there should be none */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, -1) TH_LOG("read_hidraw");
}
/*
* Attach hid_insert{0,1,2} to the given uhid device,
* retrieve and open the matching hidraw node,
* inject one event in the uhid device,
* check that the programs have been inserted in the correct order.
*/
TEST_F(hid_bpf, test_hid_attach_flags)
{
const struct test_program progs[] = {
{
.name = "hid_test_insert2",
.insert_head = 0,
},
{
.name = "hid_test_insert1",
.insert_head = 1,
},
{
.name = "hid_test_insert3",
.insert_head = 0,
},
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* inject one event */
buf[0] = 1;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[1], 1);
ASSERT_EQ(buf[2], 2);
ASSERT_EQ(buf[3], 3);
}
/*
* Attach hid_rdesc_fixup to the given uhid device,
* retrieve and open the matching hidraw node,
* check that the hidraw report descriptor has been updated.
*/
TEST_F(hid_bpf, test_rdesc_fixup)
{
struct hidraw_report_descriptor rpt_desc = {0};
const struct test_program progs[] = {
{ .name = "hid_rdesc_fixup" },
};
int err, desc_size;
LOAD_PROGRAMS(progs);
/* check that hid_rdesc_fixup() was executed */
ASSERT_EQ(self->skel->data->callback2_check, 0x21);
/* read the exposed report descriptor from hidraw */
err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGRDESCSIZE: %d", err);
/* ensure the new size of the rdesc is bigger than the old one */
ASSERT_GT(desc_size, sizeof(rdesc));
rpt_desc.size = desc_size;
err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &rpt_desc);
ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGRDESC: %d", err);
ASSERT_EQ(rpt_desc.value[4], 0x42);
}
TEST_F(hid_bpf, test_rdesc_fixup_get_data_overflow)
{
const struct test_program progs[] = {
{ .name = "hid_rdesc_fixup_get_data_overflow" },
};
LOAD_PROGRAMS(progs);
ASSERT_EQ(self->skel->bss->get_data_overflow_check, 1);
}
static int libbpf_print_fn(enum libbpf_print_level level,
const char *format, va_list args)
{
char buf[1024];
if (level == LIBBPF_DEBUG)
return 0;
snprintf(buf, sizeof(buf), "# %s", format);
vfprintf(stdout, buf, args);
return 0;
}
int main(int argc, char **argv)
{
/* Use libbpf 1.0 API mode */
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
libbpf_set_print(libbpf_print_fn);
return test_harness_run(argc, argv);
}
@@ -0,0 +1,480 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2022-2024 Red Hat */
#include "kselftest_harness.h"
#include <fcntl.h>
#include <fnmatch.h>
#include <dirent.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <linux/hidraw.h>
#include <linux/uhid.h>
#define SHOW_UHID_DEBUG 0
#define min(a, b) \
({ __typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a < _b ? _a : _b; })
struct uhid_device {
int dev_id; /* uniq (random) number to identify the device */
int uhid_fd;
int hid_id; /* HID device id in the system */
__u16 bus;
__u32 vid;
__u32 pid;
pthread_t tid; /* thread for reading uhid events */
};
static unsigned char rdesc[] = {
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
0x09, 0x21, /* Usage (Vendor Usage 0x21) */
0xa1, 0x01, /* COLLECTION (Application) */
0x09, 0x01, /* Usage (Vendor Usage 0x01) */
0xa1, 0x00, /* COLLECTION (Physical) */
0x85, 0x02, /* REPORT_ID (2) */
0x19, 0x01, /* USAGE_MINIMUM (1) */
0x29, 0x08, /* USAGE_MAXIMUM (3) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0xff, /* LOGICAL_MAXIMUM (255) */
0x95, 0x08, /* REPORT_COUNT (8) */
0x75, 0x08, /* REPORT_SIZE (8) */
0x81, 0x02, /* INPUT (Data,Var,Abs) */
0xc0, /* END_COLLECTION */
0x09, 0x01, /* Usage (Vendor Usage 0x01) */
0xa1, 0x00, /* COLLECTION (Physical) */
0x85, 0x01, /* REPORT_ID (1) */
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
0x19, 0x01, /* USAGE_MINIMUM (1) */
0x29, 0x03, /* USAGE_MAXIMUM (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, 0x01, /* 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, 0x10, /* REPORT_SIZE (16) */
0x95, 0x02, /* REPORT_COUNT (2) */
0x81, 0x06, /* INPUT (Data,Var,Rel) */
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
0x19, 0x01, /* USAGE_MINIMUM (1) */
0x29, 0x03, /* USAGE_MAXIMUM (3) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
0x95, 0x03, /* REPORT_COUNT (3) */
0x75, 0x01, /* REPORT_SIZE (1) */
0x91, 0x02, /* Output (Data,Var,Abs) */
0x95, 0x01, /* REPORT_COUNT (1) */
0x75, 0x05, /* REPORT_SIZE (5) */
0x91, 0x01, /* Output (Cnst,Var,Abs) */
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
0x19, 0x06, /* USAGE_MINIMUM (6) */
0x29, 0x08, /* USAGE_MAXIMUM (8) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
0x95, 0x03, /* REPORT_COUNT (3) */
0x75, 0x01, /* REPORT_SIZE (1) */
0xb1, 0x02, /* Feature (Data,Var,Abs) */
0x95, 0x01, /* REPORT_COUNT (1) */
0x75, 0x05, /* REPORT_SIZE (5) */
0x91, 0x01, /* Output (Cnst,Var,Abs) */
0xc0, /* END_COLLECTION */
0xc0, /* END_COLLECTION */
};
static __u8 feature_data[] = { 1, 2 };
#define ASSERT_OK(data) ASSERT_FALSE(data)
#define ASSERT_OK_PTR(ptr) ASSERT_NE(NULL, ptr)
#define UHID_LOG(fmt, ...) do { \
if (SHOW_UHID_DEBUG) \
TH_LOG(fmt, ##__VA_ARGS__); \
} while (0)
static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t uhid_output_mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t uhid_output_cond = PTHREAD_COND_INITIALIZER;
static unsigned char output_report[10];
/* no need to protect uhid_stopped, only one thread accesses it */
static bool uhid_stopped;
static int uhid_write(struct __test_metadata *_metadata, int fd, const struct uhid_event *ev)
{
ssize_t ret;
ret = write(fd, ev, sizeof(*ev));
if (ret < 0) {
TH_LOG("Cannot write to uhid: %m");
return -errno;
} else if (ret != sizeof(*ev)) {
TH_LOG("Wrong size written to uhid: %zd != %zu",
ret, sizeof(ev));
return -EFAULT;
} else {
return 0;
}
}
static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb,
__u16 bus, __u32 vid, __u32 pid, __u8 *rdesc,
size_t rdesc_size)
{
struct uhid_event ev;
char buf[25];
sprintf(buf, "test-uhid-device-%d", rand_nb);
memset(&ev, 0, sizeof(ev));
ev.type = UHID_CREATE;
strcpy((char *)ev.u.create.name, buf);
ev.u.create.rd_data = rdesc;
ev.u.create.rd_size = rdesc_size;
ev.u.create.bus = bus;
ev.u.create.vendor = vid;
ev.u.create.product = pid;
ev.u.create.version = 0;
ev.u.create.country = 0;
sprintf(buf, "%d", rand_nb);
strcpy((char *)ev.u.create.phys, buf);
return uhid_write(_metadata, fd, &ev);
}
static void uhid_destroy(struct __test_metadata *_metadata, struct uhid_device *hid)
{
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = UHID_DESTROY;
uhid_write(_metadata, hid->uhid_fd, &ev);
}
static int uhid_event(struct __test_metadata *_metadata, int fd)
{
struct uhid_event ev, answer;
ssize_t ret;
memset(&ev, 0, sizeof(ev));
ret = read(fd, &ev, sizeof(ev));
if (ret == 0) {
UHID_LOG("Read HUP on uhid-cdev");
return -EFAULT;
} else if (ret < 0) {
UHID_LOG("Cannot read uhid-cdev: %m");
return -errno;
} else if (ret != sizeof(ev)) {
UHID_LOG("Invalid size read from uhid-dev: %zd != %zu",
ret, sizeof(ev));
return -EFAULT;
}
switch (ev.type) {
case UHID_START:
pthread_mutex_lock(&uhid_started_mtx);
pthread_cond_signal(&uhid_started);
pthread_mutex_unlock(&uhid_started_mtx);
UHID_LOG("UHID_START from uhid-dev");
break;
case UHID_STOP:
uhid_stopped = true;
UHID_LOG("UHID_STOP from uhid-dev");
break;
case UHID_OPEN:
UHID_LOG("UHID_OPEN from uhid-dev");
break;
case UHID_CLOSE:
UHID_LOG("UHID_CLOSE from uhid-dev");
break;
case UHID_OUTPUT:
UHID_LOG("UHID_OUTPUT from uhid-dev");
pthread_mutex_lock(&uhid_output_mtx);
memcpy(output_report,
ev.u.output.data,
min(ev.u.output.size, sizeof(output_report)));
pthread_cond_signal(&uhid_output_cond);
pthread_mutex_unlock(&uhid_output_mtx);
break;
case UHID_GET_REPORT:
UHID_LOG("UHID_GET_REPORT from uhid-dev");
answer.type = UHID_GET_REPORT_REPLY;
answer.u.get_report_reply.id = ev.u.get_report.id;
answer.u.get_report_reply.err = ev.u.get_report.rnum == 1 ? 0 : -EIO;
answer.u.get_report_reply.size = sizeof(feature_data);
memcpy(answer.u.get_report_reply.data, feature_data, sizeof(feature_data));
uhid_write(_metadata, fd, &answer);
break;
case UHID_SET_REPORT:
UHID_LOG("UHID_SET_REPORT from uhid-dev");
answer.type = UHID_SET_REPORT_REPLY;
answer.u.set_report_reply.id = ev.u.set_report.id;
answer.u.set_report_reply.err = 0; /* success */
uhid_write(_metadata, fd, &answer);
break;
default:
TH_LOG("Invalid event from uhid-dev: %u", ev.type);
}
return 0;
}
struct uhid_thread_args {
int fd;
struct __test_metadata *_metadata;
};
static void *uhid_read_events_thread(void *arg)
{
struct uhid_thread_args *args = (struct uhid_thread_args *)arg;
struct __test_metadata *_metadata = args->_metadata;
struct pollfd pfds[1];
int fd = args->fd;
int ret = 0;
pfds[0].fd = fd;
pfds[0].events = POLLIN;
uhid_stopped = false;
while (!uhid_stopped) {
ret = poll(pfds, 1, 100);
if (ret < 0) {
TH_LOG("Cannot poll for fds: %m");
break;
}
if (pfds[0].revents & POLLIN) {
ret = uhid_event(_metadata, fd);
if (ret)
break;
}
}
return (void *)(long)ret;
}
static int uhid_start_listener(struct __test_metadata *_metadata, pthread_t *tid, int uhid_fd)
{
struct uhid_thread_args args = {
.fd = uhid_fd,
._metadata = _metadata,
};
int err;
pthread_mutex_lock(&uhid_started_mtx);
err = pthread_create(tid, NULL, uhid_read_events_thread, (void *)&args);
ASSERT_EQ(0, err) {
TH_LOG("Could not start the uhid thread: %d", err);
pthread_mutex_unlock(&uhid_started_mtx);
close(uhid_fd);
return -EIO;
}
pthread_cond_wait(&uhid_started, &uhid_started_mtx);
pthread_mutex_unlock(&uhid_started_mtx);
return 0;
}
static int uhid_send_event(struct __test_metadata *_metadata, struct uhid_device *hid,
__u8 *buf, size_t size)
{
struct uhid_event ev;
if (size > sizeof(ev.u.input.data))
return -E2BIG;
memset(&ev, 0, sizeof(ev));
ev.type = UHID_INPUT2;
ev.u.input2.size = size;
memcpy(ev.u.input2.data, buf, size);
return uhid_write(_metadata, hid->uhid_fd, &ev);
}
static bool match_sysfs_device(struct uhid_device *hid, const char *workdir, struct dirent *dir)
{
char target[20] = "";
char phys[512];
char uevent[1024];
char temp[512];
int fd, nread;
bool found = false;
snprintf(target, sizeof(target), "%04X:%04X:%04X.*", hid->bus, hid->vid, hid->pid);
if (fnmatch(target, dir->d_name, 0))
return false;
/* we found the correct VID/PID, now check for phys */
sprintf(uevent, "%s/%s/uevent", workdir, dir->d_name);
fd = open(uevent, O_RDONLY | O_NONBLOCK);
if (fd < 0)
return false;
sprintf(phys, "PHYS=%d", hid->dev_id);
nread = read(fd, temp, ARRAY_SIZE(temp));
if (nread > 0 && (strstr(temp, phys)) != NULL)
found = true;
close(fd);
return found;
}
static int get_hid_id(struct uhid_device *hid)
{
const char *workdir = "/sys/devices/virtual/misc/uhid";
const char *str_id;
DIR *d;
struct dirent *dir;
int found = -1, attempts = 3;
/* it would be nice to be able to use nftw, but the no_alu32 target doesn't support it */
while (found < 0 && attempts > 0) {
attempts--;
d = opendir(workdir);
if (d) {
while ((dir = readdir(d)) != NULL) {
if (!match_sysfs_device(hid, workdir, dir))
continue;
str_id = dir->d_name + sizeof("0000:0000:0000.");
found = (int)strtol(str_id, NULL, 16);
break;
}
closedir(d);
}
if (found < 0)
usleep(100000);
}
return found;
}
static int get_hidraw(struct uhid_device *hid)
{
const char *workdir = "/sys/devices/virtual/misc/uhid";
char sysfs[1024];
DIR *d, *subd;
struct dirent *dir, *subdir;
int i, found = -1;
/* retry 5 times in case the system is loaded */
for (i = 5; i > 0; i--) {
usleep(10);
d = opendir(workdir);
if (!d)
continue;
while ((dir = readdir(d)) != NULL) {
if (!match_sysfs_device(hid, workdir, dir))
continue;
sprintf(sysfs, "%s/%s/hidraw", workdir, dir->d_name);
subd = opendir(sysfs);
if (!subd)
continue;
while ((subdir = readdir(subd)) != NULL) {
if (fnmatch("hidraw*", subdir->d_name, 0))
continue;
found = atoi(subdir->d_name + strlen("hidraw"));
}
closedir(subd);
if (found > 0)
break;
}
closedir(d);
}
return found;
}
static int open_hidraw(struct uhid_device *hid)
{
int hidraw_number;
char hidraw_path[64] = { 0 };
hidraw_number = get_hidraw(hid);
if (hidraw_number < 0)
return hidraw_number;
/* open hidraw node to check the other side of the pipe */
sprintf(hidraw_path, "/dev/hidraw%d", hidraw_number);
return open(hidraw_path, O_RDWR | O_NONBLOCK);
}
static int setup_uhid(struct __test_metadata *_metadata, struct uhid_device *hid,
__u16 bus, __u32 vid, __u32 pid, const __u8 *rdesc, size_t rdesc_size)
{
const char *path = "/dev/uhid";
time_t t;
int ret;
/* initialize random number generator */
srand((unsigned int)time(&t));
hid->dev_id = rand() % 1024;
hid->bus = bus;
hid->vid = vid;
hid->pid = pid;
hid->uhid_fd = open(path, O_RDWR | O_CLOEXEC);
ASSERT_GE(hid->uhid_fd, 0) TH_LOG("open uhid-cdev failed; %d", hid->uhid_fd);
ret = uhid_create(_metadata, hid->uhid_fd, hid->dev_id, bus, vid, pid,
(__u8 *)rdesc, rdesc_size);
ASSERT_EQ(0, ret) {
TH_LOG("create uhid device failed: %d", ret);
close(hid->uhid_fd);
return ret;
}
/* locate the uevent file of the created device */
hid->hid_id = get_hid_id(hid);
ASSERT_GT(hid->hid_id, 0)
TH_LOG("Could not locate uhid device id: %d", hid->hid_id);
ret = uhid_start_listener(_metadata, &hid->tid, hid->uhid_fd);
ASSERT_EQ(0, ret) {
TH_LOG("could not start udev listener: %d", ret);
close(hid->uhid_fd);
return ret;
}
return 0;
}
@@ -0,0 +1,694 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022-2024 Red Hat */
#include "hid_common.h"
#include <linux/input.h>
#include <string.h>
#include <sys/ioctl.h>
/* for older kernels */
#ifndef HIDIOCREVOKE
#define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */
#endif /* HIDIOCREVOKE */
FIXTURE(hidraw) {
struct uhid_device hid;
int hidraw_fd;
};
static void close_hidraw(FIXTURE_DATA(hidraw) * self)
{
if (self->hidraw_fd)
close(self->hidraw_fd);
self->hidraw_fd = 0;
}
FIXTURE_TEARDOWN(hidraw) {
void *uhid_err;
uhid_destroy(_metadata, &self->hid);
close_hidraw(self);
pthread_join(self->hid.tid, &uhid_err);
}
#define TEARDOWN_LOG(fmt, ...) do { \
TH_LOG(fmt, ##__VA_ARGS__); \
hidraw_teardown(_metadata, self, variant); \
} while (0)
FIXTURE_SETUP(hidraw)
{
int err;
err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a37, rdesc, sizeof(rdesc));
ASSERT_OK(err);
self->hidraw_fd = open_hidraw(&self->hid);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
}
/*
* A simple test to see if the fixture is working fine.
* If this fails, none of the other tests will pass.
*/
TEST_F(hidraw, test_create_uhid)
{
}
/*
* Inject one event in the uhid device,
* check that we get the same data through hidraw
*/
TEST_F(hidraw, raw_event)
{
__u8 buf[10] = {0};
int err;
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 42);
}
/*
* After initial opening/checks of hidraw, revoke the hidraw
* node and check that we can not read any more data.
*/
TEST_F(hidraw, raw_event_revoked)
{
__u8 buf[10] = {0};
int err;
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 42);
/* call the revoke ioctl */
err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL);
ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd");
/* inject one other event */
buf[0] = 1;
buf[1] = 43;
uhid_send_event(_metadata, &self->hid, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, -1) TH_LOG("read_hidraw");
ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while reading the hidraw node: %d",
errno);
}
/*
* Revoke the hidraw node and check that we can not do any ioctl.
*/
TEST_F(hidraw, ioctl_revoked)
{
int err, desc_size = 0;
/* call the revoke ioctl */
err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL);
ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd");
/* do an ioctl */
err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
ASSERT_EQ(err, -1) TH_LOG("ioctl_hidraw");
ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while doing an ioctl: %d",
errno);
}
/*
* Setup polling of the fd, and check that revoke works properly.
*/
TEST_F(hidraw, poll_revoked)
{
struct pollfd pfds[1];
__u8 buf[10] = {0};
int err, ready;
/* setup polling */
pfds[0].fd = self->hidraw_fd;
pfds[0].events = POLLIN;
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, &self->hid, buf, 6);
while (true) {
ready = poll(pfds, 1, 5000);
ASSERT_EQ(ready, 1) TH_LOG("poll return value");
if (pfds[0].revents & POLLIN) {
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 42);
/* call the revoke ioctl */
err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL);
ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd");
} else {
break;
}
}
ASSERT_TRUE(pfds[0].revents & POLLHUP);
}
/*
* After initial opening/checks of hidraw, revoke the hidraw
* node and check that we can not read any more data.
*/
TEST_F(hidraw, write_event_revoked)
{
struct timespec time_to_wait;
__u8 buf[10] = {0};
int err;
/* inject one event from hidraw */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
pthread_mutex_lock(&uhid_output_mtx);
memset(output_report, 0, sizeof(output_report));
clock_gettime(CLOCK_REALTIME, &time_to_wait);
time_to_wait.tv_sec += 2;
err = write(self->hidraw_fd, buf, 3);
ASSERT_EQ(err, 3) TH_LOG("unexpected error while writing to hidraw node: %d", err);
err = pthread_cond_timedwait(&uhid_output_cond, &uhid_output_mtx, &time_to_wait);
ASSERT_OK(err) TH_LOG("error while calling waiting for the condition");
ASSERT_EQ(output_report[0], 1);
ASSERT_EQ(output_report[1], 2);
ASSERT_EQ(output_report[2], 42);
/* call the revoke ioctl */
err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL);
ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd");
/* inject one other event */
buf[0] = 1;
buf[1] = 43;
err = write(self->hidraw_fd, buf, 3);
ASSERT_LT(err, 0) TH_LOG("unexpected success while writing to hidraw node: %d", err);
ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while writing to hidraw node: %d",
errno);
pthread_mutex_unlock(&uhid_output_mtx);
}
/*
* Test HIDIOCGRDESCSIZE ioctl to get report descriptor size
*/
TEST_F(hidraw, ioctl_rdescsize)
{
int desc_size = 0;
int err;
/* call HIDIOCGRDESCSIZE ioctl */
err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed");
/* verify the size matches our test report descriptor */
ASSERT_EQ(desc_size, sizeof(rdesc))
TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size);
}
/*
* Test HIDIOCGRDESC ioctl to get report descriptor data
*/
TEST_F(hidraw, ioctl_rdesc)
{
struct hidraw_report_descriptor desc;
int err;
/* get the full report descriptor */
desc.size = sizeof(rdesc);
err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed");
/* verify the descriptor data matches our test descriptor */
ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0)
TH_LOG("report descriptor data mismatch");
}
/*
* Test HIDIOCGRDESC ioctl with smaller buffer size
*/
TEST_F(hidraw, ioctl_rdesc_small_buffer)
{
struct hidraw_report_descriptor desc;
int err;
size_t small_size = sizeof(rdesc) / 2; /* request half the descriptor size */
/* get partial report descriptor */
desc.size = small_size;
err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer");
/* verify we got the first part of the descriptor */
ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0)
TH_LOG("partial report descriptor data mismatch");
}
/*
* Test HIDIOCGRAWINFO ioctl to get device information
*/
TEST_F(hidraw, ioctl_rawinfo)
{
struct hidraw_devinfo devinfo;
int err;
/* get device info */
err = ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo);
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed");
/* verify device info matches our test setup */
ASSERT_EQ(devinfo.bustype, BUS_USB)
TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype);
ASSERT_EQ(devinfo.vendor, 0x0001)
TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor);
ASSERT_EQ(devinfo.product, 0x0a37)
TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product);
}
/*
* Test HIDIOCGFEATURE ioctl to get feature report
*/
TEST_F(hidraw, ioctl_gfeature)
{
__u8 buf[10] = {0};
int err;
/* set report ID 1 in first byte */
buf[0] = 1;
/* get feature report */
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed, got %d", err);
/* verify we got the expected feature data */
ASSERT_EQ(buf[0], feature_data[0])
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
ASSERT_EQ(buf[1], feature_data[1])
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
}
/*
* Test HIDIOCGFEATURE ioctl with invalid report ID
*/
TEST_F(hidraw, ioctl_gfeature_invalid)
{
__u8 buf[10] = {0};
int err;
/* set invalid report ID (not 1) */
buf[0] = 2;
/* try to get feature report */
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid report ID");
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
}
/*
* Test ioctl with incorrect nr bits
*/
TEST_F(hidraw, ioctl_invalid_nr)
{
char buf[256] = {0};
int err;
unsigned int bad_cmd;
/*
* craft an ioctl command with wrong _IOC_NR bits
*/
bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */
/* test the ioctl */
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0) should have failed");
ASSERT_EQ(errno, ENOTTY)
TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0), got errno %d", errno);
/*
* craft an ioctl command with wrong _IOC_NR bits
*/
bad_cmd = _IOC(_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */
/* test the ioctl */
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0) should have failed");
ASSERT_EQ(errno, ENOTTY)
TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0), got errno %d", errno);
/* also test with bigger number */
bad_cmd = _IOC(_IOC_READ, 'H', 0x42, sizeof(buf)); /* 0x42 is not valid as well */
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0x42) should have failed");
ASSERT_EQ(errno, ENOTTY)
TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0x42), got errno %d", errno);
/* also test with bigger number: 0x42 is not valid as well */
bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x42, sizeof(buf));
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0x42) should have failed");
ASSERT_EQ(errno, ENOTTY)
TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0x42), got errno %d", errno);
}
/*
* Test ioctl with incorrect type bits
*/
TEST_F(hidraw, ioctl_invalid_type)
{
char buf[256] = {0};
int err;
unsigned int bad_cmd;
/*
* craft an ioctl command with wrong _IOC_TYPE bits
*/
bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'I', 0x01, sizeof(buf)); /* 'I' should be 'H' */
/* test the ioctl */
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("ioctl with wrong _IOC_TYPE (I) should have failed");
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_NR, got errno %d", errno);
}
/*
* Test HIDIOCGFEATURE ioctl with incorrect _IOC_DIR bits
*/
TEST_F(hidraw, ioctl_gfeature_invalid_dir)
{
__u8 buf[10] = {0};
int err;
unsigned int bad_cmd;
/* set report ID 1 in first byte */
buf[0] = 1;
/*
* craft an ioctl command with wrong _IOC_DIR bits
* HIDIOCGFEATURE should have _IOC_WRITE|_IOC_READ, let's use only _IOC_WRITE
*/
bad_cmd = _IOC(_IOC_WRITE, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */
/* try to get feature report with wrong direction bits */
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed");
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
/* also test with only _IOC_READ */
bad_cmd = _IOC(_IOC_READ, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed");
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
}
/*
* Test read-only ioctl with incorrect _IOC_DIR bits
*/
TEST_F(hidraw, ioctl_readonly_invalid_dir)
{
char buf[256] = {0};
int err;
unsigned int bad_cmd;
/*
* craft an ioctl command with wrong _IOC_DIR bits
* HIDIOCGRAWNAME should have _IOC_READ, let's use _IOC_WRITE
*/
bad_cmd = _IOC(_IOC_WRITE, 'H', 0x04, sizeof(buf)); /* should be _IOC_READ */
/* try to get device name with wrong direction bits */
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed");
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
/* also test with _IOC_WRITE|_IOC_READ */
bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x04, sizeof(buf)); /* should be only _IOC_READ */
err = ioctl(self->hidraw_fd, bad_cmd, buf);
ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed");
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
}
/*
* Test HIDIOCSFEATURE ioctl to set feature report
*/
TEST_F(hidraw, ioctl_sfeature)
{
__u8 buf[10] = {0};
int err;
/* prepare feature report data */
buf[0] = 1; /* report ID */
buf[1] = 0x42;
buf[2] = 0x24;
/* set feature report */
err = ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf);
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err);
/*
* Note: The uhid mock doesn't validate the set report data,
* so we just verify the ioctl succeeds
*/
}
/*
* Test HIDIOCGINPUT ioctl to get input report
*/
TEST_F(hidraw, ioctl_ginput)
{
__u8 buf[10] = {0};
int err;
/* set report ID 1 in first byte */
buf[0] = 1;
/* get input report */
err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, got %d", err);
/* verify we got the expected input data */
ASSERT_EQ(buf[0], feature_data[0])
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
ASSERT_EQ(buf[1], feature_data[1])
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
}
/*
* Test HIDIOCGINPUT ioctl with invalid report ID
*/
TEST_F(hidraw, ioctl_ginput_invalid)
{
__u8 buf[10] = {0};
int err;
/* set invalid report ID (not 1) */
buf[0] = 2;
/* try to get input report */
err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid report ID");
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
}
/*
* Test HIDIOCSINPUT ioctl to set input report
*/
TEST_F(hidraw, ioctl_sinput)
{
__u8 buf[10] = {0};
int err;
/* prepare input report data */
buf[0] = 1; /* report ID */
buf[1] = 0x55;
buf[2] = 0xAA;
/* set input report */
err = ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf);
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err);
/*
* Note: The uhid mock doesn't validate the set report data,
* so we just verify the ioctl succeeds
*/
}
/*
* Test HIDIOCGOUTPUT ioctl to get output report
*/
TEST_F(hidraw, ioctl_goutput)
{
__u8 buf[10] = {0};
int err;
/* set report ID 1 in first byte */
buf[0] = 1;
/* get output report */
err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, got %d", err);
/* verify we got the expected output data */
ASSERT_EQ(buf[0], feature_data[0])
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
ASSERT_EQ(buf[1], feature_data[1])
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
}
/*
* Test HIDIOCGOUTPUT ioctl with invalid report ID
*/
TEST_F(hidraw, ioctl_goutput_invalid)
{
__u8 buf[10] = {0};
int err;
/* set invalid report ID (not 1) */
buf[0] = 2;
/* try to get output report */
err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid report ID");
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
}
/*
* Test HIDIOCSOUTPUT ioctl to set output report
*/
TEST_F(hidraw, ioctl_soutput)
{
__u8 buf[10] = {0};
int err;
/* prepare output report data */
buf[0] = 1; /* report ID */
buf[1] = 0x33;
buf[2] = 0xCC;
/* set output report */
err = ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf);
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err);
/*
* Note: The uhid mock doesn't validate the set report data,
* so we just verify the ioctl succeeds
*/
}
/*
* Test HIDIOCGRAWNAME ioctl to get device name string
*/
TEST_F(hidraw, ioctl_rawname)
{
char name[256] = {0};
char expected_name[64];
int err;
/* get device name */
err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name);
ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err);
/* construct expected name based on device id */
snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
/* verify the name matches expected pattern */
ASSERT_EQ(strcmp(name, expected_name), 0)
TH_LOG("expected name '%s', got '%s'", expected_name, name);
}
/*
* Test HIDIOCGRAWPHYS ioctl to get device physical address string
*/
TEST_F(hidraw, ioctl_rawphys)
{
char phys[256] = {0};
char expected_phys[64];
int err;
/* get device physical address */
err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys);
ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err);
/* construct expected phys based on device id */
snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id);
/* verify the phys matches expected value */
ASSERT_EQ(strcmp(phys, expected_phys), 0)
TH_LOG("expected phys '%s', got '%s'", expected_phys, phys);
}
/*
* Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string
*/
TEST_F(hidraw, ioctl_rawuniq)
{
char uniq[256] = {0};
int err;
/* get device unique identifier */
err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq);
ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err);
/* uniq is typically empty in our test setup */
ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq);
}
/*
* Test device string ioctls with small buffer sizes
*/
TEST_F(hidraw, ioctl_strings_small_buffer)
{
char small_buf[8] = {0};
char expected_name[64];
int err;
/* test HIDIOCGRAWNAME with small buffer */
err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_buf);
ASSERT_EQ(err, sizeof(small_buf))
TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err);
/* construct expected truncated name */
snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
/* verify we got truncated name (first 8 chars, no null terminator guaranteed) */
ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0)
TH_LOG("expected truncated name to match first %zu chars", sizeof(small_buf));
/* Note: hidraw driver doesn't guarantee null termination when buffer is too small */
}
int main(int argc, char **argv)
{
return test_harness_run(argc, argv);
}
@@ -0,0 +1,615 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Red hat */
#include "hid_bpf_helpers.h"
char _license[] SEC("license") = "GPL";
struct attach_prog_args {
int prog_fd;
unsigned int hid;
int retval;
int insert_head;
};
__u64 callback_check = 52;
__u64 callback2_check = 52;
__u64 get_data_overflow_check;
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
if (!rw_data)
return 0; /* EPERM check */
callback_check = rw_data[1];
rw_data[2] = rw_data[1] + 5;
return hid_ctx->size;
}
SEC(".struct_ops.link")
struct hid_bpf_ops first_event = {
.hid_device_event = (void *)hid_first_event,
.hid_id = 2,
};
int __hid_subprog_first_event(struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
if (!rw_data)
return 0; /* EPERM check */
rw_data[2] = rw_data[1] + 5;
return hid_ctx->size;
}
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_subprog_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
return __hid_subprog_first_event(hid_ctx, type);
}
SEC(".struct_ops.link")
struct hid_bpf_ops subprog_first_event = {
.hid_device_event = (void *)hid_subprog_first_event,
.hid_id = 2,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_second_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
if (!rw_data)
return 0; /* EPERM check */
rw_data[3] = rw_data[2] + 5;
return hid_ctx->size;
}
SEC(".struct_ops.link")
struct hid_bpf_ops second_event = {
.hid_device_event = (void *)hid_second_event,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_change_report_id, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
if (!rw_data)
return 0; /* EPERM check */
rw_data[0] = 2;
return 9;
}
SEC(".struct_ops.link")
struct hid_bpf_ops change_report_id = {
.hid_device_event = (void *)hid_change_report_id,
};
struct hid_hw_request_syscall_args {
/* data needs to come at offset 0 so we can use it in calls */
__u8 data[10];
unsigned int hid;
int retval;
size_t size;
enum hid_report_type type;
__u8 request_type;
};
SEC("syscall")
int hid_user_raw_request(struct hid_hw_request_syscall_args *args)
{
struct hid_bpf_ctx *ctx;
const size_t size = args->size;
int i, ret = 0;
if (size > sizeof(args->data))
return -7; /* -E2BIG */
ctx = hid_bpf_allocate_context(args->hid);
if (!ctx)
return -1; /* EPERM check */
ret = hid_bpf_hw_request(ctx,
args->data,
size,
args->type,
args->request_type);
args->retval = ret;
hid_bpf_release_context(ctx);
return 0;
}
SEC("syscall")
int hid_user_output_report(struct hid_hw_request_syscall_args *args)
{
struct hid_bpf_ctx *ctx;
const size_t size = args->size;
int i, ret = 0;
if (size > sizeof(args->data))
return -7; /* -E2BIG */
ctx = hid_bpf_allocate_context(args->hid);
if (!ctx)
return -1; /* EPERM check */
ret = hid_bpf_hw_output_report(ctx,
args->data,
size);
args->retval = ret;
hid_bpf_release_context(ctx);
return 0;
}
SEC("syscall")
int hid_user_input_report(struct hid_hw_request_syscall_args *args)
{
struct hid_bpf_ctx *ctx;
const size_t size = args->size;
int i, ret = 0;
if (size > sizeof(args->data))
return -7; /* -E2BIG */
ctx = hid_bpf_allocate_context(args->hid);
if (!ctx)
return -1; /* EPERM check */
ret = hid_bpf_input_report(ctx, HID_INPUT_REPORT, args->data, size);
args->retval = ret;
hid_bpf_release_context(ctx);
return 0;
}
static const __u8 rdesc[] = {
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
0x09, 0x32, /* USAGE (Z) */
0x95, 0x01, /* REPORT_COUNT (1) */
0x81, 0x06, /* INPUT (Data,Var,Rel) */
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
0x19, 0x01, /* USAGE_MINIMUM (1) */
0x29, 0x03, /* USAGE_MAXIMUM (3) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
0x95, 0x03, /* REPORT_COUNT (3) */
0x75, 0x01, /* REPORT_SIZE (1) */
0x91, 0x02, /* Output (Data,Var,Abs) */
0x95, 0x01, /* REPORT_COUNT (1) */
0x75, 0x05, /* REPORT_SIZE (5) */
0x91, 0x01, /* Output (Cnst,Var,Abs) */
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
0x19, 0x06, /* USAGE_MINIMUM (6) */
0x29, 0x08, /* USAGE_MAXIMUM (8) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
0x95, 0x03, /* REPORT_COUNT (3) */
0x75, 0x01, /* REPORT_SIZE (1) */
0xb1, 0x02, /* Feature (Data,Var,Abs) */
0x95, 0x01, /* REPORT_COUNT (1) */
0x75, 0x05, /* REPORT_SIZE (5) */
0x91, 0x01, /* Output (Cnst,Var,Abs) */
0xc0, /* END_COLLECTION */
0xc0, /* END_COLLECTION */
};
/*
* the following program is marked as sleepable (struct_ops.s).
* This is not strictly mandatory but is a nice test for
* sleepable struct_ops
*/
SEC("?struct_ops.s/hid_rdesc_fixup")
int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hid_ctx)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4096 /* size */);
if (!data)
return 0; /* EPERM check */
callback2_check = data[4];
/* insert rdesc at offset 73 */
__builtin_memcpy(&data[73], rdesc, sizeof(rdesc));
/* Change Usage Vendor globally */
data[4] = 0x42;
return sizeof(rdesc) + 73;
}
SEC(".struct_ops.link")
struct hid_bpf_ops rdesc_fixup = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup,
};
SEC("?struct_ops.s/hid_rdesc_fixup")
int BPF_PROG(hid_rdesc_fixup_get_data_overflow, struct hid_bpf_ctx *hid_ctx)
{
if (!hid_bpf_get_data(hid_ctx, 2 /* offset */, ~0ULL /* size */))
get_data_overflow_check = 1;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops rdesc_fixup_get_data_overflow = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup_get_data_overflow,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
if (!data)
return 0; /* EPERM check */
/* we need to be run first */
if (data[2] || data[3])
return -1;
data[1] = 1;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_insert1 = {
.hid_device_event = (void *)hid_test_insert1,
.flags = BPF_F_BEFORE,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_insert2, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
if (!data)
return 0; /* EPERM check */
/* after insert0 and before insert2 */
if (!data[1] || data[3])
return -1;
data[2] = 2;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_insert2 = {
.hid_device_event = (void *)hid_test_insert2,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_insert3, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
if (!data)
return 0; /* EPERM check */
/* at the end */
if (!data[1] || !data[2])
return -1;
data[3] = 3;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_insert3 = {
.hid_device_event = (void *)hid_test_insert3,
};
SEC("?struct_ops/hid_hw_request")
int BPF_PROG(hid_test_filter_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source)
{
return -20;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_filter_raw_request = {
.hid_hw_request = (void *)hid_test_filter_raw_request,
};
static struct file *current_file;
SEC("fentry/hidraw_open")
int BPF_PROG(hidraw_open, struct inode *inode, struct file *file)
{
current_file = file;
return 0;
}
SEC("?struct_ops.s/hid_hw_request")
int BPF_PROG(hid_test_hidraw_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */);
int ret;
if (!data)
return 0; /* EPERM check */
/* check if the incoming request comes from our hidraw operation */
if (source == (__u64)current_file) {
data[0] = reportnum;
ret = hid_bpf_hw_request(hctx, data, 2, rtype, reqtype);
if (ret != 2)
return -1;
data[0] = reportnum + 1;
data[1] = reportnum + 2;
data[2] = reportnum + 3;
return 3;
}
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_hidraw_raw_request = {
.hid_hw_request = (void *)hid_test_hidraw_raw_request,
};
SEC("?struct_ops.s/hid_hw_request")
int BPF_PROG(hid_test_infinite_loop_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */);
int ret;
if (!data)
return 0; /* EPERM check */
/* always forward the request as-is to the device, hid-bpf should prevent
* infinite loops.
*/
data[0] = reportnum;
ret = hid_bpf_hw_request(hctx, data, 2, rtype, reqtype);
if (ret == 2)
return 3;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_infinite_loop_raw_request = {
.hid_hw_request = (void *)hid_test_infinite_loop_raw_request,
};
SEC("?struct_ops/hid_hw_output_report")
int BPF_PROG(hid_test_filter_output_report, struct hid_bpf_ctx *hctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source)
{
return -25;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_filter_output_report = {
.hid_hw_output_report = (void *)hid_test_filter_output_report,
};
SEC("?struct_ops.s/hid_hw_output_report")
int BPF_PROG(hid_test_hidraw_output_report, struct hid_bpf_ctx *hctx, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */);
int ret;
if (!data)
return 0; /* EPERM check */
/* check if the incoming request comes from our hidraw operation */
if (source == (__u64)current_file)
return hid_bpf_hw_output_report(hctx, data, 2);
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_hidraw_output_report = {
.hid_hw_output_report = (void *)hid_test_hidraw_output_report,
};
SEC("?struct_ops.s/hid_hw_output_report")
int BPF_PROG(hid_test_infinite_loop_output_report, struct hid_bpf_ctx *hctx, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */);
int ret;
if (!data)
return 0; /* EPERM check */
/* always forward the request as-is to the device, hid-bpf should prevent
* infinite loops.
*/
ret = hid_bpf_hw_output_report(hctx, data, 2);
if (ret == 2)
return 2;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_infinite_loop_output_report = {
.hid_hw_output_report = (void *)hid_test_infinite_loop_output_report,
};
struct elem {
struct bpf_wq work;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct elem);
} hmap SEC(".maps");
static int wq_cb_sleepable(void *map, int *key, void *work)
{
__u8 buf[9] = {2, 3, 4, 5, 6, 7, 8, 9, 10};
struct hid_bpf_ctx *hid_ctx;
hid_ctx = hid_bpf_allocate_context(*key);
if (!hid_ctx)
return 0; /* EPERM check */
hid_bpf_input_report(hid_ctx, HID_INPUT_REPORT, buf, sizeof(buf));
hid_bpf_release_context(hid_ctx);
return 0;
}
static int test_inject_input_report_callback(int *key)
{
struct elem init = {}, *val;
struct bpf_wq *wq;
if (bpf_map_update_elem(&hmap, key, &init, 0))
return -1;
val = bpf_map_lookup_elem(&hmap, key);
if (!val)
return -2;
wq = &val->work;
if (bpf_wq_init(wq, &hmap, 0) != 0)
return -3;
if (bpf_wq_set_callback(wq, wq_cb_sleepable, 0))
return -4;
if (bpf_wq_start(wq, 0))
return -5;
return 0;
}
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_multiply_events_wq, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 9 /* size */);
int hid = hid_ctx->hid->id;
int ret;
if (!data)
return 0; /* EPERM check */
if (data[0] != 1)
return 0;
ret = test_inject_input_report_callback(&hid);
if (ret)
return ret;
data[1] += 5;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_multiply_events_wq = {
.hid_device_event = (void *)hid_test_multiply_events_wq,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_multiply_events, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 9 /* size */);
__u8 buf[9];
int ret;
if (!data)
return 0; /* EPERM check */
if (data[0] != 1)
return 0;
/*
* we have to use an intermediate buffer as hid_bpf_input_report
* will memset data to \0
*/
__builtin_memcpy(buf, data, sizeof(buf));
buf[0] = 2;
buf[1] += 5;
ret = hid_bpf_try_input_report(hid_ctx, HID_INPUT_REPORT, buf, sizeof(buf));
if (ret < 0)
return ret;
/*
* In real world we should reset the original buffer as data might be garbage now,
* but it actually now has the content of 'buf'
*/
data[1] += 5;
return 9;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_multiply_events = {
.hid_device_event = (void *)hid_test_multiply_events,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_infinite_loop_input_report, struct hid_bpf_ctx *hctx,
enum hid_report_type report_type, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 6 /* size */);
__u8 buf[6];
if (!data)
return 0; /* EPERM check */
/*
* we have to use an intermediate buffer as hid_bpf_input_report
* will memset data to \0
*/
__builtin_memcpy(buf, data, sizeof(buf));
/* always forward the request as-is to the device, hid-bpf should prevent
* infinite loops.
* the return value is ignored so the event is passing to userspace.
*/
hid_bpf_try_input_report(hctx, report_type, buf, sizeof(buf));
/* each time we process the event, we increment by one data[1]:
* after each successful call to hid_bpf_try_input_report, buf
* has been memcopied into data by the kernel.
*/
data[1] += 1;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_infinite_loop_input_report = {
.hid_device_event = (void *)hid_test_infinite_loop_input_report,
};
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2022 Benjamin Tissoires
*/
#ifndef __HID_BPF_HELPERS_H
#define __HID_BPF_HELPERS_H
/* "undefine" structs and enums in vmlinux.h, because we "override" them below */
#define bpf_wq bpf_wq___not_used
#define hid_bpf_ctx hid_bpf_ctx___not_used
#define hid_bpf_ops hid_bpf_ops___not_used
#define hid_device hid_device___not_used
#define hid_report_type hid_report_type___not_used
#define hid_class_request hid_class_request___not_used
#define hid_bpf_attach_flags hid_bpf_attach_flags___not_used
#define HID_INPUT_REPORT HID_INPUT_REPORT___not_used
#define HID_OUTPUT_REPORT HID_OUTPUT_REPORT___not_used
#define HID_FEATURE_REPORT HID_FEATURE_REPORT___not_used
#define HID_REPORT_TYPES HID_REPORT_TYPES___not_used
#define HID_REQ_GET_REPORT HID_REQ_GET_REPORT___not_used
#define HID_REQ_GET_IDLE HID_REQ_GET_IDLE___not_used
#define HID_REQ_GET_PROTOCOL HID_REQ_GET_PROTOCOL___not_used
#define HID_REQ_SET_REPORT HID_REQ_SET_REPORT___not_used
#define HID_REQ_SET_IDLE HID_REQ_SET_IDLE___not_used
#define HID_REQ_SET_PROTOCOL HID_REQ_SET_PROTOCOL___not_used
/* do not define kfunc through vmlinux.h as this messes up our custom hack */
#define BPF_NO_KFUNC_PROTOTYPES
#include "vmlinux.h"
#undef bpf_wq
#undef hid_bpf_ctx
#undef hid_bpf_ops
#undef hid_device
#undef hid_report_type
#undef hid_class_request
#undef hid_bpf_attach_flags
#undef HID_INPUT_REPORT
#undef HID_OUTPUT_REPORT
#undef HID_FEATURE_REPORT
#undef HID_REPORT_TYPES
#undef HID_REQ_GET_REPORT
#undef HID_REQ_GET_IDLE
#undef HID_REQ_GET_PROTOCOL
#undef HID_REQ_SET_REPORT
#undef HID_REQ_SET_IDLE
#undef HID_REQ_SET_PROTOCOL
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <linux/const.h>
enum hid_report_type {
HID_INPUT_REPORT = 0,
HID_OUTPUT_REPORT = 1,
HID_FEATURE_REPORT = 2,
HID_REPORT_TYPES,
};
struct hid_device {
unsigned int id;
} __attribute__((preserve_access_index));
struct bpf_wq {
__u64 __opaque[2];
};
struct hid_bpf_ctx {
struct hid_device *hid;
__u32 allocated_size;
union {
__s32 retval;
__s32 size;
};
} __attribute__((preserve_access_index));
enum hid_class_request {
HID_REQ_GET_REPORT = 0x01,
HID_REQ_GET_IDLE = 0x02,
HID_REQ_GET_PROTOCOL = 0x03,
HID_REQ_SET_REPORT = 0x09,
HID_REQ_SET_IDLE = 0x0A,
HID_REQ_SET_PROTOCOL = 0x0B,
};
struct hid_bpf_ops {
int hid_id;
u32 flags;
struct list_head list;
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);
struct hid_device *hdev;
};
#ifndef BPF_F_BEFORE
#define BPF_F_BEFORE (1U << 3)
#endif
/* following are kfuncs exported by HID for HID-BPF */
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
unsigned int offset,
const size_t __sz) __weak __ksym;
extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __weak __ksym;
extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __weak __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) __weak __ksym;
extern int hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx,
__u8 *buf, size_t buf__sz) __weak __ksym;
extern int hid_bpf_input_report(struct hid_bpf_ctx *ctx,
enum hid_report_type type,
__u8 *data,
size_t buf__sz) __weak __ksym;
extern int hid_bpf_try_input_report(struct hid_bpf_ctx *ctx,
enum hid_report_type type,
__u8 *data,
size_t buf__sz) __weak __ksym;
/* bpf_wq implementation */
extern int bpf_wq_init(struct bpf_wq *wq, void *p__map, unsigned int flags) __weak __ksym;
extern int bpf_wq_start(struct bpf_wq *wq, unsigned int flags) __weak __ksym;
extern int bpf_wq_set_callback(struct bpf_wq *wq,
int (*callback_fn)(void *, int *, void *),
unsigned int flags) __weak __ksym;
#endif /* __HID_BPF_HELPERS_H */
@@ -0,0 +1,30 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Runs tests for the HID subsystem
KSELFTEST_SKIP_TEST=4
if ! command -v python3 > /dev/null 2>&1; then
echo "hid-tools: [SKIP] python3 not installed"
exit $KSELFTEST_SKIP_TEST
fi
if ! python3 -c "import pytest" > /dev/null 2>&1; then
echo "hid: [SKIP] pytest module not installed"
exit $KSELFTEST_SKIP_TEST
fi
if ! python3 -c "import pytest_tap" > /dev/null 2>&1; then
echo "hid: [SKIP] pytest_tap module not installed"
exit $KSELFTEST_SKIP_TEST
fi
if ! python3 -c "import hidtools" > /dev/null 2>&1; then
echo "hid: [SKIP] hid-tools module not installed"
exit $KSELFTEST_SKIP_TEST
fi
TARGET=${TARGET:=.}
echo TAP version 13
python3 -u -m pytest $PYTEST_XDIST ./tests/$TARGET --tap-stream --udevd
@@ -0,0 +1,3 @@
# HID tests can be long, so give a little bit more time
# to them
timeout=200
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
# Just to make sphinx-apidoc document this directory
@@ -0,0 +1,432 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2017 Red Hat, Inc.
import dataclasses
import libevdev
import os
import pytest
import shutil
import subprocess
import time
import logging
from .base_device import BaseDevice, EvdevMatch, SysfsFile
from pathlib import Path
from typing import Final, List, Tuple
logger = logging.getLogger("hidtools.test.base")
# application to matches
application_matches: Final = {
# pyright: ignore
"Accelerometer": EvdevMatch(
req_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
]
),
"Game Pad": EvdevMatch( # in systemd, this is a lot more complex, but that will do
requires=[
libevdev.EV_ABS.ABS_X,
libevdev.EV_ABS.ABS_Y,
libevdev.EV_ABS.ABS_RX,
libevdev.EV_ABS.ABS_RY,
libevdev.EV_KEY.BTN_START,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
],
),
"Joystick": EvdevMatch( # in systemd, this is a lot more complex, but that will do
requires=[
libevdev.EV_ABS.ABS_RX,
libevdev.EV_ABS.ABS_RY,
libevdev.EV_KEY.BTN_START,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
],
),
"Key": EvdevMatch(
requires=[
libevdev.EV_KEY.KEY_A,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
libevdev.INPUT_PROP_DIRECT,
libevdev.INPUT_PROP_POINTER,
],
),
"Mouse": EvdevMatch(
requires=[
libevdev.EV_REL.REL_X,
libevdev.EV_REL.REL_Y,
libevdev.EV_KEY.BTN_LEFT,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
],
),
"Pad": EvdevMatch(
requires=[
libevdev.EV_KEY.BTN_0,
],
excludes=[
libevdev.EV_KEY.BTN_TOOL_PEN,
libevdev.EV_KEY.BTN_TOUCH,
libevdev.EV_ABS.ABS_DISTANCE,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
],
),
"Pen": EvdevMatch(
requires=[
libevdev.EV_KEY.BTN_STYLUS,
libevdev.EV_ABS.ABS_X,
libevdev.EV_ABS.ABS_Y,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
],
),
"Stylus": EvdevMatch(
requires=[
libevdev.EV_KEY.BTN_STYLUS,
libevdev.EV_ABS.ABS_X,
libevdev.EV_ABS.ABS_Y,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
],
),
"Touch Pad": EvdevMatch(
requires=[
libevdev.EV_KEY.BTN_LEFT,
libevdev.EV_ABS.ABS_X,
libevdev.EV_ABS.ABS_Y,
],
excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
req_properties=[
libevdev.INPUT_PROP_POINTER,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
],
),
"Touch Screen": EvdevMatch(
requires=[
libevdev.EV_KEY.BTN_TOUCH,
libevdev.EV_ABS.ABS_X,
libevdev.EV_ABS.ABS_Y,
],
excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
req_properties=[
libevdev.INPUT_PROP_DIRECT,
],
excl_properties=[
libevdev.INPUT_PROP_ACCELEROMETER,
],
),
}
class UHIDTestDevice(BaseDevice):
def __init__(self, name, application, rdesc_str=None, rdesc=None, input_info=None):
super().__init__(name, application, rdesc_str, rdesc, input_info)
self.application_matches = application_matches
if name is None:
name = f"uhid test {self.__class__.__name__}"
if not name.startswith("uhid test "):
name = "uhid test " + self.name
self.name = name
@dataclasses.dataclass
class HidBpf:
object_name: str
has_rdesc_fixup: bool
@dataclasses.dataclass
class KernelModule:
driver_name: str
module_name: str
class BaseTestCase:
class TestUhid(object):
syn_event = libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) # type: ignore
key_event = libevdev.InputEvent(libevdev.EV_KEY) # type: ignore
abs_event = libevdev.InputEvent(libevdev.EV_ABS) # type: ignore
rel_event = libevdev.InputEvent(libevdev.EV_REL) # type: ignore
msc_event = libevdev.InputEvent(libevdev.EV_MSC.MSC_SCAN) # type: ignore
# List of kernel modules to load before starting the test
# if any module is not available (not compiled), the test will skip.
# Each element is a KernelModule object, for example
# KernelModule("playstation", "hid-playstation")
kernel_modules: List[KernelModule] = []
# List of in kernel HID-BPF object files to load
# before starting the test
# Any existing pre-loaded HID-BPF module will be removed
# before the ones in this list will be manually loaded.
# Each Element is a HidBpf object, for example
# 'HidBpf("xppen-ArtistPro16Gen2.bpf.o", True)'
# If 'has_rdesc_fixup' is True, the test needs to wait
# for one unbind and rebind before it can be sure the kernel is
# ready
hid_bpfs: List[HidBpf] = []
def assertInputEventsIn(self, expected_events, effective_events):
effective_events = effective_events.copy()
for ev in expected_events:
assert ev in effective_events
effective_events.remove(ev)
return effective_events
def assertInputEvents(self, expected_events, effective_events):
remaining = self.assertInputEventsIn(expected_events, effective_events)
assert remaining == []
@classmethod
def debug_reports(cls, reports, uhdev=None, events=None):
data = [" ".join([f"{v:02x}" for v in r]) for r in reports]
if uhdev is not None:
human_data = [
uhdev.parsed_rdesc.format_report(r, split_lines=True)
for r in reports
]
try:
human_data = [
f'\n\t {" " * h.index("/")}'.join(h.split("\n"))
for h in human_data
]
except ValueError:
# '/' not found: not a numbered report
human_data = ["\n\t ".join(h.split("\n")) for h in human_data]
data = [f"{d}\n\t ====> {h}" for d, h in zip(data, human_data)]
reports = data
if len(reports) == 1:
print("sending 1 report:")
else:
print(f"sending {len(reports)} reports:")
for report in reports:
print("\t", report)
if events is not None:
print("events received:", events)
def create_device(self):
raise Exception("please reimplement me in subclasses")
def _load_kernel_module(self, kernel_driver, kernel_module):
sysfs_path = Path("/sys/bus/hid/drivers")
if kernel_driver is not None:
sysfs_path /= kernel_driver
else:
# special case for when testing all available modules:
# we don't know beforehand the name of the module from modinfo
sysfs_path = Path("/sys/module") / kernel_module.replace("-", "_")
if not sysfs_path.exists():
ret = subprocess.run(["/usr/sbin/modprobe", kernel_module])
if ret.returncode != 0:
pytest.skip(
f"module {kernel_module} could not be loaded, skipping the test"
)
@pytest.fixture()
def load_kernel_module(self):
for k in self.kernel_modules:
self._load_kernel_module(k.driver_name, k.module_name)
yield
def load_hid_bpfs(self):
# this function will only work when run in the kernel tree
script_dir = Path(os.path.dirname(os.path.realpath(__file__)))
root_dir = (script_dir / "../../../../..").resolve()
bpf_dir = root_dir / "drivers/hid/bpf/progs"
if not bpf_dir.exists():
pytest.skip("looks like we are not in the kernel tree, skipping")
udev_hid_bpf = shutil.which("udev-hid-bpf")
if not udev_hid_bpf:
pytest.skip("udev-hid-bpf not found in $PATH, skipping")
wait = any(b.has_rdesc_fixup for b in self.hid_bpfs)
for hid_bpf in self.hid_bpfs:
# We need to start `udev-hid-bpf` in the background
# and dispatch uhid events in case the kernel needs
# to fetch features on the device
process = subprocess.Popen(
[
"udev-hid-bpf",
"--verbose",
"add",
str(self.uhdev.sys_path),
str(bpf_dir / hid_bpf.object_name),
],
)
while process.poll() is None:
self.uhdev.dispatch(1)
if process.returncode != 0:
pytest.fail(
f"Couldn't insert hid-bpf program '{hid_bpf}', marking the test as failed"
)
if wait:
# the HID-BPF program exports a rdesc fixup, so it needs to be
# unbound by the kernel and then rebound.
# Ensure we get the bound event exactly 2 times (one for the normal
# uhid loading, and then the reload from HID-BPF)
now = time.time()
while self.uhdev.kernel_ready_count < 2 and time.time() - now < 2:
self.uhdev.dispatch(1)
if self.uhdev.kernel_ready_count < 2:
pytest.fail(
f"Couldn't insert hid-bpf programs, marking the test as failed"
)
def unload_hid_bpfs(self):
ret = subprocess.run(
["udev-hid-bpf", "--verbose", "remove", str(self.uhdev.sys_path)],
)
if ret.returncode != 0:
pytest.fail(
f"Couldn't unload hid-bpf programs, marking the test as failed"
)
@pytest.fixture()
def new_uhdev(self, load_kernel_module):
return self.create_device()
def assertName(self, uhdev):
evdev = uhdev.get_evdev()
assert uhdev.name in evdev.name
@pytest.fixture(autouse=True)
def context(self, new_uhdev, request):
try:
with HIDTestUdevRule.instance():
with new_uhdev as self.uhdev:
for skip_cond in request.node.iter_markers("skip_if_uhdev"):
test, message, *rest = skip_cond.args
if test(self.uhdev):
pytest.skip(message)
self.uhdev.create_kernel_device()
now = time.time()
while not self.uhdev.is_ready() and time.time() - now < 5:
self.uhdev.dispatch(1)
if self.hid_bpfs:
self.load_hid_bpfs()
if self.uhdev.get_evdev() is None:
logger.warning(
f"available list of input nodes: (default application is '{self.uhdev.application}')"
)
logger.warning(self.uhdev.input_nodes)
yield
if self.hid_bpfs:
self.unload_hid_bpfs()
self.uhdev = None
except PermissionError:
pytest.skip("Insufficient permissions, run me as root")
@pytest.fixture(autouse=True)
def check_taint(self):
# we are abusing SysfsFile here, it's in /proc, but meh
taint_file = SysfsFile("/proc/sys/kernel/tainted")
taint = taint_file.int_value
yield
assert taint_file.int_value == taint
def test_creation(self):
"""Make sure the device gets processed by the kernel and creates
the expected application input node.
If this fail, there is something wrong in the device report
descriptors."""
uhdev = self.uhdev
assert uhdev is not None
assert uhdev.get_evdev() is not None
self.assertName(uhdev)
assert len(uhdev.next_sync_events()) == 0
assert uhdev.get_evdev() is not None
class HIDTestUdevRule(object):
_instance = None
"""
A context-manager compatible class that sets up our udev rules file and
deletes it on context exit.
This class is tailored to our test setup: it only sets up the udev rule
on the **second** context and it cleans it up again on the last context
removed. This matches the expected pytest setup: we enter a context for
the session once, then once for each test (the first of which will
trigger the udev rule) and once the last test exited and the session
exited, we clean up after ourselves.
"""
def __init__(self):
self.refs = 0
self.rulesfile = None
def __enter__(self):
self.refs += 1
if self.refs == 2 and self.rulesfile is None:
self.create_udev_rule()
self.reload_udev_rules()
def __exit__(self, exc_type, exc_value, traceback):
self.refs -= 1
if self.refs == 0 and self.rulesfile:
os.remove(self.rulesfile.name)
self.reload_udev_rules()
def reload_udev_rules(self):
subprocess.run("udevadm control --reload-rules".split())
subprocess.run("systemd-hwdb update".split())
def create_udev_rule(self):
import tempfile
os.makedirs("/run/udev/rules.d", exist_ok=True)
with tempfile.NamedTemporaryFile(
prefix="91-uhid-test-device-REMOVEME-",
suffix=".rules",
mode="w+",
dir="/run/udev/rules.d",
delete=False,
) as f:
f.write(
"""
KERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1"
KERNELS=="*hid*", ENV{HID_NAME}=="*uhid test *", ENV{HID_BPF_IGNORE_DEVICE}="1"
KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"
"""
)
self.rulesfile = f
@classmethod
def instance(cls):
if not cls._instance:
cls._instance = HIDTestUdevRule()
return cls._instance
@@ -0,0 +1,448 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2017 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import dataclasses
import fcntl
import functools
import libevdev
import os
import threading
try:
import pyudev
except ImportError:
raise ImportError("UHID is not supported due to missing pyudev dependency")
import logging
import hidtools.hid as hid
from hidtools.uhid import UHIDDevice
from hidtools.util import BusType
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union
logger = logging.getLogger("hidtools.device.base_device")
class SysfsFile(object):
def __init__(self, path):
self.path = path
def __set_value(self, value):
with open(self.path, "w") as f:
return f.write(f"{value}\n")
def __get_value(self):
with open(self.path) as f:
return f.read().strip()
@property
def int_value(self) -> int:
return int(self.__get_value())
@int_value.setter
def int_value(self, v: int) -> None:
self.__set_value(v)
@property
def str_value(self) -> str:
return self.__get_value()
@str_value.setter
def str_value(self, v: str) -> None:
self.__set_value(v)
class LED(object):
def __init__(self, sys_path):
self.max_brightness = SysfsFile(sys_path / "max_brightness").int_value
self.__brightness = SysfsFile(sys_path / "brightness")
@property
def brightness(self) -> int:
return self.__brightness.int_value
@brightness.setter
def brightness(self, value: int) -> None:
self.__brightness.int_value = value
class PowerSupply(object):
"""Represents Linux power_supply_class sysfs nodes."""
def __init__(self, sys_path):
self._capacity = SysfsFile(sys_path / "capacity")
self._status = SysfsFile(sys_path / "status")
self._type = SysfsFile(sys_path / "type")
@property
def capacity(self) -> int:
return self._capacity.int_value
@property
def status(self) -> str:
return self._status.str_value
@property
def type(self) -> str:
return self._type.str_value
@dataclasses.dataclass
class HidReadiness:
is_ready: bool = False
count: int = 0
class HIDIsReady(object):
"""
Companion class that binds to a kernel mechanism
and that allows to know when a uhid device is ready or not.
See :meth:`is_ready` for details.
"""
def __init__(self: "HIDIsReady", uhid: UHIDDevice) -> None:
self.uhid = uhid
def is_ready(self: "HIDIsReady") -> HidReadiness:
"""
Overwrite in subclasses: should return True or False whether
the attached uhid device is ready or not.
"""
return HidReadiness()
class UdevHIDIsReady(HIDIsReady):
_pyudev_context: ClassVar[Optional[pyudev.Context]] = None
_pyudev_monitor: ClassVar[Optional[pyudev.Monitor]] = None
_uhid_devices: ClassVar[Dict[int, HidReadiness]] = {}
def __init__(self: "UdevHIDIsReady", uhid: UHIDDevice) -> None:
super().__init__(uhid)
self._init_pyudev()
@classmethod
def _init_pyudev(cls: Type["UdevHIDIsReady"]) -> None:
if cls._pyudev_context is None:
cls._pyudev_context = pyudev.Context()
cls._pyudev_monitor = pyudev.Monitor.from_netlink(cls._pyudev_context)
cls._pyudev_monitor.filter_by("hid")
cls._pyudev_monitor.start()
UHIDDevice._append_fd_to_poll(
cls._pyudev_monitor.fileno(), cls._cls_udev_event_callback
)
@classmethod
def _cls_udev_event_callback(cls: Type["UdevHIDIsReady"]) -> None:
if cls._pyudev_monitor is None:
return
event: pyudev.Device
for event in iter(functools.partial(cls._pyudev_monitor.poll, 0.02), None):
if event.action not in ["bind", "remove", "unbind"]:
return
logger.debug(f"udev event: {event.action} -> {event}")
id = int(event.sys_path.strip().split(".")[-1], 16)
readiness = cls._uhid_devices.setdefault(id, HidReadiness())
ready = event.action == "bind"
if not readiness.is_ready and ready:
readiness.count += 1
readiness.is_ready = ready
def is_ready(self: "UdevHIDIsReady") -> HidReadiness:
try:
return self._uhid_devices[self.uhid.hid_id]
except KeyError:
return HidReadiness()
class EvdevMatch(object):
def __init__(
self: "EvdevMatch",
*,
requires: List[Any] = [],
excludes: List[Any] = [],
req_properties: List[Any] = [],
excl_properties: List[Any] = [],
) -> None:
self.requires = requires
self.excludes = excludes
self.req_properties = req_properties
self.excl_properties = excl_properties
def is_a_match(self: "EvdevMatch", evdev: libevdev.Device) -> bool:
for m in self.requires:
if not evdev.has(m):
return False
for m in self.excludes:
if evdev.has(m):
return False
for p in self.req_properties:
if not evdev.has_property(p):
return False
for p in self.excl_properties:
if evdev.has_property(p):
return False
return True
class EvdevDevice(object):
"""
Represents an Evdev node and its properties.
This is a stub for the libevdev devices, as they are relying on
uevent to get the data, saving us some ioctls to fetch the names
and properties.
"""
def __init__(self: "EvdevDevice", sysfs: Path) -> None:
self.sysfs = sysfs
self.event_node: Any = None
self.libevdev: Optional[libevdev.Device] = None
self.uevents = {}
# all of the interesting properties are stored in the input uevent, so in the parent
# so convert the uevent file of the parent input node into a dict
with open(sysfs.parent / "uevent") as f:
for line in f.readlines():
key, value = line.strip().split("=")
self.uevents[key] = value.strip('"')
# we open all evdev nodes in order to not miss any event
self.open()
@property
def name(self: "EvdevDevice") -> str:
assert "NAME" in self.uevents
return self.uevents["NAME"]
@property
def evdev(self: "EvdevDevice") -> Path:
return Path("/dev/input") / self.sysfs.name
def matches_application(
self: "EvdevDevice", application: str, matches: Dict[str, EvdevMatch]
) -> bool:
if self.libevdev is None:
return False
if application in matches:
return matches[application].is_a_match(self.libevdev)
logger.error(
f"application '{application}' is unknown, please update/fix hid-tools"
)
assert False # hid-tools likely needs an update
def open(self: "EvdevDevice") -> libevdev.Device:
self.event_node = open(self.evdev, "rb")
self.libevdev = libevdev.Device(self.event_node)
assert self.libevdev.fd is not None
fd = self.libevdev.fd.fileno()
flag = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK)
return self.libevdev
def close(self: "EvdevDevice") -> None:
if self.libevdev is not None and self.libevdev.fd is not None:
self.libevdev.fd.close()
self.libevdev = None
if self.event_node is not None:
self.event_node.close()
self.event_node = None
class BaseDevice(UHIDDevice):
# default _application_matches that matches nothing. This needs
# to be set in the subclasses to have get_evdev() working
_application_matches: Dict[str, EvdevMatch] = {}
def __init__(
self,
name,
application,
rdesc_str: Optional[str] = None,
rdesc: Optional[Union[hid.ReportDescriptor, str, bytes]] = None,
input_info=None,
) -> None:
self._kernel_is_ready: HIDIsReady = UdevHIDIsReady(self)
if rdesc_str is None and rdesc is None:
raise Exception("Please provide at least a rdesc or rdesc_str")
super().__init__()
if name is None:
name = f"uhid gamepad test {self.__class__.__name__}"
if input_info is None:
input_info = (BusType.USB, 1, 2)
self.name = name
self.info = input_info
self.default_reportID = None
self.opened = False
self.started = False
self.application = application
self._input_nodes: Optional[list[EvdevDevice]] = None
if rdesc is None:
assert rdesc_str is not None
self.rdesc = hid.ReportDescriptor.from_human_descr(rdesc_str) # type: ignore
else:
self.rdesc = rdesc # type: ignore
@property
def power_supply_class(self: "BaseDevice") -> Optional[PowerSupply]:
ps = self.walk_sysfs("power_supply", "power_supply/*")
if ps is None or len(ps) < 1:
return None
return PowerSupply(ps[0])
@property
def led_classes(self: "BaseDevice") -> List[LED]:
leds = self.walk_sysfs("led", "**/max_brightness")
if leds is None:
return []
return [LED(led.parent) for led in leds]
@property
def kernel_is_ready(self: "BaseDevice") -> bool:
return self._kernel_is_ready.is_ready().is_ready and self.started
@property
def kernel_ready_count(self: "BaseDevice") -> int:
return self._kernel_is_ready.is_ready().count
@property
def input_nodes(self: "BaseDevice") -> List[EvdevDevice]:
if self._input_nodes is not None:
return self._input_nodes
if not self.kernel_is_ready or not self.started:
return []
# Starting with kernel v6.16, an event is emitted when
# userspace opens a kernel device, and for some devices
# this translates into a SET_REPORT.
# Because EvdevDevice(path) opens every single evdev node
# we need to have a separate thread to process the incoming
# SET_REPORT or we end up having to wait for the kernel
# timeout of 5 seconds.
done = False
def dispatch():
while not done:
self.dispatch(1)
t = threading.Thread(target=dispatch)
t.start()
self._input_nodes = [
EvdevDevice(path)
for path in self.walk_sysfs("input", "input/input*/event*")
]
done = True
t.join()
return self._input_nodes
def match_evdev_rule(self, application, evdev):
"""Replace this in subclasses if the device has multiple reports
of the same type and we need to filter based on the actual evdev
node.
returning True will append the corresponding report to
`self.input_nodes[type]`
returning False will ignore this report / type combination
for the device.
"""
return True
def open(self):
self.opened = True
def _close_all_opened_evdev(self):
if self._input_nodes is not None:
for e in self._input_nodes:
e.close()
def __del__(self):
self._close_all_opened_evdev()
def close(self):
self.opened = False
def start(self, flags):
self.started = True
def stop(self):
self.started = False
self._close_all_opened_evdev()
def next_sync_events(self, application=None):
evdev = self.get_evdev(application)
if evdev is not None:
return list(evdev.events())
return []
@property
def application_matches(self: "BaseDevice") -> Dict[str, EvdevMatch]:
return self._application_matches
@application_matches.setter
def application_matches(self: "BaseDevice", data: Dict[str, EvdevMatch]) -> None:
self._application_matches = data
def get_evdev(self, application=None):
if application is None:
application = self.application
if len(self.input_nodes) == 0:
return None
assert self._input_nodes is not None
if len(self._input_nodes) == 1:
evdev = self._input_nodes[0]
if self.match_evdev_rule(application, evdev.libevdev):
return evdev.libevdev
else:
for _evdev in self._input_nodes:
if _evdev.matches_application(application, self.application_matches):
if self.match_evdev_rule(application, _evdev.libevdev):
return _evdev.libevdev
def is_ready(self):
"""Returns whether a UHID device is ready. Can be overwritten in
subclasses to add extra conditions on when to consider a UHID
device ready. This can be:
- we need to wait on different types of input devices to be ready
(Touch Screen and Pen for example)
- we need to have at least 4 LEDs present
(len(self.uhdev.leds_classes) == 4)
- or any other combinations"""
return self.kernel_is_ready
@@ -0,0 +1,238 @@
# SPDX-License-Identifier: GPL-2.0
import libevdev
from .base_device import BaseDevice
from hidtools.util import BusType
class InvalidHIDCommunication(Exception):
pass
class GamepadData(object):
pass
class AxisMapping(object):
"""Represents a mapping between a HID type
and an evdev event"""
def __init__(self, hid, evdev=None):
self.hid = hid.lower()
if evdev is None:
evdev = f"ABS_{hid.upper()}"
self.evdev = libevdev.evbit("EV_ABS", evdev)
class BaseGamepad(BaseDevice):
buttons_map = {
1: "BTN_SOUTH",
2: "BTN_EAST",
3: "BTN_C",
4: "BTN_NORTH",
5: "BTN_WEST",
6: "BTN_Z",
7: "BTN_TL",
8: "BTN_TR",
9: "BTN_TL2",
10: "BTN_TR2",
11: "BTN_SELECT",
12: "BTN_START",
13: "BTN_MODE",
14: "BTN_THUMBL",
15: "BTN_THUMBR",
}
axes_map = {
"left_stick": {
"x": AxisMapping("x"),
"y": AxisMapping("y"),
},
"right_stick": {
"x": AxisMapping("z"),
"y": AxisMapping("Rz"),
},
}
def __init__(self, rdesc, application="Game Pad", name=None, input_info=None):
assert rdesc is not None
super().__init__(name, application, input_info=input_info, rdesc=rdesc)
self.buttons = (1, 2, 3)
self._buttons = {}
self.left = (127, 127)
self.right = (127, 127)
self.hat_switch = 15
assert self.parsed_rdesc is not None
self.fields = []
for r in self.parsed_rdesc.input_reports.values():
if r.application_name == self.application:
self.fields.extend([f.usage_name for f in r])
def store_axes(self, which, gamepad, data):
amap = self.axes_map[which]
x, y = data
setattr(gamepad, amap["x"].hid, x)
setattr(gamepad, amap["y"].hid, y)
def create_report(
self,
*,
left=(None, None),
right=(None, None),
hat_switch=None,
buttons=None,
reportID=None,
application="Game Pad",
):
"""
Return an input report for this device.
:param left: a tuple of absolute (x, y) value of the left joypad
where ``None`` is "leave unchanged"
:param right: a tuple of absolute (x, y) value of the right joypad
where ``None`` is "leave unchanged"
:param hat_switch: an absolute angular value of the hat switch
(expressed in 1/8 of circle, 0 being North, 2 East)
where ``None`` is "leave unchanged"
:param buttons: a dict of index/bool for the button states,
where ``None`` is "leave unchanged"
:param reportID: the numeric report ID for this report, if needed
:param application: the application used to report the values
"""
if buttons is not None:
for i, b in buttons.items():
if i not in self.buttons:
raise InvalidHIDCommunication(
f"button {i} is not part of this {self.application}"
)
if b is not None:
self._buttons[i] = b
def replace_none_in_tuple(item, default):
if item is None:
item = (None, None)
if None in item:
if item[0] is None:
item = (default[0], item[1])
if item[1] is None:
item = (item[0], default[1])
return item
right = replace_none_in_tuple(right, self.right)
self.right = right
left = replace_none_in_tuple(left, self.left)
self.left = left
if hat_switch is None:
hat_switch = self.hat_switch
else:
self.hat_switch = hat_switch
reportID = reportID or self.default_reportID
gamepad = GamepadData()
for i, b in self._buttons.items():
gamepad.__setattr__(f"b{i}", int(b) if b is not None else 0)
self.store_axes("left_stick", gamepad, left)
self.store_axes("right_stick", gamepad, right)
gamepad.hatswitch = hat_switch # type: ignore ### gamepad is by default empty
return super().create_report(
gamepad, reportID=reportID, application=application
)
def event(
self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
):
"""
Send an input event on the default report ID.
:param left: a tuple of absolute (x, y) value of the left joypad
where ``None`` is "leave unchanged"
:param right: a tuple of absolute (x, y) value of the right joypad
where ``None`` is "leave unchanged"
:param hat_switch: an absolute angular value of the hat switch
where ``None`` is "leave unchanged"
:param buttons: a dict of index/bool for the button states,
where ``None`` is "leave unchanged"
"""
r = self.create_report(
left=left, right=right, hat_switch=hat_switch, buttons=buttons
)
self.call_input_event(r)
return [r]
class JoystickGamepad(BaseGamepad):
buttons_map = {
1: "BTN_TRIGGER",
2: "BTN_THUMB",
3: "BTN_THUMB2",
4: "BTN_TOP",
5: "BTN_TOP2",
6: "BTN_PINKIE",
7: "BTN_BASE",
8: "BTN_BASE2",
9: "BTN_BASE3",
10: "BTN_BASE4",
11: "BTN_BASE5",
12: "BTN_BASE6",
13: "BTN_DEAD",
}
axes_map = {
"left_stick": {
"x": AxisMapping("x"),
"y": AxisMapping("y"),
},
"right_stick": {
"x": AxisMapping("rudder"),
"y": AxisMapping("throttle"),
},
}
def __init__(self, rdesc, application="Joystick", name=None, input_info=None):
super().__init__(rdesc, application, name, input_info)
def create_report(
self,
*,
left=(None, None),
right=(None, None),
hat_switch=None,
buttons=None,
reportID=None,
application=None,
):
"""
Return an input report for this device.
:param left: a tuple of absolute (x, y) value of the left joypad
where ``None`` is "leave unchanged"
:param right: a tuple of absolute (x, y) value of the right joypad
where ``None`` is "leave unchanged"
:param hat_switch: an absolute angular value of the hat switch
where ``None`` is "leave unchanged"
:param buttons: a dict of index/bool for the button states,
where ``None`` is "leave unchanged"
:param reportID: the numeric report ID for this report, if needed
:param application: the application for this report, if needed
"""
if application is None:
application = "Joystick"
return super().create_report(
left=left,
right=right,
hat_switch=hat_switch,
buttons=buttons,
reportID=reportID,
application=application,
)
def store_right_joystick(self, gamepad, data):
gamepad.rudder, gamepad.throttle = data
@@ -0,0 +1,95 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2017 Red Hat, Inc.
from packaging.version import Version
import platform
import pytest
import re
import resource
import subprocess
from .base import HIDTestUdevRule
from pathlib import Path
@pytest.fixture(autouse=True)
def hidtools_version_check():
HIDTOOLS_VERSION = "0.12"
try:
import hidtools
version = hidtools.__version__ # type: ignore
if Version(version) < Version(HIDTOOLS_VERSION):
pytest.skip(reason=f"have hidtools {version}, require >={HIDTOOLS_VERSION}")
except Exception:
pytest.skip(reason=f"hidtools >={HIDTOOLS_VERSION} required")
# See the comment in HIDTestUdevRule, this doesn't set up but it will clean
# up once the last test exited.
@pytest.fixture(autouse=True, scope="session")
def udev_rules_session_setup():
with HIDTestUdevRule.instance():
yield
@pytest.fixture(autouse=True, scope="session")
def setup_rlimit():
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
@pytest.fixture(autouse=True, scope="session")
def start_udevd(pytestconfig):
if pytestconfig.getoption("udevd"):
import subprocess
with subprocess.Popen("/usr/lib/systemd/systemd-udevd") as proc:
yield
proc.kill()
else:
yield
def pytest_configure(config):
config.addinivalue_line(
"markers",
"skip_if_uhdev(condition, message): mark test to skip if the condition on the uhdev device is met",
)
# Generate the list of modules and modaliases
# for the tests that need to be parametrized with those
def pytest_generate_tests(metafunc):
if "usbVidPid" in metafunc.fixturenames:
modules = (
Path("/lib/modules/")
/ platform.uname().release
/ "kernel"
/ "drivers"
/ "hid"
)
modalias_re = re.compile(r"alias:\s+hid:b0003g.*v([0-9a-fA-F]+)p([0-9a-fA-F]+)")
params = []
ids = []
for module in modules.glob("*.ko"):
p = subprocess.run(
["modinfo", module], capture_output=True, check=True, encoding="utf-8"
)
for line in p.stdout.split("\n"):
m = modalias_re.match(line)
if m is not None:
vid, pid = m.groups()
vid = int(vid, 16)
pid = int(pid, 16)
params.append([module.name.replace(".ko", ""), vid, pid])
ids.append(f"{module.name} {vid:04x}:{pid:04x}")
metafunc.parametrize("usbVidPid", params, ids=ids)
def pytest_addoption(parser):
parser.addoption("--udevd", action="store_true", default=False)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,441 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2019 Red Hat, Inc.
#
from .test_keyboard import ArrayKeyboard, TestArrayKeyboard
from hidtools.util import BusType
from . import base
import libevdev
import logging
logger = logging.getLogger("hidtools.test.apple-keyboard")
KERNEL_MODULE = base.KernelModule("apple", "hid-apple")
class KbdData(object):
pass
class AppleKeyboard(ArrayKeyboard):
# fmt: off
report_descriptor = [
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x06, # Usage (Keyboard)
0xa1, 0x01, # Collection (Application)
0x85, 0x01, # .Report ID (1)
0x05, 0x07, # .Usage Page (Keyboard)
0x19, 0xe0, # .Usage Minimum (224)
0x29, 0xe7, # .Usage Maximum (231)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x08, # .Report Count (8)
0x81, 0x02, # .Input (Data,Var,Abs)
0x75, 0x08, # .Report Size (8)
0x95, 0x01, # .Report Count (1)
0x81, 0x01, # .Input (Cnst,Arr,Abs)
0x75, 0x01, # .Report Size (1)
0x95, 0x05, # .Report Count (5)
0x05, 0x08, # .Usage Page (LEDs)
0x19, 0x01, # .Usage Minimum (1)
0x29, 0x05, # .Usage Maximum (5)
0x91, 0x02, # .Output (Data,Var,Abs)
0x75, 0x03, # .Report Size (3)
0x95, 0x01, # .Report Count (1)
0x91, 0x01, # .Output (Cnst,Arr,Abs)
0x75, 0x08, # .Report Size (8)
0x95, 0x06, # .Report Count (6)
0x15, 0x00, # .Logical Minimum (0)
0x26, 0xff, 0x00, # .Logical Maximum (255)
0x05, 0x07, # .Usage Page (Keyboard)
0x19, 0x00, # .Usage Minimum (0)
0x2a, 0xff, 0x00, # .Usage Maximum (255)
0x81, 0x00, # .Input (Data,Arr,Abs)
0xc0, # End Collection
0x05, 0x0c, # Usage Page (Consumer Devices)
0x09, 0x01, # Usage (Consumer Control)
0xa1, 0x01, # Collection (Application)
0x85, 0x47, # .Report ID (71)
0x05, 0x01, # .Usage Page (Generic Desktop)
0x09, 0x06, # .Usage (Keyboard)
0xa1, 0x02, # .Collection (Logical)
0x05, 0x06, # ..Usage Page (Generic Device Controls)
0x09, 0x20, # ..Usage (Battery Strength)
0x15, 0x00, # ..Logical Minimum (0)
0x26, 0xff, 0x00, # ..Logical Maximum (255)
0x75, 0x08, # ..Report Size (8)
0x95, 0x01, # ..Report Count (1)
0x81, 0x02, # ..Input (Data,Var,Abs)
0xc0, # .End Collection
0xc0, # End Collection
0x05, 0x0c, # Usage Page (Consumer Devices)
0x09, 0x01, # Usage (Consumer Control)
0xa1, 0x01, # Collection (Application)
0x85, 0x11, # .Report ID (17)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x03, # .Report Count (3)
0x81, 0x01, # .Input (Cnst,Arr,Abs)
0x75, 0x01, # .Report Size (1)
0x95, 0x01, # .Report Count (1)
0x05, 0x0c, # .Usage Page (Consumer Devices)
0x09, 0xb8, # .Usage (Eject)
0x81, 0x02, # .Input (Data,Var,Abs)
0x06, 0xff, 0x00, # .Usage Page (Vendor Usage Page 0xff)
0x09, 0x03, # .Usage (Vendor Usage 0x03)
0x81, 0x02, # .Input (Data,Var,Abs)
0x75, 0x01, # .Report Size (1)
0x95, 0x03, # .Report Count (3)
0x81, 0x01, # .Input (Cnst,Arr,Abs)
0x05, 0x0c, # .Usage Page (Consumer Devices)
0x85, 0x12, # .Report ID (18)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x01, # .Report Count (1)
0x09, 0xcd, # .Usage (Play/Pause)
0x81, 0x02, # .Input (Data,Var,Abs)
0x09, 0xb3, # .Usage (Fast Forward)
0x81, 0x02, # .Input (Data,Var,Abs)
0x09, 0xb4, # .Usage (Rewind)
0x81, 0x02, # .Input (Data,Var,Abs)
0x09, 0xb5, # .Usage (Scan Next Track)
0x81, 0x02, # .Input (Data,Var,Abs)
0x09, 0xb6, # .Usage (Scan Previous Track)
0x81, 0x02, # .Input (Data,Var,Abs)
0x81, 0x01, # .Input (Cnst,Arr,Abs)
0x81, 0x01, # .Input (Cnst,Arr,Abs)
0x81, 0x01, # .Input (Cnst,Arr,Abs)
0x85, 0x13, # .Report ID (19)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x01, # .Report Count (1)
0x06, 0x01, 0xff, # .Usage Page (Vendor Usage Page 0xff01)
0x09, 0x0a, # .Usage (Vendor Usage 0x0a)
0x81, 0x02, # .Input (Data,Var,Abs)
0x06, 0x01, 0xff, # .Usage Page (Vendor Usage Page 0xff01)
0x09, 0x0c, # .Usage (Vendor Usage 0x0c)
0x81, 0x22, # .Input (Data,Var,Abs,NoPref)
0x75, 0x01, # .Report Size (1)
0x95, 0x06, # .Report Count (6)
0x81, 0x01, # .Input (Cnst,Arr,Abs)
0x85, 0x09, # .Report ID (9)
0x09, 0x0b, # .Usage (Vendor Usage 0x0b)
0x75, 0x08, # .Report Size (8)
0x95, 0x01, # .Report Count (1)
0xb1, 0x02, # .Feature (Data,Var,Abs)
0x75, 0x08, # .Report Size (8)
0x95, 0x02, # .Report Count (2)
0xb1, 0x01, # .Feature (Cnst,Arr,Abs)
0xc0, # End Collection
]
# fmt: on
def __init__(
self,
rdesc=report_descriptor,
name="Apple Wireless Keyboard",
input_info=(BusType.BLUETOOTH, 0x05AC, 0x0256),
):
super().__init__(rdesc, name, input_info)
self.default_reportID = 1
def send_fn_state(self, state):
data = KbdData()
setattr(data, "0xff0003", state)
r = self.create_report(data, reportID=17)
self.call_input_event(r)
return [r]
class TestAppleKeyboard(TestArrayKeyboard):
kernel_modules = [KERNEL_MODULE]
def create_device(self):
return AppleKeyboard()
def test_single_function_key(self):
"""check for function key reliability."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.event(["F4"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
def test_single_fn_function_key(self):
"""check for function key reliability with the fn key."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.send_fn_state(1)
r.extend(uhdev.event(["F4"]))
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
r = uhdev.send_fn_state(0)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
def test_single_fn_function_key_release_first(self):
"""check for function key reliability with the fn key."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.send_fn_state(1)
r.extend(uhdev.event(["F4"]))
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
r = uhdev.send_fn_state(0)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
def test_single_fn_function_key_inverted(self):
"""check for function key reliability with the fn key."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.event(["F4"])
r.extend(uhdev.send_fn_state(1))
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
r = uhdev.send_fn_state(0)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
def test_multiple_fn_function_key_release_first(self):
"""check for function key reliability with the fn key."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.send_fn_state(1)
r.extend(uhdev.event(["F4"]))
r.extend(uhdev.event(["F4", "F6"]))
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
r = uhdev.event(["F6"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
r = uhdev.send_fn_state(0)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
def test_multiple_fn_function_key_release_between(self):
"""check for function key reliability with the fn key."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
# press F4
r = uhdev.event(["F4"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
# press Fn key
r = uhdev.send_fn_state(1)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
# keep F4 and press F6
r = uhdev.event(["F4", "F6"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
# keep F4 and F6
r = uhdev.event(["F4", "F6"])
expected = []
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
# release Fn key and all keys
r = uhdev.send_fn_state(0)
r.extend(uhdev.event([]))
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
def test_single_pageup_key_release_first(self):
"""check for function key reliability with the [page] up key."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.send_fn_state(1)
r.extend(uhdev.event(["UpArrow"]))
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_PAGEUP, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 1
assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
r = uhdev.send_fn_state(0)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 1
assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_PAGEUP, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
@@ -0,0 +1,665 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2019 Red Hat, Inc.
#
from . import base
import libevdev
import pytest
from .base_gamepad import BaseGamepad, JoystickGamepad, AxisMapping
from hidtools.util import BusType
from .base import HidBpf
import logging
logger = logging.getLogger("hidtools.test.gamepad")
class BaseTest:
class TestGamepad(base.BaseTestCase.TestUhid):
@pytest.fixture(autouse=True)
def send_initial_state(self):
"""send an empty report to initialize the axes"""
uhdev = self.uhdev
r = uhdev.event()
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
def assert_button(self, button):
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
buttons = {}
key = libevdev.evbit(uhdev.buttons_map[button])
buttons[button] = True
r = uhdev.event(buttons=buttons)
expected_event = libevdev.InputEvent(key, 1)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn((syn_event, expected_event), events)
assert evdev.value[key] == 1
buttons[button] = False
r = uhdev.event(buttons=buttons)
expected_event = libevdev.InputEvent(key, 0)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn((syn_event, expected_event), events)
assert evdev.value[key] == 0
def test_buttons(self):
"""check for button reliability."""
uhdev = self.uhdev
for b in uhdev.buttons:
self.assert_button(b)
def test_dual_buttons(self):
"""check for button reliability when pressing 2 buttons"""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
# can change intended b1 b2 values
b1 = uhdev.buttons[0]
key1 = libevdev.evbit(uhdev.buttons_map[b1])
b2 = uhdev.buttons[1]
key2 = libevdev.evbit(uhdev.buttons_map[b2])
buttons = {b1: True, b2: True}
r = uhdev.event(buttons=buttons)
expected_event0 = libevdev.InputEvent(key1, 1)
expected_event1 = libevdev.InputEvent(key2, 1)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(
(syn_event, expected_event0, expected_event1), events
)
assert evdev.value[key1] == 1
assert evdev.value[key2] == 1
buttons = {b1: False, b2: None}
r = uhdev.event(buttons=buttons)
expected_event = libevdev.InputEvent(key1, 0)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn((syn_event, expected_event), events)
assert evdev.value[key1] == 0
assert evdev.value[key2] == 1
buttons = {b1: None, b2: False}
r = uhdev.event(buttons=buttons)
expected_event = libevdev.InputEvent(key2, 0)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn((syn_event, expected_event), events)
assert evdev.value[key1] == 0
assert evdev.value[key2] == 0
def _get_libevdev_abs_events(self, which):
"""Returns which ABS_* evdev axes are expected for the given stick"""
abs_map = self.uhdev.axes_map[which]
x = abs_map["x"].evdev
y = abs_map["y"].evdev
assert x
assert y
return x, y
def _test_joystick_press(self, which, data):
uhdev = self.uhdev
libevdev_axes = self._get_libevdev_abs_events(which)
r = None
if which == "left_stick":
r = uhdev.event(left=data)
else:
r = uhdev.event(right=data)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
for i, d in enumerate(data):
if d is not None and d != 127:
assert libevdev.InputEvent(libevdev_axes[i], d) in events
else:
assert libevdev.InputEvent(libevdev_axes[i]) not in events
def test_left_joystick_press_left(self):
"""check for the left joystick reliability"""
self._test_joystick_press("left_stick", (63, None))
self._test_joystick_press("left_stick", (0, 127))
def test_left_joystick_press_right(self):
"""check for the left joystick reliability"""
self._test_joystick_press("left_stick", (191, 127))
self._test_joystick_press("left_stick", (255, None))
def test_left_joystick_press_up(self):
"""check for the left joystick reliability"""
self._test_joystick_press("left_stick", (None, 63))
self._test_joystick_press("left_stick", (127, 0))
def test_left_joystick_press_down(self):
"""check for the left joystick reliability"""
self._test_joystick_press("left_stick", (127, 191))
self._test_joystick_press("left_stick", (None, 255))
def test_right_joystick_press_left(self):
"""check for the right joystick reliability"""
self._test_joystick_press("right_stick", (63, None))
self._test_joystick_press("right_stick", (0, 127))
def test_right_joystick_press_right(self):
"""check for the right joystick reliability"""
self._test_joystick_press("right_stick", (191, 127))
self._test_joystick_press("right_stick", (255, None))
def test_right_joystick_press_up(self):
"""check for the right joystick reliability"""
self._test_joystick_press("right_stick", (None, 63))
self._test_joystick_press("right_stick", (127, 0))
def test_right_joystick_press_down(self):
"""check for the right joystick reliability"""
self._test_joystick_press("right_stick", (127, 191))
self._test_joystick_press("right_stick", (None, 255))
@pytest.mark.skip_if_uhdev(
lambda uhdev: "Hat switch" not in uhdev.fields,
"Device not compatible, missing Hat switch usage",
)
@pytest.mark.parametrize(
"hat_value,expected_evdev,evdev_value",
[
(0, "ABS_HAT0Y", -1),
(2, "ABS_HAT0X", 1),
(4, "ABS_HAT0Y", 1),
(6, "ABS_HAT0X", -1),
],
)
def test_hat_switch(self, hat_value, expected_evdev, evdev_value):
uhdev = self.uhdev
r = uhdev.event(hat_switch=hat_value)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert (
libevdev.InputEvent(
libevdev.evbit("EV_ABS", expected_evdev), evdev_value
)
in events
)
class SaitekGamepad(JoystickGamepad):
# fmt: off
report_descriptor = [
0x05, 0x01, # Usage Page (Generic Desktop) 0
0x09, 0x04, # Usage (Joystick) 2
0xa1, 0x01, # Collection (Application) 4
0x09, 0x01, # .Usage (Pointer) 6
0xa1, 0x00, # .Collection (Physical) 8
0x85, 0x01, # ..Report ID (1) 10
0x09, 0x30, # ..Usage (X) 12
0x15, 0x00, # ..Logical Minimum (0) 14
0x26, 0xff, 0x00, # ..Logical Maximum (255) 16
0x35, 0x00, # ..Physical Minimum (0) 19
0x46, 0xff, 0x00, # ..Physical Maximum (255) 21
0x75, 0x08, # ..Report Size (8) 24
0x95, 0x01, # ..Report Count (1) 26
0x81, 0x02, # ..Input (Data,Var,Abs) 28
0x09, 0x31, # ..Usage (Y) 30
0x81, 0x02, # ..Input (Data,Var,Abs) 32
0x05, 0x02, # ..Usage Page (Simulation Controls) 34
0x09, 0xba, # ..Usage (Rudder) 36
0x81, 0x02, # ..Input (Data,Var,Abs) 38
0x09, 0xbb, # ..Usage (Throttle) 40
0x81, 0x02, # ..Input (Data,Var,Abs) 42
0x05, 0x09, # ..Usage Page (Button) 44
0x19, 0x01, # ..Usage Minimum (1) 46
0x29, 0x0c, # ..Usage Maximum (12) 48
0x25, 0x01, # ..Logical Maximum (1) 50
0x45, 0x01, # ..Physical Maximum (1) 52
0x75, 0x01, # ..Report Size (1) 54
0x95, 0x0c, # ..Report Count (12) 56
0x81, 0x02, # ..Input (Data,Var,Abs) 58
0x95, 0x01, # ..Report Count (1) 60
0x75, 0x00, # ..Report Size (0) 62
0x81, 0x03, # ..Input (Cnst,Var,Abs) 64
0x05, 0x01, # ..Usage Page (Generic Desktop) 66
0x09, 0x39, # ..Usage (Hat switch) 68
0x25, 0x07, # ..Logical Maximum (7) 70
0x46, 0x3b, 0x01, # ..Physical Maximum (315) 72
0x55, 0x00, # ..Unit Exponent (0) 75
0x65, 0x44, # ..Unit (Degrees^4,EngRotation) 77
0x75, 0x04, # ..Report Size (4) 79
0x81, 0x42, # ..Input (Data,Var,Abs,Null) 81
0x65, 0x00, # ..Unit (None) 83
0xc0, # .End Collection 85
0x05, 0x0f, # .Usage Page (Vendor Usage Page 0x0f) 86
0x09, 0x92, # .Usage (Vendor Usage 0x92) 88
0xa1, 0x02, # .Collection (Logical) 90
0x85, 0x02, # ..Report ID (2) 92
0x09, 0xa0, # ..Usage (Vendor Usage 0xa0) 94
0x09, 0x9f, # ..Usage (Vendor Usage 0x9f) 96
0x25, 0x01, # ..Logical Maximum (1) 98
0x45, 0x00, # ..Physical Maximum (0) 100
0x75, 0x01, # ..Report Size (1) 102
0x95, 0x02, # ..Report Count (2) 104
0x81, 0x02, # ..Input (Data,Var,Abs) 106
0x75, 0x06, # ..Report Size (6) 108
0x95, 0x01, # ..Report Count (1) 110
0x81, 0x03, # ..Input (Cnst,Var,Abs) 112
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 114
0x75, 0x07, # ..Report Size (7) 116
0x25, 0x7f, # ..Logical Maximum (127) 118
0x81, 0x02, # ..Input (Data,Var,Abs) 120
0x09, 0x94, # ..Usage (Vendor Usage 0x94) 122
0x75, 0x01, # ..Report Size (1) 124
0x25, 0x01, # ..Logical Maximum (1) 126
0x81, 0x02, # ..Input (Data,Var,Abs) 128
0xc0, # .End Collection 130
0x09, 0x21, # .Usage (Vendor Usage 0x21) 131
0xa1, 0x02, # .Collection (Logical) 133
0x85, 0x0b, # ..Report ID (11) 135
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 137
0x26, 0xff, 0x00, # ..Logical Maximum (255) 139
0x75, 0x08, # ..Report Size (8) 142
0x91, 0x02, # ..Output (Data,Var,Abs) 144
0x09, 0x53, # ..Usage (Vendor Usage 0x53) 146
0x25, 0x0a, # ..Logical Maximum (10) 148
0x91, 0x02, # ..Output (Data,Var,Abs) 150
0x09, 0x50, # ..Usage (Vendor Usage 0x50) 152
0x27, 0xfe, 0xff, 0x00, 0x00, # ..Logical Maximum (65534) 154
0x47, 0xfe, 0xff, 0x00, 0x00, # ..Physical Maximum (65534) 159
0x75, 0x10, # ..Report Size (16) 164
0x55, 0xfd, # ..Unit Exponent (237) 166
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 168
0x91, 0x02, # ..Output (Data,Var,Abs) 171
0x55, 0x00, # ..Unit Exponent (0) 173
0x65, 0x00, # ..Unit (None) 175
0x09, 0x54, # ..Usage (Vendor Usage 0x54) 177
0x55, 0xfd, # ..Unit Exponent (237) 179
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 181
0x91, 0x02, # ..Output (Data,Var,Abs) 184
0x55, 0x00, # ..Unit Exponent (0) 186
0x65, 0x00, # ..Unit (None) 188
0x09, 0xa7, # ..Usage (Vendor Usage 0xa7) 190
0x55, 0xfd, # ..Unit Exponent (237) 192
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 194
0x91, 0x02, # ..Output (Data,Var,Abs) 197
0x55, 0x00, # ..Unit Exponent (0) 199
0x65, 0x00, # ..Unit (None) 201
0xc0, # .End Collection 203
0x09, 0x5a, # .Usage (Vendor Usage 0x5a) 204
0xa1, 0x02, # .Collection (Logical) 206
0x85, 0x0c, # ..Report ID (12) 208
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 210
0x26, 0xff, 0x00, # ..Logical Maximum (255) 212
0x45, 0x00, # ..Physical Maximum (0) 215
0x75, 0x08, # ..Report Size (8) 217
0x91, 0x02, # ..Output (Data,Var,Abs) 219
0x09, 0x5c, # ..Usage (Vendor Usage 0x5c) 221
0x26, 0x10, 0x27, # ..Logical Maximum (10000) 223
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 226
0x75, 0x10, # ..Report Size (16) 229
0x55, 0xfd, # ..Unit Exponent (237) 231
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 233
0x91, 0x02, # ..Output (Data,Var,Abs) 236
0x55, 0x00, # ..Unit Exponent (0) 238
0x65, 0x00, # ..Unit (None) 240
0x09, 0x5b, # ..Usage (Vendor Usage 0x5b) 242
0x25, 0x7f, # ..Logical Maximum (127) 244
0x75, 0x08, # ..Report Size (8) 246
0x91, 0x02, # ..Output (Data,Var,Abs) 248
0x09, 0x5e, # ..Usage (Vendor Usage 0x5e) 250
0x26, 0x10, 0x27, # ..Logical Maximum (10000) 252
0x75, 0x10, # ..Report Size (16) 255
0x55, 0xfd, # ..Unit Exponent (237) 257
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 259
0x91, 0x02, # ..Output (Data,Var,Abs) 262
0x55, 0x00, # ..Unit Exponent (0) 264
0x65, 0x00, # ..Unit (None) 266
0x09, 0x5d, # ..Usage (Vendor Usage 0x5d) 268
0x25, 0x7f, # ..Logical Maximum (127) 270
0x75, 0x08, # ..Report Size (8) 272
0x91, 0x02, # ..Output (Data,Var,Abs) 274
0xc0, # .End Collection 276
0x09, 0x73, # .Usage (Vendor Usage 0x73) 277
0xa1, 0x02, # .Collection (Logical) 279
0x85, 0x0d, # ..Report ID (13) 281
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 283
0x26, 0xff, 0x00, # ..Logical Maximum (255) 285
0x45, 0x00, # ..Physical Maximum (0) 288
0x91, 0x02, # ..Output (Data,Var,Abs) 290
0x09, 0x70, # ..Usage (Vendor Usage 0x70) 292
0x15, 0x81, # ..Logical Minimum (-127) 294
0x25, 0x7f, # ..Logical Maximum (127) 296
0x36, 0xf0, 0xd8, # ..Physical Minimum (-10000) 298
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 301
0x91, 0x02, # ..Output (Data,Var,Abs) 304
0xc0, # .End Collection 306
0x09, 0x6e, # .Usage (Vendor Usage 0x6e) 307
0xa1, 0x02, # .Collection (Logical) 309
0x85, 0x0e, # ..Report ID (14) 311
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 313
0x15, 0x00, # ..Logical Minimum (0) 315
0x26, 0xff, 0x00, # ..Logical Maximum (255) 317
0x35, 0x00, # ..Physical Minimum (0) 320
0x45, 0x00, # ..Physical Maximum (0) 322
0x91, 0x02, # ..Output (Data,Var,Abs) 324
0x09, 0x70, # ..Usage (Vendor Usage 0x70) 326
0x25, 0x7f, # ..Logical Maximum (127) 328
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 330
0x91, 0x02, # ..Output (Data,Var,Abs) 333
0x09, 0x6f, # ..Usage (Vendor Usage 0x6f) 335
0x15, 0x81, # ..Logical Minimum (-127) 337
0x36, 0xf0, 0xd8, # ..Physical Minimum (-10000) 339
0x91, 0x02, # ..Output (Data,Var,Abs) 342
0x09, 0x71, # ..Usage (Vendor Usage 0x71) 344
0x15, 0x00, # ..Logical Minimum (0) 346
0x26, 0xff, 0x00, # ..Logical Maximum (255) 348
0x35, 0x00, # ..Physical Minimum (0) 351
0x46, 0x68, 0x01, # ..Physical Maximum (360) 353
0x91, 0x02, # ..Output (Data,Var,Abs) 356
0x09, 0x72, # ..Usage (Vendor Usage 0x72) 358
0x75, 0x10, # ..Report Size (16) 360
0x26, 0x10, 0x27, # ..Logical Maximum (10000) 362
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 365
0x55, 0xfd, # ..Unit Exponent (237) 368
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 370
0x91, 0x02, # ..Output (Data,Var,Abs) 373
0x55, 0x00, # ..Unit Exponent (0) 375
0x65, 0x00, # ..Unit (None) 377
0xc0, # .End Collection 379
0x09, 0x77, # .Usage (Vendor Usage 0x77) 380
0xa1, 0x02, # .Collection (Logical) 382
0x85, 0x51, # ..Report ID (81) 384
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 386
0x25, 0x7f, # ..Logical Maximum (127) 388
0x45, 0x00, # ..Physical Maximum (0) 390
0x75, 0x08, # ..Report Size (8) 392
0x91, 0x02, # ..Output (Data,Var,Abs) 394
0x09, 0x78, # ..Usage (Vendor Usage 0x78) 396
0xa1, 0x02, # ..Collection (Logical) 398
0x09, 0x7b, # ...Usage (Vendor Usage 0x7b) 400
0x09, 0x79, # ...Usage (Vendor Usage 0x79) 402
0x09, 0x7a, # ...Usage (Vendor Usage 0x7a) 404
0x15, 0x01, # ...Logical Minimum (1) 406
0x25, 0x03, # ...Logical Maximum (3) 408
0x91, 0x00, # ...Output (Data,Arr,Abs) 410
0xc0, # ..End Collection 412
0x09, 0x7c, # ..Usage (Vendor Usage 0x7c) 413
0x15, 0x00, # ..Logical Minimum (0) 415
0x26, 0xfe, 0x00, # ..Logical Maximum (254) 417
0x91, 0x02, # ..Output (Data,Var,Abs) 420
0xc0, # .End Collection 422
0x09, 0x92, # .Usage (Vendor Usage 0x92) 423
0xa1, 0x02, # .Collection (Logical) 425
0x85, 0x52, # ..Report ID (82) 427
0x09, 0x96, # ..Usage (Vendor Usage 0x96) 429
0xa1, 0x02, # ..Collection (Logical) 431
0x09, 0x9a, # ...Usage (Vendor Usage 0x9a) 433
0x09, 0x99, # ...Usage (Vendor Usage 0x99) 435
0x09, 0x97, # ...Usage (Vendor Usage 0x97) 437
0x09, 0x98, # ...Usage (Vendor Usage 0x98) 439
0x09, 0x9b, # ...Usage (Vendor Usage 0x9b) 441
0x09, 0x9c, # ...Usage (Vendor Usage 0x9c) 443
0x15, 0x01, # ...Logical Minimum (1) 445
0x25, 0x06, # ...Logical Maximum (6) 447
0x91, 0x00, # ...Output (Data,Arr,Abs) 449
0xc0, # ..End Collection 451
0xc0, # .End Collection 452
0x05, 0xff, # .Usage Page (Vendor Usage Page 0xff) 453
0x0a, 0x01, 0x03, # .Usage (Vendor Usage 0x301) 455
0xa1, 0x02, # .Collection (Logical) 458
0x85, 0x40, # ..Report ID (64) 460
0x0a, 0x02, 0x03, # ..Usage (Vendor Usage 0x302) 462
0xa1, 0x02, # ..Collection (Logical) 465
0x1a, 0x11, 0x03, # ...Usage Minimum (785) 467
0x2a, 0x20, 0x03, # ...Usage Maximum (800) 470
0x25, 0x10, # ...Logical Maximum (16) 473
0x91, 0x00, # ...Output (Data,Arr,Abs) 475
0xc0, # ..End Collection 477
0x0a, 0x03, 0x03, # ..Usage (Vendor Usage 0x303) 478
0x15, 0x00, # ..Logical Minimum (0) 481
0x27, 0xff, 0xff, 0x00, 0x00, # ..Logical Maximum (65535) 483
0x75, 0x10, # ..Report Size (16) 488
0x91, 0x02, # ..Output (Data,Var,Abs) 490
0xc0, # .End Collection 492
0x05, 0x0f, # .Usage Page (Vendor Usage Page 0x0f) 493
0x09, 0x7d, # .Usage (Vendor Usage 0x7d) 495
0xa1, 0x02, # .Collection (Logical) 497
0x85, 0x43, # ..Report ID (67) 499
0x09, 0x7e, # ..Usage (Vendor Usage 0x7e) 501
0x26, 0x80, 0x00, # ..Logical Maximum (128) 503
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 506
0x75, 0x08, # ..Report Size (8) 509
0x91, 0x02, # ..Output (Data,Var,Abs) 511
0xc0, # .End Collection 513
0x09, 0x7f, # .Usage (Vendor Usage 0x7f) 514
0xa1, 0x02, # .Collection (Logical) 516
0x85, 0x0b, # ..Report ID (11) 518
0x09, 0x80, # ..Usage (Vendor Usage 0x80) 520
0x26, 0xff, 0x7f, # ..Logical Maximum (32767) 522
0x45, 0x00, # ..Physical Maximum (0) 525
0x75, 0x0f, # ..Report Size (15) 527
0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 529
0x09, 0xa9, # ..Usage (Vendor Usage 0xa9) 531
0x25, 0x01, # ..Logical Maximum (1) 533
0x75, 0x01, # ..Report Size (1) 535
0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 537
0x09, 0x83, # ..Usage (Vendor Usage 0x83) 539
0x26, 0xff, 0x00, # ..Logical Maximum (255) 541
0x75, 0x08, # ..Report Size (8) 544
0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 546
0xc0, # .End Collection 548
0x09, 0xab, # .Usage (Vendor Usage 0xab) 549
0xa1, 0x03, # .Collection (Report) 551
0x85, 0x15, # ..Report ID (21) 553
0x09, 0x25, # ..Usage (Vendor Usage 0x25) 555
0xa1, 0x02, # ..Collection (Logical) 557
0x09, 0x26, # ...Usage (Vendor Usage 0x26) 559
0x09, 0x30, # ...Usage (Vendor Usage 0x30) 561
0x09, 0x32, # ...Usage (Vendor Usage 0x32) 563
0x09, 0x31, # ...Usage (Vendor Usage 0x31) 565
0x09, 0x33, # ...Usage (Vendor Usage 0x33) 567
0x09, 0x34, # ...Usage (Vendor Usage 0x34) 569
0x15, 0x01, # ...Logical Minimum (1) 571
0x25, 0x06, # ...Logical Maximum (6) 573
0xb1, 0x00, # ...Feature (Data,Arr,Abs) 575
0xc0, # ..End Collection 577
0xc0, # .End Collection 578
0x09, 0x89, # .Usage (Vendor Usage 0x89) 579
0xa1, 0x03, # .Collection (Report) 581
0x85, 0x16, # ..Report ID (22) 583
0x09, 0x8b, # ..Usage (Vendor Usage 0x8b) 585
0xa1, 0x02, # ..Collection (Logical) 587
0x09, 0x8c, # ...Usage (Vendor Usage 0x8c) 589
0x09, 0x8d, # ...Usage (Vendor Usage 0x8d) 591
0x09, 0x8e, # ...Usage (Vendor Usage 0x8e) 593
0x25, 0x03, # ...Logical Maximum (3) 595
0xb1, 0x00, # ...Feature (Data,Arr,Abs) 597
0xc0, # ..End Collection 599
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 600
0x15, 0x00, # ..Logical Minimum (0) 602
0x26, 0xfe, 0x00, # ..Logical Maximum (254) 604
0xb1, 0x02, # ..Feature (Data,Var,Abs) 607
0xc0, # .End Collection 609
0x09, 0x90, # .Usage (Vendor Usage 0x90) 610
0xa1, 0x03, # .Collection (Report) 612
0x85, 0x50, # ..Report ID (80) 614
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 616
0x26, 0xff, 0x00, # ..Logical Maximum (255) 618
0x91, 0x02, # ..Output (Data,Var,Abs) 621
0xc0, # .End Collection 623
0xc0, # End Collection 624
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None):
super().__init__(rdesc, name=name, input_info=(BusType.USB, 0x06A3, 0xFF0D))
self.buttons = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
class AsusGamepad(BaseGamepad):
# fmt: off
report_descriptor = [
0x05, 0x01, # Usage Page (Generic Desktop) 0
0x09, 0x05, # Usage (Game Pad) 2
0xa1, 0x01, # Collection (Application) 4
0x85, 0x01, # .Report ID (1) 6
0x05, 0x09, # .Usage Page (Button) 8
0x0a, 0x01, 0x00, # .Usage (Vendor Usage 0x01) 10
0x0a, 0x02, 0x00, # .Usage (Vendor Usage 0x02) 13
0x0a, 0x04, 0x00, # .Usage (Vendor Usage 0x04) 16
0x0a, 0x05, 0x00, # .Usage (Vendor Usage 0x05) 19
0x0a, 0x07, 0x00, # .Usage (Vendor Usage 0x07) 22
0x0a, 0x08, 0x00, # .Usage (Vendor Usage 0x08) 25
0x0a, 0x0e, 0x00, # .Usage (Vendor Usage 0x0e) 28
0x0a, 0x0f, 0x00, # .Usage (Vendor Usage 0x0f) 31
0x0a, 0x0d, 0x00, # .Usage (Vendor Usage 0x0d) 34
0x05, 0x0c, # .Usage Page (Consumer Devices) 37
0x0a, 0x24, 0x02, # .Usage (AC Back) 39
0x0a, 0x23, 0x02, # .Usage (AC Home) 42
0x15, 0x00, # .Logical Minimum (0) 45
0x25, 0x01, # .Logical Maximum (1) 47
0x75, 0x01, # .Report Size (1) 49
0x95, 0x0b, # .Report Count (11) 51
0x81, 0x02, # .Input (Data,Var,Abs) 53
0x75, 0x01, # .Report Size (1) 55
0x95, 0x01, # .Report Count (1) 57
0x81, 0x03, # .Input (Cnst,Var,Abs) 59
0x05, 0x01, # .Usage Page (Generic Desktop) 61
0x75, 0x04, # .Report Size (4) 63
0x95, 0x01, # .Report Count (1) 65
0x25, 0x07, # .Logical Maximum (7) 67
0x46, 0x3b, 0x01, # .Physical Maximum (315) 69
0x66, 0x14, 0x00, # .Unit (Degrees,EngRotation) 72
0x09, 0x39, # .Usage (Hat switch) 75
0x81, 0x42, # .Input (Data,Var,Abs,Null) 77
0x66, 0x00, 0x00, # .Unit (None) 79
0x09, 0x01, # .Usage (Pointer) 82
0xa1, 0x00, # .Collection (Physical) 84
0x09, 0x30, # ..Usage (X) 86
0x09, 0x31, # ..Usage (Y) 88
0x09, 0x32, # ..Usage (Z) 90
0x09, 0x35, # ..Usage (Rz) 92
0x05, 0x02, # ..Usage Page (Simulation Controls) 94
0x09, 0xc5, # ..Usage (Brake) 96
0x09, 0xc4, # ..Usage (Accelerator) 98
0x15, 0x00, # ..Logical Minimum (0) 100
0x26, 0xff, 0x00, # ..Logical Maximum (255) 102
0x35, 0x00, # ..Physical Minimum (0) 105
0x46, 0xff, 0x00, # ..Physical Maximum (255) 107
0x75, 0x08, # ..Report Size (8) 110
0x95, 0x06, # ..Report Count (6) 112
0x81, 0x02, # ..Input (Data,Var,Abs) 114
0xc0, # .End Collection 116
0x85, 0x02, # .Report ID (2) 117
0x05, 0x08, # .Usage Page (LEDs) 119
0x0a, 0x01, 0x00, # .Usage (Num Lock) 121
0x0a, 0x02, 0x00, # .Usage (Caps Lock) 124
0x0a, 0x03, 0x00, # .Usage (Scroll Lock) 127
0x0a, 0x04, 0x00, # .Usage (Compose) 130
0x15, 0x00, # .Logical Minimum (0) 133
0x25, 0x01, # .Logical Maximum (1) 135
0x75, 0x01, # .Report Size (1) 137
0x95, 0x04, # .Report Count (4) 139
0x91, 0x02, # .Output (Data,Var,Abs) 141
0x75, 0x04, # .Report Size (4) 143
0x95, 0x01, # .Report Count (1) 145
0x91, 0x03, # .Output (Cnst,Var,Abs) 147
0xc0, # End Collection 149
0x05, 0x0c, # Usage Page (Consumer Devices) 150
0x09, 0x01, # Usage (Consumer Control) 152
0xa1, 0x01, # Collection (Application) 154
0x85, 0x03, # .Report ID (3) 156
0x05, 0x01, # .Usage Page (Generic Desktop) 158
0x09, 0x06, # .Usage (Keyboard) 160
0xa1, 0x02, # .Collection (Logical) 162
0x05, 0x06, # ..Usage Page (Generic Device Controls) 164
0x09, 0x20, # ..Usage (Battery Strength) 166
0x15, 0x00, # ..Logical Minimum (0) 168
0x26, 0xff, 0x00, # ..Logical Maximum (255) 170
0x75, 0x08, # ..Report Size (8) 173
0x95, 0x01, # ..Report Count (1) 175
0x81, 0x02, # ..Input (Data,Var,Abs) 177
0x06, 0xbc, 0xff, # ..Usage Page (Vendor Usage Page 0xffbc) 179
0x0a, 0xad, 0xbd, # ..Usage (Vendor Usage 0xbdad) 182
0x75, 0x08, # ..Report Size (8) 185
0x95, 0x06, # ..Report Count (6) 187
0x81, 0x02, # ..Input (Data,Var,Abs) 189
0xc0, # .End Collection 191
0xc0, # End Collection 192
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None):
super().__init__(rdesc, name=name, input_info=(BusType.USB, 0x18D1, 0x2C40))
self.buttons = (1, 2, 4, 5, 7, 8, 14, 15, 13)
class RaptorMach2Joystick(JoystickGamepad):
axes_map = {
"left_stick": {
"x": AxisMapping("x"),
"y": AxisMapping("y"),
},
"right_stick": {
"x": AxisMapping("z"),
"y": AxisMapping("Rz"),
},
}
def __init__(
self,
name,
rdesc=None,
application="Joystick",
input_info=(BusType.USB, 0x11C0, 0x5606),
):
super().__init__(rdesc, application, name, input_info)
self.buttons = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
self.hat_switch = 240 # null value is 240 as max is 239
def event(
self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
):
if hat_switch is not None:
hat_switch *= 30
return super().event(
left=left, right=right, hat_switch=hat_switch, buttons=buttons
)
class TestSaitekGamepad(BaseTest.TestGamepad):
def create_device(self):
return SaitekGamepad()
class TestAsusGamepad(BaseTest.TestGamepad):
def create_device(self):
return AsusGamepad()
class TestRaptorMach2Joystick(BaseTest.TestGamepad):
hid_bpfs = [HidBpf("FR-TEC__Raptor-Mach-2.bpf.o", True)]
def create_device(self):
return RaptorMach2Joystick(
"uhid test Sanmos Group FR-TEC Raptor MACH 2",
rdesc="05 01 09 04 a1 01 05 01 85 01 05 01 09 30 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 31 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 33 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 00 09 00 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 32 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 35 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 34 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 36 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 09 19 01 2a 1d 00 15 00 25 01 75 01 96 80 00 81 02 05 01 09 39 26 ef 00 46 68 01 65 14 75 10 95 01 81 42 05 01 09 00 75 08 95 1d 81 01 15 00 26 ef 00 85 58 26 ff 00 46 ff 00 75 08 95 3f 09 00 91 02 85 59 75 08 95 80 09 00 b1 02 c0",
input_info=(BusType.USB, 0x11C0, 0x5606),
)
@@ -0,0 +1,154 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2017 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This is for generic devices
from . import base
import logging
logger = logging.getLogger("hidtools.test.hid")
class TestCollectionOverflow(base.BaseTestCase.TestUhid):
"""
Test class to test re-allocation of the HID collection stack in
hid-core.c.
"""
def create_device(self):
# fmt: off
report_descriptor = [
0x05, 0x01, # .Usage Page (Generic Desktop)
0x09, 0x02, # .Usage (Mouse)
0xa1, 0x01, # .Collection (Application)
0x09, 0x02, # ..Usage (Mouse)
0xa1, 0x02, # ..Collection (Logical)
0x09, 0x01, # ...Usage (Pointer)
0xa1, 0x00, # ...Collection (Physical)
0x05, 0x09, # ....Usage Page (Button)
0x19, 0x01, # ....Usage Minimum (1)
0x29, 0x03, # ....Usage Maximum (3)
0x15, 0x00, # ....Logical Minimum (0)
0x25, 0x01, # ....Logical Maximum (1)
0x75, 0x01, # ....Report Size (1)
0x95, 0x03, # ....Report Count (3)
0x81, 0x02, # ....Input (Data,Var,Abs)
0x75, 0x05, # ....Report Size (5)
0x95, 0x01, # ....Report Count (1)
0x81, 0x03, # ....Input (Cnst,Var,Abs)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
0xa1, 0x02, # ....Collection (Logical)
0x09, 0x01, # .....Usage (Pointer)
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)
0xa1, 0x02, # ...Collection (Logical)
0x85, 0x12, # ....Report ID (18)
0x09, 0x48, # ....Usage (Resolution Multiplier)
0x95, 0x01, # ....Report Count (1)
0x75, 0x02, # ....Report Size (2)
0x15, 0x00, # ....Logical Minimum (0)
0x25, 0x01, # ....Logical Maximum (1)
0x35, 0x01, # ....Physical Minimum (1)
0x45, 0x0c, # ....Physical Maximum (12)
0xb1, 0x02, # ....Feature (Data,Var,Abs)
0x85, 0x1a, # ....Report ID (26)
0x09, 0x38, # ....Usage (Wheel)
0x35, 0x00, # ....Physical Minimum (0)
0x45, 0x00, # ....Physical Maximum (0)
0x95, 0x01, # ....Report Count (1)
0x75, 0x10, # ....Report Size (16)
0x16, 0x01, 0x80, # ....Logical Minimum (-32767)
0x26, 0xff, 0x7f, # ....Logical Maximum (32767)
0x81, 0x06, # ....Input (Data,Var,Rel)
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ...End Collection
0xc0, # ..End Collection
0xc0, # .End Collection
]
# fmt: on
return base.UHIDTestDevice(
name=None, rdesc=report_descriptor, application="Mouse"
)
def test_rdesc(self):
"""
This test can only check for negatives. If the kernel crashes, you
know why. If this test passes, either the bug isn't present or just
didn't get triggered. No way to know.
For an explanation, see kernel patch
HID: core: replace the collection tree pointers with indices
"""
pass
@@ -0,0 +1,167 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2020 Red Hat, Inc.
#
from .test_keyboard import ArrayKeyboard, TestArrayKeyboard
from hidtools.util import BusType
import libevdev
import logging
from . import base
logger = logging.getLogger("hidtools.test.ite-keyboard")
KERNEL_MODULE = base.KernelModule("itetech", "hid_ite")
class KbdData(object):
pass
# The ITE keyboards have an issue regarding the Wifi key:
# nothing comes in when pressing the key, but we get a null
# event on the key release.
# This test covers this case.
class ITEKeyboard(ArrayKeyboard):
# fmt: off
report_descriptor = [
0x06, 0x85, 0xff, # Usage Page (Vendor Usage Page 0xff85)
0x09, 0x95, # Usage (Vendor Usage 0x95) 3
0xa1, 0x01, # Collection (Application) 5
0x85, 0x5a, # .Report ID (90) 7
0x09, 0x01, # .Usage (Vendor Usage 0x01) 9
0x15, 0x00, # .Logical Minimum (0) 11
0x26, 0xff, 0x00, # .Logical Maximum (255) 13
0x75, 0x08, # .Report Size (8) 16
0x95, 0x10, # .Report Count (16) 18
0xb1, 0x00, # .Feature (Data,Arr,Abs) 20
0xc0, # End Collection 22
0x05, 0x01, # Usage Page (Generic Desktop) 23
0x09, 0x06, # Usage (Keyboard) 25
0xa1, 0x01, # Collection (Application) 27
0x85, 0x01, # .Report ID (1) 29
0x75, 0x01, # .Report Size (1) 31
0x95, 0x08, # .Report Count (8) 33
0x05, 0x07, # .Usage Page (Keyboard) 35
0x19, 0xe0, # .Usage Minimum (224) 37
0x29, 0xe7, # .Usage Maximum (231) 39
0x15, 0x00, # .Logical Minimum (0) 41
0x25, 0x01, # .Logical Maximum (1) 43
0x81, 0x02, # .Input (Data,Var,Abs) 45
0x95, 0x01, # .Report Count (1) 47
0x75, 0x08, # .Report Size (8) 49
0x81, 0x03, # .Input (Cnst,Var,Abs) 51
0x95, 0x05, # .Report Count (5) 53
0x75, 0x01, # .Report Size (1) 55
0x05, 0x08, # .Usage Page (LEDs) 57
0x19, 0x01, # .Usage Minimum (1) 59
0x29, 0x05, # .Usage Maximum (5) 61
0x91, 0x02, # .Output (Data,Var,Abs) 63
0x95, 0x01, # .Report Count (1) 65
0x75, 0x03, # .Report Size (3) 67
0x91, 0x03, # .Output (Cnst,Var,Abs) 69
0x95, 0x06, # .Report Count (6) 71
0x75, 0x08, # .Report Size (8) 73
0x15, 0x00, # .Logical Minimum (0) 75
0x26, 0xff, 0x00, # .Logical Maximum (255) 77
0x05, 0x07, # .Usage Page (Keyboard) 80
0x19, 0x00, # .Usage Minimum (0) 82
0x2a, 0xff, 0x00, # .Usage Maximum (255) 84
0x81, 0x00, # .Input (Data,Arr,Abs) 87
0xc0, # End Collection 89
0x05, 0x0c, # Usage Page (Consumer Devices) 90
0x09, 0x01, # Usage (Consumer Control) 92
0xa1, 0x01, # Collection (Application) 94
0x85, 0x02, # .Report ID (2) 96
0x19, 0x00, # .Usage Minimum (0) 98
0x2a, 0x3c, 0x02, # .Usage Maximum (572) 100
0x15, 0x00, # .Logical Minimum (0) 103
0x26, 0x3c, 0x02, # .Logical Maximum (572) 105
0x75, 0x10, # .Report Size (16) 108
0x95, 0x01, # .Report Count (1) 110
0x81, 0x00, # .Input (Data,Arr,Abs) 112
0xc0, # End Collection 114
0x05, 0x01, # Usage Page (Generic Desktop) 115
0x09, 0x0c, # Usage (Wireless Radio Controls) 117
0xa1, 0x01, # Collection (Application) 119
0x85, 0x03, # .Report ID (3) 121
0x15, 0x00, # .Logical Minimum (0) 123
0x25, 0x01, # .Logical Maximum (1) 125
0x09, 0xc6, # .Usage (Wireless Radio Button) 127
0x95, 0x01, # .Report Count (1) 129
0x75, 0x01, # .Report Size (1) 131
0x81, 0x06, # .Input (Data,Var,Rel) 133
0x75, 0x07, # .Report Size (7) 135
0x81, 0x03, # .Input (Cnst,Var,Abs) 137
0xc0, # End Collection 139
0x05, 0x88, # Usage Page (Vendor Usage Page 0x88) 140
0x09, 0x01, # Usage (Vendor Usage 0x01) 142
0xa1, 0x01, # Collection (Application) 144
0x85, 0x04, # .Report ID (4) 146
0x19, 0x00, # .Usage Minimum (0) 148
0x2a, 0xff, 0xff, # .Usage Maximum (65535) 150
0x15, 0x00, # .Logical Minimum (0) 153
0x26, 0xff, 0xff, # .Logical Maximum (65535) 155
0x75, 0x08, # .Report Size (8) 158
0x95, 0x02, # .Report Count (2) 160
0x81, 0x02, # .Input (Data,Var,Abs) 162
0xc0, # End Collection 164
0x05, 0x01, # Usage Page (Generic Desktop) 165
0x09, 0x80, # Usage (System Control) 167
0xa1, 0x01, # Collection (Application) 169
0x85, 0x05, # .Report ID (5) 171
0x19, 0x81, # .Usage Minimum (129) 173
0x29, 0x83, # .Usage Maximum (131) 175
0x15, 0x00, # .Logical Minimum (0) 177
0x25, 0x01, # .Logical Maximum (1) 179
0x95, 0x08, # .Report Count (8) 181
0x75, 0x01, # .Report Size (1) 183
0x81, 0x02, # .Input (Data,Var,Abs) 185
0xc0, # End Collection 187
]
# fmt: on
def __init__(
self,
rdesc=report_descriptor,
name=None,
input_info=(BusType.USB, 0x06CB, 0x2968),
):
super().__init__(rdesc, name, input_info)
def event(self, keys, reportID=None, application=None):
application = application or "Keyboard"
return super().event(keys, reportID, application)
class TestITEKeyboard(TestArrayKeyboard):
kernel_modules = [KERNEL_MODULE]
def create_device(self):
return ITEKeyboard()
def test_wifi_key(self):
uhdev = self.uhdev
syn_event = self.syn_event
# the following sends a 'release' event on the Wifi key.
# the kernel is supposed to translate this into Wifi key
# down and up
r = [0x03, 0x00]
uhdev.call_input_event(r)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_RFKILL, 1))
events = uhdev.next_sync_events()
self.debug_reports([r], uhdev, events)
self.assertInputEventsIn(expected, events)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_RFKILL, 0))
# the kernel sends the two down/up key events in a batch, no need to
# call events = uhdev.next_sync_events()
self.debug_reports([], uhdev, events)
self.assertInputEventsIn(expected, events)
@@ -0,0 +1,485 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2018 Red Hat, Inc.
#
from . import base
import hidtools.hid
import libevdev
import logging
logger = logging.getLogger("hidtools.test.keyboard")
class InvalidHIDCommunication(Exception):
pass
class KeyboardData(object):
pass
class BaseKeyboard(base.UHIDTestDevice):
def __init__(self, rdesc, name=None, input_info=None):
assert rdesc is not None
super().__init__(name, "Key", input_info=input_info, rdesc=rdesc)
self.keystates = {}
def _update_key_state(self, keys):
"""
Update the internal state of keys with the new state given.
:param key: a tuple of chars for the currently pressed keys.
"""
# First remove the already released keys
unused_keys = [k for k, v in self.keystates.items() if not v]
for key in unused_keys:
del self.keystates[key]
# self.keystates contains now the list of currently pressed keys,
# release them...
for key in self.keystates.keys():
self.keystates[key] = False
# ...and press those that are in parameter
for key in keys:
self.keystates[key] = True
def _create_report_data(self):
keyboard = KeyboardData()
for key, value in self.keystates.items():
key = key.replace(" ", "").lower()
setattr(keyboard, key, value)
return keyboard
def create_array_report(self, keys, reportID=None, application=None):
"""
Return an input report for this device.
:param keys: a tuple of chars for the pressed keys. The class maintains
the list of currently pressed keys, so to release a key, the caller
needs to call again this function without the key in this tuple.
:param reportID: the numeric report ID for this report, if needed
"""
self._update_key_state(keys)
reportID = reportID or self.default_reportID
keyboard = self._create_report_data()
return self.create_report(keyboard, reportID=reportID, application=application)
def event(self, keys, reportID=None, application=None):
"""
Send an input event on the default report ID.
:param keys: a tuple of chars for the pressed keys. The class maintains
the list of currently pressed keys, so to release a key, the caller
needs to call again this function without the key in this tuple.
"""
r = self.create_array_report(keys, reportID, application)
self.call_input_event(r)
return [r]
class PlainKeyboard(BaseKeyboard):
# fmt: off
report_descriptor = [
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x06, # Usage (Keyboard)
0xa1, 0x01, # Collection (Application)
0x85, 0x01, # .Report ID (1)
0x05, 0x07, # .Usage Page (Keyboard)
0x19, 0xe0, # .Usage Minimum (224)
0x29, 0xe7, # .Usage Maximum (231)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x08, # .Report Count (8)
0x81, 0x02, # .Input (Data,Var,Abs)
0x19, 0x00, # .Usage Minimum (0)
0x29, 0x97, # .Usage Maximum (151)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x98, # .Report Count (152)
0x81, 0x02, # .Input (Data,Var,Abs)
0xc0, # End Collection
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
super().__init__(rdesc, name, input_info)
self.default_reportID = 1
class ArrayKeyboard(BaseKeyboard):
# fmt: off
report_descriptor = [
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x06, # Usage (Keyboard)
0xa1, 0x01, # Collection (Application)
0x05, 0x07, # .Usage Page (Keyboard)
0x19, 0xe0, # .Usage Minimum (224)
0x29, 0xe7, # .Usage Maximum (231)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x08, # .Report Count (8)
0x81, 0x02, # .Input (Data,Var,Abs)
0x95, 0x06, # .Report Count (6)
0x75, 0x08, # .Report Size (8)
0x15, 0x00, # .Logical Minimum (0)
0x26, 0xa4, 0x00, # .Logical Maximum (164)
0x05, 0x07, # .Usage Page (Keyboard)
0x19, 0x00, # .Usage Minimum (0)
0x29, 0xa4, # .Usage Maximum (164)
0x81, 0x00, # .Input (Data,Arr,Abs)
0xc0, # End Collection
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
super().__init__(rdesc, name, input_info)
def _create_report_data(self):
data = KeyboardData()
array = []
hut = hidtools.hut.HUT
# strip modifiers from the array
for k, v in self.keystates.items():
# we ignore depressed keys
if not v:
continue
usage = hut[0x07].from_name[k].usage
if usage >= 224 and usage <= 231:
# modifier
setattr(data, k.lower(), 1)
else:
array.append(k)
# if array length is bigger than 6, report ErrorRollOver
if len(array) > 6:
array = ["ErrorRollOver"] * 6
data.keyboard = array
return data
class LEDKeyboard(ArrayKeyboard):
# fmt: off
report_descriptor = [
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x06, # Usage (Keyboard)
0xa1, 0x01, # Collection (Application)
0x05, 0x07, # .Usage Page (Keyboard)
0x19, 0xe0, # .Usage Minimum (224)
0x29, 0xe7, # .Usage Maximum (231)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x08, # .Report Count (8)
0x81, 0x02, # .Input (Data,Var,Abs)
0x95, 0x01, # .Report Count (1)
0x75, 0x08, # .Report Size (8)
0x81, 0x01, # .Input (Cnst,Arr,Abs)
0x95, 0x05, # .Report Count (5)
0x75, 0x01, # .Report Size (1)
0x05, 0x08, # .Usage Page (LEDs)
0x19, 0x01, # .Usage Minimum (1)
0x29, 0x05, # .Usage Maximum (5)
0x91, 0x02, # .Output (Data,Var,Abs)
0x95, 0x01, # .Report Count (1)
0x75, 0x03, # .Report Size (3)
0x91, 0x01, # .Output (Cnst,Arr,Abs)
0x95, 0x06, # .Report Count (6)
0x75, 0x08, # .Report Size (8)
0x15, 0x00, # .Logical Minimum (0)
0x26, 0xa4, 0x00, # .Logical Maximum (164)
0x05, 0x07, # .Usage Page (Keyboard)
0x19, 0x00, # .Usage Minimum (0)
0x29, 0xa4, # .Usage Maximum (164)
0x81, 0x00, # .Input (Data,Arr,Abs)
0xc0, # End Collection
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
super().__init__(rdesc, name, input_info)
# Some Primax manufactured keyboards set the Usage Page after having defined
# some local Usages. It relies on the fact that the specification states that
# Usages are to be concatenated with Usage Pages upon finding a Main item (see
# 6.2.2.8). This test covers this case.
class PrimaxKeyboard(ArrayKeyboard):
# fmt: off
report_descriptor = [
0x05, 0x01, # Usage Page (Generic Desktop)
0x09, 0x06, # Usage (Keyboard)
0xA1, 0x01, # Collection (Application)
0x05, 0x07, # .Usage Page (Keyboard)
0x19, 0xE0, # .Usage Minimum (224)
0x29, 0xE7, # .Usage Maximum (231)
0x15, 0x00, # .Logical Minimum (0)
0x25, 0x01, # .Logical Maximum (1)
0x75, 0x01, # .Report Size (1)
0x95, 0x08, # .Report Count (8)
0x81, 0x02, # .Input (Data,Var,Abs)
0x75, 0x08, # .Report Size (8)
0x95, 0x01, # .Report Count (1)
0x81, 0x01, # .Input (Data,Var,Abs)
0x05, 0x08, # .Usage Page (LEDs)
0x19, 0x01, # .Usage Minimum (1)
0x29, 0x03, # .Usage Maximum (3)
0x75, 0x01, # .Report Size (1)
0x95, 0x03, # .Report Count (3)
0x91, 0x02, # .Output (Data,Var,Abs)
0x95, 0x01, # .Report Count (1)
0x75, 0x05, # .Report Size (5)
0x91, 0x01, # .Output (Constant)
0x15, 0x00, # .Logical Minimum (0)
0x26, 0xFF, 0x00, # .Logical Maximum (255)
0x19, 0x00, # .Usage Minimum (0)
0x2A, 0xFF, 0x00, # .Usage Maximum (255)
0x05, 0x07, # .Usage Page (Keyboard)
0x75, 0x08, # .Report Size (8)
0x95, 0x06, # .Report Count (6)
0x81, 0x00, # .Input (Data,Arr,Abs)
0xC0, # End Collection
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
super().__init__(rdesc, name, input_info)
class BaseTest:
class TestKeyboard(base.BaseTestCase.TestUhid):
def test_single_key(self):
"""check for key reliability."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.event(["a and A"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
def test_two_keys(self):
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.event(["a and A", "q and Q"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
assert evdev.value[libevdev.EV_KEY.KEY_Q] == 0
r = uhdev.event(["c and C"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
r = uhdev.event(["c and C", "Spacebar"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.KEY_C) not in events
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
r = uhdev.event(["Spacebar"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE) not in events
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_C] == 0
assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 0
def test_modifiers(self):
# ctrl-alt-del would be very nice :)
uhdev = self.uhdev
syn_event = self.syn_event
r = uhdev.event(["LeftControl", "LeftShift", "= and +"])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTCTRL, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTSHIFT, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_EQUAL, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
class TestPlainKeyboard(BaseTest.TestKeyboard):
def create_device(self):
return PlainKeyboard()
def test_10_keys(self):
uhdev = self.uhdev
syn_event = self.syn_event
r = uhdev.event(
[
"1 and !",
"2 and @",
"3 and #",
"4 and $",
"5 and %",
"6 and ^",
"7 and &",
"8 and *",
"9 and (",
"0 and )",
]
)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
class TestArrayKeyboard(BaseTest.TestKeyboard):
def create_device(self):
return ArrayKeyboard()
def test_10_keys(self):
uhdev = self.uhdev
syn_event = self.syn_event
r = uhdev.event(
[
"1 and !",
"2 and @",
"3 and #",
"4 and $",
"5 and %",
"6 and ^",
]
)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
# ErrRollOver
r = uhdev.event(
[
"1 and !",
"2 and @",
"3 and #",
"4 and $",
"5 and %",
"6 and ^",
"7 and &",
"8 and *",
"9 and (",
"0 and )",
]
)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert len(events) == 0
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
class TestLEDKeyboard(BaseTest.TestKeyboard):
def create_device(self):
return LEDKeyboard()
class TestPrimaxKeyboard(BaseTest.TestKeyboard):
def create_device(self):
return PrimaxKeyboard()
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,343 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2020 Red Hat, Inc.
#
from .base import application_matches
from .base import KernelModule
from .test_gamepad import BaseTest
from hidtools.device.sony_gamepad import (
PS3Controller,
PS4ControllerBluetooth,
PS4ControllerUSB,
PS5ControllerBluetooth,
PS5ControllerUSB,
PSTouchPoint,
)
from hidtools.util import BusType
import libevdev
import logging
import pytest
logger = logging.getLogger("hidtools.test.sony")
PS3_MODULE = KernelModule("sony", "hid_sony")
PS4_MODULE = KernelModule("playstation", "hid_playstation")
PS5_MODULE = KernelModule("playstation", "hid_playstation")
class SonyBaseTest:
class SonyTest(BaseTest.TestGamepad):
pass
class SonyPS4ControllerTest(SonyTest):
kernel_modules = [PS4_MODULE]
def test_accelerometer(self):
uhdev = self.uhdev
evdev = uhdev.get_evdev("Accelerometer")
for x in range(-32000, 32000, 4000):
r = uhdev.event(accel=(x, None, None))
events = uhdev.next_sync_events("Accelerometer")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X) in events
value = evdev.value[libevdev.EV_ABS.ABS_X]
# Check against range due to small loss in precision due
# to inverse calibration, followed by calibration by hid-sony.
assert x - 1 <= value <= x + 1
for y in range(-32000, 32000, 4000):
r = uhdev.event(accel=(None, y, None))
events = uhdev.next_sync_events("Accelerometer")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y) in events
value = evdev.value[libevdev.EV_ABS.ABS_Y]
assert y - 1 <= value <= y + 1
for z in range(-32000, 32000, 4000):
r = uhdev.event(accel=(None, None, z))
events = uhdev.next_sync_events("Accelerometer")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Z) in events
value = evdev.value[libevdev.EV_ABS.ABS_Z]
assert z - 1 <= value <= z + 1
def test_gyroscope(self):
uhdev = self.uhdev
evdev = uhdev.get_evdev("Accelerometer")
for rx in range(-2000000, 2000000, 200000):
r = uhdev.event(gyro=(rx, None, None))
events = uhdev.next_sync_events("Accelerometer")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RX) in events
value = evdev.value[libevdev.EV_ABS.ABS_RX]
# Sensor internal value is 16-bit, but calibrated is 22-bit, so
# 6-bit (64) difference, so allow a range of +/- 64.
assert rx - 64 <= value <= rx + 64
for ry in range(-2000000, 2000000, 200000):
r = uhdev.event(gyro=(None, ry, None))
events = uhdev.next_sync_events("Accelerometer")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RY) in events
value = evdev.value[libevdev.EV_ABS.ABS_RY]
assert ry - 64 <= value <= ry + 64
for rz in range(-2000000, 2000000, 200000):
r = uhdev.event(gyro=(None, None, rz))
events = uhdev.next_sync_events("Accelerometer")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RZ) in events
value = evdev.value[libevdev.EV_ABS.ABS_RZ]
assert rz - 64 <= value <= rz + 64
def test_battery(self):
uhdev = self.uhdev
assert uhdev.power_supply_class is not None
# DS4 capacity levels are in increments of 10.
# Battery is never below 5%.
for i in range(5, 105, 10):
uhdev.battery.capacity = i
uhdev.event()
assert uhdev.power_supply_class.capacity == i
# Discharging tests only make sense for BlueTooth.
if uhdev.bus == BusType.BLUETOOTH:
uhdev.battery.cable_connected = False
uhdev.battery.capacity = 45
uhdev.event()
assert uhdev.power_supply_class.status == "Discharging"
uhdev.battery.cable_connected = True
uhdev.battery.capacity = 5
uhdev.event()
assert uhdev.power_supply_class.status == "Charging"
uhdev.battery.capacity = 100
uhdev.event()
assert uhdev.power_supply_class.status == "Charging"
uhdev.battery.full = True
uhdev.event()
assert uhdev.power_supply_class.status == "Full"
def test_mt_single_touch(self):
"""send a single touch in the first slot of the device,
and release it."""
uhdev = self.uhdev
evdev = uhdev.get_evdev("Touch Pad")
t0 = PSTouchPoint(1, 50, 100)
r = uhdev.event(touch=[t0])
events = uhdev.next_sync_events("Touch Pad")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
t0.tipswitch = False
r = uhdev.event(touch=[t0])
events = uhdev.next_sync_events("Touch Pad")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
def test_mt_dual_touch(self):
"""Send 2 touches in the first 2 slots.
Make sure the kernel sees this as a dual touch.
Release and check
Note: PTP will send here BTN_DOUBLETAP emulation"""
uhdev = self.uhdev
evdev = uhdev.get_evdev("Touch Pad")
t0 = PSTouchPoint(1, 50, 100)
t1 = PSTouchPoint(2, 150, 200)
r = uhdev.event(touch=[t0])
events = uhdev.next_sync_events("Touch Pad")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
r = uhdev.event(touch=[t0, t1])
events = uhdev.next_sync_events("Touch Pad")
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events
assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
assert (
libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events
)
assert (
libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events
)
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
t0.tipswitch = False
r = uhdev.event(touch=[t0, t1])
events = uhdev.next_sync_events("Touch Pad")
self.debug_reports(r, uhdev, events)
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events
t1.tipswitch = False
r = uhdev.event(touch=[t1])
events = uhdev.next_sync_events("Touch Pad")
self.debug_reports(r, uhdev, events)
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
class TestPS3Controller(SonyBaseTest.SonyTest):
kernel_modules = [PS3_MODULE]
def create_device(self):
controller = PS3Controller()
controller.application_matches = application_matches
return controller
@pytest.fixture(autouse=True)
def start_controller(self):
# emulate a 'PS' button press to tell the kernel we are ready to accept events
self.assert_button(17)
# drain any remaining udev events
while self.uhdev.dispatch(10):
pass
def test_led(self):
for k, v in self.uhdev.led_classes.items():
# the kernel might have set a LED for us
logger.info(f"{k}: {v.brightness}")
idx = int(k[-1]) - 1
assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness)
v.brightness = 0
self.uhdev.dispatch(10)
assert self.uhdev.hw_leds.get_led(idx)[0] is False
v.brightness = v.max_brightness
self.uhdev.dispatch(10)
assert self.uhdev.hw_leds.get_led(idx)[0]
class CalibratedPS4Controller(object):
# DS4 reports uncalibrated sensor data. Calibration coefficients
# can be retrieved using a feature report (0x2 USB / 0x5 BT).
# The values below are the processed calibration values for the
# DS4s matching the feature reports of PS4ControllerBluetooth/USB
# as dumped from hid-sony 'ds4_get_calibration_data'.
#
# Note we duplicate those values here in case the kernel changes them
# so we can have tests passing even if hid-tools doesn't have the
# correct values.
accelerometer_calibration_data = {
"x": {"bias": -73, "numer": 16384, "denom": 16472},
"y": {"bias": -352, "numer": 16384, "denom": 16344},
"z": {"bias": 81, "numer": 16384, "denom": 16319},
}
gyroscope_calibration_data = {
"x": {"bias": 0, "numer": 1105920, "denom": 17827},
"y": {"bias": 0, "numer": 1105920, "denom": 17777},
"z": {"bias": 0, "numer": 1105920, "denom": 17748},
}
class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller, PS4ControllerBluetooth):
pass
class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
def create_device(self):
controller = CalibratedPS4ControllerBluetooth()
controller.application_matches = application_matches
return controller
class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB):
pass
class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
def create_device(self):
controller = CalibratedPS4ControllerUSB()
controller.application_matches = application_matches
return controller
class CalibratedPS5Controller(object):
# DualSense reports uncalibrated sensor data. Calibration coefficients
# can be retrieved using feature report 0x09.
# The values below are the processed calibration values for the
# DualSene matching the feature reports of PS5ControllerBluetooth/USB
# as dumped from hid-playstation 'dualsense_get_calibration_data'.
#
# Note we duplicate those values here in case the kernel changes them
# so we can have tests passing even if hid-tools doesn't have the
# correct values.
accelerometer_calibration_data = {
"x": {"bias": 0, "numer": 16384, "denom": 16374},
"y": {"bias": -114, "numer": 16384, "denom": 16362},
"z": {"bias": 2, "numer": 16384, "denom": 16395},
}
gyroscope_calibration_data = {
"x": {"bias": 0, "numer": 1105920, "denom": 17727},
"y": {"bias": 0, "numer": 1105920, "denom": 17728},
"z": {"bias": 0, "numer": 1105920, "denom": 17769},
}
class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller, PS5ControllerBluetooth):
pass
class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
kernel_modules = [PS5_MODULE]
def create_device(self):
controller = CalibratedPS5ControllerBluetooth()
controller.application_matches = application_matches
return controller
class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB):
pass
class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
kernel_modules = [PS5_MODULE]
def create_device(self):
controller = CalibratedPS5ControllerUSB()
controller.application_matches = application_matches
return controller
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,103 @@
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2021 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2021 Red Hat, Inc.
#
# This is to ensure we don't crash when emulating USB devices
from . import base
import pytest
import logging
logger = logging.getLogger("hidtools.test.usb")
class USBDev(base.UHIDTestDevice):
# fmt: off
report_descriptor = [
0x05, 0x01, # .Usage Page (Generic Desktop) 0
0x09, 0x02, # .Usage (Mouse) 2
0xa1, 0x01, # .Collection (Application) 4
0x09, 0x02, # ..Usage (Mouse) 6
0xa1, 0x02, # ..Collection (Logical) 8
0x09, 0x01, # ...Usage (Pointer) 10
0xa1, 0x00, # ...Collection (Physical) 12
0x05, 0x09, # ....Usage Page (Button) 14
0x19, 0x01, # ....Usage Minimum (1) 16
0x29, 0x03, # ....Usage Maximum (3) 18
0x15, 0x00, # ....Logical Minimum (0) 20
0x25, 0x01, # ....Logical Maximum (1) 22
0x75, 0x01, # ....Report Size (1) 24
0x95, 0x03, # ....Report Count (3) 26
0x81, 0x02, # ....Input (Data,Var,Abs) 28
0x75, 0x05, # ....Report Size (5) 30
0x95, 0x01, # ....Report Count (1) 32
0x81, 0x03, # ....Input (Cnst,Var,Abs) 34
0x05, 0x01, # ....Usage Page (Generic Desktop) 36
0x09, 0x30, # ....Usage (X) 38
0x09, 0x31, # ....Usage (Y) 40
0x15, 0x81, # ....Logical Minimum (-127) 42
0x25, 0x7f, # ....Logical Maximum (127) 44
0x75, 0x08, # ....Report Size (8) 46
0x95, 0x02, # ....Report Count (2) 48
0x81, 0x06, # ....Input (Data,Var,Rel) 50
0xc0, # ...End Collection 52
0xc0, # ..End Collection 53
0xc0, # .End Collection 54
]
# fmt: on
def __init__(self, name=None, input_info=None):
super().__init__(
name, "Mouse", input_info=input_info, rdesc=USBDev.report_descriptor
)
# skip witing for udev events, it's likely that the report
# descriptor is wrong
def is_ready(self):
return True
# we don't have an evdev node here, so paper over
# the checks
def get_evdev(self, application=None):
return "OK"
class TestUSBDevice(base.BaseTestCase.TestUhid):
"""
Test class to test if an emulated USB device crashes
the kernel.
"""
# conftest.py is generating the following fixture:
#
# @pytest.fixture(params=[('modulename', 1, 2)])
# def usbVidPid(self, request):
# return request.param
@pytest.fixture()
def new_uhdev(self, usbVidPid, request):
self.module, self.vid, self.pid = usbVidPid
self._load_kernel_module(None, self.module)
return USBDev(input_info=(3, self.vid, self.pid))
def test_creation(self):
"""
inject the USB dev through uhid and immediately see if there is a crash:
uhid can create a USB device with the BUS_USB bus, and some
drivers assume that they can then access USB related structures
when they are actually provided a uhid device. This leads to
a crash because those access result in a segmentation fault.
The kernel should not crash on any (random) user space correct
use of its API. So run through all available modules and declared
devices to see if we can generate a uhid device without a crash.
The test is empty as the fixture `check_taint` is doing the job (and
honestly, when the kernel crashes, the whole machine freezes).
"""
assert True
File diff suppressed because it is too large Load Diff
+474
View File
@@ -0,0 +1,474 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2025 Red Hat
# Copyright (c) 2025 Meta Platforms, Inc. and affiliates
#
# Dependencies:
# * virtme-ng
# * busybox-static (used by virtme-ng)
# * qemu (used by virtme-ng)
readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
readonly HID_BPF_TEST="${SCRIPT_DIR}"/hid_bpf
readonly HIDRAW_TEST="${SCRIPT_DIR}"/hidraw
readonly HID_BPF_PROGS="${KERNEL_CHECKOUT}/drivers/hid/bpf/progs"
readonly SSH_GUEST_PORT=22
readonly WAIT_PERIOD=3
readonly WAIT_PERIOD_MAX=60
readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))
readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_hid_vmtest_XXXX.pid)
readonly QEMU_OPTS="\
--pidfile ${QEMU_PIDFILE} \
"
readonly KERNEL_CMDLINE=""
readonly LOG=$(mktemp /tmp/hid_vmtest_XXXX.log)
readonly TEST_NAMES=(vm_hid_bpf vm_hidraw vm_pytest)
readonly TEST_DESCS=(
"Run hid_bpf tests in the VM."
"Run hidraw tests in the VM."
"Run the hid-tools test-suite in the VM."
)
VERBOSE=0
SHELL_MODE=0
BUILD_HOST=""
BUILD_HOST_PODMAN_CONTAINER_NAME=""
usage() {
local name
local desc
local i
echo
echo "$0 [OPTIONS] [TEST]... [-- tests-args]"
echo "If no TEST argument is given, all tests will be run."
echo
echo "Options"
echo " -b: build the kernel from the current source tree and use it for guest VMs"
echo " -H: hostname for remote build host (used with -b)"
echo " -p: podman container name for remote build host (used with -b)"
echo " Example: -H beefyserver -p vng"
echo " -q: set the path to or name of qemu binary"
echo " -s: start a shell in the VM instead of running tests"
echo " -v: more verbose output (can be repeated multiple times)"
echo
echo "Available tests"
for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do
name=${TEST_NAMES[${i}]}
desc=${TEST_DESCS[${i}]}
printf "\t%-35s%-35s\n" "${name}" "${desc}"
done
echo
exit 1
}
die() {
echo "$*" >&2
exit "${KSFT_FAIL}"
}
vm_ssh() {
# vng --ssh-client keeps shouting "Warning: Permanently added 'virtme-ng%22'
# (ED25519) to the list of known hosts.",
# So replace the command with what's actually called and add the "-q" option
stdbuf -oL ssh -q \
-F ${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf \
-l root virtme-ng%${SSH_GUEST_PORT} \
"$@"
return $?
}
cleanup() {
if [[ -s "${QEMU_PIDFILE}" ]]; then
pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1
fi
# If failure occurred during or before qemu start up, then we need
# to clean this up ourselves.
if [[ -e "${QEMU_PIDFILE}" ]]; then
rm "${QEMU_PIDFILE}"
fi
}
check_args() {
local found
for arg in "$@"; do
found=0
for name in "${TEST_NAMES[@]}"; do
if [[ "${name}" = "${arg}" ]]; then
found=1
break
fi
done
if [[ "${found}" -eq 0 ]]; then
echo "${arg} is not an available test" >&2
usage
fi
done
for arg in "$@"; do
if ! command -v > /dev/null "test_${arg}"; then
echo "Test ${arg} not found" >&2
usage
fi
done
}
check_deps() {
for dep in vng ${QEMU} busybox pkill ssh pytest; do
if [[ ! -x $(command -v "${dep}") ]]; then
echo -e "skip: dependency ${dep} not found!\n"
exit "${KSFT_SKIP}"
fi
done
if [[ ! -x $(command -v "${HID_BPF_TEST}") ]]; then
printf "skip: %s not found!" "${HID_BPF_TEST}"
printf " Please build the kselftest hid_bpf target.\n"
exit "${KSFT_SKIP}"
fi
if [[ ! -x $(command -v "${HIDRAW_TEST}") ]]; then
printf "skip: %s not found!" "${HIDRAW_TEST}"
printf " Please build the kselftest hidraw target.\n"
exit "${KSFT_SKIP}"
fi
}
check_vng() {
local tested_versions
local version
local ok
tested_versions=("1.36" "1.37")
version="$(vng --version)"
ok=0
for tv in "${tested_versions[@]}"; do
if [[ "${version}" == *"${tv}"* ]]; then
ok=1
break
fi
done
if [[ ! "${ok}" -eq 1 ]]; then
printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
printf "not function properly.\n\tThe following versions have been tested: " >&2
echo "${tested_versions[@]}" >&2
fi
}
handle_build() {
if [[ ! "${BUILD}" -eq 1 ]]; then
return
fi
if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then
echo "-b requires vmtest.sh called from the kernel source tree" >&2
exit 1
fi
pushd "${KERNEL_CHECKOUT}" &>/dev/null
if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then
die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})"
fi
local vng_args=("-v" "--config" "${SCRIPT_DIR}/config" "--build")
if [[ -n "${BUILD_HOST}" ]]; then
vng_args+=("--build-host" "${BUILD_HOST}")
fi
if [[ -n "${BUILD_HOST_PODMAN_CONTAINER_NAME}" ]]; then
vng_args+=("--build-host-exec-prefix" \
"podman exec -ti ${BUILD_HOST_PODMAN_CONTAINER_NAME}")
fi
if ! vng "${vng_args[@]}"; then
die "failed to build kernel from source tree (${KERNEL_CHECKOUT})"
fi
if ! make -j$(nproc) -C "${HID_BPF_PROGS}"; then
die "failed to build HID bpf objects from source tree (${HID_BPF_PROGS})"
fi
if ! make -j$(nproc) -C "${SCRIPT_DIR}"; then
die "failed to build HID selftests from source tree (${SCRIPT_DIR})"
fi
popd &>/dev/null
}
vm_start() {
local logfile=/dev/null
local verbose_opt=""
local kernel_opt=""
local qemu
qemu=$(command -v "${QEMU}")
if [[ "${VERBOSE}" -eq 2 ]]; then
verbose_opt="--verbose"
logfile=/dev/stdout
fi
# If we are running from within the kernel source tree, use the kernel source tree
# as the kernel to boot, otherwise use the currently running kernel.
if [[ "$(realpath "$(pwd)")" == "${KERNEL_CHECKOUT}"* ]]; then
kernel_opt="${KERNEL_CHECKOUT}"
fi
vng \
--run \
${kernel_opt} \
${verbose_opt} \
--qemu-opts="${QEMU_OPTS}" \
--qemu="${qemu}" \
--user root \
--append "${KERNEL_CMDLINE}" \
--ssh "${SSH_GUEST_PORT}" \
--rw &> ${logfile} &
local vng_pid=$!
local elapsed=0
while [[ ! -s "${QEMU_PIDFILE}" ]]; do
if ! kill -0 "${vng_pid}" 2>/dev/null; then
echo "vng process (PID ${vng_pid}) exited early, check logs for details" >&2
die "failed to boot VM"
fi
if [[ ${elapsed} -ge ${WAIT_TOTAL} ]]; then
echo "Timed out after ${WAIT_TOTAL} seconds waiting for VM to boot" >&2
die "failed to boot VM"
fi
sleep 1
elapsed=$((elapsed + 1))
done
}
vm_wait_for_ssh() {
local i
i=0
while true; do
if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then
die "Timed out waiting for guest ssh"
fi
if vm_ssh -- true; then
break
fi
i=$(( i + 1 ))
sleep ${WAIT_PERIOD}
done
}
vm_mount_bpffs() {
vm_ssh -- mount bpffs -t bpf /sys/fs/bpf
}
__log_stdin() {
stdbuf -oL awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0; fflush() }'
}
__log_args() {
echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }'
}
log() {
local verbose="$1"
shift
local prefix="$1"
shift
local redirect=
if [[ ${verbose} -le 0 ]]; then
redirect=/dev/null
else
redirect=/dev/stdout
fi
if [[ "$#" -eq 0 ]]; then
__log_stdin | tee -a "${LOG}" > ${redirect}
else
__log_args "$@" | tee -a "${LOG}" > ${redirect}
fi
}
log_setup() {
log $((VERBOSE-1)) "setup" "$@"
}
log_host() {
local testname=$1
shift
log $((VERBOSE-1)) "test:${testname}:host" "$@"
}
log_guest() {
local testname=$1
shift
log ${VERBOSE} "# test:${testname}" "$@"
}
test_vm_hid_bpf() {
local testname="${FUNCNAME[0]#test_}"
vm_ssh -- "${HID_BPF_TEST}" \
2>&1 | log_guest "${testname}"
return ${PIPESTATUS[0]}
}
test_vm_hidraw() {
local testname="${FUNCNAME[0]#test_}"
vm_ssh -- "${HIDRAW_TEST}" \
2>&1 | log_guest "${testname}"
return ${PIPESTATUS[0]}
}
test_vm_pytest() {
local testname="${FUNCNAME[0]#test_}"
shift
vm_ssh -- pytest ${SCRIPT_DIR}/tests --color=yes "$@" \
2>&1 | log_guest "${testname}"
return ${PIPESTATUS[0]}
}
run_test() {
local vm_oops_cnt_before
local vm_warn_cnt_before
local vm_oops_cnt_after
local vm_warn_cnt_after
local name
local rc
vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops')
vm_error_cnt_before=$(vm_ssh -- dmesg --level=err | wc -l)
name=$(echo "${1}" | awk '{ print $1 }')
eval test_"${name}" "$@"
rc=$?
vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l)
if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then
echo "FAIL: kernel oops detected on vm" | log_host "${name}"
rc=$KSFT_FAIL
fi
vm_error_cnt_after=$(vm_ssh -- dmesg --level=err | wc -l)
if [[ ${vm_error_cnt_after} -gt ${vm_error_cnt_before} ]]; then
echo "FAIL: kernel error detected on vm" | log_host "${name}"
vm_ssh -- dmesg --level=err | log_host "${name}"
rc=$KSFT_FAIL
fi
return "${rc}"
}
QEMU="qemu-system-$(uname -m)"
while getopts :hvsbq:H:p: o
do
case $o in
v) VERBOSE=$((VERBOSE+1));;
s) SHELL_MODE=1;;
b) BUILD=1;;
q) QEMU=$OPTARG;;
H) BUILD_HOST=$OPTARG;;
p) BUILD_HOST_PODMAN_CONTAINER_NAME=$OPTARG;;
h|*) usage;;
esac
done
shift $((OPTIND-1))
trap cleanup EXIT
PARAMS=""
if [[ ${#} -eq 0 ]]; then
ARGS=("${TEST_NAMES[@]}")
else
ARGS=()
COUNT=0
for arg in $@; do
COUNT=$((COUNT+1))
if [[ x"$arg" == x"--" ]]; then
break
fi
ARGS+=($arg)
done
shift $COUNT
PARAMS="$@"
fi
if [[ "${SHELL_MODE}" -eq 0 ]]; then
check_args "${ARGS[@]}"
echo "1..${#ARGS[@]}"
fi
check_deps
check_vng
handle_build
log_setup "Booting up VM"
vm_start
vm_wait_for_ssh
vm_mount_bpffs
log_setup "VM booted up"
if [[ "${SHELL_MODE}" -eq 1 ]]; then
log_setup "Starting interactive shell in VM"
echo "Starting shell in VM. Use 'exit' to quit and shutdown the VM."
CURRENT_DIR="$(pwd)"
vm_ssh -t -- "cd '${CURRENT_DIR}' && exec bash -l"
exit "$KSFT_PASS"
fi
cnt_pass=0
cnt_fail=0
cnt_skip=0
cnt_total=0
for arg in "${ARGS[@]}"; do
run_test "${arg}" "${PARAMS}"
rc=$?
if [[ ${rc} -eq $KSFT_PASS ]]; then
cnt_pass=$(( cnt_pass + 1 ))
echo "ok ${cnt_total} ${arg}"
elif [[ ${rc} -eq $KSFT_SKIP ]]; then
cnt_skip=$(( cnt_skip + 1 ))
echo "ok ${cnt_total} ${arg} # SKIP"
elif [[ ${rc} -eq $KSFT_FAIL ]]; then
cnt_fail=$(( cnt_fail + 1 ))
echo "not ok ${cnt_total} ${arg} # exit=$rc"
fi
cnt_total=$(( cnt_total + 1 ))
done
echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}"
echo "Log: ${LOG}"
if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then
exit "$KSFT_PASS"
else
exit "$KSFT_FAIL"
fi