diff options
Diffstat (limited to 'tools')
254 files changed, 19969 insertions, 4928 deletions
diff --git a/tools/accounting/getdelays.c b/tools/accounting/getdelays.c index 938dec0dfaad..1334214546d7 100644 --- a/tools/accounting/getdelays.c +++ b/tools/accounting/getdelays.c @@ -198,17 +198,19 @@ static void print_delayacct(struct taskstats *t) printf("\n\nCPU %15s%15s%15s%15s%15s\n" " %15llu%15llu%15llu%15llu%15.3fms\n" "IO %15s%15s%15s\n" - " %15llu%15llu%15llums\n" + " %15llu%15llu%15.3fms\n" "SWAP %15s%15s%15s\n" - " %15llu%15llu%15llums\n" + " %15llu%15llu%15.3fms\n" "RECLAIM %12s%15s%15s\n" - " %15llu%15llu%15llums\n" + " %15llu%15llu%15.3fms\n" "THRASHING%12s%15s%15s\n" - " %15llu%15llu%15llums\n" + " %15llu%15llu%15.3fms\n" "COMPACT %12s%15s%15s\n" - " %15llu%15llu%15llums\n" + " %15llu%15llu%15.3fms\n" "WPCOPY %12s%15s%15s\n" - " %15llu%15llu%15llums\n", + " %15llu%15llu%15.3fms\n" + "IRQ %15s%15s%15s\n" + " %15llu%15llu%15.3fms\n", "count", "real total", "virtual total", "delay total", "delay average", (unsigned long long)t->cpu_count, @@ -219,27 +221,31 @@ static void print_delayacct(struct taskstats *t) "count", "delay total", "delay average", (unsigned long long)t->blkio_count, (unsigned long long)t->blkio_delay_total, - average_ms(t->blkio_delay_total, t->blkio_count), + average_ms((double)t->blkio_delay_total, t->blkio_count), "count", "delay total", "delay average", (unsigned long long)t->swapin_count, (unsigned long long)t->swapin_delay_total, - average_ms(t->swapin_delay_total, t->swapin_count), + average_ms((double)t->swapin_delay_total, t->swapin_count), "count", "delay total", "delay average", (unsigned long long)t->freepages_count, (unsigned long long)t->freepages_delay_total, - average_ms(t->freepages_delay_total, t->freepages_count), + average_ms((double)t->freepages_delay_total, t->freepages_count), "count", "delay total", "delay average", (unsigned long long)t->thrashing_count, (unsigned long long)t->thrashing_delay_total, - average_ms(t->thrashing_delay_total, t->thrashing_count), + average_ms((double)t->thrashing_delay_total, t->thrashing_count), "count", "delay total", "delay average", (unsigned long long)t->compact_count, (unsigned long long)t->compact_delay_total, - average_ms(t->compact_delay_total, t->compact_count), + average_ms((double)t->compact_delay_total, t->compact_count), "count", "delay total", "delay average", (unsigned long long)t->wpcopy_count, (unsigned long long)t->wpcopy_delay_total, - average_ms(t->wpcopy_delay_total, t->wpcopy_count)); + average_ms((double)t->wpcopy_delay_total, t->wpcopy_count), + "count", "delay total", "delay average", + (unsigned long long)t->irq_count, + (unsigned long long)t->irq_delay_total, + average_ms((double)t->irq_delay_total, t->irq_count)); } static void task_context_switch_counts(struct taskstats *t) diff --git a/tools/arch/x86/include/asm/orc_types.h b/tools/arch/x86/include/asm/orc_types.h index 1343a62106de..46d7e06763c9 100644 --- a/tools/arch/x86/include/asm/orc_types.h +++ b/tools/arch/x86/include/asm/orc_types.h @@ -39,6 +39,12 @@ #define ORC_REG_SP_INDIRECT 9 #define ORC_REG_MAX 15 +#define ORC_TYPE_UNDEFINED 0 +#define ORC_TYPE_END_OF_STACK 1 +#define ORC_TYPE_CALL 2 +#define ORC_TYPE_REGS 3 +#define ORC_TYPE_REGS_PARTIAL 4 + #ifndef __ASSEMBLY__ #include <asm/byteorder.h> @@ -56,16 +62,14 @@ struct orc_entry { #if defined(__LITTLE_ENDIAN_BITFIELD) unsigned sp_reg:4; unsigned bp_reg:4; - unsigned type:2; + unsigned type:3; unsigned signal:1; - unsigned end:1; #elif defined(__BIG_ENDIAN_BITFIELD) unsigned bp_reg:4; unsigned sp_reg:4; unsigned unused:4; - unsigned end:1; unsigned signal:1; - unsigned type:2; + unsigned type:3; #endif } __packed; diff --git a/tools/arch/x86/kcpuid/cpuid.csv b/tools/arch/x86/kcpuid/cpuid.csv index 4f1c4b0c29e9..e0c25b75327e 100644 --- a/tools/arch/x86/kcpuid/cpuid.csv +++ b/tools/arch/x86/kcpuid/cpuid.csv @@ -184,8 +184,8 @@ 7, 0, EBX, 27, avx512er, AVX512 Exponent Reciproca instr 7, 0, EBX, 28, avx512cd, AVX512 Conflict Detection instr 7, 0, EBX, 29, sha, Intel Secure Hash Algorithm Extensions instr - 7, 0, EBX, 26, avx512bw, AVX512 Byte & Word instr - 7, 0, EBX, 28, avx512vl, AVX512 Vector Length Extentions (VL) + 7, 0, EBX, 30, avx512bw, AVX512 Byte & Word instr + 7, 0, EBX, 31, avx512vl, AVX512 Vector Length Extentions (VL) 7, 0, ECX, 0, prefetchwt1, X 7, 0, ECX, 1, avx512vbmi, AVX512 Vector Byte Manipulation Instructions 7, 0, ECX, 2, umip, User-mode Instruction Prevention @@ -340,19 +340,70 @@ # According to SDM # 40000000H - 4FFFFFFFH is invalid range - # Leaf 80000001H # Extended Processor Signature and Feature Bits +0x80000001, 0, EAX, 27:20, extfamily, Extended family +0x80000001, 0, EAX, 19:16, extmodel, Extended model +0x80000001, 0, EAX, 11:8, basefamily, Description of Family +0x80000001, 0, EAX, 11:8, basemodel, Model numbers vary with product +0x80000001, 0, EAX, 3:0, stepping, Processor stepping (revision) for a specific model + +0x80000001, 0, EBX, 31:28, pkgtype, Specifies the package type + 0x80000001, 0, ECX, 0, lahf_lm, LAHF/SAHF available in 64-bit mode +0x80000001, 0, ECX, 1, cmplegacy, Core multi-processing legacy mode +0x80000001, 0, ECX, 2, svm, Indicates support for: VMRUN, VMLOAD, VMSAVE, CLGI, VMMCALL, and INVLPGA +0x80000001, 0, ECX, 3, extapicspace, Extended APIC register space +0x80000001, 0, ECX, 4, altmovecr8, Indicates support for LOCK MOV CR0 means MOV CR8 0x80000001, 0, ECX, 5, lzcnt, LZCNT +0x80000001, 0, ECX, 6, sse4a, EXTRQ, INSERTQ, MOVNTSS, and MOVNTSD instruction support +0x80000001, 0, ECX, 7, misalignsse, Misaligned SSE Mode 0x80000001, 0, ECX, 8, prefetchw, PREFETCHW - +0x80000001, 0, ECX, 9, osvw, OS Visible Work-around support +0x80000001, 0, ECX, 10, ibs, Instruction Based Sampling +0x80000001, 0, ECX, 11, xop, Extended operation support +0x80000001, 0, ECX, 12, skinit, SKINIT and STGI support +0x80000001, 0, ECX, 13, wdt, Watchdog timer support +0x80000001, 0, ECX, 15, lwp, Lightweight profiling support +0x80000001, 0, ECX, 16, fma4, Four-operand FMA instruction support +0x80000001, 0, ECX, 17, tce, Translation cache extension +0x80000001, 0, ECX, 22, TopologyExtensions, Indicates support for Core::X86::Cpuid::CachePropEax0 and Core::X86::Cpuid::ExtApicId +0x80000001, 0, ECX, 23, perfctrextcore, Indicates support for Core::X86::Msr::PERF_CTL0 - 5 and Core::X86::Msr::PERF_CTR +0x80000001, 0, ECX, 24, perfctrextdf, Indicates support for Core::X86::Msr::DF_PERF_CTL and Core::X86::Msr::DF_PERF_CTR +0x80000001, 0, ECX, 26, databreakpointextension, Indicates data breakpoint support for Core::X86::Msr::DR0_ADDR_MASK, Core::X86::Msr::DR1_ADDR_MASK, Core::X86::Msr::DR2_ADDR_MASK and Core::X86::Msr::DR3_ADDR_MASK +0x80000001, 0, ECX, 27, perftsc, Performance time-stamp counter supported +0x80000001, 0, ECX, 28, perfctrextllc, Indicates support for L3 performance counter extensions +0x80000001, 0, ECX, 29, mwaitextended, MWAITX and MONITORX capability is supported +0x80000001, 0, ECX, 30, admskextn, Indicates support for address mask extension (to 32 bits and to all 4 DRs) for instruction breakpoints + +0x80000001, 0, EDX, 0, fpu, x87 floating point unit on-chip +0x80000001, 0, EDX, 1, vme, Virtual-mode enhancements +0x80000001, 0, EDX, 2, de, Debugging extensions, IO breakpoints, CR4.DE +0x80000001, 0, EDX, 3, pse, Page-size extensions (4 MB pages) +0x80000001, 0, EDX, 4, tsc, Time stamp counter, RDTSC/RDTSCP instructions, CR4.TSD +0x80000001, 0, EDX, 5, msr, Model-specific registers (MSRs), with RDMSR and WRMSR instructions +0x80000001, 0, EDX, 6, pae, Physical-address extensions (PAE) +0x80000001, 0, EDX, 7, mce, Machine Check Exception, CR4.MCE +0x80000001, 0, EDX, 8, cmpxchg8b, CMPXCHG8B instruction +0x80000001, 0, EDX, 9, apic, advanced programmable interrupt controller (APIC) exists and is enabled 0x80000001, 0, EDX, 11, sysret, SYSCALL/SYSRET supported +0x80000001, 0, EDX, 12, mtrr, Memory-type range registers +0x80000001, 0, EDX, 13, pge, Page global extension, CR4.PGE +0x80000001, 0, EDX, 14, mca, Machine check architecture, MCG_CAP +0x80000001, 0, EDX, 15, cmov, Conditional move instructions, CMOV, FCOMI, FCMOV +0x80000001, 0, EDX, 16, pat, Page attribute table +0x80000001, 0, EDX, 17, pse36, Page-size extensions 0x80000001, 0, EDX, 20, exec_dis, Execute Disable Bit available +0x80000001, 0, EDX, 22, mmxext, AMD extensions to MMX instructions +0x80000001, 0, EDX, 23, mmx, MMX instructions +0x80000001, 0, EDX, 24, fxsr, FXSAVE and FXRSTOR instructions +0x80000001, 0, EDX, 25, ffxsr, FXSAVE and FXRSTOR instruction optimizations 0x80000001, 0, EDX, 26, 1gb_page, 1GB page supported 0x80000001, 0, EDX, 27, rdtscp, RDTSCP and IA32_TSC_AUX are available -#0x80000001, 0, EDX, 29, 64b, 64b Architecture supported +0x80000001, 0, EDX, 29, lm, 64b Architecture supported +0x80000001, 0, EDX, 30, threednowext, AMD extensions to 3DNow! instructions +0x80000001, 0, EDX, 31, threednow, 3DNow! instructions # Leaf 80000002H/80000003H/80000004H # Processor Brand String diff --git a/tools/arch/x86/kcpuid/kcpuid.c b/tools/arch/x86/kcpuid/kcpuid.c index dae75511fef7..416f5b35dd8f 100644 --- a/tools/arch/x86/kcpuid/kcpuid.c +++ b/tools/arch/x86/kcpuid/kcpuid.c @@ -33,7 +33,7 @@ struct reg_desc { struct bits_desc descs[32]; }; -enum { +enum cpuid_reg { R_EAX = 0, R_EBX, R_ECX, @@ -41,6 +41,10 @@ enum { NR_REGS }; +static const char * const reg_names[] = { + "EAX", "EBX", "ECX", "EDX", +}; + struct subleaf { u32 index; u32 sub; @@ -428,12 +432,18 @@ static void parse_text(void) /* Decode every eax/ebx/ecx/edx */ -static void decode_bits(u32 value, struct reg_desc *rdesc) +static void decode_bits(u32 value, struct reg_desc *rdesc, enum cpuid_reg reg) { struct bits_desc *bdesc; int start, end, i; u32 mask; + if (!rdesc->nr) { + if (show_details) + printf("\t %s: 0x%08x\n", reg_names[reg], value); + return; + } + for (i = 0; i < rdesc->nr; i++) { bdesc = &rdesc->descs[i]; @@ -468,13 +478,21 @@ static void show_leaf(struct subleaf *leaf) if (!leaf) return; - if (show_raw) + if (show_raw) { leaf_print_raw(leaf); + } else { + if (show_details) + printf("CPUID_0x%x_ECX[0x%x]:\n", + leaf->index, leaf->sub); + } + + decode_bits(leaf->eax, &leaf->info[R_EAX], R_EAX); + decode_bits(leaf->ebx, &leaf->info[R_EBX], R_EBX); + decode_bits(leaf->ecx, &leaf->info[R_ECX], R_ECX); + decode_bits(leaf->edx, &leaf->info[R_EDX], R_EDX); - decode_bits(leaf->eax, &leaf->info[R_EAX]); - decode_bits(leaf->ebx, &leaf->info[R_EBX]); - decode_bits(leaf->ecx, &leaf->info[R_ECX]); - decode_bits(leaf->edx, &leaf->info[R_EDX]); + if (!show_raw && show_details) + printf("\n"); } static void show_func(struct cpuid_func *func) diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c index 77058174082d..27a23196d58e 100644 --- a/tools/bpf/resolve_btfids/main.c +++ b/tools/bpf/resolve_btfids/main.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) /* - * resolve_btfids scans Elf object for .BTF_ids section and resolves + * resolve_btfids scans ELF object for .BTF_ids section and resolves * its symbols with BTF ID values. * * Each symbol points to 4 bytes data and is expected to have diff --git a/tools/include/linux/err.h b/tools/include/linux/err.h index 25f2bb3a991d..332b983ead1e 100644 --- a/tools/include/linux/err.h +++ b/tools/include/linux/err.h @@ -20,7 +20,7 @@ * Userspace note: * The same principle works for userspace, because 'error' pointers * fall down to the unused hole far from user space, as described - * in Documentation/x86/x86_64/mm.rst for x86_64 arch: + * in Documentation/arch/x86/x86_64/mm.rst for x86_64 arch: * * 0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm hole caused by [48:63] sign extension * ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole diff --git a/tools/include/linux/objtool.h b/tools/include/linux/objtool.h deleted file mode 100644 index 9ac3df3fccf0..000000000000 --- a/tools/include/linux/objtool.h +++ /dev/null @@ -1,200 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _LINUX_OBJTOOL_H -#define _LINUX_OBJTOOL_H - -#ifndef __ASSEMBLY__ - -#include <linux/types.h> - -/* - * This struct is used by asm and inline asm code to manually annotate the - * location of registers on the stack. - */ -struct unwind_hint { - u32 ip; - s16 sp_offset; - u8 sp_reg; - u8 type; - u8 signal; - u8 end; -}; -#endif - -/* - * UNWIND_HINT_TYPE_CALL: Indicates that sp_reg+sp_offset resolves to PREV_SP - * (the caller's SP right before it made the call). Used for all callable - * functions, i.e. all C code and all callable asm functions. - * - * UNWIND_HINT_TYPE_REGS: Used in entry code to indicate that sp_reg+sp_offset - * points to a fully populated pt_regs from a syscall, interrupt, or exception. - * - * UNWIND_HINT_TYPE_REGS_PARTIAL: Used in entry code to indicate that - * sp_reg+sp_offset points to the iret return frame. - * - * UNWIND_HINT_FUNC: Generate the unwind metadata of a callable function. - * Useful for code which doesn't have an ELF function annotation. - * - * UNWIND_HINT_ENTRY: machine entry without stack, SYSCALL/SYSENTER etc. - */ -#define UNWIND_HINT_TYPE_CALL 0 -#define UNWIND_HINT_TYPE_REGS 1 -#define UNWIND_HINT_TYPE_REGS_PARTIAL 2 -#define UNWIND_HINT_TYPE_FUNC 3 -#define UNWIND_HINT_TYPE_ENTRY 4 -#define UNWIND_HINT_TYPE_SAVE 5 -#define UNWIND_HINT_TYPE_RESTORE 6 - -#ifdef CONFIG_OBJTOOL - -#include <asm/asm.h> - -#ifndef __ASSEMBLY__ - -#define UNWIND_HINT(sp_reg, sp_offset, type, signal, end) \ - "987: \n\t" \ - ".pushsection .discard.unwind_hints\n\t" \ - /* struct unwind_hint */ \ - ".long 987b - .\n\t" \ - ".short " __stringify(sp_offset) "\n\t" \ - ".byte " __stringify(sp_reg) "\n\t" \ - ".byte " __stringify(type) "\n\t" \ - ".byte " __stringify(signal) "\n\t" \ - ".byte " __stringify(end) "\n\t" \ - ".balign 4 \n\t" \ - ".popsection\n\t" - -/* - * This macro marks the given function's stack frame as "non-standard", which - * tells objtool to ignore the function when doing stack metadata validation. - * It should only be used in special cases where you're 100% sure it won't - * affect the reliability of frame pointers and kernel stack traces. - * - * For more information, see tools/objtool/Documentation/objtool.txt. - */ -#define STACK_FRAME_NON_STANDARD(func) \ - static void __used __section(".discard.func_stack_frame_non_standard") \ - *__func_stack_frame_non_standard_##func = func - -/* - * STACK_FRAME_NON_STANDARD_FP() is a frame-pointer-specific function ignore - * for the case where a function is intentionally missing frame pointer setup, - * but otherwise needs objtool/ORC coverage when frame pointers are disabled. - */ -#ifdef CONFIG_FRAME_POINTER -#define STACK_FRAME_NON_STANDARD_FP(func) STACK_FRAME_NON_STANDARD(func) -#else -#define STACK_FRAME_NON_STANDARD_FP(func) -#endif - -#define ANNOTATE_NOENDBR \ - "986: \n\t" \ - ".pushsection .discard.noendbr\n\t" \ - _ASM_PTR " 986b\n\t" \ - ".popsection\n\t" - -#define ASM_REACHABLE \ - "998:\n\t" \ - ".pushsection .discard.reachable\n\t" \ - ".long 998b - .\n\t" \ - ".popsection\n\t" - -#else /* __ASSEMBLY__ */ - -/* - * This macro indicates that the following intra-function call is valid. - * Any non-annotated intra-function call will cause objtool to issue a warning. - */ -#define ANNOTATE_INTRA_FUNCTION_CALL \ - 999: \ - .pushsection .discard.intra_function_calls; \ - .long 999b; \ - .popsection; - -/* - * In asm, there are two kinds of code: normal C-type callable functions and - * the rest. The normal callable functions can be called by other code, and - * don't do anything unusual with the stack. Such normal callable functions - * are annotated with the ENTRY/ENDPROC macros. Most asm code falls in this - * category. In this case, no special debugging annotations are needed because - * objtool can automatically generate the ORC data for the ORC unwinder to read - * at runtime. - * - * Anything which doesn't fall into the above category, such as syscall and - * interrupt handlers, tends to not be called directly by other functions, and - * often does unusual non-C-function-type things with the stack pointer. Such - * code needs to be annotated such that objtool can understand it. The - * following CFI hint macros are for this type of code. - * - * These macros provide hints to objtool about the state of the stack at each - * instruction. Objtool starts from the hints and follows the code flow, - * making automatic CFI adjustments when it sees pushes and pops, filling out - * the debuginfo as necessary. It will also warn if it sees any - * inconsistencies. - */ -.macro UNWIND_HINT type:req sp_reg=0 sp_offset=0 signal=0 end=0 -.Lunwind_hint_ip_\@: - .pushsection .discard.unwind_hints - /* struct unwind_hint */ - .long .Lunwind_hint_ip_\@ - . - .short \sp_offset - .byte \sp_reg - .byte \type - .byte \signal - .byte \end - .balign 4 - .popsection -.endm - -.macro STACK_FRAME_NON_STANDARD func:req - .pushsection .discard.func_stack_frame_non_standard, "aw" - _ASM_PTR \func - .popsection -.endm - -.macro STACK_FRAME_NON_STANDARD_FP func:req -#ifdef CONFIG_FRAME_POINTER - STACK_FRAME_NON_STANDARD \func -#endif -.endm - -.macro ANNOTATE_NOENDBR -.Lhere_\@: - .pushsection .discard.noendbr - .quad .Lhere_\@ - .popsection -.endm - -.macro REACHABLE -.Lhere_\@: - .pushsection .discard.reachable - .long .Lhere_\@ - . - .popsection -.endm - -#endif /* __ASSEMBLY__ */ - -#else /* !CONFIG_OBJTOOL */ - -#ifndef __ASSEMBLY__ - -#define UNWIND_HINT(sp_reg, sp_offset, type, signal, end) \ - "\n\t" -#define STACK_FRAME_NON_STANDARD(func) -#define STACK_FRAME_NON_STANDARD_FP(func) -#define ANNOTATE_NOENDBR -#define ASM_REACHABLE -#else -#define ANNOTATE_INTRA_FUNCTION_CALL -.macro UNWIND_HINT type:req sp_reg=0 sp_offset=0 signal=0 end=0 -.endm -.macro STACK_FRAME_NON_STANDARD func:req -.endm -.macro ANNOTATE_NOENDBR -.endm -.macro REACHABLE -.endm -#endif - -#endif /* CONFIG_OBJTOOL */ - -#endif /* _LINUX_OBJTOOL_H */ diff --git a/tools/include/linux/objtool_types.h b/tools/include/linux/objtool_types.h new file mode 100644 index 000000000000..453a4f4ef39d --- /dev/null +++ b/tools/include/linux/objtool_types.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_OBJTOOL_TYPES_H +#define _LINUX_OBJTOOL_TYPES_H + +#ifndef __ASSEMBLY__ + +#include <linux/types.h> + +/* + * This struct is used by asm and inline asm code to manually annotate the + * location of registers on the stack. + */ +struct unwind_hint { + u32 ip; + s16 sp_offset; + u8 sp_reg; + u8 type; + u8 signal; +}; + +#endif /* __ASSEMBLY__ */ + +/* + * UNWIND_HINT_TYPE_UNDEFINED: A blind spot in ORC coverage which can result in + * a truncated and unreliable stack unwind. + * + * UNWIND_HINT_TYPE_END_OF_STACK: The end of the kernel stack unwind before + * hitting user entry, boot code, or fork entry (when there are no pt_regs + * available). + * + * UNWIND_HINT_TYPE_CALL: Indicates that sp_reg+sp_offset resolves to PREV_SP + * (the caller's SP right before it made the call). Used for all callable + * functions, i.e. all C code and all callable asm functions. + * + * UNWIND_HINT_TYPE_REGS: Used in entry code to indicate that sp_reg+sp_offset + * points to a fully populated pt_regs from a syscall, interrupt, or exception. + * + * UNWIND_HINT_TYPE_REGS_PARTIAL: Used in entry code to indicate that + * sp_reg+sp_offset points to the iret return frame. + * + * UNWIND_HINT_TYPE_FUNC: Generate the unwind metadata of a callable function. + * Useful for code which doesn't have an ELF function annotation. + * + * UNWIND_HINT_TYPE_{SAVE,RESTORE}: Save the unwind metadata at a certain + * location so that it can be restored later. + */ +#define UNWIND_HINT_TYPE_UNDEFINED 0 +#define UNWIND_HINT_TYPE_END_OF_STACK 1 +#define UNWIND_HINT_TYPE_CALL 2 +#define UNWIND_HINT_TYPE_REGS 3 +#define UNWIND_HINT_TYPE_REGS_PARTIAL 4 +/* The below hint types don't have corresponding ORC types */ +#define UNWIND_HINT_TYPE_FUNC 5 +#define UNWIND_HINT_TYPE_SAVE 6 +#define UNWIND_HINT_TYPE_RESTORE 7 + +#endif /* _LINUX_OBJTOOL_TYPES_H */ diff --git a/tools/include/linux/types.h b/tools/include/linux/types.h index 051fdeaf2670..8519386acd23 100644 --- a/tools/include/linux/types.h +++ b/tools/include/linux/types.h @@ -49,7 +49,12 @@ typedef __s8 s8; #endif #define __force +/* This is defined in linux/compiler_types.h and is left for backward + * compatibility. + */ +#ifndef __user #define __user +#endif #define __must_check #define __cold diff --git a/tools/include/nolibc/.gitignore b/tools/include/nolibc/.gitignore new file mode 100644 index 000000000000..dea22eaaed2b --- /dev/null +++ b/tools/include/nolibc/.gitignore @@ -0,0 +1 @@ +sysroot diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index cfd06764b5ae..9839feafd38a 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -25,8 +25,8 @@ endif nolibc_arch := $(patsubst arm64,aarch64,$(ARCH)) arch_file := arch-$(nolibc_arch).h -all_files := ctype.h errno.h nolibc.h signal.h std.h stdio.h stdlib.h string.h \ - sys.h time.h types.h unistd.h +all_files := ctype.h errno.h nolibc.h signal.h stackprotector.h std.h stdint.h \ + stdio.h stdlib.h string.h sys.h time.h types.h unistd.h # install all headers needed to support a bare-metal compiler all: headers diff --git a/tools/include/nolibc/arch-i386.h b/tools/include/nolibc/arch-i386.h index e8d0cf545bf1..2d98d78fd3f3 100644 --- a/tools/include/nolibc/arch-i386.h +++ b/tools/include/nolibc/arch-i386.h @@ -181,6 +181,8 @@ struct sys_stat_struct { char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); +#define __ARCH_SUPPORTS_STACK_PROTECTOR + /* startup code */ /* * i386 System V ABI mandates: @@ -188,9 +190,12 @@ const unsigned long *_auxv __attribute__((weak)); * 2) The deepest stack frame should be set to zero * */ -void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"),no_stack_protector)) _start(void) { __asm__ volatile ( +#ifdef NOLIBC_STACKPROTECTOR + "call __stack_chk_init\n" // initialize stack protector +#endif "pop %eax\n" // argc (first arg, %eax) "mov %esp, %ebx\n" // argv[] (second arg, %ebx) "lea 4(%ebx,%eax,4),%ecx\n" // then a NULL then envp (third arg, %ecx) diff --git a/tools/include/nolibc/arch-loongarch.h b/tools/include/nolibc/arch-loongarch.h new file mode 100644 index 000000000000..029ee3cd6baf --- /dev/null +++ b/tools/include/nolibc/arch-loongarch.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * LoongArch specific definitions for NOLIBC + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef _NOLIBC_ARCH_LOONGARCH_H +#define _NOLIBC_ARCH_LOONGARCH_H + +/* Syscalls for LoongArch : + * - stack is 16-byte aligned + * - syscall number is passed in a7 + * - arguments are in a0, a1, a2, a3, a4, a5 + * - the system call is performed by calling "syscall 0" + * - syscall return comes in a0 + * - the arguments are cast to long and assigned into the target + * registers which are then simply passed as registers to the asm code, + * so that we don't have to experience issues with register constraints. + * + * On LoongArch, select() is not implemented so we have to use pselect6(). + */ +#define __ARCH_WANT_SYS_PSELECT6 + +#define my_syscall0(num) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0"); \ + \ + __asm__ volatile ( \ + "syscall 0\n" \ + : "=r"(_arg1) \ + : "r"(_num) \ + : "memory", "$t0", "$t1", "$t2", "$t3", \ + "$t4", "$t5", "$t6", "$t7", "$t8" \ + ); \ + _arg1; \ +}) + +#define my_syscall1(num, arg1) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0") = (long)(arg1); \ + \ + __asm__ volatile ( \ + "syscall 0\n" \ + : "+r"(_arg1) \ + : "r"(_num) \ + : "memory", "$t0", "$t1", "$t2", "$t3", \ + "$t4", "$t5", "$t6", "$t7", "$t8" \ + ); \ + _arg1; \ +}) + +#define my_syscall2(num, arg1, arg2) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0") = (long)(arg1); \ + register long _arg2 __asm__ ("a1") = (long)(arg2); \ + \ + __asm__ volatile ( \ + "syscall 0\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), \ + "r"(_num) \ + : "memory", "$t0", "$t1", "$t2", "$t3", \ + "$t4", "$t5", "$t6", "$t7", "$t8" \ + ); \ + _arg1; \ +}) + +#define my_syscall3(num, arg1, arg2, arg3) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0") = (long)(arg1); \ + register long _arg2 __asm__ ("a1") = (long)(arg2); \ + register long _arg3 __asm__ ("a2") = (long)(arg3); \ + \ + __asm__ volatile ( \ + "syscall 0\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), \ + "r"(_num) \ + : "memory", "$t0", "$t1", "$t2", "$t3", \ + "$t4", "$t5", "$t6", "$t7", "$t8" \ + ); \ + _arg1; \ +}) + +#define my_syscall4(num, arg1, arg2, arg3, arg4) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0") = (long)(arg1); \ + register long _arg2 __asm__ ("a1") = (long)(arg2); \ + register long _arg3 __asm__ ("a2") = (long)(arg3); \ + register long _arg4 __asm__ ("a3") = (long)(arg4); \ + \ + __asm__ volatile ( \ + "syscall 0\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), \ + "r"(_num) \ + : "memory", "$t0", "$t1", "$t2", "$t3", \ + "$t4", "$t5", "$t6", "$t7", "$t8" \ + ); \ + _arg1; \ +}) + +#define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0") = (long)(arg1); \ + register long _arg2 __asm__ ("a1") = (long)(arg2); \ + register long _arg3 __asm__ ("a2") = (long)(arg3); \ + register long _arg4 __asm__ ("a3") = (long)(arg4); \ + register long _arg5 __asm__ ("a4") = (long)(arg5); \ + \ + __asm__ volatile ( \ + "syscall 0\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ + "r"(_num) \ + : "memory", "$t0", "$t1", "$t2", "$t3", \ + "$t4", "$t5", "$t6", "$t7", "$t8" \ + ); \ + _arg1; \ +}) + +#define my_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0") = (long)(arg1); \ + register long _arg2 __asm__ ("a1") = (long)(arg2); \ + register long _arg3 __asm__ ("a2") = (long)(arg3); \ + register long _arg4 __asm__ ("a3") = (long)(arg4); \ + register long _arg5 __asm__ ("a4") = (long)(arg5); \ + register long _arg6 __asm__ ("a5") = (long)(arg6); \ + \ + __asm__ volatile ( \ + "syscall 0\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), "r"(_arg6), \ + "r"(_num) \ + : "memory", "$t0", "$t1", "$t2", "$t3", \ + "$t4", "$t5", "$t6", "$t7", "$t8" \ + ); \ + _arg1; \ +}) + +char **environ __attribute__((weak)); +const unsigned long *_auxv __attribute__((weak)); + +#if __loongarch_grlen == 32 +#define LONGLOG "2" +#define SZREG "4" +#define REG_L "ld.w" +#define LONG_S "st.w" +#define LONG_ADD "add.w" +#define LONG_ADDI "addi.w" +#define LONG_SLL "slli.w" +#define LONG_BSTRINS "bstrins.w" +#else // __loongarch_grlen == 64 +#define LONGLOG "3" +#define SZREG "8" +#define REG_L "ld.d" +#define LONG_S "st.d" +#define LONG_ADD "add.d" +#define LONG_ADDI "addi.d" +#define LONG_SLL "slli.d" +#define LONG_BSTRINS "bstrins.d" +#endif + +/* startup code */ +void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) +{ + __asm__ volatile ( + REG_L " $a0, $sp, 0\n" // argc (a0) was in the stack + LONG_ADDI " $a1, $sp, "SZREG"\n" // argv (a1) = sp + SZREG + LONG_SLL " $a2, $a0, "LONGLOG"\n" // envp (a2) = SZREG*argc ... + LONG_ADDI " $a2, $a2, "SZREG"\n" // + SZREG (skip null) + LONG_ADD " $a2, $a2, $a1\n" // + argv + + "move $a3, $a2\n" // iterate a3 over envp to find auxv (after NULL) + "0:\n" // do { + REG_L " $a4, $a3, 0\n" // a4 = *a3; + LONG_ADDI " $a3, $a3, "SZREG"\n" // a3 += sizeof(void*); + "bne $a4, $zero, 0b\n" // } while (a4); + "la.pcrel $a4, _auxv\n" // a4 = &_auxv + LONG_S " $a3, $a4, 0\n" // store a3 into _auxv + + "la.pcrel $a3, environ\n" // a3 = &environ + LONG_S " $a2, $a3, 0\n" // store envp(a2) into environ + LONG_BSTRINS " $sp, $zero, 3, 0\n" // sp must be 16-byte aligned + "bl main\n" // main() returns the status code, we'll exit with it. + "li.w $a7, 93\n" // NR_exit == 93 + "syscall 0\n" + ); + __builtin_unreachable(); +} + +#endif // _NOLIBC_ARCH_LOONGARCH_H diff --git a/tools/include/nolibc/arch-x86_64.h b/tools/include/nolibc/arch-x86_64.h index 17f6751208e7..f7f2a11d4c3b 100644 --- a/tools/include/nolibc/arch-x86_64.h +++ b/tools/include/nolibc/arch-x86_64.h @@ -181,6 +181,8 @@ struct sys_stat_struct { char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); +#define __ARCH_SUPPORTS_STACK_PROTECTOR + /* startup code */ /* * x86-64 System V ABI mandates: @@ -191,6 +193,9 @@ const unsigned long *_auxv __attribute__((weak)); void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) { __asm__ volatile ( +#ifdef NOLIBC_STACKPROTECTOR + "call __stack_chk_init\n" // initialize stack protector +#endif "pop %rdi\n" // argc (first arg, %rdi) "mov %rsp, %rsi\n" // argv[] (second arg, %rsi) "lea 8(%rsi,%rdi,8),%rdx\n" // then a NULL then envp (third arg, %rdx) diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index 78b067a4fa47..2d5386a8d6aa 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -29,6 +29,8 @@ #include "arch-riscv.h" #elif defined(__s390x__) #include "arch-s390.h" +#elif defined(__loongarch__) +#include "arch-loongarch.h" #endif #endif /* _NOLIBC_ARCH_H */ diff --git a/tools/include/nolibc/nolibc.h b/tools/include/nolibc/nolibc.h index b2bc48d3cfe4..04739a6293c4 100644 --- a/tools/include/nolibc/nolibc.h +++ b/tools/include/nolibc/nolibc.h @@ -104,6 +104,7 @@ #include "string.h" #include "time.h" #include "unistd.h" +#include "stackprotector.h" /* Used by programs to avoid std includes */ #define NOLIBC diff --git a/tools/include/nolibc/stackprotector.h b/tools/include/nolibc/stackprotector.h new file mode 100644 index 000000000000..d119cbbbc256 --- /dev/null +++ b/tools/include/nolibc/stackprotector.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * Stack protector support for NOLIBC + * Copyright (C) 2023 Thomas Weißschuh <linux@weissschuh.net> + */ + +#ifndef _NOLIBC_STACKPROTECTOR_H +#define _NOLIBC_STACKPROTECTOR_H + +#include "arch.h" + +#if defined(NOLIBC_STACKPROTECTOR) + +#if !defined(__ARCH_SUPPORTS_STACK_PROTECTOR) +#error "nolibc does not support stack protectors on this arch" +#endif + +#include "sys.h" +#include "stdlib.h" + +/* The functions in this header are using raw syscall macros to avoid + * triggering stack protector errors themselves + */ + +__attribute__((weak,noreturn,section(".text.nolibc_stack_chk"))) +void __stack_chk_fail(void) +{ + pid_t pid; + my_syscall3(__NR_write, STDERR_FILENO, "!!Stack smashing detected!!\n", 28); + pid = my_syscall0(__NR_getpid); + my_syscall2(__NR_kill, pid, SIGABRT); + for (;;); +} + +__attribute__((weak,noreturn,section(".text.nolibc_stack_chk"))) +void __stack_chk_fail_local(void) +{ + __stack_chk_fail(); +} + +__attribute__((weak,section(".data.nolibc_stack_chk"))) +uintptr_t __stack_chk_guard; + +__attribute__((weak,no_stack_protector,section(".text.nolibc_stack_chk"))) +void __stack_chk_init(void) +{ + my_syscall3(__NR_getrandom, &__stack_chk_guard, sizeof(__stack_chk_guard), 0); + /* a bit more randomness in case getrandom() fails */ + __stack_chk_guard ^= (uintptr_t) &__stack_chk_guard; +} +#endif // defined(NOLIBC_STACKPROTECTOR) + +#endif // _NOLIBC_STACKPROTECTOR_H diff --git a/tools/include/nolibc/std.h b/tools/include/nolibc/std.h index 1747ae125392..933bc0be7e1c 100644 --- a/tools/include/nolibc/std.h +++ b/tools/include/nolibc/std.h @@ -18,20 +18,7 @@ #define NULL ((void *)0) #endif -/* stdint types */ -typedef unsigned char uint8_t; -typedef signed char int8_t; -typedef unsigned short uint16_t; -typedef signed short int16_t; -typedef unsigned int uint32_t; -typedef signed int int32_t; -typedef unsigned long long uint64_t; -typedef signed long long int64_t; -typedef unsigned long size_t; -typedef signed long ssize_t; -typedef unsigned long uintptr_t; -typedef signed long intptr_t; -typedef signed long ptrdiff_t; +#include "stdint.h" /* those are commonly provided by sys/types.h */ typedef unsigned int dev_t; diff --git a/tools/include/nolibc/stdint.h b/tools/include/nolibc/stdint.h new file mode 100644 index 000000000000..c1ce4f5e0603 --- /dev/null +++ b/tools/include/nolibc/stdint.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * Standard definitions and types for NOLIBC + * Copyright (C) 2023 Vincent Dagonneau <v@vda.io> + */ + +#ifndef _NOLIBC_STDINT_H +#define _NOLIBC_STDINT_H + +typedef unsigned char uint8_t; +typedef signed char int8_t; +typedef unsigned short uint16_t; +typedef signed short int16_t; +typedef unsigned int uint32_t; +typedef signed int int32_t; +typedef unsigned long long uint64_t; +typedef signed long long int64_t; +typedef unsigned long size_t; +typedef signed long ssize_t; +typedef unsigned long uintptr_t; +typedef signed long intptr_t; +typedef signed long ptrdiff_t; + +typedef int8_t int_least8_t; +typedef uint8_t uint_least8_t; +typedef int16_t int_least16_t; +typedef uint16_t uint_least16_t; +typedef int32_t int_least32_t; +typedef uint32_t uint_least32_t; +typedef int64_t int_least64_t; +typedef uint64_t uint_least64_t; + +typedef int8_t int_fast8_t; +typedef uint8_t uint_fast8_t; +typedef ssize_t int_fast16_t; +typedef size_t uint_fast16_t; +typedef ssize_t int_fast32_t; +typedef size_t uint_fast32_t; +typedef ssize_t int_fast64_t; +typedef size_t uint_fast64_t; + +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + +/* limits of integral types */ + +#define INT8_MIN (-128) +#define INT16_MIN (-32767-1) +#define INT32_MIN (-2147483647-1) +#define INT64_MIN (-9223372036854775807LL-1) + +#define INT8_MAX (127) +#define INT16_MAX (32767) +#define INT32_MAX (2147483647) +#define INT64_MAX (9223372036854775807LL) + +#define UINT8_MAX (255) +#define UINT16_MAX (65535) +#define UINT32_MAX (4294967295U) +#define UINT64_MAX (18446744073709551615ULL) + +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST64_MIN INT64_MIN + +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MAX INT64_MAX + +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +#define SIZE_MAX ((size_t)(__LONG_MAX__) * 2 + 1) +#define INTPTR_MIN (-__LONG_MAX__ - 1) +#define INTPTR_MAX __LONG_MAX__ +#define PTRDIFF_MIN INTPTR_MIN +#define PTRDIFF_MAX INTPTR_MAX +#define UINTPTR_MAX SIZE_MAX + +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST16_MIN INTPTR_MIN +#define INT_FAST32_MIN INTPTR_MIN +#define INT_FAST64_MIN INTPTR_MIN + +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MAX INTPTR_MAX +#define INT_FAST32_MAX INTPTR_MAX +#define INT_FAST64_MAX INTPTR_MAX + +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX SIZE_MAX +#define UINT_FAST32_MAX SIZE_MAX +#define UINT_FAST64_MAX SIZE_MAX + +#endif /* _NOLIBC_STDINT_H */ diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index 96ac8afc5aee..6cbbb52836a0 100644 --- a/tools/include/nolibc/stdio.h +++ b/tools/include/nolibc/stdio.h @@ -273,6 +273,12 @@ int vfprintf(FILE *stream, const char *fmt, va_list args) return written; } +static __attribute__((unused)) +int vprintf(const char *fmt, va_list args) +{ + return vfprintf(stdout, fmt, args); +} + static __attribute__((unused, format(printf, 2, 3))) int fprintf(FILE *stream, const char *fmt, ...) { diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index b5f8cd35c03b..5d624dc63a42 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -11,7 +11,6 @@ #include "std.h" /* system includes */ -#include <asm/fcntl.h> // for O_* #include <asm/unistd.h> #include <asm/signal.h> // for SIGCHLD #include <asm/ioctls.h> @@ -20,6 +19,8 @@ #include <linux/loop.h> #include <linux/time.h> #include <linux/auxvec.h> +#include <linux/fcntl.h> // for O_* and AT_* +#include <linux/stat.h> // for statx() #include "arch.h" #include "errno.h" @@ -411,6 +412,27 @@ int getdents64(int fd, struct linux_dirent64 *dirp, int count) /* + * uid_t geteuid(void); + */ + +static __attribute__((unused)) +uid_t sys_geteuid(void) +{ +#ifdef __NR_geteuid32 + return my_syscall0(__NR_geteuid32); +#else + return my_syscall0(__NR_geteuid); +#endif +} + +static __attribute__((unused)) +uid_t geteuid(void) +{ + return sys_geteuid(); +} + + +/* * pid_t getpgid(pid_t pid); */ @@ -545,6 +567,27 @@ int gettimeofday(struct timeval *tv, struct timezone *tz) /* + * uid_t getuid(void); + */ + +static __attribute__((unused)) +uid_t sys_getuid(void) +{ +#ifdef __NR_getuid32 + return my_syscall0(__NR_getuid32); +#else + return my_syscall0(__NR_getuid); +#endif +} + +static __attribute__((unused)) +uid_t getuid(void) +{ + return sys_getuid(); +} + + +/* * int ioctl(int fd, unsigned long req, void *value); */ @@ -1048,12 +1091,66 @@ pid_t setsid(void) return ret; } +#if defined(__NR_statx) +/* + * int statx(int fd, const char *path, int flags, unsigned int mask, struct statx *buf); + */ + +static __attribute__((unused)) +int sys_statx(int fd, const char *path, int flags, unsigned int mask, struct statx *buf) +{ + return my_syscall5(__NR_statx, fd, path, flags, mask, buf); +} + +static __attribute__((unused)) +int statx(int fd, const char *path, int flags, unsigned int mask, struct statx *buf) +{ + int ret = sys_statx(fd, path, flags, mask, buf); + + if (ret < 0) { + SET_ERRNO(-ret); + ret = -1; + } + return ret; +} +#endif /* * int stat(const char *path, struct stat *buf); * Warning: the struct stat's layout is arch-dependent. */ +#if defined(__NR_statx) && !defined(__NR_newfstatat) && !defined(__NR_stat) +/* + * Maybe we can just use statx() when available for all architectures? + */ +static __attribute__((unused)) +int sys_stat(const char *path, struct stat *buf) +{ + struct statx statx; + long ret; + + ret = sys_statx(AT_FDCWD, path, AT_NO_AUTOMOUNT, STATX_BASIC_STATS, &statx); + buf->st_dev = ((statx.stx_dev_minor & 0xff) + | (statx.stx_dev_major << 8) + | ((statx.stx_dev_minor & ~0xff) << 12)); + buf->st_ino = statx.stx_ino; + buf->st_mode = statx.stx_mode; + buf->st_nlink = statx.stx_nlink; + buf->st_uid = statx.stx_uid; + buf->st_gid = statx.stx_gid; + buf->st_rdev = ((statx.stx_rdev_minor & 0xff) + | (statx.stx_rdev_major << 8) + | ((statx.stx_rdev_minor & ~0xff) << 12)); + buf->st_size = statx.stx_size; + buf->st_blksize = statx.stx_blksize; + buf->st_blocks = statx.stx_blocks; + buf->st_atime = statx.stx_atime.tv_sec; + buf->st_mtime = statx.stx_mtime.tv_sec; + buf->st_ctime = statx.stx_ctime.tv_sec; + return ret; +} +#else static __attribute__((unused)) int sys_stat(const char *path, struct stat *buf) { @@ -1083,6 +1180,7 @@ int sys_stat(const char *path, struct stat *buf) buf->st_ctime = stat.st_ctime; return ret; } +#endif static __attribute__((unused)) int stat(const char *path, struct stat *buf) diff --git a/tools/include/nolibc/types.h b/tools/include/nolibc/types.h index fbbc0e68c001..aedd7d9e3f64 100644 --- a/tools/include/nolibc/types.h +++ b/tools/include/nolibc/types.h @@ -9,6 +9,7 @@ #include "std.h" #include <linux/time.h> +#include <linux/stat.h> /* Only the generic macros and types may be defined here. The arch-specific @@ -16,7 +17,11 @@ * the layout of sys_stat_struct must not be defined here. */ -/* stat flags (WARNING, octal here) */ +/* stat flags (WARNING, octal here). We need to check for an existing + * definition because linux/stat.h may omit to define those if it finds + * that any glibc header was already included. + */ +#if !defined(S_IFMT) #define S_IFDIR 0040000 #define S_IFCHR 0020000 #define S_IFBLK 0060000 @@ -34,6 +39,22 @@ #define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) #define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#define S_IRWXU 00700 +#define S_IRUSR 00400 +#define S_IWUSR 00200 +#define S_IXUSR 00100 + +#define S_IRWXG 00070 +#define S_IRGRP 00040 +#define S_IWGRP 00020 +#define S_IXGRP 00010 + +#define S_IRWXO 00007 +#define S_IROTH 00004 +#define S_IWOTH 00002 +#define S_IXOTH 00001 +#endif + /* dirent types */ #define DT_UNKNOWN 0x0 #define DT_FIFO 0x1 @@ -60,11 +81,6 @@ #define MAXPATHLEN (PATH_MAX) #endif -/* Special FD used by all the *at functions */ -#ifndef AT_FDCWD -#define AT_FDCWD (-100) -#endif - /* whence values for lseek() */ #define SEEK_SET 0 #define SEEK_CUR 1 @@ -81,6 +97,8 @@ /* Macros used on waitpid()'s return status */ #define WEXITSTATUS(status) (((status) & 0xff00) >> 8) #define WIFEXITED(status) (((status) & 0x7f) == 0) +#define WTERMSIG(status) ((status) & 0x7f) +#define WIFSIGNALED(status) ((status) - 1 < 0xff) /* waitpid() flags */ #define WNOHANG 1 diff --git a/tools/include/nolibc/unistd.h b/tools/include/nolibc/unistd.h index 1cfcd52106a4..ac7d53d986cd 100644 --- a/tools/include/nolibc/unistd.h +++ b/tools/include/nolibc/unistd.h @@ -13,6 +13,11 @@ #include "sys.h" +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + + static __attribute__((unused)) int msleep(unsigned int msecs) { diff --git a/tools/include/uapi/asm-generic/fcntl.h b/tools/include/uapi/asm-generic/fcntl.h index b02c8e0f4057..1c7a0f6632c0 100644 --- a/tools/include/uapi/asm-generic/fcntl.h +++ b/tools/include/uapi/asm-generic/fcntl.h @@ -91,7 +91,6 @@ /* a horrid kludge trying to make sure that this will fail on old kernels */ #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) -#define O_TMPFILE_MASK (__O_TMPFILE | O_DIRECTORY | O_CREAT) #ifndef O_NDELAY #define O_NDELAY O_NONBLOCK diff --git a/tools/include/uapi/linux/hw_breakpoint.h b/tools/include/uapi/linux/hw_breakpoint.h index 965e4d8606d8..1575d3ca6f0d 100644 --- a/tools/include/uapi/linux/hw_breakpoint.h +++ b/tools/include/uapi/linux/hw_breakpoint.h @@ -22,14 +22,4 @@ enum { HW_BREAKPOINT_INVALID = HW_BREAKPOINT_RW | HW_BREAKPOINT_X, }; -enum bp_type_idx { - TYPE_INST = 0, -#ifdef CONFIG_HAVE_MIXED_BREAKPOINTS_REGS - TYPE_DATA = 0, -#else - TYPE_DATA = 1, -#endif - TYPE_MAX -}; - #endif /* _UAPI_LINUX_HW_BREAKPOINT_H */ diff --git a/tools/include/uapi/linux/prctl.h b/tools/include/uapi/linux/prctl.h index 1312a137f7fb..759b3f53e53f 100644 --- a/tools/include/uapi/linux/prctl.h +++ b/tools/include/uapi/linux/prctl.h @@ -290,4 +290,6 @@ struct prctl_mm_map { #define PR_SET_VMA 0x53564d41 # define PR_SET_VMA_ANON_NAME 0 +#define PR_SET_MEMORY_MERGE 67 +#define PR_GET_MEMORY_MERGE 68 #endif /* _LINUX_PRCTL_H */ diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 6f28180ffeea..15bf00e79e3f 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -627,7 +627,7 @@ class TracepointProvider(Provider): name)'. All available events have directories under - /sys/kernel/debug/tracing/events/ which export information + /sys/kernel/tracing/events/ which export information about the specific event. Therefore, listing the dirs gives us a list of all available events. diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 1cbacf9e71f3..ad1ec893b41b 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -1361,7 +1361,7 @@ static int bpf_object__elf_init(struct bpf_object *obj) goto errout; } - /* Elf is corrupted/truncated, avoid calling elf_strptr. */ + /* ELF is corrupted/truncated, avoid calling elf_strptr. */ if (!elf_rawdata(elf_getscn(elf, obj->efile.shstrndx), NULL)) { pr_warn("elf: failed to get section names strings from %s: %s\n", obj->path, elf_errmsg(-1)); diff --git a/tools/lib/bpf/usdt.c b/tools/lib/bpf/usdt.c index b8402e3f9eb2..086eef355ab3 100644 --- a/tools/lib/bpf/usdt.c +++ b/tools/lib/bpf/usdt.c @@ -771,7 +771,7 @@ static int collect_usdt_targets(struct usdt_manager *man, Elf *elf, const char * target->rel_ip = usdt_rel_ip; target->sema_off = usdt_sema_off; - /* notes.args references strings from Elf itself, so they can + /* notes.args references strings from ELF itself, so they can * be referenced safely until elf_end() call */ target->spec_str = note.args; diff --git a/tools/memory-model/Documentation/explanation.txt b/tools/memory-model/Documentation/explanation.txt index 8e7085238470..6dc8b3642458 100644 --- a/tools/memory-model/Documentation/explanation.txt +++ b/tools/memory-model/Documentation/explanation.txt @@ -28,9 +28,10 @@ Explanation of the Linux-Kernel Memory Consistency Model 20. THE HAPPENS-BEFORE RELATION: hb 21. THE PROPAGATES-BEFORE RELATION: pb 22. RCU RELATIONS: rcu-link, rcu-gp, rcu-rscsi, rcu-order, rcu-fence, and rb - 23. LOCKING - 24. PLAIN ACCESSES AND DATA RACES - 25. ODDS AND ENDS + 23. SRCU READ-SIDE CRITICAL SECTIONS + 24. LOCKING + 25. PLAIN ACCESSES AND DATA RACES + 26. ODDS AND ENDS @@ -1848,14 +1849,169 @@ section in P0 both starts before P1's grace period does and ends before it does, and the critical section in P2 both starts after P1's grace period does and ends after it does. -Addendum: The LKMM now supports SRCU (Sleepable Read-Copy-Update) in -addition to normal RCU. The ideas involved are much the same as -above, with new relations srcu-gp and srcu-rscsi added to represent -SRCU grace periods and read-side critical sections. There is a -restriction on the srcu-gp and srcu-rscsi links that can appear in an -rcu-order sequence (the srcu-rscsi links must be paired with srcu-gp -links having the same SRCU domain with proper nesting); the details -are relatively unimportant. +The LKMM supports SRCU (Sleepable Read-Copy-Update) in addition to +normal RCU. The ideas involved are much the same as above, with new +relations srcu-gp and srcu-rscsi added to represent SRCU grace periods +and read-side critical sections. However, there are some significant +differences between RCU read-side critical sections and their SRCU +counterparts, as described in the next section. + + +SRCU READ-SIDE CRITICAL SECTIONS +-------------------------------- + +The LKMM uses the srcu-rscsi relation to model SRCU read-side critical +sections. They differ from RCU read-side critical sections in the +following respects: + +1. Unlike the analogous RCU primitives, synchronize_srcu(), + srcu_read_lock(), and srcu_read_unlock() take a pointer to a + struct srcu_struct as an argument. This structure is called + an SRCU domain, and calls linked by srcu-rscsi must have the + same domain. Read-side critical sections and grace periods + associated with different domains are independent of one + another; the SRCU version of the RCU Guarantee applies only + to pairs of critical sections and grace periods having the + same domain. + +2. srcu_read_lock() returns a value, called the index, which must + be passed to the matching srcu_read_unlock() call. Unlike + rcu_read_lock() and rcu_read_unlock(), an srcu_read_lock() + call does not always have to match the next unpaired + srcu_read_unlock(). In fact, it is possible for two SRCU + read-side critical sections to overlap partially, as in the + following example (where s is an srcu_struct and idx1 and idx2 + are integer variables): + + idx1 = srcu_read_lock(&s); // Start of first RSCS + idx2 = srcu_read_lock(&s); // Start of second RSCS + srcu_read_unlock(&s, idx1); // End of first RSCS + srcu_read_unlock(&s, idx2); // End of second RSCS + + The matching is determined entirely by the domain pointer and + index value. By contrast, if the calls had been + rcu_read_lock() and rcu_read_unlock() then they would have + created two nested (fully overlapping) read-side critical + sections: an inner one and an outer one. + +3. The srcu_down_read() and srcu_up_read() primitives work + exactly like srcu_read_lock() and srcu_read_unlock(), except + that matching calls don't have to execute on the same CPU. + (The names are meant to be suggestive of operations on + semaphores.) Since the matching is determined by the domain + pointer and index value, these primitives make it possible for + an SRCU read-side critical section to start on one CPU and end + on another, so to speak. + +In order to account for these properties of SRCU, the LKMM models +srcu_read_lock() as a special type of load event (which is +appropriate, since it takes a memory location as argument and returns +a value, just as a load does) and srcu_read_unlock() as a special type +of store event (again appropriate, since it takes as arguments a +memory location and a value). These loads and stores are annotated as +belonging to the "srcu-lock" and "srcu-unlock" event classes +respectively. + +This approach allows the LKMM to tell whether two events are +associated with the same SRCU domain, simply by checking whether they +access the same memory location (i.e., they are linked by the loc +relation). It also gives a way to tell which unlock matches a +particular lock, by checking for the presence of a data dependency +from the load (srcu-lock) to the store (srcu-unlock). For example, +given the situation outlined earlier (with statement labels added): + + A: idx1 = srcu_read_lock(&s); + B: idx2 = srcu_read_lock(&s); + C: srcu_read_unlock(&s, idx1); + D: srcu_read_unlock(&s, idx2); + +the LKMM will treat A and B as loads from s yielding values saved in +idx1 and idx2 respectively. Similarly, it will treat C and D as +though they stored the values from idx1 and idx2 in s. The end result +is much as if we had written: + + A: idx1 = READ_ONCE(s); + B: idx2 = READ_ONCE(s); + C: WRITE_ONCE(s, idx1); + D: WRITE_ONCE(s, idx2); + +except for the presence of the special srcu-lock and srcu-unlock +annotations. You can see at once that we have A ->data C and +B ->data D. These dependencies tell the LKMM that C is the +srcu-unlock event matching srcu-lock event A, and D is the +srcu-unlock event matching srcu-lock event B. + +This approach is admittedly a hack, and it has the potential to lead +to problems. For example, in: + + idx1 = srcu_read_lock(&s); + srcu_read_unlock(&s, idx1); + idx2 = srcu_read_lock(&s); + srcu_read_unlock(&s, idx2); + +the LKMM will believe that idx2 must have the same value as idx1, +since it reads from the immediately preceding store of idx1 in s. +Fortunately this won't matter, assuming that litmus tests never do +anything with SRCU index values other than pass them to +srcu_read_unlock() or srcu_up_read() calls. + +However, sometimes it is necessary to store an index value in a +shared variable temporarily. In fact, this is the only way for +srcu_down_read() to pass the index it gets to an srcu_up_read() call +on a different CPU. In more detail, we might have soething like: + + struct srcu_struct s; + int x; + + P0() + { + int r0; + + A: r0 = srcu_down_read(&s); + B: WRITE_ONCE(x, r0); + } + + P1() + { + int r1; + + C: r1 = READ_ONCE(x); + D: srcu_up_read(&s, r1); + } + +Assuming that P1 executes after P0 and does read the index value +stored in x, we can write this (using brackets to represent event +annotations) as: + + A[srcu-lock] ->data B[once] ->rf C[once] ->data D[srcu-unlock]. + +The LKMM defines a carry-srcu-data relation to express this pattern; +it permits an arbitrarily long sequence of + + data ; rf + +pairs (that is, a data link followed by an rf link) to occur between +an srcu-lock event and the final data dependency leading to the +matching srcu-unlock event. carry-srcu-data is complicated by the +need to ensure that none of the intermediate store events in this +sequence are instances of srcu-unlock. This is necessary because in a +pattern like the one above: + + A: idx1 = srcu_read_lock(&s); + B: srcu_read_unlock(&s, idx1); + C: idx2 = srcu_read_lock(&s); + D: srcu_read_unlock(&s, idx2); + +the LKMM treats B as a store to the variable s and C as a load from +that variable, creating an undesirable rf link from B to C: + + A ->data B ->rf C ->data D. + +This would cause carry-srcu-data to mistakenly extend a data +dependency from A to D, giving the impression that D was the +srcu-unlock event matching A's srcu-lock. To avoid such problems, +carry-srcu-data does not accept sequences in which the ends of any of +the intermediate ->data links (B above) is an srcu-unlock event. LOCKING diff --git a/tools/memory-model/Documentation/litmus-tests.txt b/tools/memory-model/Documentation/litmus-tests.txt index 26554b1c5575..acac527328a1 100644 --- a/tools/memory-model/Documentation/litmus-tests.txt +++ b/tools/memory-model/Documentation/litmus-tests.txt @@ -1028,32 +1028,7 @@ Limitations of the Linux-kernel memory model (LKMM) include: additional call_rcu() process to the site of the emulated rcu-barrier(). - e. Although sleepable RCU (SRCU) is now modeled, there - are some subtle differences between its semantics and - those in the Linux kernel. For example, the kernel - might interpret the following sequence as two partially - overlapping SRCU read-side critical sections: - - 1 r1 = srcu_read_lock(&my_srcu); - 2 do_something_1(); - 3 r2 = srcu_read_lock(&my_srcu); - 4 do_something_2(); - 5 srcu_read_unlock(&my_srcu, r1); - 6 do_something_3(); - 7 srcu_read_unlock(&my_srcu, r2); - - In contrast, LKMM will interpret this as a nested pair of - SRCU read-side critical sections, with the outer critical - section spanning lines 1-7 and the inner critical section - spanning lines 3-5. - - This difference would be more of a concern had anyone - identified a reasonable use case for partially overlapping - SRCU read-side critical sections. For more information - on the trickiness of such overlapping, please see: - https://paulmck.livejournal.com/40593.html - - f. Reader-writer locking is not modeled. It can be + e. Reader-writer locking is not modeled. It can be emulated in litmus tests using atomic read-modify-write operations. diff --git a/tools/memory-model/Documentation/locking.txt b/tools/memory-model/Documentation/locking.txt new file mode 100644 index 000000000000..65c898c64a93 --- /dev/null +++ b/tools/memory-model/Documentation/locking.txt @@ -0,0 +1,298 @@ +Locking +======= + +Locking is well-known and the common use cases are straightforward: Any +CPU holding a given lock sees any changes previously seen or made by any +CPU before it previously released that same lock. This last sentence +is the only part of this document that most developers will need to read. + +However, developers who would like to also access lock-protected shared +variables outside of their corresponding locks should continue reading. + + +Locking and Prior Accesses +-------------------------- + +The basic rule of locking is worth repeating: + + Any CPU holding a given lock sees any changes previously seen + or made by any CPU before it previously released that same lock. + +Note that this statement is a bit stronger than "Any CPU holding a +given lock sees all changes made by any CPU during the time that CPU was +previously holding this same lock". For example, consider the following +pair of code fragments: + + /* See MP+polocks.litmus. */ + void CPU0(void) + { + WRITE_ONCE(x, 1); + spin_lock(&mylock); + WRITE_ONCE(y, 1); + spin_unlock(&mylock); + } + + void CPU1(void) + { + spin_lock(&mylock); + r0 = READ_ONCE(y); + spin_unlock(&mylock); + r1 = READ_ONCE(x); + } + +The basic rule guarantees that if CPU0() acquires mylock before CPU1(), +then both r0 and r1 must be set to the value 1. This also has the +consequence that if the final value of r0 is equal to 1, then the final +value of r1 must also be equal to 1. In contrast, the weaker rule would +say nothing about the final value of r1. + + +Locking and Subsequent Accesses +------------------------------- + +The converse to the basic rule also holds: Any CPU holding a given +lock will not see any changes that will be made by any CPU after it +subsequently acquires this same lock. This converse statement is +illustrated by the following litmus test: + + /* See MP+porevlocks.litmus. */ + void CPU0(void) + { + r0 = READ_ONCE(y); + spin_lock(&mylock); + r1 = READ_ONCE(x); + spin_unlock(&mylock); + } + + void CPU1(void) + { + spin_lock(&mylock); + WRITE_ONCE(x, 1); + spin_unlock(&mylock); + WRITE_ONCE(y, 1); + } + +This converse to the basic rule guarantees that if CPU0() acquires +mylock before CPU1(), then both r0 and r1 must be set to the value 0. +This also has the consequence that if the final value of r1 is equal +to 0, then the final value of r0 must also be equal to 0. In contrast, +the weaker rule would say nothing about the final value of r0. + +These examples show only a single pair of CPUs, but the effects of the +locking basic rule extend across multiple acquisitions of a given lock +across multiple CPUs. + + +Double-Checked Locking +---------------------- + +It is well known that more than just a lock is required to make +double-checked locking work correctly, This litmus test illustrates +one incorrect approach: + + /* See Documentation/litmus-tests/locking/DCL-broken.litmus. */ + void CPU0(void) + { + r0 = READ_ONCE(flag); + if (r0 == 0) { + spin_lock(&lck); + r1 = READ_ONCE(flag); + if (r1 == 0) { + WRITE_ONCE(data, 1); + WRITE_ONCE(flag, 1); + } + spin_unlock(&lck); + } + r2 = READ_ONCE(data); + } + /* CPU1() is the exactly the same as CPU0(). */ + +There are two problems. First, there is no ordering between the first +READ_ONCE() of "flag" and the READ_ONCE() of "data". Second, there is +no ordering between the two WRITE_ONCE() calls. It should therefore be +no surprise that "r2" can be zero, and a quick herd7 run confirms this. + +One way to fix this is to use smp_load_acquire() and smp_store_release() +as shown in this corrected version: + + /* See Documentation/litmus-tests/locking/DCL-fixed.litmus. */ + void CPU0(void) + { + r0 = smp_load_acquire(&flag); + if (r0 == 0) { + spin_lock(&lck); + r1 = READ_ONCE(flag); + if (r1 == 0) { + WRITE_ONCE(data, 1); + smp_store_release(&flag, 1); + } + spin_unlock(&lck); + } + r2 = READ_ONCE(data); + } + /* CPU1() is the exactly the same as CPU0(). */ + +The smp_load_acquire() guarantees that its load from "flags" will +be ordered before the READ_ONCE() from data, thus solving the first +problem. The smp_store_release() guarantees that its store will be +ordered after the WRITE_ONCE() to "data", solving the second problem. +The smp_store_release() pairs with the smp_load_acquire(), thus ensuring +that the ordering provided by each actually takes effect. Again, a +quick herd7 run confirms this. + +In short, if you access a lock-protected variable without holding the +corresponding lock, you will need to provide additional ordering, in +this case, via the smp_load_acquire() and the smp_store_release(). + + +Ordering Provided by a Lock to CPUs Not Holding That Lock +--------------------------------------------------------- + +It is not necessarily the case that accesses ordered by locking will be +seen as ordered by CPUs not holding that lock. Consider this example: + + /* See Z6.0+pooncelock+pooncelock+pombonce.litmus. */ + void CPU0(void) + { + spin_lock(&mylock); + WRITE_ONCE(x, 1); + WRITE_ONCE(y, 1); + spin_unlock(&mylock); + } + + void CPU1(void) + { + spin_lock(&mylock); + r0 = READ_ONCE(y); + WRITE_ONCE(z, 1); + spin_unlock(&mylock); + } + + void CPU2(void) + { + WRITE_ONCE(z, 2); + smp_mb(); + r1 = READ_ONCE(x); + } + +Counter-intuitive though it might be, it is quite possible to have +the final value of r0 be 1, the final value of z be 2, and the final +value of r1 be 0. The reason for this surprising outcome is that CPU2() +never acquired the lock, and thus did not fully benefit from the lock's +ordering properties. + +Ordering can be extended to CPUs not holding the lock by careful use +of smp_mb__after_spinlock(): + + /* See Z6.0+pooncelock+poonceLock+pombonce.litmus. */ + void CPU0(void) + { + spin_lock(&mylock); + WRITE_ONCE(x, 1); + WRITE_ONCE(y, 1); + spin_unlock(&mylock); + } + + void CPU1(void) + { + spin_lock(&mylock); + smp_mb__after_spinlock(); + r0 = READ_ONCE(y); + WRITE_ONCE(z, 1); + spin_unlock(&mylock); + } + + void CPU2(void) + { + WRITE_ONCE(z, 2); + smp_mb(); + r1 = READ_ONCE(x); + } + +This addition of smp_mb__after_spinlock() strengthens the lock +acquisition sufficiently to rule out the counter-intuitive outcome. +In other words, the addition of the smp_mb__after_spinlock() prohibits +the counter-intuitive result where the final value of r0 is 1, the final +value of z is 2, and the final value of r1 is 0. + + +No Roach-Motel Locking! +----------------------- + +This example requires familiarity with the herd7 "filter" clause, so +please read up on that topic in litmus-tests.txt. + +It is tempting to allow memory-reference instructions to be pulled +into a critical section, but this cannot be allowed in the general case. +For example, consider a spin loop preceding a lock-based critical section. +Now, herd7 does not model spin loops, but we can emulate one with two +loads, with a "filter" clause to constrain the first to return the +initial value and the second to return the updated value, as shown below: + + /* See Documentation/litmus-tests/locking/RM-fixed.litmus. */ + void CPU0(void) + { + spin_lock(&lck); + r2 = atomic_inc_return(&y); + WRITE_ONCE(x, 1); + spin_unlock(&lck); + } + + void CPU1(void) + { + r0 = READ_ONCE(x); + r1 = READ_ONCE(x); + spin_lock(&lck); + r2 = atomic_inc_return(&y); + spin_unlock(&lck); + } + + filter (1:r0=0 /\ 1:r1=1) + exists (1:r2=1) + +The variable "x" is the control variable for the emulated spin loop. +CPU0() sets it to "1" while holding the lock, and CPU1() emulates the +spin loop by reading it twice, first into "1:r0" (which should get the +initial value "0") and then into "1:r1" (which should get the updated +value "1"). + +The "filter" clause takes this into account, constraining "1:r0" to +equal "0" and "1:r1" to equal 1. + +Then the "exists" clause checks to see if CPU1() acquired its lock first, +which should not happen given the filter clause because CPU0() updates +"x" while holding the lock. And herd7 confirms this. + +But suppose that the compiler was permitted to reorder the spin loop +into CPU1()'s critical section, like this: + + /* See Documentation/litmus-tests/locking/RM-broken.litmus. */ + void CPU0(void) + { + int r2; + + spin_lock(&lck); + r2 = atomic_inc_return(&y); + WRITE_ONCE(x, 1); + spin_unlock(&lck); + } + + void CPU1(void) + { + spin_lock(&lck); + r0 = READ_ONCE(x); + r1 = READ_ONCE(x); + r2 = atomic_inc_return(&y); + spin_unlock(&lck); + } + + filter (1:r0=0 /\ 1:r1=1) + exists (1:r2=1) + +If "1:r0" is equal to "0", "1:r1" can never equal "1" because CPU0() +cannot update "x" while CPU1() holds the lock. And herd7 confirms this, +showing zero executions matching the "filter" criteria. + +And this is why Linux-kernel lock and unlock primitives must prevent +code from entering critical sections. It is not sufficient to only +prevent code from leaving them. diff --git a/tools/memory-model/linux-kernel.bell b/tools/memory-model/linux-kernel.bell index 70a9073dec3e..ce068700939c 100644 --- a/tools/memory-model/linux-kernel.bell +++ b/tools/memory-model/linux-kernel.bell @@ -31,7 +31,8 @@ enum Barriers = 'wmb (*smp_wmb*) || 'before-atomic (*smp_mb__before_atomic*) || 'after-atomic (*smp_mb__after_atomic*) || 'after-spinlock (*smp_mb__after_spinlock*) || - 'after-unlock-lock (*smp_mb__after_unlock_lock*) + 'after-unlock-lock (*smp_mb__after_unlock_lock*) || + 'after-srcu-read-unlock (*smp_mb__after_srcu_read_unlock*) instructions F[Barriers] (* SRCU *) @@ -53,38 +54,31 @@ let rcu-rscs = let rec in matched (* Validate nesting *) -flag ~empty Rcu-lock \ domain(rcu-rscs) as unbalanced-rcu-locking -flag ~empty Rcu-unlock \ range(rcu-rscs) as unbalanced-rcu-locking +flag ~empty Rcu-lock \ domain(rcu-rscs) as unmatched-rcu-lock +flag ~empty Rcu-unlock \ range(rcu-rscs) as unmatched-rcu-unlock (* Compute matching pairs of nested Srcu-lock and Srcu-unlock *) -let srcu-rscs = let rec - unmatched-locks = Srcu-lock \ domain(matched) - and unmatched-unlocks = Srcu-unlock \ range(matched) - and unmatched = unmatched-locks | unmatched-unlocks - and unmatched-po = ([unmatched] ; po ; [unmatched]) & loc - and unmatched-locks-to-unlocks = - ([unmatched-locks] ; po ; [unmatched-unlocks]) & loc - and matched = matched | (unmatched-locks-to-unlocks \ - (unmatched-po ; unmatched-po)) - in matched +let carry-srcu-data = (data ; [~ Srcu-unlock] ; rf)* +let srcu-rscs = ([Srcu-lock] ; carry-srcu-data ; data ; [Srcu-unlock]) & loc (* Validate nesting *) -flag ~empty Srcu-lock \ domain(srcu-rscs) as unbalanced-srcu-locking -flag ~empty Srcu-unlock \ range(srcu-rscs) as unbalanced-srcu-locking +flag ~empty Srcu-lock \ domain(srcu-rscs) as unmatched-srcu-lock +flag ~empty Srcu-unlock \ range(srcu-rscs) as unmatched-srcu-unlock +flag ~empty (srcu-rscs^-1 ; srcu-rscs) \ id as multiple-srcu-matches (* Check for use of synchronize_srcu() inside an RCU critical section *) flag ~empty rcu-rscs & (po ; [Sync-srcu] ; po) as invalid-sleep (* Validate SRCU dynamic match *) -flag ~empty different-values(srcu-rscs) as srcu-bad-nesting +flag ~empty different-values(srcu-rscs) as srcu-bad-value-match (* Compute marked and plain memory accesses *) let Marked = (~M) | IW | Once | Release | Acquire | domain(rmw) | range(rmw) | - LKR | LKW | UL | LF | RL | RU + LKR | LKW | UL | LF | RL | RU | Srcu-lock | Srcu-unlock let Plain = M \ Marked (* Redefine dependencies to include those carried through plain accesses *) -let carry-dep = (data ; rfi)* +let carry-dep = (data ; [~ Srcu-unlock] ; rfi)* let addr = carry-dep ; addr let ctrl = carry-dep ; ctrl let data = carry-dep ; data diff --git a/tools/memory-model/linux-kernel.cat b/tools/memory-model/linux-kernel.cat index 07f884f9b2bf..adf3c4f41229 100644 --- a/tools/memory-model/linux-kernel.cat +++ b/tools/memory-model/linux-kernel.cat @@ -37,8 +37,20 @@ let mb = ([M] ; fencerel(Mb) ; [M]) | ([M] ; fencerel(Before-atomic) ; [RMW] ; po? ; [M]) | ([M] ; po? ; [RMW] ; fencerel(After-atomic) ; [M]) | ([M] ; po? ; [LKW] ; fencerel(After-spinlock) ; [M]) | - ([M] ; po ; [UL] ; (co | po) ; [LKW] ; - fencerel(After-unlock-lock) ; [M]) +(* + * Note: The po-unlock-lock-po relation only passes the lock to the direct + * successor, perhaps giving the impression that the ordering of the + * smp_mb__after_unlock_lock() fence only affects a single lock handover. + * However, in a longer sequence of lock handovers, the implicit + * A-cumulative release fences of lock-release ensure that any stores that + * propagate to one of the involved CPUs before it hands over the lock to + * the next CPU will also propagate to the final CPU handing over the lock + * to the CPU that executes the fence. Therefore, all those stores are + * also affected by the fence. + *) + ([M] ; po-unlock-lock-po ; + [After-unlock-lock] ; po ; [M]) | + ([M] ; po? ; [Srcu-unlock] ; fencerel(After-srcu-read-unlock) ; [M]) let gp = po ; [Sync-rcu | Sync-srcu] ; po? let strong-fence = mb | gp @@ -69,8 +81,8 @@ let dep = addr | data let rwdep = (dep | ctrl) ; [W] let overwrite = co | fr let to-w = rwdep | (overwrite & int) | (addr ; [Plain] ; wmb) -let to-r = addr | (dep ; [Marked] ; rfi) -let ppo = to-r | to-w | fence | (po-unlock-lock-po & int) +let to-r = (addr ; [R]) | (dep ; [Marked] ; rfi) +let ppo = to-r | to-w | (fence & int) | (po-unlock-lock-po & int) (* Propagation: Ordering from release operations and strong fences. *) let A-cumul(r) = (rfe ; [Marked])? ; r diff --git a/tools/memory-model/linux-kernel.def b/tools/memory-model/linux-kernel.def index ef0f3c1850de..88a39601f525 100644 --- a/tools/memory-model/linux-kernel.def +++ b/tools/memory-model/linux-kernel.def @@ -24,6 +24,7 @@ smp_mb__before_atomic() { __fence{before-atomic}; } smp_mb__after_atomic() { __fence{after-atomic}; } smp_mb__after_spinlock() { __fence{after-spinlock}; } smp_mb__after_unlock_lock() { __fence{after-unlock-lock}; } +smp_mb__after_srcu_read_unlock() { __fence{after-srcu-read-unlock}; } barrier() { __fence{barrier}; } // Exchange @@ -49,8 +50,10 @@ synchronize_rcu() { __fence{sync-rcu}; } synchronize_rcu_expedited() { __fence{sync-rcu}; } // SRCU -srcu_read_lock(X) __srcu{srcu-lock}(X) -srcu_read_unlock(X,Y) { __srcu{srcu-unlock}(X,Y); } +srcu_read_lock(X) __load{srcu-lock}(*X) +srcu_read_unlock(X,Y) { __store{srcu-unlock}(*X,Y); } +srcu_down_read(X) __load{srcu-lock}(*X) +srcu_up_read(X,Y) { __store{srcu-unlock}(*X,Y); } synchronize_srcu(X) { __srcu{sync-srcu}(X); } synchronize_srcu_expedited(X) { __srcu{sync-srcu}(X); } diff --git a/tools/memory-model/litmus-tests/.gitignore b/tools/memory-model/litmus-tests/.gitignore index c492a1ddad91..19c379cf069d 100644 --- a/tools/memory-model/litmus-tests/.gitignore +++ b/tools/memory-model/litmus-tests/.gitignore @@ -1,2 +1,2 @@ # SPDX-License-Identifier: GPL-2.0-only -*.litmus.out +*.litmus.* diff --git a/tools/memory-model/lock.cat b/tools/memory-model/lock.cat index 6b52f365d73a..53b5a492739d 100644 --- a/tools/memory-model/lock.cat +++ b/tools/memory-model/lock.cat @@ -36,9 +36,9 @@ let RU = try RU with emptyset (* Treat RL as a kind of LF: a read with no ordering properties *) let LF = LF | RL -(* There should be no ordinary R or W accesses to spinlocks *) -let ALL-LOCKS = LKR | LKW | UL | LF | RU -flag ~empty [M \ IW] ; loc ; [ALL-LOCKS] as mixed-lock-accesses +(* There should be no ordinary R or W accesses to spinlocks or SRCU structs *) +let ALL-LOCKS = LKR | LKW | UL | LF | RU | Srcu-lock | Srcu-unlock | Sync-srcu +flag ~empty [M \ IW \ ALL-LOCKS] ; loc ; [ALL-LOCKS] as mixed-lock-accesses (* Link Lock-Reads to their RMW-partner Lock-Writes *) let lk-rmw = ([LKR] ; po-loc ; [LKW]) \ (po ; po) diff --git a/tools/memory-model/scripts/README b/tools/memory-model/scripts/README index 095c7eb36f9f..fb39bd0fd1b9 100644 --- a/tools/memory-model/scripts/README +++ b/tools/memory-model/scripts/README @@ -27,6 +27,14 @@ checklitmushist.sh checklitmus.sh Check a single litmus test against its "Result:" expected result. + Not intended to for manual use. + +checktheselitmus.sh + + Check the specified list of litmus tests against their "Result:" + expected results. This takes optional parseargs.sh arguments, + followed by "--" followed by pathnames starting from the current + directory. cmplitmushist.sh @@ -43,10 +51,10 @@ initlitmushist.sh judgelitmus.sh - Given a .litmus file and its .litmus.out herd7 output, check the - .litmus.out file against the .litmus file's "Result:" comment to - judge whether the test ran correctly. Not normally run manually, - provided instead for use by other scripts. + Given a .litmus file and its herd7 output, check the output file + against the .litmus file's "Result:" comment to judge whether + the test ran correctly. Not normally run manually, provided + instead for use by other scripts. newlitmushist.sh @@ -68,3 +76,35 @@ runlitmushist.sh README This file + +Testing a change to LKMM might go as follows: + + # Populate expected results without that change, and + # runs for about an hour on an 8-CPU x86 system: + scripts/initlitmushist.sh --timeout 10m --procs 10 + # Incorporate the change: + git am -s -3 /path/to/patch # Or whatever it takes. + + # Test the new version of LKMM as follows... + + # Runs in seconds, good smoke test: + scripts/checkalllitmus.sh + + # Compares results to those produced by initlitmushist.sh, + # and runs for about an hour on an 8-CPU x86 system: + scripts/checklitmushist.sh --timeout 10m --procs 10 + + # Checks results against Result tags, runs in minutes: + scripts/checkghlitmus.sh --timeout 10m --procs 10 + +The checkghlitmus.sh should not report errors in cases where the +checklitmushist.sh script did not also report a change. However, +this check is nevertheless valuable because it can find errors in the +original version of LKMM. Note however, that given the above procedure, +an error in the original LKMM version that is fixed by the patch will +be reported both as a mismatch by checklitmushist.sh and as an error +by checkghlitmus.sh. One exception to this rule of thumb is when the +test fails completely on the original version of LKMM and passes on the +new version. In this case, checklitmushist.sh will report a mismatch +and checkghlitmus.sh will report success. This happens when the change +to LKMM introduces a new primitive for which litmus tests already existed. diff --git a/tools/memory-model/scripts/checkalllitmus.sh b/tools/memory-model/scripts/checkalllitmus.sh index 3c0c7fbbd223..2d3ee850a839 100755 --- a/tools/memory-model/scripts/checkalllitmus.sh +++ b/tools/memory-model/scripts/checkalllitmus.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # SPDX-License-Identifier: GPL-2.0+ # # Run herd7 tests on all .litmus files in the litmus-tests directory @@ -8,6 +8,11 @@ # "^^^". It also outputs verification results to a file whose name is # that of the specified litmus test, but with ".out" appended. # +# If the --hw argument is specified, this script translates the .litmus +# C-language file to the specified type of assembly and verifies that. +# But in this case, litmus tests using complex synchronization (such as +# locking, RCU, and SRCU) are cheerfully ignored. +# # Usage: # checkalllitmus.sh # @@ -17,7 +22,7 @@ # # Copyright IBM Corporation, 2018 # -# Author: Paul E. McKenney <paulmck@linux.vnet.ibm.com> +# Author: Paul E. McKenney <paulmck@linux.ibm.com> . scripts/parseargs.sh @@ -30,29 +35,23 @@ else exit 255 fi -# Create any new directories that have appeared in the github litmus -# repo since the last run. +# Create any new directories that have appeared in the litmus-tests +# directory since the last run. if test "$LKMM_DESTDIR" != "." then find $litmusdir -type d -print | ( cd "$LKMM_DESTDIR"; sed -e 's/^/mkdir -p /' | sh ) fi -# Find the checklitmus script. If it is not where we expect it, then -# assume that the caller has the PATH environment variable set -# appropriately. -if test -x scripts/checklitmus.sh -then - clscript=scripts/checklitmus.sh -else - clscript=checklitmus.sh -fi - # Run the script on all the litmus tests in the specified directory ret=0 for i in $litmusdir/*.litmus do - if ! $clscript $i + if test -n "$LKMM_HW_MAP_FILE" && ! scripts/simpletest.sh $i + then + continue + fi + if ! scripts/checklitmus.sh $i then ret=1 fi diff --git a/tools/memory-model/scripts/checkghlitmus.sh b/tools/memory-model/scripts/checkghlitmus.sh index 6589fbb6f653..d3dfb321259f 100755 --- a/tools/memory-model/scripts/checkghlitmus.sh +++ b/tools/memory-model/scripts/checkghlitmus.sh @@ -10,6 +10,7 @@ # parseargs.sh scripts for arguments. . scripts/parseargs.sh +. scripts/hwfnseg.sh T=/tmp/checkghlitmus.sh.$$ trap 'rm -rf $T' 0 @@ -32,19 +33,19 @@ then ( cd "$LKMM_DESTDIR"; sed -e 's/^/mkdir -p /' | sh ) fi -# Create a list of the C-language litmus tests previously run. -( cd $LKMM_DESTDIR; find litmus -name '*.litmus.out' -print ) | - sed -e 's/\.out$//' | - xargs -r egrep -l '^ \* Result: (Never|Sometimes|Always|DEADLOCK)' | +# Create a list of the specified litmus tests previously run. +( cd $LKMM_DESTDIR; find litmus -name "*.litmus${hwfnseg}.out" -print ) | + sed -e "s/${hwfnseg}"'\.out$//' | + xargs -r grep -E -l '^ \* Result: (Never|Sometimes|Always|DEADLOCK)' | xargs -r grep -L "^P${LKMM_PROCS}"> $T/list-C-already # Create a list of C-language litmus tests with "Result:" commands and # no more than the specified number of processes. -find litmus -name '*.litmus' -exec grep -l -m 1 "^C " {} \; > $T/list-C -xargs < $T/list-C -r egrep -l '^ \* Result: (Never|Sometimes|Always|DEADLOCK)' > $T/list-C-result +find litmus -name '*.litmus' -print | mselect7 -arch C > $T/list-C +xargs < $T/list-C -r grep -E -l '^ \* Result: (Never|Sometimes|Always|DEADLOCK)' > $T/list-C-result xargs < $T/list-C-result -r grep -L "^P${LKMM_PROCS}" > $T/list-C-result-short -# Form list of tests without corresponding .litmus.out files +# Form list of tests without corresponding .out files sort $T/list-C-already $T/list-C-result-short | uniq -u > $T/list-C-needed # Run any needed tests. diff --git a/tools/memory-model/scripts/checklitmus.sh b/tools/memory-model/scripts/checklitmus.sh index 11461ed40b5e..4c1d0cf0ddad 100755 --- a/tools/memory-model/scripts/checklitmus.sh +++ b/tools/memory-model/scripts/checklitmus.sh @@ -1,10 +1,8 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0+ # -# Run a herd7 test and invokes judgelitmus.sh to check the result against -# a "Result:" comment within the litmus test. It also outputs verification -# results to a file whose name is that of the specified litmus test, but -# with ".out" appended. +# Invokes runlitmus.sh and judgelitmus.sh on its arguments to run the +# specified litmus test and pass judgment on the results. # # Usage: # checklitmus.sh file.litmus @@ -15,20 +13,7 @@ # # Copyright IBM Corporation, 2018 # -# Author: Paul E. McKenney <paulmck@linux.vnet.ibm.com> +# Author: Paul E. McKenney <paulmck@linux.ibm.com> -litmus=$1 -herdoptions=${LKMM_HERD_OPTIONS--conf linux-kernel.cfg} - -if test -f "$litmus" -a -r "$litmus" -then - : -else - echo ' --- ' error: \"$litmus\" is not a readable file - exit 255 -fi - -echo Herd options: $herdoptions > $LKMM_DESTDIR/$litmus.out -/usr/bin/time $LKMM_TIMEOUT_CMD herd7 $herdoptions $litmus >> $LKMM_DESTDIR/$litmus.out 2>&1 - -scripts/judgelitmus.sh $litmus +scripts/runlitmus.sh $1 +scripts/judgelitmus.sh $1 diff --git a/tools/memory-model/scripts/checklitmushist.sh b/tools/memory-model/scripts/checklitmushist.sh index 1d210ffb7c8a..406ecfc0aee4 100755 --- a/tools/memory-model/scripts/checklitmushist.sh +++ b/tools/memory-model/scripts/checklitmushist.sh @@ -12,7 +12,7 @@ # # Copyright IBM Corporation, 2018 # -# Author: Paul E. McKenney <paulmck@linux.vnet.ibm.com> +# Author: Paul E. McKenney <paulmck@linux.ibm.com> . scripts/parseargs.sh diff --git a/tools/memory-model/scripts/checktheselitmus.sh b/tools/memory-model/scripts/checktheselitmus.sh new file mode 100755 index 000000000000..10eeb5ecea6d --- /dev/null +++ b/tools/memory-model/scripts/checktheselitmus.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0+ +# +# Invokes checklitmus.sh on its arguments to run the specified litmus +# test and pass judgment on the results. +# +# Usage: +# checktheselitmus.sh -- [ file1.litmus [ file2.litmus ... ] ] +# +# Run this in the directory containing the memory model, specifying the +# pathname of the litmus test to check. The usual parseargs.sh arguments +# can be specified prior to the "--". +# +# This script is intended for use with pathnames that start from the +# tools/memory-model directory. If some of the pathnames instead start at +# the root directory, they all must do so and the "--destdir /" parseargs.sh +# argument must be specified prior to the "--". Alternatively, some other +# "--destdir" argument can be supplied as long as the needed subdirectories +# are populated. +# +# Copyright IBM Corporation, 2018 +# +# Author: Paul E. McKenney <paulmck@linux.ibm.com> + +. scripts/parseargs.sh + +ret=0 +for i in "$@" +do + if scripts/checklitmus.sh $i + then + : + else + ret=1 + fi +done +if test "$ret" -ne 0 +then + echo " ^^^ VERIFICATION MISMATCHES" 1>&2 +else + echo All litmus tests verified as was expected. 1>&2 +fi +exit $ret diff --git a/tools/memory-model/scripts/cmplitmushist.sh b/tools/memory-model/scripts/cmplitmushist.sh index 0f498aeeccf5..ca1ac8b64614 100755 --- a/tools/memory-model/scripts/cmplitmushist.sh +++ b/tools/memory-model/scripts/cmplitmushist.sh @@ -12,12 +12,49 @@ trap 'rm -rf $T' 0 mkdir $T # comparetest oldpath newpath +badmacnam=0 +timedout=0 perfect=0 obsline=0 noobsline=0 obsresult=0 badcompare=0 comparetest () { + if grep -q ': Unknown macro ' $1 || grep -q ': Unknown macro ' $2 + then + if grep -q ': Unknown macro ' $1 + then + badname=`grep ': Unknown macro ' $1 | + sed -e 's/^.*: Unknown macro //' | + sed -e 's/ (User error).*$//'` + echo 'Current LKMM version does not know "'$badname'"' $1 + fi + if grep -q ': Unknown macro ' $2 + then + badname=`grep ': Unknown macro ' $2 | + sed -e 's/^.*: Unknown macro //' | + sed -e 's/ (User error).*$//'` + echo 'Current LKMM version does not know "'$badname'"' $2 + fi + badmacnam=`expr "$badmacnam" + 1` + return 0 + elif grep -q '^Command exited with non-zero status 124' $1 || + grep -q '^Command exited with non-zero status 124' $2 + then + if grep -q '^Command exited with non-zero status 124' $1 && + grep -q '^Command exited with non-zero status 124' $2 + then + echo Both runs timed out: $2 + elif grep -q '^Command exited with non-zero status 124' $1 + then + echo Old run timed out: $2 + elif grep -q '^Command exited with non-zero status 124' $2 + then + echo New run timed out: $2 + fi + timedout=`expr "$timedout" + 1` + return 0 + fi grep -v 'maxresident)k\|minor)pagefaults\|^Time' $1 > $T/oldout grep -v 'maxresident)k\|minor)pagefaults\|^Time' $2 > $T/newout if cmp -s $T/oldout $T/newout && grep -q '^Observation' $1 @@ -38,7 +75,7 @@ comparetest () { return 0 fi else - echo Missing Observation line "(e.g., herd7 timeout)": $2 + echo Missing Observation line "(e.g., syntax error)": $2 noobsline=`expr "$noobsline" + 1` return 0 fi @@ -72,12 +109,20 @@ then fi if test "$noobsline" -ne 0 then - echo Missing Observation line "(e.g., herd7 timeout)": $noobsline 1>&2 + echo Missing Observation line "(e.g., syntax error)": $noobsline 1>&2 fi if test "$obsresult" -ne 0 then echo Matching Observation Always/Sometimes/Never result: $obsresult 1>&2 fi +if test "$timedout" -ne 0 +then + echo "!!!" Timed out: $timedout 1>&2 +fi +if test "$badmacnam" -ne 0 +then + echo "!!!" Unknown primitive: $badmacnam 1>&2 +fi if test "$badcompare" -ne 0 then echo "!!!" Result changed: $badcompare 1>&2 diff --git a/tools/memory-model/scripts/hwfnseg.sh b/tools/memory-model/scripts/hwfnseg.sh new file mode 100755 index 000000000000..580c3281181c --- /dev/null +++ b/tools/memory-model/scripts/hwfnseg.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0+ +# +# Generate the hardware extension to the litmus-test filename, or the +# empty string if this is an LKMM run. The extension is placed in +# the shell variable hwfnseg. +# +# Usage: +# . hwfnseg.sh +# +# Copyright IBM Corporation, 2019 +# +# Author: Paul E. McKenney <paulmck@linux.ibm.com> + +if test -z "$LKMM_HW_MAP_FILE" +then + hwfnseg= +else + hwfnseg=".$LKMM_HW_MAP_FILE" +fi diff --git a/tools/memory-model/scripts/initlitmushist.sh b/tools/memory-model/scripts/initlitmushist.sh index 956b6957484d..31ea782955d3 100755 --- a/tools/memory-model/scripts/initlitmushist.sh +++ b/tools/memory-model/scripts/initlitmushist.sh @@ -60,7 +60,7 @@ fi # Create a list of the C-language litmus tests with no more than the # specified number of processes (per the --procs argument). -find litmus -name '*.litmus' -exec grep -l -m 1 "^C " {} \; > $T/list-C +find litmus -name '*.litmus' -print | mselect7 -arch C > $T/list-C xargs < $T/list-C -r grep -L "^P${LKMM_PROCS}" > $T/list-C-short scripts/runlitmushist.sh < $T/list-C-short diff --git a/tools/memory-model/scripts/judgelitmus.sh b/tools/memory-model/scripts/judgelitmus.sh index 0cc63875e395..1ec5d89fcfbb 100755 --- a/tools/memory-model/scripts/judgelitmus.sh +++ b/tools/memory-model/scripts/judgelitmus.sh @@ -1,9 +1,22 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0+ # -# Given a .litmus test and the corresponding .litmus.out file, check -# the .litmus.out file against the "Result:" comment to judge whether -# the test ran correctly. +# Given a .litmus test and the corresponding litmus output file, check +# the .litmus.out file against the "Result:" comment to judge whether the +# test ran correctly. If the --hw argument is omitted, check against the +# LKMM output, which is assumed to be in file.litmus.out. If either a +# "DATARACE" marker in the "Result:" comment or a "Flag data-race" marker +# in the LKMM output is present, the other must also be as well, at least +# for litmus tests having a "Result:" comment. In this case, a failure of +# the Always/Sometimes/Never portion of the "Result:" prediction will be +# noted, but forgiven. +# +# If the --hw argument is provided, this is assumed to be a hardware +# test, and the output is assumed to be in file.litmus.HW.out, where +# "HW" is the --hw argument. In addition, non-Sometimes verification +# results will be noted, but forgiven. Furthermore, if there is no +# "Result:" comment but there is an LKMM .litmus.out file, the observation +# in that file will be used to judge the assembly-language verification. # # Usage: # judgelitmus.sh file.litmus @@ -13,7 +26,7 @@ # # Copyright IBM Corporation, 2018 # -# Author: Paul E. McKenney <paulmck@linux.vnet.ibm.com> +# Author: Paul E. McKenney <paulmck@linux.ibm.com> litmus=$1 @@ -24,55 +37,120 @@ else echo ' --- ' error: \"$litmus\" is not a readable file exit 255 fi -if test -f "$LKMM_DESTDIR/$litmus".out -a -r "$LKMM_DESTDIR/$litmus".out +if test -z "$LKMM_HW_MAP_FILE" +then + litmusout=$litmus.out + lkmmout= +else + litmusout="`echo $litmus | + sed -e 's/\.litmus$/.litmus.'${LKMM_HW_MAP_FILE}'/'`.out" + lkmmout=$litmus.out +fi +if test -f "$LKMM_DESTDIR/$litmusout" -a -r "$LKMM_DESTDIR/$litmusout" then : else - echo ' --- ' error: \"$LKMM_DESTDIR/$litmus\".out is not a readable file + echo ' --- ' error: \"$LKMM_DESTDIR/$litmusout is not a readable file exit 255 fi -if grep -q '^ \* Result: ' $litmus +if grep -q '^Flag data-race$' "$LKMM_DESTDIR/$litmusout" +then + datarace_modeled=1 +fi +if grep -q '^[( ]\* Result: ' $litmus +then + outcome=`grep -m 1 '^[( ]\* Result: ' $litmus | awk '{ print $3 }'` + if grep -m1 '^[( ]\* Result: .* DATARACE' $litmus + then + datarace_predicted=1 + fi + if test -n "$datarace_predicted" -a -z "$datarace_modeled" -a -z "$LKMM_HW_MAP_FILE" + then + echo '!!! Predicted data race not modeled' $litmus + exit 252 + elif test -z "$datarace_predicted" -a -n "$datarace_modeled" + then + # Note that hardware models currently don't model data races + echo '!!! Unexpected data race modeled' $litmus + exit 253 + fi +elif test -n "$LKMM_HW_MAP_FILE" && grep -q '^Observation' $LKMM_DESTDIR/$lkmmout > /dev/null 2>&1 then - outcome=`grep -m 1 '^ \* Result: ' $litmus | awk '{ print $3 }'` + outcome=`grep -m 1 '^Observation ' $LKMM_DESTDIR/$lkmmout | awk '{ print $3 }'` else outcome=specified fi -grep '^Observation' $LKMM_DESTDIR/$litmus.out -if grep -q '^Observation' $LKMM_DESTDIR/$litmus.out +grep '^Observation' $LKMM_DESTDIR/$litmusout +if grep -q '^Observation' $LKMM_DESTDIR/$litmusout then : +elif grep ': Unknown macro ' $LKMM_DESTDIR/$litmusout +then + badname=`grep ': Unknown macro ' $LKMM_DESTDIR/$litmusout | + sed -e 's/^.*: Unknown macro //' | + sed -e 's/ (User error).*$//'` + badmsg=' !!! Current LKMM version does not know "'$badname'"'" $litmus" + echo $badmsg + if ! grep -q '!!!' $LKMM_DESTDIR/$litmusout + then + echo ' !!! '$badmsg >> $LKMM_DESTDIR/$litmusout 2>&1 + fi + exit 254 +elif grep '^Command exited with non-zero status 124' $LKMM_DESTDIR/$litmusout +then + echo ' !!! Timeout' $litmus + if ! grep -q '!!!' $LKMM_DESTDIR/$litmusout + then + echo ' !!! Timeout' >> $LKMM_DESTDIR/$litmusout 2>&1 + fi + exit 124 else echo ' !!! Verification error' $litmus - if ! grep -q '!!!' $LKMM_DESTDIR/$litmus.out + if ! grep -q '!!!' $LKMM_DESTDIR/$litmusout then - echo ' !!! Verification error' >> $LKMM_DESTDIR/$litmus.out 2>&1 + echo ' !!! Verification error' >> $LKMM_DESTDIR/$litmusout 2>&1 fi exit 255 fi if test "$outcome" = DEADLOCK then - if grep '^Observation' $LKMM_DESTDIR/$litmus.out | grep -q 'Never 0 0$' + if grep '^Observation' $LKMM_DESTDIR/$litmusout | grep -q 'Never 0 0$' then ret=0 else echo " !!! Unexpected non-$outcome verification" $litmus - if ! grep -q '!!!' $LKMM_DESTDIR/$litmus.out + if ! grep -q '!!!' $LKMM_DESTDIR/$litmusout then - echo " !!! Unexpected non-$outcome verification" >> $LKMM_DESTDIR/$litmus.out 2>&1 + echo " !!! Unexpected non-$outcome verification" >> $LKMM_DESTDIR/$litmusout 2>&1 fi ret=1 fi -elif grep '^Observation' $LKMM_DESTDIR/$litmus.out | grep -q $outcome || test "$outcome" = Maybe +elif grep '^Observation' $LKMM_DESTDIR/$litmusout | grep -q 'Never 0 0$' +then + echo " !!! Unexpected non-$outcome deadlock" $litmus + if ! grep -q '!!!' $LKMM_DESTDIR/$litmusout + then + echo " !!! Unexpected non-$outcome deadlock" $litmus >> $LKMM_DESTDIR/$litmusout 2>&1 + fi + ret=1 +elif grep '^Observation' $LKMM_DESTDIR/$litmusout | grep -q $outcome || test "$outcome" = Maybe then ret=0 else - echo " !!! Unexpected non-$outcome verification" $litmus - if ! grep -q '!!!' $LKMM_DESTDIR/$litmus.out + if test \( -n "$LKMM_HW_MAP_FILE" -a "$outcome" = Sometimes \) -o -n "$datarace_modeled" then - echo " !!! Unexpected non-$outcome verification" >> $LKMM_DESTDIR/$litmus.out 2>&1 + flag="--- Forgiven" + ret=0 + else + flag="!!! Unexpected" + ret=1 + fi + echo " $flag non-$outcome verification" $litmus + if ! grep -qe "$flag" $LKMM_DESTDIR/$litmusout + then + echo " $flag non-$outcome verification" >> $LKMM_DESTDIR/$litmusout 2>&1 fi - ret=1 fi -tail -2 $LKMM_DESTDIR/$litmus.out | head -1 +tail -2 $LKMM_DESTDIR/$litmusout | head -1 exit $ret diff --git a/tools/memory-model/scripts/newlitmushist.sh b/tools/memory-model/scripts/newlitmushist.sh index 991f8f814881..25235e2049cf 100755 --- a/tools/memory-model/scripts/newlitmushist.sh +++ b/tools/memory-model/scripts/newlitmushist.sh @@ -12,7 +12,7 @@ # # Copyright IBM Corporation, 2018 # -# Author: Paul E. McKenney <paulmck@linux.vnet.ibm.com> +# Author: Paul E. McKenney <paulmck@linux.ibm.com> . scripts/parseargs.sh @@ -43,7 +43,7 @@ fi # Form full list of litmus tests with no more than the specified # number of processes (per the --procs argument). -find litmus -name '*.litmus' -exec grep -l -m 1 "^C " {} \; > $T/list-C-all +find litmus -name '*.litmus' -print | mselect7 -arch C > $T/list-C-all xargs < $T/list-C-all -r grep -L "^P${LKMM_PROCS}" > $T/list-C-short # Form list of new tests. Note: This does not handle litmus-test deletion! diff --git a/tools/memory-model/scripts/parseargs.sh b/tools/memory-model/scripts/parseargs.sh index 40f52080fdbd..08ded5909860 100755 --- a/tools/memory-model/scripts/parseargs.sh +++ b/tools/memory-model/scripts/parseargs.sh @@ -1,7 +1,7 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0+ # -# the corresponding .litmus.out file, and does not judge the result. +# Parse arguments common to the various scripts. # # . scripts/parseargs.sh # @@ -9,7 +9,7 @@ # # Copyright IBM Corporation, 2018 # -# Author: Paul E. McKenney <paulmck@linux.vnet.ibm.com> +# Author: Paul E. McKenney <paulmck@linux.ibm.com> T=/tmp/parseargs.sh.$$ mkdir $T @@ -27,6 +27,7 @@ initparam () { initparam LKMM_DESTDIR "." initparam LKMM_HERD_OPTIONS "-conf linux-kernel.cfg" +initparam LKMM_HW_MAP_FILE "" initparam LKMM_JOBS `getconf _NPROCESSORS_ONLN` initparam LKMM_PROCS "3" initparam LKMM_TIMEOUT "1m" @@ -37,10 +38,11 @@ usagehelp () { echo "Usage $scriptname [ arguments ]" echo " --destdir path (place for .litmus.out, default by .litmus)" echo " --herdopts -conf linux-kernel.cfg ..." + echo " --hw AArch64" echo " --jobs N (number of jobs, default one per CPU)" echo " --procs N (litmus tests with at most this many processes)" echo " --timeout N (herd7 timeout (e.g., 10s, 1m, 2hr, 1d, '')" - echo "Defaults: --destdir '$LKMM_DESTDIR_DEF' --herdopts '$LKMM_HERD_OPTIONS_DEF' --jobs '$LKMM_JOBS_DEF' --procs '$LKMM_PROCS_DEF' --timeout '$LKMM_TIMEOUT_DEF'" + echo "Defaults: --destdir '$LKMM_DESTDIR_DEF' --herdopts '$LKMM_HERD_OPTIONS_DEF' --hw '$LKMM_HW_MAP_FILE' --jobs '$LKMM_JOBS_DEF' --procs '$LKMM_PROCS_DEF' --timeout '$LKMM_TIMEOUT_DEF'" exit 1 } @@ -81,7 +83,7 @@ do echo "Cannot create directory --destdir '$LKMM_DESTDIR'" usage fi - if test -d "$LKMM_DESTDIR" -a -w "$LKMM_DESTDIR" -a -x "$LKMM_DESTDIR" + if test -d "$LKMM_DESTDIR" -a -x "$LKMM_DESTDIR" then : else @@ -95,6 +97,11 @@ do LKMM_HERD_OPTIONS="$2" shift ;; + --hw) + checkarg --hw "(.map file architecture name)" "$#" "$2" '^[A-Za-z0-9_-]\+' '^--' + LKMM_HW_MAP_FILE="$2" + shift + ;; -j[1-9]*) njobs="`echo $1 | sed -e 's/^-j//'`" trailchars="`echo $njobs | sed -e 's/[0-9]\+\(.*\)$/\1/'`" @@ -106,7 +113,7 @@ do LKMM_JOBS="`echo $njobs | sed -e 's/^\([0-9]\+\).*$/\1/'`" ;; --jobs|--job|-j) - checkarg --jobs "(number)" "$#" "$2" '^[1-9][0-9]\+$' '^--' + checkarg --jobs "(number)" "$#" "$2" '^[1-9][0-9]*$' '^--' LKMM_JOBS="$2" shift ;; @@ -120,6 +127,10 @@ do LKMM_TIMEOUT="$2" shift ;; + --) + shift + break + ;; *) echo Unknown argument $1 usage diff --git a/tools/memory-model/scripts/runlitmus.sh b/tools/memory-model/scripts/runlitmus.sh new file mode 100755 index 000000000000..94608d4b6502 --- /dev/null +++ b/tools/memory-model/scripts/runlitmus.sh @@ -0,0 +1,80 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0+ +# +# Without the -hw argument, runs a herd7 test and outputs verification +# results to a file whose name is that of the specified litmus test, +# but with ".out" appended. +# +# If the --hw argument is specified, this script translates the .litmus +# C-language file to the specified type of assembly and verifies that. +# But in this case, litmus tests using complex synchronization (such as +# locking, RCU, and SRCU) are cheerfully ignored. +# +# Either way, return the status of the herd7 command. +# +# Usage: +# runlitmus.sh file.litmus +# +# Run this in the directory containing the memory model, specifying the +# pathname of the litmus test to check. The caller is expected to have +# properly set up the LKMM environment variables. +# +# Copyright IBM Corporation, 2019 +# +# Author: Paul E. McKenney <paulmck@linux.ibm.com> + +litmus=$1 +if test -f "$litmus" -a -r "$litmus" +then + : +else + echo ' !!! ' error: \"$litmus\" is not a readable file + exit 255 +fi + +if test -z "$LKMM_HW_MAP_FILE" -o ! -e $LKMM_DESTDIR/$litmus.out +then + # LKMM run + herdoptions=${LKMM_HERD_OPTIONS--conf linux-kernel.cfg} + echo Herd options: $herdoptions > $LKMM_DESTDIR/$litmus.out + /usr/bin/time $LKMM_TIMEOUT_CMD herd7 $herdoptions $litmus >> $LKMM_DESTDIR/$litmus.out 2>&1 + ret=$? + if test -z "$LKMM_HW_MAP_FILE" + then + exit $ret + fi + echo " --- " Automatically generated LKMM output for '"'--hw $LKMM_HW_MAP_FILE'"' run +fi + +# Hardware run + +T=/tmp/checklitmushw.sh.$$ +trap 'rm -rf $T' 0 2 +mkdir $T + +# Generate filenames +mapfile="Linux2${LKMM_HW_MAP_FILE}.map" +themefile="$T/${LKMM_HW_MAP_FILE}.theme" +herdoptions="-model $LKMM_HW_CAT_FILE" +hwlitmus=`echo $litmus | sed -e 's/\.litmus$/.litmus.'${LKMM_HW_MAP_FILE}'/'` +hwlitmusfile=`echo $hwlitmus | sed -e 's,^.*/,,'` + +# Don't run on litmus tests with complex synchronization +if ! scripts/simpletest.sh $litmus +then + echo ' --- ' error: \"$litmus\" contains locking, RCU, or SRCU + exit 254 +fi + +# Generate the assembly code and run herd7 on it. +gen_theme7 -n 10 -map $mapfile -call Linux.call > $themefile +jingle7 -v -theme $themefile $litmus > $LKMM_DESTDIR/$hwlitmus 2> $T/$hwlitmusfile.jingle7.out +if grep -q "Generated 0 tests" $T/$hwlitmusfile.jingle7.out +then + echo ' !!! ' jingle7 failed, errors in $hwlitmus.err + cp $T/$hwlitmusfile.jingle7.out $LKMM_DESTDIR/$hwlitmus.err + exit 253 +fi +/usr/bin/time $LKMM_TIMEOUT_CMD herd7 -unroll 0 $LKMM_DESTDIR/$hwlitmus > $LKMM_DESTDIR/$hwlitmus.out 2>&1 + +exit $? diff --git a/tools/memory-model/scripts/runlitmushist.sh b/tools/memory-model/scripts/runlitmushist.sh index 6ed376f495bb..c6c2bdc67a50 100755 --- a/tools/memory-model/scripts/runlitmushist.sh +++ b/tools/memory-model/scripts/runlitmushist.sh @@ -13,7 +13,9 @@ # # Copyright IBM Corporation, 2018 # -# Author: Paul E. McKenney <paulmck@linux.vnet.ibm.com> +# Author: Paul E. McKenney <paulmck@linux.ibm.com> + +. scripts/hwfnseg.sh T=/tmp/runlitmushist.sh.$$ trap 'rm -rf $T' 0 @@ -30,15 +32,12 @@ fi # Prefixes for per-CPU scripts for ((i=0;i<$LKMM_JOBS;i++)) do - echo dir="$LKMM_DESTDIR" > $T/$i.sh echo T=$T >> $T/$i.sh - echo herdoptions=\"$LKMM_HERD_OPTIONS\" >> $T/$i.sh cat << '___EOF___' >> $T/$i.sh runtest () { - echo ' ... ' /usr/bin/time $LKMM_TIMEOUT_CMD herd7 $herdoptions $1 '>' $dir/$1.out '2>&1' - if /usr/bin/time $LKMM_TIMEOUT_CMD herd7 $herdoptions $1 > $dir/$1.out 2>&1 + if scripts/runlitmus.sh $1 then - if ! grep -q '^Observation ' $dir/$1.out + if ! grep -q '^Observation ' $LKMM_DESTDIR/$1$2.out then echo ' !!! Herd failed, no Observation:' $1 fi @@ -47,10 +46,16 @@ do if test "$exitcode" -eq 124 then exitmsg="timed out" + elif test "$exitcode" -eq 253 + then + exitmsg= else exitmsg="failed, exit code $exitcode" fi - echo ' !!! Herd' ${exitmsg}: $1 + if test -n "$exitmsg" + then + echo ' !!! Herd' ${exitmsg}: $1 + fi fi } ___EOF___ @@ -59,11 +64,13 @@ done awk -v q="'" -v b='\\' ' { print "echo `grep " q "^P[0-9]" b "+(" q " " $0 " | tail -1 | sed -e " q "s/^P" b "([0-9]" b "+" b ")(.*$/" b "1/" q "` " $0 -}' | bash | -sort -k1n | -awk -v ncpu=$LKMM_JOBS -v t=$T ' +}' | sh | sort -k1n | +awk -v dq='"' -v hwfnseg="$hwfnseg" -v ncpu="$LKMM_JOBS" -v t="$T" ' { - print "runtest " $2 >> t "/" NR % ncpu ".sh"; + print "if test -z " dq hwfnseg dq " || scripts/simpletest.sh " dq $2 dq + print "then" + print "\techo runtest " dq $2 dq " " hwfnseg " >> " t "/" NR % ncpu ".sh"; + print "fi" } END { diff --git a/tools/memory-model/scripts/simpletest.sh b/tools/memory-model/scripts/simpletest.sh new file mode 100755 index 000000000000..7edc5d361665 --- /dev/null +++ b/tools/memory-model/scripts/simpletest.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0+ +# +# Give zero status if this is a simple test and non-zero otherwise. +# Simple tests do not contain locking, RCU, or SRCU. +# +# Usage: +# simpletest.sh file.litmus +# +# Copyright IBM Corporation, 2019 +# +# Author: Paul E. McKenney <paulmck@linux.ibm.com> + + +litmus=$1 + +if test -f "$litmus" -a -r "$litmus" +then + : +else + echo ' --- ' error: \"$litmus\" is not a readable file + exit 255 +fi +exclude="^[[:space:]]*\(" +exclude="${exclude}spin_lock(\|spin_unlock(\|spin_trylock(\|spin_is_locked(" +exclude="${exclude}\|rcu_read_lock(\|rcu_read_unlock(" +exclude="${exclude}\|synchronize_rcu(\|synchronize_rcu_expedited(" +exclude="${exclude}\|srcu_read_lock(\|srcu_read_unlock(" +exclude="${exclude}\|synchronize_srcu(\|synchronize_srcu_expedited(" +exclude="${exclude}\)" +if grep -q $exclude $litmus +then + exit 255 +fi +exit 0 diff --git a/tools/mm/page-types.c b/tools/mm/page-types.c index 381dcc00cb62..8d5595b6c59f 100644 --- a/tools/mm/page-types.c +++ b/tools/mm/page-types.c @@ -85,7 +85,6 @@ */ #define KPF_ANON_EXCLUSIVE 47 #define KPF_READAHEAD 48 -#define KPF_SLOB_FREE 49 #define KPF_SLUB_FROZEN 50 #define KPF_SLUB_DEBUG 51 #define KPF_FILE 61 @@ -141,7 +140,6 @@ static const char * const page_flag_names[] = { [KPF_ANON_EXCLUSIVE] = "d:anon_exclusive", [KPF_READAHEAD] = "I:readahead", - [KPF_SLOB_FREE] = "P:slob_free", [KPF_SLUB_FROZEN] = "A:slub_frozen", [KPF_SLUB_DEBUG] = "E:slub_debug", @@ -478,10 +476,8 @@ static uint64_t expand_overloaded_flags(uint64_t flags, uint64_t pme) if ((flags & BIT(ANON)) && (flags & BIT(MAPPEDTODISK))) flags ^= BIT(MAPPEDTODISK) | BIT(ANON_EXCLUSIVE); - /* SLOB/SLUB overload several page flags */ + /* SLUB overloads several page flags */ if (flags & BIT(SLAB)) { - if (flags & BIT(PRIVATE)) - flags ^= BIT(PRIVATE) | BIT(SLOB_FREE); if (flags & BIT(ACTIVE)) flags ^= BIT(ACTIVE) | BIT(SLUB_FROZEN); if (flags & BIT(ERROR)) diff --git a/tools/objtool/Documentation/objtool.txt b/tools/objtool/Documentation/objtool.txt index 8e53fc6735ef..744db4218e7a 100644 --- a/tools/objtool/Documentation/objtool.txt +++ b/tools/objtool/Documentation/objtool.txt @@ -181,7 +181,7 @@ b) ORC (Oops Rewind Capability) unwind table generation band. So it doesn't affect runtime performance and it can be reliable even when interrupts or exceptions are involved. - For more details, see Documentation/x86/orc-unwinder.rst. + For more details, see Documentation/arch/x86/orc-unwinder.rst. c) Higher live patching compatibility rate diff --git a/tools/objtool/check.c b/tools/objtool/check.c index f937be1afe65..0fcf99c91400 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -17,7 +17,7 @@ #include <objtool/warn.h> #include <objtool/endianness.h> -#include <linux/objtool.h> +#include <linux/objtool_types.h> #include <linux/hashtable.h> #include <linux/kernel.h> #include <linux/static_call_types.h> @@ -202,6 +202,9 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "__reiserfs_panic", "__stack_chk_fail", "__ubsan_handle_builtin_unreachable", + "arch_call_rest_init", + "arch_cpu_idle_dead", + "btrfs_assertfail", "cpu_bringup_and_idle", "cpu_startup_entry", "do_exit", @@ -209,18 +212,28 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "do_task_dead", "ex_handler_msr_mce", "fortify_panic", + "hlt_play_dead", + "hv_ghcb_terminate", "kthread_complete_and_exit", "kthread_exit", "kunit_try_catch_throw", "lbug_with_loc", "machine_real_restart", "make_task_dead", + "mpt_halt_firmware", + "nmi_panic_self_stop", "panic", + "panic_smp_self_stop", + "rest_init", + "resume_play_dead", "rewind_stack_and_make_dead", "sev_es_terminate", "snp_abort", + "start_kernel", "stop_this_cpu", "usercopy_abort", + "x86_64_start_kernel", + "x86_64_start_reservations", "xen_cpu_bringup_again", "xen_start_kernel", }; @@ -228,14 +241,14 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, if (!func) return false; - if (func->bind == STB_WEAK) - return false; - - if (func->bind == STB_GLOBAL) + if (func->bind == STB_GLOBAL || func->bind == STB_WEAK) for (i = 0; i < ARRAY_SIZE(global_noreturns); i++) if (!strcmp(func->name, global_noreturns[i])) return true; + if (func->bind == STB_WEAK) + return false; + if (!func->len) return false; @@ -469,7 +482,7 @@ static int decode_instructions(struct objtool_file *file) // printf("%s: last chunk used: %d\n", sec->name, (int)idx); - list_for_each_entry(func, &sec->symbol_list, list) { + sec_for_each_sym(sec, func) { if (func->type != STT_NOTYPE && func->type != STT_FUNC) continue; @@ -923,7 +936,7 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file) static int create_cfi_sections(struct objtool_file *file) { - struct section *sec, *s; + struct section *sec; struct symbol *sym; unsigned int *loc; int idx; @@ -936,19 +949,14 @@ static int create_cfi_sections(struct objtool_file *file) } idx = 0; - for_each_sec(file, s) { - if (!s->text) + for_each_sym(file, sym) { + if (sym->type != STT_FUNC) continue; - list_for_each_entry(sym, &s->symbol_list, list) { - if (sym->type != STT_FUNC) - continue; - - if (strncmp(sym->name, "__cfi_", 6)) - continue; + if (strncmp(sym->name, "__cfi_", 6)) + continue; - idx++; - } + idx++; } sec = elf_create_section(file->elf, ".cfi_sites", 0, sizeof(unsigned int), idx); @@ -956,28 +964,23 @@ static int create_cfi_sections(struct objtool_file *file) return -1; idx = 0; - for_each_sec(file, s) { - if (!s->text) + for_each_sym(file, sym) { + if (sym->type != STT_FUNC) continue; - list_for_each_entry(sym, &s->symbol_list, list) { - if (sym->type != STT_FUNC) - continue; - - if (strncmp(sym->name, "__cfi_", 6)) - continue; + if (strncmp(sym->name, "__cfi_", 6)) + continue; - loc = (unsigned int *)sec->data->d_buf + idx; - memset(loc, 0, sizeof(unsigned int)); + loc = (unsigned int *)sec->data->d_buf + idx; + memset(loc, 0, sizeof(unsigned int)); - if (elf_add_reloc_to_insn(file->elf, sec, - idx * sizeof(unsigned int), - R_X86_64_PC32, - s, sym->offset)) - return -1; + if (elf_add_reloc_to_insn(file->elf, sec, + idx * sizeof(unsigned int), + R_X86_64_PC32, + sym->sec, sym->offset)) + return -1; - idx++; - } + idx++; } return 0; @@ -1278,15 +1281,17 @@ static const char *uaccess_safe_builtin[] = { "__ubsan_handle_type_mismatch_v1", "__ubsan_handle_shift_out_of_bounds", "__ubsan_handle_load_invalid_value", + /* STACKLEAK */ + "stackleak_track_stack", /* misc */ "csum_partial_copy_generic", "copy_mc_fragile", "copy_mc_fragile_handle_tail", "copy_mc_enhanced_fast_string", "ftrace_likely_update", /* CONFIG_TRACE_BRANCH_PROFILING */ - "clear_user_erms", - "clear_user_rep_good", - "clear_user_original", + "rep_stos_alternative", + "rep_movs_alternative", + "__copy_user_nocache", NULL }; @@ -1443,7 +1448,7 @@ static void annotate_call_site(struct objtool_file *file, if (opts.mcount && sym->fentry) { if (sibling) - WARN_FUNC("Tail call to __fentry__ !?!?", insn->sec, insn->offset); + WARN_INSN(insn, "tail call to __fentry__ !?!?"); if (opts.mnop) { if (reloc) { reloc->type = R_NONE; @@ -1645,9 +1650,8 @@ static int add_jump_destinations(struct objtool_file *file) continue; } - WARN_FUNC("can't find jump dest instruction at %s+0x%lx", - insn->sec, insn->offset, dest_sec->name, - dest_off); + WARN_INSN(insn, "can't find jump dest instruction at %s+0x%lx", + dest_sec->name, dest_off); return -1; } @@ -1730,13 +1734,12 @@ static int add_call_destinations(struct objtool_file *file) continue; if (!insn_call_dest(insn)) { - WARN_FUNC("unannotated intra-function call", insn->sec, insn->offset); + WARN_INSN(insn, "unannotated intra-function call"); return -1; } if (insn_func(insn) && insn_call_dest(insn)->type != STT_FUNC) { - WARN_FUNC("unsupported call to non-function", - insn->sec, insn->offset); + WARN_INSN(insn, "unsupported call to non-function"); return -1; } @@ -1744,10 +1747,8 @@ static int add_call_destinations(struct objtool_file *file) dest_off = arch_dest_reloc_offset(reloc->addend); dest = find_call_destination(reloc->sym->sec, dest_off); if (!dest) { - WARN_FUNC("can't find call dest symbol at %s+0x%lx", - insn->sec, insn->offset, - reloc->sym->sec->name, - dest_off); + WARN_INSN(insn, "can't find call dest symbol at %s+0x%lx", + reloc->sym->sec->name, dest_off); return -1; } @@ -1807,8 +1808,7 @@ static int handle_group_alt(struct objtool_file *file, } else { if (orig_alt_group->last_insn->offset + orig_alt_group->last_insn->len - orig_alt_group->first_insn->offset != special_alt->orig_len) { - WARN_FUNC("weirdly overlapping alternative! %ld != %d", - orig_insn->sec, orig_insn->offset, + WARN_INSN(orig_insn, "weirdly overlapping alternative! %ld != %d", orig_alt_group->last_insn->offset + orig_alt_group->last_insn->len - orig_alt_group->first_insn->offset, @@ -1877,8 +1877,7 @@ static int handle_group_alt(struct objtool_file *file, if (alt_reloc && arch_pc_relative_reloc(alt_reloc) && !arch_support_alt_relocation(special_alt, insn, alt_reloc)) { - WARN_FUNC("unsupported relocation in alternatives section", - insn->sec, insn->offset); + WARN_INSN(insn, "unsupported relocation in alternatives section"); return -1; } @@ -1892,8 +1891,7 @@ static int handle_group_alt(struct objtool_file *file, if (dest_off == special_alt->new_off + special_alt->new_len) { insn->jump_dest = next_insn_same_sec(file, orig_alt_group->last_insn); if (!insn->jump_dest) { - WARN_FUNC("can't find alternative jump destination", - insn->sec, insn->offset); + WARN_INSN(insn, "can't find alternative jump destination"); return -1; } } @@ -1927,8 +1925,7 @@ static int handle_jump_alt(struct objtool_file *file, if (orig_insn->type != INSN_JUMP_UNCONDITIONAL && orig_insn->type != INSN_NOP) { - WARN_FUNC("unsupported instruction at jump label", - orig_insn->sec, orig_insn->offset); + WARN_INSN(orig_insn, "unsupported instruction at jump label"); return -1; } @@ -2007,8 +2004,7 @@ static int add_special_section_alts(struct objtool_file *file) if (special_alt->group) { if (!special_alt->orig_len) { - WARN_FUNC("empty alternative entry", - orig_insn->sec, orig_insn->offset); + WARN_INSN(orig_insn, "empty alternative entry"); continue; } @@ -2099,8 +2095,7 @@ static int add_jump_table(struct objtool_file *file, struct instruction *insn, } if (!prev_offset) { - WARN_FUNC("can't find switch jump table", - insn->sec, insn->offset); + WARN_INSN(insn, "can't find switch jump table"); return -1; } @@ -2214,23 +2209,20 @@ static int add_func_jump_tables(struct objtool_file *file, */ static int add_jump_table_alts(struct objtool_file *file) { - struct section *sec; struct symbol *func; int ret; if (!file->rodata) return 0; - for_each_sec(file, sec) { - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC) - continue; + for_each_sym(file, func) { + if (func->type != STT_FUNC) + continue; - mark_func_jump_tables(file, func); - ret = add_func_jump_tables(file, func); - if (ret) - return ret; - } + mark_func_jump_tables(file, func); + ret = add_func_jump_tables(file, func); + if (ret) + return ret; } return 0; @@ -2242,6 +2234,7 @@ static void set_func_state(struct cfi_state *state) memcpy(&state->regs, &initial_func_cfi.regs, CFI_NUM_REGS * sizeof(struct cfi_reg)); state->stack_size = initial_func_cfi.cfa.offset; + state->type = UNWIND_HINT_TYPE_CALL; } static int read_unwind_hints(struct objtool_file *file) @@ -2303,19 +2296,11 @@ static int read_unwind_hints(struct objtool_file *file) if (sym && sym->bind == STB_GLOBAL) { if (opts.ibt && insn->type != INSN_ENDBR && !insn->noendbr) { - WARN_FUNC("UNWIND_HINT_IRET_REGS without ENDBR", - insn->sec, insn->offset); + WARN_INSN(insn, "UNWIND_HINT_IRET_REGS without ENDBR"); } - - insn->entry = 1; } } - if (hint->type == UNWIND_HINT_TYPE_ENTRY) { - hint->type = UNWIND_HINT_TYPE_CALL; - insn->entry = 1; - } - if (hint->type == UNWIND_HINT_TYPE_FUNC) { insn->cfi = &func_cfi; continue; @@ -2325,15 +2310,13 @@ static int read_unwind_hints(struct objtool_file *file) cfi = *(insn->cfi); if (arch_decode_hint_reg(hint->sp_reg, &cfi.cfa.base)) { - WARN_FUNC("unsupported unwind_hint sp base reg %d", - insn->sec, insn->offset, hint->sp_reg); + WARN_INSN(insn, "unsupported unwind_hint sp base reg %d", hint->sp_reg); return -1; } cfi.cfa.offset = bswap_if_needed(file->elf, hint->sp_offset); cfi.type = hint->type; cfi.signal = hint->signal; - cfi.end = hint->end; insn->cfi = cfi_hash_find_or_add(&cfi); } @@ -2390,8 +2373,7 @@ static int read_retpoline_hints(struct objtool_file *file) insn->type != INSN_CALL_DYNAMIC && insn->type != INSN_RETURN && insn->type != INSN_NOP) { - WARN_FUNC("retpoline_safe hint not an indirect jump/call/ret/nop", - insn->sec, insn->offset); + WARN_INSN(insn, "retpoline_safe hint not an indirect jump/call/ret/nop"); return -1; } @@ -2448,6 +2430,34 @@ static int read_instr_hints(struct objtool_file *file) return 0; } +static int read_validate_unret_hints(struct objtool_file *file) +{ + struct section *sec; + struct instruction *insn; + struct reloc *reloc; + + sec = find_section_by_name(file->elf, ".rela.discard.validate_unret"); + if (!sec) + return 0; + + list_for_each_entry(reloc, &sec->reloc_list, list) { + if (reloc->sym->type != STT_SECTION) { + WARN("unexpected relocation symbol type in %s", sec->name); + return -1; + } + + insn = find_insn(file, reloc->sym->sec, reloc->addend); + if (!insn) { + WARN("bad .discard.instr_end entry"); + return -1; + } + insn->unret = 1; + } + + return 0; +} + + static int read_intra_function_calls(struct objtool_file *file) { struct instruction *insn; @@ -2474,8 +2484,7 @@ static int read_intra_function_calls(struct objtool_file *file) } if (insn->type != INSN_CALL) { - WARN_FUNC("intra_function_call not a direct call", - insn->sec, insn->offset); + WARN_INSN(insn, "intra_function_call not a direct call"); return -1; } @@ -2489,8 +2498,7 @@ static int read_intra_function_calls(struct objtool_file *file) dest_off = arch_jump_destination(insn); insn->jump_dest = find_insn(file, insn->sec, dest_off); if (!insn->jump_dest) { - WARN_FUNC("can't find call dest at %s+0x%lx", - insn->sec, insn->offset, + WARN_INSN(insn, "can't find call dest at %s+0x%lx", insn->sec->name, dest_off); return -1; } @@ -2526,30 +2534,27 @@ static bool is_profiling_func(const char *name) static int classify_symbols(struct objtool_file *file) { - struct section *sec; struct symbol *func; - for_each_sec(file, sec) { - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->bind != STB_GLOBAL) - continue; + for_each_sym(file, func) { + if (func->bind != STB_GLOBAL) + continue; - if (!strncmp(func->name, STATIC_CALL_TRAMP_PREFIX_STR, - strlen(STATIC_CALL_TRAMP_PREFIX_STR))) - func->static_call_tramp = true; + if (!strncmp(func->name, STATIC_CALL_TRAMP_PREFIX_STR, + strlen(STATIC_CALL_TRAMP_PREFIX_STR))) + func->static_call_tramp = true; - if (arch_is_retpoline(func)) - func->retpoline_thunk = true; + if (arch_is_retpoline(func)) + func->retpoline_thunk = true; - if (arch_is_rethunk(func)) - func->return_thunk = true; + if (arch_is_rethunk(func)) + func->return_thunk = true; - if (arch_ftrace_match(func->name)) - func->fentry = true; + if (arch_ftrace_match(func->name)) + func->fentry = true; - if (is_profiling_func(func->name)) - func->profiling_func = true; - } + if (is_profiling_func(func->name)) + func->profiling_func = true; } return 0; @@ -2666,6 +2671,10 @@ static int decode_sections(struct objtool_file *file) if (ret) return ret; + ret = read_validate_unret_hints(file); + if (ret) + return ret; + return 0; } @@ -2827,7 +2836,7 @@ static int update_cfi_state(struct instruction *insn, /* stack operations don't make sense with an undefined CFA */ if (cfa->base == CFI_UNDEFINED) { if (insn_func(insn)) { - WARN_FUNC("undefined stack state", insn->sec, insn->offset); + WARN_INSN(insn, "undefined stack state"); return -1; } return 0; @@ -2976,17 +2985,6 @@ static int update_cfi_state(struct instruction *insn, break; } - if (!cfi->drap && op->src.reg == CFI_SP && - op->dest.reg == CFI_BP && cfa->base == CFI_SP && - check_reg_frame_pos(®s[CFI_BP], -cfa->offset + op->src.offset)) { - - /* lea disp(%rsp), %rbp */ - cfa->base = CFI_BP; - cfa->offset -= op->src.offset; - cfi->bp_scratch = false; - break; - } - if (op->src.reg == CFI_SP && cfa->base == CFI_SP) { /* drap: lea disp(%rsp), %drap */ @@ -3021,8 +3019,7 @@ static int update_cfi_state(struct instruction *insn, } if (op->dest.reg == cfi->cfa.base && !(next_insn && next_insn->hint)) { - WARN_FUNC("unsupported stack register modification", - insn->sec, insn->offset); + WARN_INSN(insn, "unsupported stack register modification"); return -1; } @@ -3032,8 +3029,7 @@ static int update_cfi_state(struct instruction *insn, if (op->dest.reg != CFI_SP || (cfi->drap_reg != CFI_UNDEFINED && cfa->base != CFI_SP) || (cfi->drap_reg == CFI_UNDEFINED && cfa->base != CFI_BP)) { - WARN_FUNC("unsupported stack pointer realignment", - insn->sec, insn->offset); + WARN_INSN(insn, "unsupported stack pointer realignment"); return -1; } @@ -3128,8 +3124,7 @@ static int update_cfi_state(struct instruction *insn, break; default: - WARN_FUNC("unknown stack-related instruction", - insn->sec, insn->offset); + WARN_INSN(insn, "unknown stack-related instruction"); return -1; } @@ -3218,8 +3213,7 @@ static int update_cfi_state(struct instruction *insn, case OP_DEST_MEM: if (op->src.type != OP_SRC_POP && op->src.type != OP_SRC_POPF) { - WARN_FUNC("unknown stack-related memory operation", - insn->sec, insn->offset); + WARN_INSN(insn, "unknown stack-related memory operation"); return -1; } @@ -3231,8 +3225,7 @@ static int update_cfi_state(struct instruction *insn, break; default: - WARN_FUNC("unknown stack-related instruction", - insn->sec, insn->offset); + WARN_INSN(insn, "unknown stack-related instruction"); return -1; } @@ -3271,8 +3264,7 @@ static int propagate_alt_cfi(struct objtool_file *file, struct instruction *insn struct alt_group *orig_group = insn->alt_group->orig_group ?: insn->alt_group; struct instruction *orig = orig_group->first_insn; char *where = offstr(insn->sec, insn->offset); - WARN_FUNC("stack layout conflict in alternatives: %s", - orig->sec, orig->offset, where); + WARN_INSN(orig, "stack layout conflict in alternatives: %s", where); free(where); return -1; } @@ -3299,8 +3291,7 @@ static int handle_insn_ops(struct instruction *insn, if (!state->uaccess_stack) { state->uaccess_stack = 1; } else if (state->uaccess_stack >> 31) { - WARN_FUNC("PUSHF stack exhausted", - insn->sec, insn->offset); + WARN_INSN(insn, "PUSHF stack exhausted"); return 1; } state->uaccess_stack <<= 1; @@ -3332,8 +3323,7 @@ static bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) if (memcmp(&cfi1->cfa, &cfi2->cfa, sizeof(cfi1->cfa))) { - WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d", - insn->sec, insn->offset, + WARN_INSN(insn, "stack state mismatch: cfa1=%d%+d cfa2=%d%+d", cfi1->cfa.base, cfi1->cfa.offset, cfi2->cfa.base, cfi2->cfa.offset); @@ -3343,8 +3333,7 @@ static bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) sizeof(struct cfi_reg))) continue; - WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", - insn->sec, insn->offset, + WARN_INSN(insn, "stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", i, cfi1->regs[i].base, cfi1->regs[i].offset, i, cfi2->regs[i].base, cfi2->regs[i].offset); break; @@ -3352,15 +3341,14 @@ static bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) } else if (cfi1->type != cfi2->type) { - WARN_FUNC("stack state mismatch: type1=%d type2=%d", - insn->sec, insn->offset, cfi1->type, cfi2->type); + WARN_INSN(insn, "stack state mismatch: type1=%d type2=%d", + cfi1->type, cfi2->type); } else if (cfi1->drap != cfi2->drap || (cfi1->drap && cfi1->drap_reg != cfi2->drap_reg) || (cfi1->drap && cfi1->drap_offset != cfi2->drap_offset)) { - WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", - insn->sec, insn->offset, + WARN_INSN(insn, "stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", cfi1->drap, cfi1->drap_reg, cfi1->drap_offset, cfi2->drap, cfi2->drap_reg, cfi2->drap_offset); @@ -3468,20 +3456,17 @@ static int validate_call(struct objtool_file *file, { if (state->noinstr && state->instr <= 0 && !noinstr_call_dest(file, insn, insn_call_dest(insn))) { - WARN_FUNC("call to %s() leaves .noinstr.text section", - insn->sec, insn->offset, call_dest_name(insn)); + WARN_INSN(insn, "call to %s() leaves .noinstr.text section", call_dest_name(insn)); return 1; } if (state->uaccess && !func_uaccess_safe(insn_call_dest(insn))) { - WARN_FUNC("call to %s() with UACCESS enabled", - insn->sec, insn->offset, call_dest_name(insn)); + WARN_INSN(insn, "call to %s() with UACCESS enabled", call_dest_name(insn)); return 1; } if (state->df) { - WARN_FUNC("call to %s() with DF set", - insn->sec, insn->offset, call_dest_name(insn)); + WARN_INSN(insn, "call to %s() with DF set", call_dest_name(insn)); return 1; } @@ -3493,8 +3478,7 @@ static int validate_sibling_call(struct objtool_file *file, struct insn_state *state) { if (insn_func(insn) && has_modified_stack_frame(insn, state)) { - WARN_FUNC("sibling call from callable instruction with modified stack frame", - insn->sec, insn->offset); + WARN_INSN(insn, "sibling call from callable instruction with modified stack frame"); return 1; } @@ -3504,38 +3488,32 @@ static int validate_sibling_call(struct objtool_file *file, static int validate_return(struct symbol *func, struct instruction *insn, struct insn_state *state) { if (state->noinstr && state->instr > 0) { - WARN_FUNC("return with instrumentation enabled", - insn->sec, insn->offset); + WARN_INSN(insn, "return with instrumentation enabled"); return 1; } if (state->uaccess && !func_uaccess_safe(func)) { - WARN_FUNC("return with UACCESS enabled", - insn->sec, insn->offset); + WARN_INSN(insn, "return with UACCESS enabled"); return 1; } if (!state->uaccess && func_uaccess_safe(func)) { - WARN_FUNC("return with UACCESS disabled from a UACCESS-safe function", - insn->sec, insn->offset); + WARN_INSN(insn, "return with UACCESS disabled from a UACCESS-safe function"); return 1; } if (state->df) { - WARN_FUNC("return with DF set", - insn->sec, insn->offset); + WARN_INSN(insn, "return with DF set"); return 1; } if (func && has_modified_stack_frame(insn, state)) { - WARN_FUNC("return with modified stack frame", - insn->sec, insn->offset); + WARN_INSN(insn, "return with modified stack frame"); return 1; } if (state->cfi.bp_scratch) { - WARN_FUNC("BP used as a scratch register", - insn->sec, insn->offset); + WARN_INSN(insn, "BP used as a scratch register"); return 1; } @@ -3607,8 +3585,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, } if (func && insn->ignore) { - WARN_FUNC("BUG: why am I validating an ignored function?", - sec, insn->offset); + WARN_INSN(insn, "BUG: why am I validating an ignored function?"); return 1; } @@ -3641,14 +3618,12 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, } if (!save_insn) { - WARN_FUNC("no corresponding CFI save for CFI restore", - sec, insn->offset); + WARN_INSN(insn, "no corresponding CFI save for CFI restore"); return 1; } if (!save_insn->visited) { - WARN_FUNC("objtool isn't smart enough to handle this CFI save/restore combo", - sec, insn->offset); + WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo"); return 1; } @@ -3708,8 +3683,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, if (opts.stackval && func && !is_fentry_call(insn) && !has_valid_stack_frame(&state)) { - WARN_FUNC("call without frame pointer save/setup", - sec, insn->offset); + WARN_INSN(insn, "call without frame pointer save/setup"); return 1; } @@ -3755,15 +3729,14 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, case INSN_CONTEXT_SWITCH: if (func && (!next_insn || !next_insn->hint)) { - WARN_FUNC("unsupported instruction in callable function", - sec, insn->offset); + WARN_INSN(insn, "unsupported instruction in callable function"); return 1; } return 0; case INSN_STAC: if (state.uaccess) { - WARN_FUNC("recursive UACCESS enable", sec, insn->offset); + WARN_INSN(insn, "recursive UACCESS enable"); return 1; } @@ -3772,12 +3745,12 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, case INSN_CLAC: if (!state.uaccess && func) { - WARN_FUNC("redundant UACCESS disable", sec, insn->offset); + WARN_INSN(insn, "redundant UACCESS disable"); return 1; } if (func_uaccess_safe(func) && !state.uaccess_stack) { - WARN_FUNC("UACCESS-safe disables UACCESS", sec, insn->offset); + WARN_INSN(insn, "UACCESS-safe disables UACCESS"); return 1; } @@ -3786,7 +3759,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, case INSN_STD: if (state.df) { - WARN_FUNC("recursive STD", sec, insn->offset); + WARN_INSN(insn, "recursive STD"); return 1; } @@ -3795,7 +3768,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, case INSN_CLD: if (!state.df && func) { - WARN_FUNC("redundant CLD", sec, insn->offset); + WARN_INSN(insn, "redundant CLD"); return 1; } @@ -3862,10 +3835,10 @@ static int validate_unwind_hints(struct objtool_file *file, struct section *sec) /* * Validate rethunk entry constraint: must untrain RET before the first RET. * - * Follow every branch (intra-function) and ensure ANNOTATE_UNRET_END comes + * Follow every branch (intra-function) and ensure VALIDATE_UNRET_END comes * before an actual RET instruction. */ -static int validate_entry(struct objtool_file *file, struct instruction *insn) +static int validate_unret(struct objtool_file *file, struct instruction *insn) { struct instruction *next, *dest; int ret, warnings = 0; @@ -3873,10 +3846,10 @@ static int validate_entry(struct objtool_file *file, struct instruction *insn) for (;;) { next = next_insn_to_validate(file, insn); - if (insn->visited & VISITED_ENTRY) + if (insn->visited & VISITED_UNRET) return 0; - insn->visited |= VISITED_ENTRY; + insn->visited |= VISITED_UNRET; if (!insn->ignore_alts && insn->alts) { struct alternative *alt; @@ -3886,7 +3859,7 @@ static int validate_entry(struct objtool_file *file, struct instruction *insn) if (alt->skip_orig) skip_orig = true; - ret = validate_entry(file, alt->insn); + ret = validate_unret(file, alt->insn); if (ret) { if (opts.backtrace) BT_FUNC("(alt)", insn); @@ -3903,18 +3876,17 @@ static int validate_entry(struct objtool_file *file, struct instruction *insn) case INSN_CALL_DYNAMIC: case INSN_JUMP_DYNAMIC: case INSN_JUMP_DYNAMIC_CONDITIONAL: - WARN_FUNC("early indirect call", insn->sec, insn->offset); + WARN_INSN(insn, "early indirect call"); return 1; case INSN_JUMP_UNCONDITIONAL: case INSN_JUMP_CONDITIONAL: if (!is_sibling_call(insn)) { if (!insn->jump_dest) { - WARN_FUNC("unresolved jump target after linking?!?", - insn->sec, insn->offset); + WARN_INSN(insn, "unresolved jump target after linking?!?"); return -1; } - ret = validate_entry(file, insn->jump_dest); + ret = validate_unret(file, insn->jump_dest); if (ret) { if (opts.backtrace) { BT_FUNC("(branch%s)", insn, @@ -3939,7 +3911,7 @@ static int validate_entry(struct objtool_file *file, struct instruction *insn) return -1; } - ret = validate_entry(file, dest); + ret = validate_unret(file, dest); if (ret) { if (opts.backtrace) BT_FUNC("(call)", insn); @@ -3952,7 +3924,7 @@ static int validate_entry(struct objtool_file *file, struct instruction *insn) return 0; case INSN_RETURN: - WARN_FUNC("RET before UNTRAIN", insn->sec, insn->offset); + WARN_INSN(insn, "RET before UNTRAIN"); return 1; case INSN_NOP: @@ -3965,7 +3937,7 @@ static int validate_entry(struct objtool_file *file, struct instruction *insn) } if (!next) { - WARN_FUNC("teh end!", insn->sec, insn->offset); + WARN_INSN(insn, "teh end!"); return -1; } insn = next; @@ -3975,21 +3947,21 @@ static int validate_entry(struct objtool_file *file, struct instruction *insn) } /* - * Validate that all branches starting at 'insn->entry' encounter UNRET_END - * before RET. + * Validate that all branches starting at VALIDATE_UNRET_BEGIN encounter + * VALIDATE_UNRET_END before RET. */ -static int validate_unret(struct objtool_file *file) +static int validate_unrets(struct objtool_file *file) { struct instruction *insn; int ret, warnings = 0; for_each_insn(file, insn) { - if (!insn->entry) + if (!insn->unret) continue; - ret = validate_entry(file, insn); + ret = validate_unret(file, insn); if (ret < 0) { - WARN_FUNC("Failed UNRET validation", insn->sec, insn->offset); + WARN_INSN(insn, "Failed UNRET validation"); return ret; } warnings += ret; @@ -4017,13 +3989,11 @@ static int validate_retpoline(struct objtool_file *file) if (insn->type == INSN_RETURN) { if (opts.rethunk) { - WARN_FUNC("'naked' return found in RETHUNK build", - insn->sec, insn->offset); + WARN_INSN(insn, "'naked' return found in RETHUNK build"); } else continue; } else { - WARN_FUNC("indirect %s found in RETPOLINE build", - insn->sec, insn->offset, + WARN_INSN(insn, "indirect %s found in RETPOLINE build", insn->type == INSN_JUMP_DYNAMIC ? "jump" : "call"); } @@ -4120,8 +4090,7 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio * It may also insert a UD2 after calling a __noreturn function. */ prev_insn = prev_insn_same_sec(file, insn); - if ((prev_insn->dead_end || - dead_end_function(file, insn_call_dest(prev_insn))) && + if (prev_insn->dead_end && (insn->type == INSN_BUG || (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest && insn->jump_dest->type == INSN_BUG))) @@ -4157,54 +4126,75 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio return false; } -static int add_prefix_symbol(struct objtool_file *file, struct symbol *func, - struct instruction *insn) +static int add_prefix_symbol(struct objtool_file *file, struct symbol *func) { - if (!opts.prefix) - return 0; + struct instruction *insn, *prev; + struct cfi_state *cfi; - for (;;) { - struct instruction *prev = prev_insn_same_sec(file, insn); - u64 offset; + insn = find_insn(file, func->sec, func->offset); + if (!insn) + return -1; - if (!prev) - break; + for (prev = prev_insn_same_sec(file, insn); + prev; + prev = prev_insn_same_sec(file, prev)) { + u64 offset; if (prev->type != INSN_NOP) - break; + return -1; offset = func->offset - prev->offset; - if (offset >= opts.prefix) { - if (offset == opts.prefix) { - /* - * Since the sec->symbol_list is ordered by - * offset (see elf_add_symbol()) the added - * symbol will not be seen by the iteration in - * validate_section(). - * - * Hence the lack of list_for_each_entry_safe() - * there. - * - * The direct concequence is that prefix symbols - * don't get visited (because pointless), except - * for the logic in ignore_unreachable_insn() - * that needs the terminating insn to be visited - * otherwise it will report the hole. - * - * Hence mark the first instruction of the - * prefix symbol as visisted. - */ - prev->visited |= VISITED_BRANCH; - elf_create_prefix_symbol(file->elf, func, opts.prefix); - } - break; - } - insn = prev; + + if (offset > opts.prefix) + return -1; + + if (offset < opts.prefix) + continue; + + elf_create_prefix_symbol(file->elf, func, opts.prefix); + break; } + if (!prev) + return -1; + + if (!insn->cfi) { + /* + * This can happen if stack validation isn't enabled or the + * function is annotated with STACK_FRAME_NON_STANDARD. + */ + return 0; + } + + /* Propagate insn->cfi to the prefix code */ + cfi = cfi_hash_find_or_add(insn->cfi); + for (; prev != insn; prev = next_insn_same_sec(file, prev)) + prev->cfi = cfi; + return 0; } +static int add_prefix_symbols(struct objtool_file *file) +{ + struct section *sec; + struct symbol *func; + int warnings = 0; + + for_each_sec(file, sec) { + if (!(sec->sh.sh_flags & SHF_EXECINSTR)) + continue; + + sec_for_each_sym(sec, func) { + if (func->type != STT_FUNC) + continue; + + add_prefix_symbol(file, func); + } + } + + return warnings; +} + static int validate_symbol(struct objtool_file *file, struct section *sec, struct symbol *sym, struct insn_state *state) { @@ -4223,8 +4213,6 @@ static int validate_symbol(struct objtool_file *file, struct section *sec, if (!insn || insn->ignore || insn->visited) return 0; - add_prefix_symbol(file, sym, insn); - state->uaccess = sym->uaccess_safe; ret = validate_branch(file, insn_func(insn), insn, *state); @@ -4239,7 +4227,7 @@ static int validate_section(struct objtool_file *file, struct section *sec) struct symbol *func; int warnings = 0; - list_for_each_entry(func, &sec->symbol_list, list) { + sec_for_each_sym(sec, func) { if (func->type != STT_FUNC) continue; @@ -4402,9 +4390,7 @@ static int validate_ibt_insn(struct objtool_file *file, struct instruction *insn if (noendbr_range(file, dest)) continue; - WARN_FUNC("relocation to !ENDBR: %s", - insn->sec, insn->offset, - offstr(dest->sec, dest->offset)); + WARN_INSN(insn, "relocation to !ENDBR: %s", offstr(dest->sec, dest->offset)); warnings++; } @@ -4506,16 +4492,14 @@ static int validate_sls(struct objtool_file *file) switch (insn->type) { case INSN_RETURN: if (!next_insn || next_insn->type != INSN_TRAP) { - WARN_FUNC("missing int3 after ret", - insn->sec, insn->offset); + WARN_INSN(insn, "missing int3 after ret"); warnings++; } break; case INSN_JUMP_DYNAMIC: if (!next_insn || next_insn->type != INSN_TRAP) { - WARN_FUNC("missing int3 after indirect jump", - insn->sec, insn->offset); + WARN_INSN(insn, "missing int3 after indirect jump"); warnings++; } break; @@ -4538,7 +4522,7 @@ static int validate_reachable_instructions(struct objtool_file *file) if (insn->visited || ignore_unreachable_insn(file, insn)) continue; - WARN_FUNC("unreachable instruction", insn->sec, insn->offset); + WARN_INSN(insn, "unreachable instruction"); return 1; } @@ -4606,7 +4590,7 @@ int check(struct objtool_file *file) * Must be after validate_branch() and friends, it plays * further games with insn->visited. */ - ret = validate_unret(file); + ret = validate_unrets(file); if (ret < 0) return ret; warnings += ret; @@ -4668,6 +4652,13 @@ int check(struct objtool_file *file) warnings += ret; } + if (opts.prefix) { + ret = add_prefix_symbols(file); + if (ret < 0) + return ret; + warnings += ret; + } + if (opts.ibt) { ret = create_ibt_endbr_seal_sections(file); if (ret < 0) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 6806ce01d933..500e92979a31 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -474,7 +474,7 @@ static int read_symbols(struct elf *elf) /* Create parent/child links for any cold subfunctions */ list_for_each_entry(sec, &elf->sections, list) { - list_for_each_entry(sym, &sec->symbol_list, list) { + sec_for_each_sym(sec, sym) { char pname[MAX_NAME_LEN + 1]; size_t pnamelen; if (sym->type != STT_FUNC) diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index 3e7c7004f7df..daa46f1f0965 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -61,7 +61,7 @@ struct instruction { restore : 1, retpoline_safe : 1, noendbr : 1, - entry : 1, + unret : 1, visited : 4, no_reloc : 1; /* 10 bit hole */ @@ -92,7 +92,7 @@ static inline struct symbol *insn_func(struct instruction *insn) #define VISITED_BRANCH 0x01 #define VISITED_BRANCH_UACCESS 0x02 #define VISITED_BRANCH_MASK 0x03 -#define VISITED_ENTRY 0x04 +#define VISITED_UNRET 0x04 static inline bool is_static_jump(struct instruction *insn) { diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index ad0024da262b..e1ca588eb69d 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -188,4 +188,13 @@ struct symbol *find_func_containing(struct section *sec, unsigned long offset); #define for_each_sec(file, sec) \ list_for_each_entry(sec, &file->elf->sections, list) +#define sec_for_each_sym(sec, sym) \ + list_for_each_entry(sym, &sec->symbol_list, list) + +#define for_each_sym(file, sym) \ + for (struct section *__sec, *__fake = (struct section *)1; \ + __fake; __fake = NULL) \ + for_each_sec(file, __sec) \ + sec_for_each_sym(__sec, sym) + #endif /* _OBJTOOL_ELF_H */ diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index a3e79ae75f2e..b1c920dc9516 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -53,6 +53,11 @@ static inline char *offstr(struct section *sec, unsigned long offset) free(_str); \ }) +#define WARN_INSN(insn, format, ...) \ +({ \ + WARN_FUNC(format, insn->sec, insn->offset, ##__VA_ARGS__); \ +}) + #define BT_FUNC(format, insn, ...) \ ({ \ struct instruction *_insn = (insn); \ diff --git a/tools/objtool/orc_dump.c b/tools/objtool/orc_dump.c index 2d8ebdcd1db3..0e183bb1c720 100644 --- a/tools/objtool/orc_dump.c +++ b/tools/objtool/orc_dump.c @@ -4,7 +4,6 @@ */ #include <unistd.h> -#include <linux/objtool.h> #include <asm/orc_types.h> #include <objtool/objtool.h> #include <objtool/warn.h> @@ -39,11 +38,15 @@ static const char *reg_name(unsigned int reg) static const char *orc_type_name(unsigned int type) { switch (type) { - case UNWIND_HINT_TYPE_CALL: + case ORC_TYPE_UNDEFINED: + return "(und)"; + case ORC_TYPE_END_OF_STACK: + return "end"; + case ORC_TYPE_CALL: return "call"; - case UNWIND_HINT_TYPE_REGS: + case ORC_TYPE_REGS: return "regs"; - case UNWIND_HINT_TYPE_REGS_PARTIAL: + case ORC_TYPE_REGS_PARTIAL: return "regs (partial)"; default: return "?"; @@ -202,6 +205,7 @@ int orc_dump(const char *_objname) printf("%llx:", (unsigned long long)(orc_ip_addr + (i * sizeof(int)) + orc_ip[i])); } + printf("type:%s", orc_type_name(orc[i].type)); printf(" sp:"); @@ -211,8 +215,7 @@ int orc_dump(const char *_objname) print_reg(orc[i].bp_reg, bswap_if_needed(&dummy_elf, orc[i].bp_offset)); - printf(" type:%s signal:%d end:%d\n", - orc_type_name(orc[i].type), orc[i].signal, orc[i].end); + printf(" signal:%d\n", orc[i].signal); } elf_end(elf); diff --git a/tools/objtool/orc_gen.c b/tools/objtool/orc_gen.c index 57a4527d5988..48efd1e2f00d 100644 --- a/tools/objtool/orc_gen.c +++ b/tools/objtool/orc_gen.c @@ -6,7 +6,7 @@ #include <stdlib.h> #include <string.h> -#include <linux/objtool.h> +#include <linux/objtool_types.h> #include <asm/orc_types.h> #include <objtool/check.h> @@ -21,19 +21,38 @@ static int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, memset(orc, 0, sizeof(*orc)); if (!cfi) { - orc->end = 0; - orc->sp_reg = ORC_REG_UNDEFINED; + /* + * This is usually either unreachable nops/traps (which don't + * trigger unreachable instruction warnings), or + * STACK_FRAME_NON_STANDARD functions. + */ + orc->type = ORC_TYPE_UNDEFINED; return 0; } - orc->end = cfi->end; - orc->signal = cfi->signal; - - if (cfi->cfa.base == CFI_UNDEFINED) { - orc->sp_reg = ORC_REG_UNDEFINED; + switch (cfi->type) { + case UNWIND_HINT_TYPE_UNDEFINED: + orc->type = ORC_TYPE_UNDEFINED; + return 0; + case UNWIND_HINT_TYPE_END_OF_STACK: + orc->type = ORC_TYPE_END_OF_STACK; return 0; + case UNWIND_HINT_TYPE_CALL: + orc->type = ORC_TYPE_CALL; + break; + case UNWIND_HINT_TYPE_REGS: + orc->type = ORC_TYPE_REGS; + break; + case UNWIND_HINT_TYPE_REGS_PARTIAL: + orc->type = ORC_TYPE_REGS_PARTIAL; + break; + default: + WARN_INSN(insn, "unknown unwind hint type %d", cfi->type); + return -1; } + orc->signal = cfi->signal; + switch (cfi->cfa.base) { case CFI_SP: orc->sp_reg = ORC_REG_SP; @@ -60,8 +79,7 @@ static int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, orc->sp_reg = ORC_REG_DX; break; default: - WARN_FUNC("unknown CFA base reg %d", - insn->sec, insn->offset, cfi->cfa.base); + WARN_INSN(insn, "unknown CFA base reg %d", cfi->cfa.base); return -1; } @@ -76,14 +94,12 @@ static int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, orc->bp_reg = ORC_REG_BP; break; default: - WARN_FUNC("unknown BP base reg %d", - insn->sec, insn->offset, bp->base); + WARN_INSN(insn, "unknown BP base reg %d", bp->base); return -1; } orc->sp_offset = cfi->cfa.offset; orc->bp_offset = bp->offset; - orc->type = cfi->type; return 0; } @@ -148,11 +164,7 @@ int orc_create(struct objtool_file *file) struct orc_list_entry *entry; struct list_head orc_list; - struct orc_entry null = { - .sp_reg = ORC_REG_UNDEFINED, - .bp_reg = ORC_REG_UNDEFINED, - .type = UNWIND_HINT_TYPE_CALL, - }; + struct orc_entry null = { .type = ORC_TYPE_UNDEFINED }; /* Build a deduplicated list of ORC entries: */ INIT_LIST_HEAD(&orc_list); diff --git a/tools/objtool/sync-check.sh b/tools/objtool/sync-check.sh index 105a291ff8e7..81d120d05442 100755 --- a/tools/objtool/sync-check.sh +++ b/tools/objtool/sync-check.sh @@ -6,7 +6,7 @@ if [ -z "$SRCARCH" ]; then exit 1 fi -FILES="include/linux/objtool.h" +FILES="include/linux/objtool_types.h" if [ "$SRCARCH" = "x86" ]; then FILES="$FILES diff --git a/tools/perf/arch/common.c b/tools/perf/arch/common.c index 59dd875fd5e4..cfe63dfab03a 100644 --- a/tools/perf/arch/common.c +++ b/tools/perf/arch/common.c @@ -51,9 +51,7 @@ const char *const s390_triplets[] = { const char *const sh_triplets[] = { "sh-unknown-linux-gnu-", - "sh64-unknown-linux-gnu-", "sh-linux-gnu-", - "sh64-linux-gnu-", NULL }; diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 41882ae8452e..554289fd6df9 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -213,7 +213,7 @@ Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, Elf_Scn *sec = NULL; size_t cnt = 1; - /* Elf is corrupted/truncated, avoid calling elf_strptr. */ + /* ELF is corrupted/truncated, avoid calling elf_strptr. */ if (!elf_rawdata(elf_getscn(elf, ep->e_shstrndx), NULL)) return NULL; diff --git a/tools/power/acpi/common/cmfsize.c b/tools/power/acpi/common/cmfsize.c index 38f9b9da8170..68b9ea86b86c 100644 --- a/tools/power/acpi/common/cmfsize.c +++ b/tools/power/acpi/common/cmfsize.c @@ -3,7 +3,7 @@ * * Module Name: cmfsize - Common get file size function * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/common/getopt.c b/tools/power/acpi/common/getopt.c index 96fd6cec78e2..6a0cdba6fdfd 100644 --- a/tools/power/acpi/common/getopt.c +++ b/tools/power/acpi/common/getopt.c @@ -3,7 +3,7 @@ * * Module Name: getopt * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c b/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c index bd08f36df4a7..9d70d8c945af 100644 --- a/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c +++ b/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c @@ -3,7 +3,7 @@ * * Module Name: oslinuxtbl - Linux OSL for obtaining ACPI tables * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/os_specific/service_layers/osunixdir.c b/tools/power/acpi/os_specific/service_layers/osunixdir.c index 5107892d054b..39f3bffd9355 100644 --- a/tools/power/acpi/os_specific/service_layers/osunixdir.c +++ b/tools/power/acpi/os_specific/service_layers/osunixdir.c @@ -3,7 +3,7 @@ * * Module Name: osunixdir - Unix directory access interfaces * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/os_specific/service_layers/osunixmap.c b/tools/power/acpi/os_specific/service_layers/osunixmap.c index 6ff4edd8dc3b..2b7d56252684 100644 --- a/tools/power/acpi/os_specific/service_layers/osunixmap.c +++ b/tools/power/acpi/os_specific/service_layers/osunixmap.c @@ -3,7 +3,7 @@ * * Module Name: osunixmap - Unix OSL for file mappings * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/os_specific/service_layers/osunixxf.c b/tools/power/acpi/os_specific/service_layers/osunixxf.c index b3651a04d68c..46429417c71a 100644 --- a/tools/power/acpi/os_specific/service_layers/osunixxf.c +++ b/tools/power/acpi/os_specific/service_layers/osunixxf.c @@ -3,7 +3,7 @@ * * Module Name: osunixxf - UNIX OSL interfaces * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/tools/acpidump/acpidump.h b/tools/power/acpi/tools/acpidump/acpidump.h index 153249c87fd7..643e3e722340 100644 --- a/tools/power/acpi/tools/acpidump/acpidump.h +++ b/tools/power/acpi/tools/acpidump/acpidump.h @@ -3,7 +3,7 @@ * * Module Name: acpidump.h - Include file for acpi_dump utility * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/tools/acpidump/apdump.c b/tools/power/acpi/tools/acpidump/apdump.c index ea44b0ed5dcb..0742b00b61a1 100644 --- a/tools/power/acpi/tools/acpidump/apdump.c +++ b/tools/power/acpi/tools/acpidump/apdump.c @@ -3,7 +3,7 @@ * * Module Name: apdump - Dump routines for ACPI tables (acpidump) * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/tools/acpidump/apfiles.c b/tools/power/acpi/tools/acpidump/apfiles.c index 2d9b45a9b526..13817f9112c0 100644 --- a/tools/power/acpi/tools/acpidump/apfiles.c +++ b/tools/power/acpi/tools/acpidump/apfiles.c @@ -3,7 +3,7 @@ * * Module Name: apfiles - File-related functions for acpidump utility * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/acpi/tools/acpidump/apmain.c b/tools/power/acpi/tools/acpidump/apmain.c index 44b23fc53dd9..666a9675e743 100644 --- a/tools/power/acpi/tools/acpidump/apmain.c +++ b/tools/power/acpi/tools/acpidump/apmain.c @@ -3,7 +3,7 @@ * * Module Name: apmain - Main module for the acpidump utility * - * Copyright (C) 2000 - 2022, Intel Corp. + * Copyright (C) 2000 - 2023, Intel Corp. * *****************************************************************************/ diff --git a/tools/power/pm-graph/README b/tools/power/pm-graph/README index 3213dbe63b74..047ce1d76467 100644 --- a/tools/power/pm-graph/README +++ b/tools/power/pm-graph/README @@ -6,7 +6,7 @@ |_| |___/ |_| pm-graph: suspend/resume/boot timing analysis tools - Version: 5.10 + Version: 5.11 Author: Todd Brandt <todd.e.brandt@intel.com> Home Page: https://www.intel.com/content/www/us/en/developer/topic-technology/open/pm-graph/overview.html diff --git a/tools/power/pm-graph/install_latest_from_github.sh b/tools/power/pm-graph/install_latest_from_github.sh new file mode 100755 index 000000000000..eaa332399d36 --- /dev/null +++ b/tools/power/pm-graph/install_latest_from_github.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# Script which clones and installs the latest pm-graph +# from http://github.com/intel/pm-graph.git + +OUT=`mktemp -d 2>/dev/null` +if [ -z "$OUT" -o ! -e $OUT ]; then + echo "ERROR: mktemp failed to create folder" + exit +fi + +cleanup() { + if [ -e "$OUT" ]; then + cd $OUT + rm -rf pm-graph + cd /tmp + rmdir $OUT + fi +} + +git clone http://github.com/intel/pm-graph.git $OUT/pm-graph +if [ ! -e "$OUT/pm-graph/sleepgraph.py" ]; then + echo "ERROR: pm-graph github repo failed to clone" + cleanup + exit +fi + +cd $OUT/pm-graph +echo "INSTALLING PM-GRAPH" +sudo make install +if [ $? -eq 0 ]; then + echo "INSTALL SUCCESS" + sleepgraph -v +else + echo "INSTALL FAILED" +fi +cleanup diff --git a/tools/power/pm-graph/sleepgraph.py b/tools/power/pm-graph/sleepgraph.py index bf4ac24a1c7a..4a356a706785 100755 --- a/tools/power/pm-graph/sleepgraph.py +++ b/tools/power/pm-graph/sleepgraph.py @@ -86,7 +86,7 @@ def ascii(text): # store system values and test parameters class SystemValues: title = 'SleepGraph' - version = '5.10' + version = '5.11' ansi = False rs = 0 display = '' @@ -300,6 +300,7 @@ class SystemValues: [0, 'acpidevices', 'sh', '-c', 'ls -l /sys/bus/acpi/devices/*/physical_node'], [0, 's0ix_require', 'cat', '/sys/kernel/debug/pmc_core/substate_requirements'], [0, 's0ix_debug', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_debug_status'], + [0, 'ethtool', 'ethtool', '{ethdev}'], [1, 's0ix_residency', 'cat', '/sys/kernel/debug/pmc_core/slp_s0_residency_usec'], [1, 'interrupts', 'cat', '/proc/interrupts'], [1, 'wakeups', 'cat', '/sys/kernel/debug/wakeup_sources'], @@ -1078,18 +1079,35 @@ class SystemValues: else: out[data[0].strip()] = data[1] return out + def cmdinfovar(self, arg): + if arg == 'ethdev': + try: + cmd = [self.getExec('ip'), '-4', '-o', '-br', 'addr'] + fp = Popen(cmd, stdout=PIPE, stderr=PIPE).stdout + info = ascii(fp.read()).strip() + fp.close() + except: + return 'iptoolcrash' + for line in info.split('\n'): + if line[0] == 'e' and 'UP' in line: + return line.split()[0] + return 'nodevicefound' + return 'unknown' def cmdinfo(self, begin, debug=False): out = [] if begin: self.cmd1 = dict() for cargs in self.infocmds: - delta, name = cargs[0], cargs[1] - cmdline, cmdpath = ' '.join(cargs[2:]), self.getExec(cargs[2]) + delta, name, args = cargs[0], cargs[1], cargs[2:] + for i in range(len(args)): + if args[i][0] == '{' and args[i][-1] == '}': + args[i] = self.cmdinfovar(args[i][1:-1]) + cmdline, cmdpath = ' '.join(args[0:]), self.getExec(args[0]) if not cmdpath or (begin and not delta): continue self.dlog('[%s]' % cmdline) try: - fp = Popen([cmdpath]+cargs[3:], stdout=PIPE, stderr=PIPE).stdout + fp = Popen([cmdpath]+args[1:], stdout=PIPE, stderr=PIPE).stdout info = ascii(fp.read()).strip() fp.close() except: @@ -1452,6 +1470,7 @@ class Data: errlist = { 'HWERROR' : r'.*\[ *Hardware Error *\].*', 'FWBUG' : r'.*\[ *Firmware Bug *\].*', + 'TASKFAIL': r'.*Freezing .*after *.*', 'BUG' : r'(?i).*\bBUG\b.*', 'ERROR' : r'(?i).*\bERROR\b.*', 'WARNING' : r'(?i).*\bWARNING\b.*', @@ -1462,7 +1481,6 @@ class Data: 'TIMEOUT' : r'(?i).*\bTIMEOUT\b.*', 'ABORT' : r'(?i).*\bABORT\b.*', 'IRQ' : r'.*\bgenirq: .*', - 'TASKFAIL': r'.*Freezing .*after *.*', 'ACPI' : r'.*\bACPI *(?P<b>[A-Za-z]*) *Error[: ].*', 'DISKFULL': r'.*\bNo space left on device.*', 'USBERR' : r'.*usb .*device .*, error [0-9-]*', @@ -1602,7 +1620,7 @@ class Data: pend = self.dmesg[phase]['end'] if start <= pend: return phase - return 'resume_complete' + return 'resume_complete' if 'resume_complete' in self.dmesg else '' def sourceDevice(self, phaselist, start, end, pid, type): tgtdev = '' for phase in phaselist: @@ -1645,6 +1663,8 @@ class Data: else: threadname = '%s-%d' % (proc, pid) tgtphase = self.sourcePhase(start) + if not tgtphase: + return False self.newAction(tgtphase, threadname, pid, '', start, end, '', ' kth', '') return self.addDeviceFunctionCall(displayname, kprobename, proc, pid, start, end, cdata, rdata) # this should not happen @@ -1835,9 +1855,9 @@ class Data: hwr = self.hwend - timedelta(microseconds=rtime) self.tLow.append('%.0f'%((hwr - hws).total_seconds() * 1000)) def getTimeValues(self): - sktime = (self.tSuspended - self.tKernSus) * 1000 - rktime = (self.tKernRes - self.tResumed) * 1000 - return (sktime, rktime) + s = (self.tSuspended - self.tKernSus) * 1000 + r = (self.tKernRes - self.tResumed) * 1000 + return (max(s, 0), max(r, 0)) def setPhase(self, phase, ktime, isbegin, order=-1): if(isbegin): # phase start over current phase @@ -3961,7 +3981,7 @@ def parseKernelLog(data): 'suspend_machine': ['PM: suspend-to-idle', 'PM: noirq suspend of devices complete after.*', 'PM: noirq freeze of devices complete after.*'], - 'resume_machine': ['PM: Timekeeping suspended for.*', + 'resume_machine': ['[PM: ]*Timekeeping suspended for.*', 'ACPI: Low-level resume complete.*', 'ACPI: resume from mwait', 'Suspended for [0-9\.]* seconds'], @@ -3979,14 +3999,14 @@ def parseKernelLog(data): # action table (expected events that occur and show up in dmesg) at = { 'sync_filesystems': { - 'smsg': 'PM: Syncing filesystems.*', - 'emsg': 'PM: Preparing system for mem sleep.*' }, + 'smsg': '.*[Ff]+ilesystems.*', + 'emsg': 'PM: Preparing system for[a-z]* sleep.*' }, 'freeze_user_processes': { - 'smsg': 'Freezing user space processes .*', + 'smsg': 'Freezing user space processes.*', 'emsg': 'Freezing remaining freezable tasks.*' }, 'freeze_tasks': { 'smsg': 'Freezing remaining freezable tasks.*', - 'emsg': 'PM: Entering (?P<mode>[a-z,A-Z]*) sleep.*' }, + 'emsg': 'PM: Suspending system.*' }, 'ACPI prepare': { 'smsg': 'ACPI: Preparing to enter system sleep state.*', 'emsg': 'PM: Saving platform NVS memory.*' }, @@ -4120,10 +4140,9 @@ def parseKernelLog(data): for a in sorted(at): if(re.match(at[a]['smsg'], msg)): if(a not in actions): - actions[a] = [] - actions[a].append({'begin': ktime, 'end': ktime}) + actions[a] = [{'begin': ktime, 'end': ktime}] if(re.match(at[a]['emsg'], msg)): - if(a in actions): + if(a in actions and actions[a][-1]['begin'] == actions[a][-1]['end']): actions[a][-1]['end'] = ktime # now look for CPU on/off events if(re.match('Disabling non-boot CPUs .*', msg)): @@ -4132,9 +4151,12 @@ def parseKernelLog(data): elif(re.match('Enabling non-boot CPUs .*', msg)): # start of first cpu resume cpu_start = ktime - elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)): + elif(re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg)) \ + or re.match('psci: CPU(?P<cpu>[0-9]*) killed.*', msg)): # end of a cpu suspend, start of the next m = re.match('smpboot: CPU (?P<cpu>[0-9]*) is now offline', msg) + if(not m): + m = re.match('psci: CPU(?P<cpu>[0-9]*) killed.*', msg) cpu = 'CPU'+m.group('cpu') if(cpu not in actions): actions[cpu] = [] diff --git a/tools/power/x86/intel-speed-select/Build b/tools/power/x86/intel-speed-select/Build index 81e36bd578b1..5a9637e1678c 100644 --- a/tools/power/x86/intel-speed-select/Build +++ b/tools/power/x86/intel-speed-select/Build @@ -1 +1 @@ -intel-speed-select-y += isst-config.o isst-core.o isst-display.o isst-daemon.o hfi-events.o +intel-speed-select-y += isst-config.o isst-core.o isst-display.o isst-daemon.o hfi-events.o isst-core-mbox.o isst-core-tpmi.o diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c index 55d0a35df41c..2ca0cedd418f 100644 --- a/tools/power/x86/intel-speed-select/isst-config.c +++ b/tools/power/x86/intel-speed-select/isst-config.c @@ -15,9 +15,9 @@ struct process_cmd_struct { int arg; }; -static const char *version_str = "v1.14"; +static const char *version_str = "v1.15"; -static const int supported_api_ver = 1; +static const int supported_api_ver = 2; static struct isst_if_platform_info isst_platform_info; static char *progname; static int debug_flag; @@ -44,9 +44,7 @@ static int cmd_help; static int force_online_offline; static int auto_mode; static int fact_enable_fail; - -static int mbox_delay; -static int mbox_retries = 3; +static int cgroupv2; /* clos related */ static int current_clos = -1; @@ -61,6 +59,7 @@ struct _cpu_map { unsigned short core_id; unsigned short pkg_id; unsigned short die_id; + unsigned short punit_id; unsigned short punit_cpu; unsigned short punit_cpu_core; unsigned short initialized; @@ -79,6 +78,11 @@ FILE *get_output_file(void) return outf; } +int is_debug_enabled(void) +{ + return debug_flag; +} + void debug_printf(const char *format, ...) { va_list args; @@ -110,12 +114,21 @@ int is_skx_based_platform(void) int is_spr_platform(void) { - if (cpu_model == 0x8F || cpu_model == 0xCF) + if (cpu_model == 0x8F) + return 1; + + return 0; +} + +int is_emr_platform(void) +{ + if (cpu_model == 0xCF) return 1; return 0; } + int is_icx_platform(void) { if (cpu_model == 0x6A || cpu_model == 0x6C) @@ -163,6 +176,11 @@ static int update_cpu_model(void) return 0; } +int api_version(void) +{ + return isst_platform_info.api_version; +} + /* Open a file, and exit on failure */ static FILE *fopen_or_exit(const char *path, const char *mode) { @@ -378,6 +396,17 @@ static int get_physical_die_id(int cpu) return ret; } +static int get_physical_punit_id(int cpu) +{ + if (cpu < 0) + return -1; + + if (cpu_map && cpu_map[cpu].initialized) + return cpu_map[cpu].punit_id; + + return -1; +} + void set_isst_id(struct isst_id *id, int cpu) { id->cpu = cpu; @@ -389,6 +418,10 @@ void set_isst_id(struct isst_id *id, int cpu) id->die = get_physical_die_id(cpu); if (id->die >= MAX_DIE_PER_PACKAGE) id->die = -1; + + id->punit = get_physical_punit_id(cpu); + if (id->punit >= MAX_PUNIT_PER_DIE) + id->punit = -1; } int is_cpu_in_power_domain(int cpu, struct isst_id *id) @@ -397,7 +430,7 @@ int is_cpu_in_power_domain(int cpu, struct isst_id *id) set_isst_id(&tid, cpu); - if (id->pkg == tid.pkg && id->die == tid.die) + if (id->pkg == tid.pkg && id->die == tid.die && id->punit == tid.punit) return 1; return 0; @@ -481,51 +514,59 @@ static void force_all_cpus_online(void) unlink("/var/run/isst_cpu_topology.dat"); } -void for_each_online_package_in_set(void (*callback)(struct isst_id *, void *, void *, +void for_each_online_power_domain_in_set(void (*callback)(struct isst_id *, void *, void *, void *, void *), void *arg1, void *arg2, void *arg3, void *arg4) { - int max_packages[MAX_PACKAGE_COUNT * MAX_PACKAGE_COUNT]; - int pkg_index = 0, i; struct isst_id id; + int cpus[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE][MAX_PUNIT_PER_DIE]; + int valid_mask[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE] = {0}; + int i, j, k; + + memset(cpus, -1, sizeof(cpus)); - memset(max_packages, 0xff, sizeof(max_packages)); for (i = 0; i < topo_max_cpus; ++i) { - int j, online, pkg_id, die_id = 0, skip = 0; + int online; if (!CPU_ISSET_S(i, present_cpumask_size, present_cpumask)) continue; - if (i) - online = parse_int_file( - 1, "/sys/devices/system/cpu/cpu%d/online", i); - else - online = - 1; /* online entry for CPU 0 needs some special configs */ - die_id = get_physical_die_id(i); - if (die_id < 0) - die_id = 0; + online = parse_int_file( + i != 0, "/sys/devices/system/cpu/cpu%d/online", i); + if (online < 0) + online = 1; /* online entry for CPU 0 needs some special configs */ - pkg_id = parse_int_file(0, - "/sys/devices/system/cpu/cpu%d/topology/physical_package_id", i); - if (pkg_id < 0) + if (!online) continue; - /* Create an unique id for package, die combination to store */ - pkg_id = (MAX_PACKAGE_COUNT * pkg_id + die_id); + set_isst_id(&id, i); - for (j = 0; j < pkg_index; ++j) { - if (max_packages[j] == pkg_id) { - skip = 1; - break; - } - } + if (id.pkg < 0 || id.die < 0 || id.punit < 0) + continue; - set_isst_id(&id, i); - if (!skip && online && callback) { - callback(&id, arg1, arg2, arg3, arg4); - max_packages[pkg_index++] = pkg_id; + valid_mask[id.pkg][id.die] = 1; + + if (cpus[id.pkg][id.die][id.punit] == -1) + cpus[id.pkg][id.die][id.punit] = i; + } + + for (i = 0; i < MAX_PACKAGE_COUNT; i++) { + for (j = 0; j < MAX_DIE_PER_PACKAGE; j++) { + /* + * Fix me: + * How to check a non-cpu die for a package/die with all cpu offlined? + */ + if (!valid_mask[i][j]) + continue; + for (k = 0; k < MAX_PUNIT_PER_DIE; k++) { + id.cpu = cpus[i][j][k]; + id.pkg = i; + id.die = j; + id.punit = k; + if (isst_is_punit_valid(&id)) + callback(&id, arg1, arg2, arg3, arg4); + } } } } @@ -610,7 +651,7 @@ void free_cpu_set(cpu_set_t *cpu_set) CPU_FREE(cpu_set); } -static int cpu_cnt[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE]; +static int cpu_cnt[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE][MAX_PUNIT_PER_DIE]; int get_max_punit_core_id(struct isst_id *id) { @@ -632,10 +673,50 @@ int get_max_punit_core_id(struct isst_id *id) int get_cpu_count(struct isst_id *id) { - if (id->pkg < 0 || id->die < 0) + if (id->pkg < 0 || id->die < 0 || id->punit < 0) return 0; - return cpu_cnt[id->pkg][id->die]; + return cpu_cnt[id->pkg][id->die][id->punit]; +} + +static void update_punit_cpu_info(__u32 physical_cpu, struct _cpu_map *cpu_map) +{ + if (api_version() > 1) { + /* + * MSR 0x54 format + * [15:11] PM_DOMAIN_ID + * [10:3] MODULE_ID (aka IDI_AGENT_ID) + * [2:0] LP_ID (We don't care about these bits we only + * care die and core id + * For Atom: + * [2] Always 0 + * [1:0] core ID within module + * For Core + * [2:1] Always 0 + * [0] thread ID + */ + cpu_map->punit_id = (physical_cpu >> 11) & 0x1f; + cpu_map->punit_cpu_core = (physical_cpu >> 3) & 0xff; + cpu_map->punit_cpu = physical_cpu & 0x7ff; + } else { + int punit_id; + + /* + * MSR 0x53 format + * Format + * Bit 0 – thread ID + * Bit 8:1 – core ID + * Bit 13:9 – punit ID + */ + cpu_map->punit_cpu = physical_cpu & 0x1ff; + cpu_map->punit_cpu_core = (cpu_map->punit_cpu >> 1); // shift to get core id + punit_id = (physical_cpu >> 9) & 0x1f; + + if (punit_id >= MAX_PUNIT_PER_DIE) + punit_id = 0; + + cpu_map->punit_id = punit_id; + } } static void create_cpu_map(void) @@ -660,7 +741,7 @@ static void create_cpu_map(void) for (i = 0; i < topo_max_cpus; ++i) { char buffer[256]; - int pkg_id, die_id, core_id; + int pkg_id, die_id, core_id, punit_id; /* check if CPU is online */ snprintf(buffer, sizeof(buffer), @@ -682,31 +763,32 @@ static void create_cpu_map(void) cpu_map[i].pkg_id = pkg_id; cpu_map[i].die_id = die_id; cpu_map[i].core_id = core_id; - cpu_map[i].initialized = 1; - cpu_cnt[pkg_id][die_id]++; - if (fd < 0) - continue; - map.cmd_count = 1; - map.cpu_map[0].logical_cpu = i; - debug_printf(" map logical_cpu:%d\n", - map.cpu_map[0].logical_cpu); - if (ioctl(fd, ISST_IF_GET_PHY_ID, &map) == -1) { - perror("ISST_IF_GET_PHY_ID"); - fprintf(outf, "Error: map logical_cpu:%d\n", - map.cpu_map[0].logical_cpu); - continue; + punit_id = 0; + + if (fd >= 0) { + map.cmd_count = 1; + map.cpu_map[0].logical_cpu = i; + debug_printf(" map logical_cpu:%d\n", + map.cpu_map[0].logical_cpu); + if (ioctl(fd, ISST_IF_GET_PHY_ID, &map) == -1) { + perror("ISST_IF_GET_PHY_ID"); + fprintf(outf, "Error: map logical_cpu:%d\n", + map.cpu_map[0].logical_cpu); + } else { + update_punit_cpu_info(map.cpu_map[0].physical_cpu, &cpu_map[i]); + } } - cpu_map[i].punit_cpu = map.cpu_map[0].physical_cpu; - cpu_map[i].punit_cpu_core = (map.cpu_map[0].physical_cpu >> - 1); // shift to get core id + cpu_map[i].initialized = 1; + + cpu_cnt[pkg_id][die_id][punit_id]++; debug_printf( - "map logical_cpu:%d core: %d die:%d pkg:%d punit_cpu:%d punit_core:%d\n", + "map logical_cpu:%d core: %d die:%d pkg:%d punit:%d punit_cpu:%d punit_core:%d\n", i, cpu_map[i].core_id, cpu_map[i].die_id, - cpu_map[i].pkg_id, cpu_map[i].punit_cpu, - cpu_map[i].punit_cpu_core); + cpu_map[i].pkg_id, cpu_map[i].punit_id, + cpu_map[i].punit_cpu, cpu_map[i].punit_cpu_core); } if (fd >= 0) close(fd); @@ -728,6 +810,9 @@ void set_cpu_mask_from_punit_coremask(struct isst_id *id, unsigned long long cor { int i, cnt = 0; + if (id->cpu < 0) + return; + *cpu_cnt = 0; for (i = 0; i < 64; ++i) { @@ -759,182 +844,135 @@ int find_phy_core_num(int logical_cpu) return -EINVAL; } -static int isst_send_mmio_command(unsigned int cpu, unsigned int reg, int write, - unsigned int *value) +int use_cgroupv2(void) { - struct isst_if_io_regs io_regs; - const char *pathname = "/dev/isst_interface"; - int cmd; - int fd; - - debug_printf("mmio_cmd cpu:%d reg:%d write:%d\n", cpu, reg, write); + return cgroupv2; +} - fd = open(pathname, O_RDWR); - if (fd < 0) - err(-1, "%s open failed", pathname); +int enable_cpuset_controller(void) +{ + int fd, ret; - io_regs.req_count = 1; - io_regs.io_reg[0].logical_cpu = cpu; - io_regs.io_reg[0].reg = reg; - cmd = ISST_IF_IO_CMD; - if (write) { - io_regs.io_reg[0].read_write = 1; - io_regs.io_reg[0].value = *value; - } else { - io_regs.io_reg[0].read_write = 0; + fd = open("/sys/fs/cgroup/cgroup.subtree_control", O_RDWR, 0); + if (fd < 0) { + debug_printf("Can't activate cpuset controller\n"); + debug_printf("Either you are not root user or CGroup v2 is not supported\n"); + return fd; } - if (ioctl(fd, cmd, &io_regs) == -1) { - if (errno == ENOTTY) { - perror("ISST_IF_IO_COMMAND\n"); - fprintf(stderr, "Check presence of kernel modules: isst_if_mmio\n"); - exit(0); - } - fprintf(outf, "Error: mmio_cmd cpu:%d reg:%x read_write:%x\n", - cpu, reg, write); - } else { - if (!write) - *value = io_regs.io_reg[0].value; + ret = write(fd, " +cpuset", strlen(" +cpuset")); + close(fd); - debug_printf( - "mmio_cmd response: cpu:%d reg:%x rd_write:%x resp:%x\n", - cpu, reg, write, *value); + if (ret == -1) { + debug_printf("Can't activate cpuset controller: Write failed\n"); + return ret; } - close(fd); - return 0; } -int isst_send_mbox_command(unsigned int cpu, unsigned char command, - unsigned char sub_command, unsigned int parameter, - unsigned int req_data, unsigned int *resp) +int isolate_cpus(struct isst_id *id, int mask_size, cpu_set_t *cpu_mask, int level) { - const char *pathname = "/dev/isst_interface"; - int fd, retry; - struct isst_if_mbox_cmds mbox_cmds = { 0 }; - - debug_printf( - "mbox_send: cpu:%d command:%x sub_command:%x parameter:%x req_data:%x\n", - cpu, command, sub_command, parameter, req_data); + int i, first, curr_index, index, ret, fd; + static char str[512], dir_name[64]; + static char cpuset_cpus[128]; + int str_len = sizeof(str); + DIR *dir; - if (!is_skx_based_platform() && command == CONFIG_CLOS && - sub_command != CLOS_PM_QOS_CONFIG) { - unsigned int value; - int write = 0; - int clos_id, core_id, ret = 0; + snprintf(dir_name, sizeof(dir_name), "/sys/fs/cgroup/%d-%d-%d", id->pkg, id->die, id->punit); + dir = opendir(dir_name); + if (!dir) { + ret = mkdir(dir_name, 0744); + if (ret) { + debug_printf("Can't create dir:%s errno:%d\n", dir_name, errno); + return ret; + } + } + closedir(dir); - debug_printf("CPU %d\n", cpu); + if (!level) { + sprintf(cpuset_cpus, "%s/cpuset.cpus.partition", dir_name); - if (parameter & BIT(MBOX_CMD_WRITE_BIT)) { - value = req_data; - write = 1; + fd = open(cpuset_cpus, O_RDWR, 0); + if (fd < 0) { + return fd; } - switch (sub_command) { - case CLOS_PQR_ASSOC: - core_id = parameter & 0xff; - ret = isst_send_mmio_command( - cpu, PQR_ASSOC_OFFSET + core_id * 4, write, - &value); - if (!ret && !write) - *resp = value; - break; - case CLOS_PM_CLOS: - clos_id = parameter & 0x03; - ret = isst_send_mmio_command( - cpu, PM_CLOS_OFFSET + clos_id * 4, write, - &value); - if (!ret && !write) - *resp = value; - break; - case CLOS_STATUS: - break; - default: - break; + ret = write(fd, "member", strlen("member")); + if (ret == -1) { + printf("Can't update to member\n"); + return ret; } - return ret; - } - mbox_cmds.cmd_count = 1; - mbox_cmds.mbox_cmd[0].logical_cpu = cpu; - mbox_cmds.mbox_cmd[0].command = command; - mbox_cmds.mbox_cmd[0].sub_command = sub_command; - mbox_cmds.mbox_cmd[0].parameter = parameter; - mbox_cmds.mbox_cmd[0].req_data = req_data; + return 0; + } - if (mbox_delay) - usleep(mbox_delay * 1000); + if (!CPU_COUNT_S(mask_size, cpu_mask)) { + return -1; + } - fd = open(pathname, O_RDWR); - if (fd < 0) - err(-1, "%s open failed", pathname); + curr_index = 0; + first = 1; + str[0] = '\0'; + for (i = 0; i < get_topo_max_cpus(); ++i) { + if (!is_cpu_in_power_domain(i, id)) + continue; - retry = mbox_retries; + if (CPU_ISSET_S(i, mask_size, cpu_mask)) + continue; - do { - if (ioctl(fd, ISST_IF_MBOX_COMMAND, &mbox_cmds) == -1) { - if (errno == ENOTTY) { - perror("ISST_IF_MBOX_COMMAND\n"); - fprintf(stderr, "Check presence of kernel modules: isst_if_mbox_pci or isst_if_mbox_msr\n"); - exit(0); - } - debug_printf( - "Error: mbox_cmd cpu:%d command:%x sub_command:%x parameter:%x req_data:%x errorno:%d\n", - cpu, command, sub_command, parameter, req_data, errno); - --retry; - } else { - *resp = mbox_cmds.mbox_cmd[0].resp_data; - debug_printf( - "mbox_cmd response: cpu:%d command:%x sub_command:%x parameter:%x req_data:%x resp:%x\n", - cpu, command, sub_command, parameter, req_data, *resp); - break; + if (!first) { + index = snprintf(&str[curr_index], + str_len - curr_index, ","); + curr_index += index; + if (curr_index >= str_len) + break; } - } while (retry); + index = snprintf(&str[curr_index], str_len - curr_index, "%d", + i); + curr_index += index; + if (curr_index >= str_len) + break; + first = 0; + } - close(fd); + debug_printf("isolated CPUs list: package:%d curr_index:%d [%s]\n", id->pkg, curr_index ,str); - if (!retry) { - debug_printf("Failed mbox command even after retries\n"); - return -1; + snprintf(cpuset_cpus, sizeof(cpuset_cpus), "%s/cpuset.cpus", dir_name); + fd = open(cpuset_cpus, O_RDWR, 0); + if (fd < 0) { + return fd; } - return 0; -} -int isst_send_msr_command(unsigned int cpu, unsigned int msr, int write, - unsigned long long *req_resp) -{ - struct isst_if_msr_cmds msr_cmds; - const char *pathname = "/dev/isst_interface"; - int fd; + ret = write(fd, str, strlen(str)); + close(fd); - fd = open(pathname, O_RDWR); - if (fd < 0) - err(-1, "%s open failed", pathname); + if (ret == -1) { + debug_printf("Can't activate cpuset controller: Write failed\n"); + return ret; + } - msr_cmds.cmd_count = 1; - msr_cmds.msr_cmd[0].logical_cpu = cpu; - msr_cmds.msr_cmd[0].msr = msr; - msr_cmds.msr_cmd[0].read_write = write; - if (write) - msr_cmds.msr_cmd[0].data = *req_resp; - - if (ioctl(fd, ISST_IF_MSR_COMMAND, &msr_cmds) == -1) { - perror("ISST_IF_MSR_COMMAND"); - fprintf(outf, "Error: msr_cmd cpu:%d msr:%x read_write:%d\n", - cpu, msr, write); - } else { - if (!write) - *req_resp = msr_cmds.msr_cmd[0].data; + snprintf(cpuset_cpus, sizeof(cpuset_cpus), "%s/cpuset.cpus.partition", dir_name); - debug_printf( - "msr_cmd response: cpu:%d msr:%x rd_write:%x resp:%llx %llx\n", - cpu, msr, write, *req_resp, msr_cmds.msr_cmd[0].data); + fd = open(cpuset_cpus, O_RDWR, 0); + if (fd < 0) { + return fd; + } + + ret = write(fd, "isolated", strlen("isolated")); + if (ret == -1) { + debug_printf("Can't update to isolated\n"); + ret = write(fd, "root", strlen("root")); + if (ret == -1) + debug_printf("Can't update to root\n"); } close(fd); + if (ret < 0) + return ret; + return 0; } @@ -943,6 +981,11 @@ static int isst_fill_platform_info(void) const char *pathname = "/dev/isst_interface"; int fd; + if (is_clx_n_platform()) { + isst_platform_info.api_version = 1; + goto set_platform_ops; + } + fd = open(pathname, O_RDWR); if (fd < 0) err(-1, "%s open failed", pathname); @@ -959,77 +1002,96 @@ static int isst_fill_platform_info(void) printf("Incompatible API versions; Upgrade of tool is required\n"); return -1; } + +set_platform_ops: + if (isst_set_platform_ops(isst_platform_info.api_version)) { + fprintf(stderr, "Failed to set platform callbacks\n"); + exit(0); + } return 0; } -static void isst_print_extended_platform_info(void) +void get_isst_status(struct isst_id *id, void *arg1, void *arg2, void *arg3, void *arg4) { - int cp_state, cp_cap, fact_support = 0, pbf_support = 0; - struct isst_pkg_ctdp_level_info ctdp_level; struct isst_pkg_ctdp pkg_dev; - int ret, i, j; - FILE *filep; - struct isst_id id; - - for (i = 0; i < 256; ++i) { - char path[256]; - - snprintf(path, sizeof(path), - "/sys/devices/system/cpu/cpu%d/topology/thread_siblings", i); - filep = fopen(path, "r"); - if (filep) - break; - } + struct isst_id *tid = (struct isst_id *)arg2; + int *mask = (int *)arg3; + int *max_level = (int *)arg4; + int j, ret; - if (!filep) + /* Only check the first cpu power domain */ + if (id->cpu < 0 || tid->cpu >= 0) return; - fclose(filep); - - set_isst_id(&id, i); - ret = isst_get_ctdp_levels(&id, &pkg_dev); + ret = isst_get_ctdp_levels(id, &pkg_dev); if (ret) return; - if (pkg_dev.enabled) { - fprintf(outf, "Intel(R) SST-PP (feature perf-profile) is supported\n"); - } else { - fprintf(outf, "Intel(R) SST-PP (feature perf-profile) is not supported\n"); - fprintf(outf, "Only performance level 0 (base level) is present\n"); - } + if (pkg_dev.enabled) + *mask |= BIT(0); if (pkg_dev.locked) - fprintf(outf, "TDP level change control is locked\n"); - else - fprintf(outf, "TDP level change control is unlocked, max level: %d \n", pkg_dev.levels); + *mask |= BIT(1); + + if (*max_level < pkg_dev.levels) + *max_level = pkg_dev.levels; for (j = 0; j <= pkg_dev.levels; ++j) { - ret = isst_get_ctdp_control(&id, j, &ctdp_level); + struct isst_pkg_ctdp_level_info ctdp_level; + + ret = isst_get_ctdp_control(id, j, &ctdp_level); if (ret) continue; - if (!fact_support && ctdp_level.fact_support) - fact_support = 1; + if (ctdp_level.fact_support) + *mask |= BIT(2); + + if (ctdp_level.pbf_support) + *mask |= BIT(3); + } + + tid->cpu = id->cpu; + tid->pkg = id->pkg; + tid->die = id->die; + tid->punit = id->punit; +} + +static void isst_print_extended_platform_info(void) +{ + int cp_state, cp_cap; + struct isst_id id; + int mask = 0, max_level = 0; - if (!pbf_support && ctdp_level.pbf_support) - pbf_support = 1; + id.cpu = -1; + for_each_online_power_domain_in_set(get_isst_status, NULL, &id, &mask, &max_level); + + if (mask & BIT(0)) { + fprintf(outf, "Intel(R) SST-PP (feature perf-profile) is supported\n"); + } else { + fprintf(outf, "Intel(R) SST-PP (feature perf-profile) is not supported\n"); + fprintf(outf, "Only performance level 0 (base level) is present\n"); } - if (fact_support) + if (mask & BIT(1)) + fprintf(outf, "TDP level change control is locked\n"); + else + fprintf(outf, "TDP level change control is unlocked, max level: %d\n", max_level); + + if (mask & BIT(2)) fprintf(outf, "Intel(R) SST-TF (feature turbo-freq) is supported\n"); else fprintf(outf, "Intel(R) SST-TF (feature turbo-freq) is not supported\n"); - if (pbf_support) + if (mask & BIT(3)) fprintf(outf, "Intel(R) SST-BF (feature base-freq) is supported\n"); else fprintf(outf, "Intel(R) SST-BF (feature base-freq) is not supported\n"); - ret = isst_read_pm_config(&id, &cp_state, &cp_cap); - if (ret) { + if (isst_read_pm_config(&id, &cp_state, &cp_cap)) { fprintf(outf, "Intel(R) SST-CP (feature core-power) status is unknown\n"); return; } + if (cp_cap) fprintf(outf, "Intel(R) SST-CP (feature core-power) is supported\n"); else @@ -1038,10 +1100,6 @@ static void isst_print_extended_platform_info(void) static void isst_print_platform_information(void) { - struct isst_if_platform_info platform_info; - const char *pathname = "/dev/isst_interface"; - int fd; - if (is_clx_n_platform()) { fprintf(stderr, "\nThis option in not supported on this platform\n"); exit(0); @@ -1051,25 +1109,15 @@ static void isst_print_platform_information(void) set_max_cpu_num(); create_cpu_map(); - fd = open(pathname, O_RDWR); - if (fd < 0) - err(-1, "%s open failed", pathname); - - if (ioctl(fd, ISST_IF_GET_PLATFORM_INFO, &platform_info) == -1) { - perror("ISST_IF_GET_PLATFORM_INFO"); - } else { - fprintf(outf, "Platform: API version : %d\n", - platform_info.api_version); - fprintf(outf, "Platform: Driver version : %d\n", - platform_info.driver_version); - fprintf(outf, "Platform: mbox supported : %d\n", - platform_info.mbox_supported); - fprintf(outf, "Platform: mmio supported : %d\n", - platform_info.mmio_supported); - isst_print_extended_platform_info(); - } - - close(fd); + fprintf(outf, "Platform: API version : %d\n", + isst_platform_info.api_version); + fprintf(outf, "Platform: Driver version : %d\n", + isst_platform_info.driver_version); + fprintf(outf, "Platform: mbox supported : %d\n", + isst_platform_info.mbox_supported); + fprintf(outf, "Platform: mmio supported : %d\n", + isst_platform_info.mmio_supported); + isst_print_extended_platform_info(); exit(0); } @@ -1110,7 +1158,7 @@ static void exec_on_get_ctdp_cpu(struct isst_id *id, void *arg1, void *arg2, voi exec_on_get_ctdp_cpu, isst_get_ctdp_##suffix, \ &ctdp, desc, &ctdp.object); \ else \ - for_each_online_package_in_set(exec_on_get_ctdp_cpu, \ + for_each_online_power_domain_in_set(exec_on_get_ctdp_cpu, \ isst_get_ctdp_##suffix, \ &ctdp, desc, \ &ctdp.object); \ @@ -1314,92 +1362,91 @@ static void dump_isst_config(int arg) if (max_target_cpus) for_each_online_target_cpu_in_set(fn, NULL, NULL, NULL, NULL); else - for_each_online_package_in_set(fn, NULL, NULL, NULL, NULL); + for_each_online_power_domain_in_set(fn, NULL, NULL, NULL, NULL); isst_ctdp_display_information_end(outf); } -static int set_uncore_min_max(struct isst_id *id, int max, int freq) -{ - char buffer[128], freq_str[16]; - int fd, ret, len; - - if (max) - snprintf(buffer, sizeof(buffer), - "/sys/devices/system/cpu/intel_uncore_frequency/package_%02d_die_%02d/max_freq_khz", id->pkg, id->die); - else - snprintf(buffer, sizeof(buffer), - "/sys/devices/system/cpu/intel_uncore_frequency/package_%02d_die_%02d/min_freq_khz", id->pkg, id->die); - - fd = open(buffer, O_WRONLY); - if (fd < 0) - return fd; - - snprintf(freq_str, sizeof(freq_str), "%d", freq); - len = strlen(freq_str); - ret = write(fd, freq_str, len); - if (ret == -1) { - close(fd); - return ret; - } - close(fd); - - return 0; -} - static void adjust_scaling_max_from_base_freq(int cpu); static void set_tdp_level_for_cpu(struct isst_id *id, void *arg1, void *arg2, void *arg3, void *arg4) { + struct isst_pkg_ctdp pkg_dev; int ret; + ret = isst_get_ctdp_levels(id, &pkg_dev); + if (ret) { + isst_display_error_info_message(1, "Get TDP level failed", 0, 0); + isst_ctdp_display_information_end(outf); + exit(1); + } + + if (pkg_dev.current_level == tdp_level) { + debug_printf("TDP level already set. Skipped\n"); + goto display_result; + } + ret = isst_set_tdp_level(id, tdp_level); if (ret) { isst_display_error_info_message(1, "Set TDP level failed", 0, 0); isst_ctdp_display_information_end(outf); exit(1); - } else { - isst_display_result(id, outf, "perf-profile", "set_tdp_level", - ret); - if (force_online_offline) { - struct isst_pkg_ctdp_level_info ctdp_level; - - /* Wait for updated base frequencies */ - usleep(2000); - - /* Adjusting uncore freq */ - isst_get_uncore_p0_p1_info(id, tdp_level, &ctdp_level); - if (ctdp_level.uncore_pm) - set_uncore_min_max(id, 0, ctdp_level.uncore_pm * 100000); - - if (ctdp_level.uncore_p0) - set_uncore_min_max(id, 1, ctdp_level.uncore_p0 * 100000); - - fprintf(stderr, "Option is set to online/offline\n"); - ctdp_level.core_cpumask_size = - alloc_cpu_set(&ctdp_level.core_cpumask); - ret = isst_get_coremask_info(id, tdp_level, &ctdp_level); - if (ret) { - isst_display_error_info_message(1, "Can't get coremask, online/offline option is ignored", 0, 0); - return; - } - if (ctdp_level.cpu_count) { - int i, max_cpus = get_topo_max_cpus(); - for (i = 0; i < max_cpus; ++i) { - if (!is_cpu_in_power_domain(i, id)) - continue; - if (CPU_ISSET_S(i, ctdp_level.core_cpumask_size, ctdp_level.core_cpumask)) { - fprintf(stderr, "online cpu %d\n", i); - set_cpu_online_offline(i, 1); - adjust_scaling_max_from_base_freq(i); - } else { - fprintf(stderr, "offline cpu %d\n", i); - set_cpu_online_offline(i, 0); - } + } + +display_result: + isst_display_result(id, outf, "perf-profile", "set_tdp_level", ret); + if (force_online_offline && id->cpu >= 0) { + struct isst_pkg_ctdp_level_info ctdp_level; + + /* Wait for updated base frequencies */ + usleep(2000); + + /* Adjusting uncore freq */ + isst_adjust_uncore_freq(id, tdp_level, &ctdp_level); + + fprintf(stderr, "Option is set to online/offline\n"); + ctdp_level.core_cpumask_size = + alloc_cpu_set(&ctdp_level.core_cpumask); + ret = isst_get_coremask_info(id, tdp_level, &ctdp_level); + if (ret) { + isst_display_error_info_message(1, "Can't get coremask, online/offline option is ignored", 0, 0); + goto free_mask; + } + + if (use_cgroupv2()) { + int ret; + + fprintf(stderr, "Using cgroup v2 in lieu of online/offline\n"); + ret = enable_cpuset_controller(); + if (ret) + goto use_offline; + + ret = isolate_cpus(id, ctdp_level.core_cpumask_size, ctdp_level.core_cpumask, tdp_level); + if (ret) + goto use_offline; + + goto free_mask; + } + +use_offline: + if (ctdp_level.cpu_count) { + int i, max_cpus = get_topo_max_cpus(); + for (i = 0; i < max_cpus; ++i) { + if (!is_cpu_in_power_domain(i, id)) + continue; + if (CPU_ISSET_S(i, ctdp_level.core_cpumask_size, ctdp_level.core_cpumask)) { + fprintf(stderr, "online cpu %d\n", i); + set_cpu_online_offline(i, 1); + adjust_scaling_max_from_base_freq(i); + } else { + fprintf(stderr, "offline cpu %d\n", i); + set_cpu_online_offline(i, 0); } } } +free_mask: + free_cpu_set(ctdp_level.core_cpumask); } } @@ -1425,7 +1472,7 @@ static void set_tdp_level(int arg) for_each_online_target_cpu_in_set(set_tdp_level_for_cpu, NULL, NULL, NULL, NULL); else - for_each_online_package_in_set(set_tdp_level_for_cpu, NULL, + for_each_online_power_domain_in_set(set_tdp_level_for_cpu, NULL, NULL, NULL, NULL); isst_ctdp_display_information_end(outf); } @@ -1463,7 +1510,7 @@ static void dump_pbf_config_for_cpu(struct isst_id *id, void *arg1, void *arg2, exit(1); } else { isst_pbf_display_information(id, outf, tdp_level, &pbf_info); - isst_get_pbf_info_complete(&pbf_info); + free_cpu_set(pbf_info.core_cpumask); } } @@ -1494,7 +1541,7 @@ static void dump_pbf_config(int arg) if (max_target_cpus) for_each_online_target_cpu_in_set(fn, NULL, NULL, NULL, NULL); else - for_each_online_package_in_set(fn, NULL, NULL, NULL, NULL); + for_each_online_power_domain_in_set(fn, NULL, NULL, NULL, NULL); isst_ctdp_display_information_end(outf); } @@ -1662,6 +1709,9 @@ static void set_scaling_min_to_cpuinfo_max(struct isst_id *id) { int i; + if (id->cpu < 0) + return; + for (i = 0; i < get_topo_max_cpus(); ++i) { if (!is_cpu_in_power_domain(i, id)) continue; @@ -1679,6 +1729,9 @@ static void set_scaling_min_to_cpuinfo_min(struct isst_id *id) { int i; + if (id->cpu < 0) + return; + for (i = 0; i < get_topo_max_cpus(); ++i) { if (!is_cpu_in_power_domain(i, id)) continue; @@ -1758,6 +1811,9 @@ static int set_pbf_core_power(struct isst_id *id) struct isst_pkg_ctdp pkg_dev; int ret; + if (id->cpu < 0) + return 0; + ret = isst_get_ctdp_levels(id, &pkg_dev); if (ret) { debug_printf("isst_get_ctdp_levels failed"); @@ -1900,7 +1956,7 @@ static void set_pbf_enable(int arg) for_each_online_target_cpu_in_set(set_pbf_for_cpu, NULL, NULL, NULL, &enable); else - for_each_online_package_in_set(set_pbf_for_cpu, NULL, NULL, + for_each_online_power_domain_in_set(set_pbf_for_cpu, NULL, NULL, NULL, &enable); isst_ctdp_display_information_end(outf); } @@ -1946,7 +2002,7 @@ static void dump_fact_config(int arg) for_each_online_target_cpu_in_set(dump_fact_config_for_cpu, NULL, NULL, NULL, NULL); else - for_each_online_package_in_set(dump_fact_config_for_cpu, NULL, + for_each_online_power_domain_in_set(dump_fact_config_for_cpu, NULL, NULL, NULL, NULL); isst_ctdp_display_information_end(outf); } @@ -2003,7 +2059,7 @@ static void set_fact_for_cpu(struct isst_id *id, void *arg1, void *arg2, void *a struct isst_pkg_ctdp pkg_dev; ret = isst_get_ctdp_levels(id, &pkg_dev); - if (!ret) + if (!ret && id->cpu >= 0) ret = isst_set_trl(id, fact_trl); if (ret && auto_mode) isst_pm_qos_config(id, 0, 0); @@ -2055,7 +2111,7 @@ static void set_fact_enable(int arg) for_each_online_target_cpu_in_set(set_fact_for_cpu, NULL, NULL, NULL, &enable); else - for_each_online_package_in_set(set_fact_for_cpu, NULL, NULL, + for_each_online_power_domain_in_set(set_fact_for_cpu, NULL, NULL, NULL, &enable); isst_ctdp_display_information_end(outf); @@ -2194,7 +2250,7 @@ static void set_clos_enable(int arg) for_each_online_target_cpu_in_set(enable_clos_qos_config, NULL, NULL, NULL, &enable); else - for_each_online_package_in_set(enable_clos_qos_config, NULL, + for_each_online_power_domain_in_set(enable_clos_qos_config, NULL, NULL, NULL, &enable); isst_ctdp_display_information_end(outf); } @@ -2205,6 +2261,9 @@ static void dump_clos_config_for_cpu(struct isst_id *id, void *arg1, void *arg2, struct isst_clos_config clos_config; int ret; + if (id->cpu < 0) + return; + ret = isst_pm_get_clos(id, current_clos, &clos_config); if (ret) isst_display_error_info_message(1, "isst_pm_get_clos failed", 0, 0); @@ -2233,7 +2292,7 @@ static void dump_clos_config(int arg) for_each_online_target_cpu_in_set(dump_clos_config_for_cpu, NULL, NULL, NULL, NULL); else - for_each_online_package_in_set(dump_clos_config_for_cpu, NULL, + for_each_online_power_domain_in_set(dump_clos_config_for_cpu, NULL, NULL, NULL, NULL); isst_ctdp_display_information_end(outf); } @@ -2269,7 +2328,7 @@ static void dump_clos_info(int arg) for_each_online_target_cpu_in_set(get_clos_info_for_cpu, NULL, NULL, NULL, NULL); else - for_each_online_package_in_set(get_clos_info_for_cpu, NULL, + for_each_online_power_domain_in_set(get_clos_info_for_cpu, NULL, NULL, NULL, NULL); isst_ctdp_display_information_end(outf); @@ -2281,6 +2340,9 @@ static void set_clos_config_for_cpu(struct isst_id *id, void *arg1, void *arg2, struct isst_clos_config clos_config; int ret; + if (id->cpu < 0) + return; + clos_config.epp = clos_epp; clos_config.clos_prop_prio = clos_prop_prio; clos_config.clos_min = clos_min; @@ -2341,7 +2403,7 @@ static void set_clos_config(int arg) for_each_online_target_cpu_in_set(set_clos_config_for_cpu, NULL, NULL, NULL, NULL); else - for_each_online_package_in_set(set_clos_config_for_cpu, NULL, + for_each_online_power_domain_in_set(set_clos_config_for_cpu, NULL, NULL, NULL, NULL); isst_ctdp_display_information_end(outf); } @@ -2508,7 +2570,7 @@ static void process_trl(int arg) for_each_online_target_cpu_in_set(get_set_trl, NULL, NULL, NULL, &arg); else - for_each_online_package_in_set(get_set_trl, NULL, + for_each_online_power_domain_in_set(get_set_trl, NULL, NULL, NULL, &arg); isst_ctdp_display_information_end(outf); } @@ -2683,7 +2745,7 @@ static void parse_cmd_args(int argc, int start, char **argv) break; case 'd': clos_desired = atoi(optarg); - clos_desired /= DISP_FREQ_MULTIPLIER; + clos_desired /= isst_get_disp_freq_multiplier(); break; case 'e': clos_epp = atoi(optarg); @@ -2694,11 +2756,11 @@ static void parse_cmd_args(int argc, int start, char **argv) break; case 'n': clos_min = atoi(optarg); - clos_min /= DISP_FREQ_MULTIPLIER; + clos_min /= isst_get_disp_freq_multiplier(); break; case 'm': clos_max = atoi(optarg); - clos_max /= DISP_FREQ_MULTIPLIER; + clos_max /= isst_get_disp_freq_multiplier(); break; case 'p': clos_priority_type = atoi(optarg); @@ -2882,6 +2944,7 @@ static void usage(void) printf("\t[-b|--oob : Start a daemon to process HFI events for perf profile change from Out of Band agent.\n"); printf("\t[-n|--no-daemon : Don't run as daemon. By default --oob will turn on daemon mode\n"); printf("\t[-w|--delay : Delay for reading config level state change in OOB poll mode.\n"); + printf("\t[-g|--cgroupv2 : Try to use cgroup v2 CPU isolation instead of CPU online/offline.\n"); printf("\nResult format\n"); printf("\tResult display uses a common format for each command:\n"); printf("\tResults are formatted in text/JSON with\n"); @@ -2918,6 +2981,7 @@ static void cmdline(int argc, char **argv) int oob_mode = 0; int poll_interval = -1; int no_daemon = 0; + int mbox_delay = 0, mbox_retries = 3; static struct option long_options[] = { { "all-cpus-online", no_argument, 0, 'a' }, @@ -2933,6 +2997,7 @@ static void cmdline(int argc, char **argv) { "oob", no_argument, 0, 'b' }, { "no-daemon", no_argument, 0, 'n' }, { "poll-interval", required_argument, 0, 'w' }, + { "cgroupv2", required_argument, 0, 'g' }, { 0, 0, 0, 0 } }; @@ -2958,8 +3023,12 @@ static void cmdline(int argc, char **argv) fclose(fp); } + ret = isst_fill_platform_info(); + if (ret) + goto out; + progname = argv[0]; - while ((opt = getopt_long_only(argc, argv, "+c:df:hio:vabw:n", long_options, + while ((opt = getopt_long_only(argc, argv, "+c:df:hio:vabw:ng", long_options, &option_index)) != -1) { switch (opt) { case 'a': @@ -3018,6 +3087,9 @@ static void cmdline(int argc, char **argv) } poll_interval = ret; break; + case 'g': + cgroupv2 = 1; + break; default: usage(); } @@ -3027,6 +3099,10 @@ static void cmdline(int argc, char **argv) usage(); exit(0); } + + isst_update_platform_param(ISST_PARAM_MBOX_DELAY, mbox_delay); + isst_update_platform_param(ISST_PARAM_MBOX_RETRIES, mbox_retries); + set_max_cpu_num(); if (force_cpus_online) force_all_cpus_online(); @@ -3044,9 +3120,6 @@ static void cmdline(int argc, char **argv) } if (!is_clx_n_platform()) { - ret = isst_fill_platform_info(); - if (ret) - goto out; process_command(argc, argv, isst_help_cmds, isst_cmds); } else { process_command(argc, argv, clx_n_help_cmds, clx_n_cmds); diff --git a/tools/power/x86/intel-speed-select/isst-core-mbox.c b/tools/power/x86/intel-speed-select/isst-core-mbox.c new file mode 100644 index 000000000000..24bea57f4ff5 --- /dev/null +++ b/tools/power/x86/intel-speed-select/isst-core-mbox.c @@ -0,0 +1,1066 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select -- Enumerate and control features for Mailbox Interface + * Copyright (c) 2023 Intel Corporation. + */ +#include "isst.h" + +static int mbox_delay; +static int mbox_retries = 3; + +#define MAX_TRL_LEVELS_EMR 5 + +static int mbox_get_disp_freq_multiplier(void) +{ + return DISP_FREQ_MULTIPLIER; +} + +static int mbox_get_trl_max_levels(void) +{ + if (is_emr_platform()) + return MAX_TRL_LEVELS_EMR; + + return 3; +} + +static char *mbox_get_trl_level_name(int level) +{ + if (is_emr_platform()) { + static char level_str[18]; + + if (level >= MAX_TRL_LEVELS_EMR) + return NULL; + + snprintf(level_str, sizeof(level_str), "level-%d", level); + return level_str; + } + + switch (level) { + case 0: + return "sse"; + case 1: + return "avx2"; + case 2: + return "avx512"; + default: + return NULL; + } +} + +static void mbox_update_platform_param(enum isst_platform_param param, int value) +{ + switch (param) { + case ISST_PARAM_MBOX_DELAY: + mbox_delay = value; + break; + case ISST_PARAM_MBOX_RETRIES: + mbox_retries = value; + break; + default: + break; + } +} + +static int mbox_is_punit_valid(struct isst_id *id) +{ + if (id->cpu < 0) + return 0; + + if (id->pkg < 0 || id->die < 0 || id->punit) + return 0; + + return 1; +} + +static int _send_mmio_command(unsigned int cpu, unsigned int reg, int write, + unsigned int *value) +{ + struct isst_if_io_regs io_regs; + const char *pathname = "/dev/isst_interface"; + int cmd; + FILE *outf = get_output_file(); + int fd; + + debug_printf("mmio_cmd cpu:%d reg:%d write:%d\n", cpu, reg, write); + + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); + + io_regs.req_count = 1; + io_regs.io_reg[0].logical_cpu = cpu; + io_regs.io_reg[0].reg = reg; + cmd = ISST_IF_IO_CMD; + if (write) { + io_regs.io_reg[0].read_write = 1; + io_regs.io_reg[0].value = *value; + } else { + io_regs.io_reg[0].read_write = 0; + } + + if (ioctl(fd, cmd, &io_regs) == -1) { + if (errno == ENOTTY) { + perror("ISST_IF_IO_COMMAND\n"); + fprintf(stderr, "Check presence of kernel modules: isst_if_mmio\n"); + exit(0); + } + fprintf(outf, "Error: mmio_cmd cpu:%d reg:%x read_write:%x\n", + cpu, reg, write); + } else { + if (!write) + *value = io_regs.io_reg[0].value; + + debug_printf( + "mmio_cmd response: cpu:%d reg:%x rd_write:%x resp:%x\n", + cpu, reg, write, *value); + } + + close(fd); + + return 0; +} + +int _send_mbox_command(unsigned int cpu, unsigned char command, + unsigned char sub_command, unsigned int parameter, + unsigned int req_data, unsigned int *resp) +{ + const char *pathname = "/dev/isst_interface"; + int fd, retry; + struct isst_if_mbox_cmds mbox_cmds = { 0 }; + + debug_printf( + "mbox_send: cpu:%d command:%x sub_command:%x parameter:%x req_data:%x\n", + cpu, command, sub_command, parameter, req_data); + + if (!is_skx_based_platform() && command == CONFIG_CLOS && + sub_command != CLOS_PM_QOS_CONFIG) { + unsigned int value; + int write = 0; + int clos_id, core_id, ret = 0; + + debug_printf("CPU %d\n", cpu); + + if (parameter & BIT(MBOX_CMD_WRITE_BIT)) { + value = req_data; + write = 1; + } + + switch (sub_command) { + case CLOS_PQR_ASSOC: + core_id = parameter & 0xff; + ret = _send_mmio_command( + cpu, PQR_ASSOC_OFFSET + core_id * 4, write, + &value); + if (!ret && !write) + *resp = value; + break; + case CLOS_PM_CLOS: + clos_id = parameter & 0x03; + ret = _send_mmio_command( + cpu, PM_CLOS_OFFSET + clos_id * 4, write, + &value); + if (!ret && !write) + *resp = value; + break; + case CLOS_STATUS: + break; + default: + break; + } + return ret; + } + + mbox_cmds.cmd_count = 1; + mbox_cmds.mbox_cmd[0].logical_cpu = cpu; + mbox_cmds.mbox_cmd[0].command = command; + mbox_cmds.mbox_cmd[0].sub_command = sub_command; + mbox_cmds.mbox_cmd[0].parameter = parameter; + mbox_cmds.mbox_cmd[0].req_data = req_data; + + if (mbox_delay) + usleep(mbox_delay * 1000); + + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); + + retry = mbox_retries; + do { + if (ioctl(fd, ISST_IF_MBOX_COMMAND, &mbox_cmds) == -1) { + if (errno == ENOTTY) { + perror("ISST_IF_MBOX_COMMAND\n"); + fprintf(stderr, "Check presence of kernel modules: isst_if_mbox_pci or isst_if_mbox_msr\n"); + exit(0); + } + debug_printf( + "Error: mbox_cmd cpu:%d command:%x sub_command:%x parameter:%x req_data:%x errorno:%d\n", + cpu, command, sub_command, parameter, req_data, errno); + --retry; + } else { + *resp = mbox_cmds.mbox_cmd[0].resp_data; + debug_printf( + "mbox_cmd response: cpu:%d command:%x sub_command:%x parameter:%x req_data:%x resp:%x\n", + cpu, command, sub_command, parameter, req_data, *resp); + break; + } + } while (retry); + + close(fd); + + if (!retry) { + debug_printf("Failed mbox command even after retries\n"); + return -1; + + } + + return 0; +} + +static int mbox_read_pm_config(struct isst_id *id, int *cp_state, int *cp_cap) +{ + unsigned int resp; + int ret; + + ret = _send_mbox_command(id->cpu, READ_PM_CONFIG, PM_FEATURE, 0, 0, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d READ_PM_CONFIG resp:%x\n", id->cpu, resp); + + *cp_state = resp & BIT(16); + *cp_cap = resp & BIT(0) ? 1 : 0; + + return 0; +} + +static int mbox_get_config_levels(struct isst_id *id, struct isst_pkg_ctdp *pkg_dev) +{ + unsigned int resp; + int ret; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_LEVELS_INFO, 0, 0, &resp); + if (ret) { + pkg_dev->levels = 0; + pkg_dev->locked = 1; + pkg_dev->current_level = 0; + pkg_dev->version = 0; + pkg_dev->enabled = 0; + return 0; + } + + debug_printf("cpu:%d CONFIG_TDP_GET_LEVELS_INFO resp:%x\n", id->cpu, resp); + + pkg_dev->version = resp & 0xff; + pkg_dev->levels = (resp >> 8) & 0xff; + pkg_dev->current_level = (resp >> 16) & 0xff; + pkg_dev->locked = !!(resp & BIT(24)); + pkg_dev->enabled = !!(resp & BIT(31)); + + return 0; +} + +static int mbox_get_ctdp_control(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + int cp_state, cp_cap; + unsigned int resp; + int ret; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_TDP_CONTROL, 0, + config_index, &resp); + if (ret) + return ret; + + ctdp_level->fact_support = resp & BIT(0); + ctdp_level->pbf_support = !!(resp & BIT(1)); + ctdp_level->fact_enabled = !!(resp & BIT(16)); + ctdp_level->pbf_enabled = !!(resp & BIT(17)); + + ret = isst_read_pm_config(id, &cp_state, &cp_cap); + if (ret) { + debug_printf("cpu:%d pm_config is not supported\n", id->cpu); + } else { + debug_printf("cpu:%d pm_config SST-CP state:%d cap:%d\n", id->cpu, cp_state, cp_cap); + ctdp_level->sst_cp_support = cp_cap; + ctdp_level->sst_cp_enabled = cp_state; + } + + debug_printf( + "cpu:%d CONFIG_TDP_GET_TDP_CONTROL resp:%x fact_support:%d pbf_support: %d fact_enabled:%d pbf_enabled:%d\n", + id->cpu, resp, ctdp_level->fact_support, ctdp_level->pbf_support, + ctdp_level->fact_enabled, ctdp_level->pbf_enabled); + + return 0; +} + +static void _get_uncore_p0_p1_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + + ctdp_level->uncore_pm = 0; + ctdp_level->uncore_p0 = 0; + ctdp_level->uncore_p1 = 0; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_RATIO_INFO, 0, + (BIT(16) | config_index) , &resp); + if (ret) { + goto try_uncore_mbox; + } + + ctdp_level->uncore_p0 = resp & GENMASK(7, 0); + ctdp_level->uncore_p1 = (resp & GENMASK(15, 8)) >> 8; + ctdp_level->uncore_pm = (resp & GENMASK(31, 24)) >> 24; + + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_RATIO_INFO resp:%x uncore p0:%d uncore p1:%d uncore pm:%d\n", + id->cpu, config_index, resp, ctdp_level->uncore_p0, ctdp_level->uncore_p1, + ctdp_level->uncore_pm); + + return; + +try_uncore_mbox: + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_UNCORE_P0_P1_INFO, 0, + config_index, &resp); + if (ret) { + ctdp_level->uncore_p0 = 0; + ctdp_level->uncore_p1 = 0; + return; + } + + ctdp_level->uncore_p0 = resp & GENMASK(7, 0); + ctdp_level->uncore_p1 = (resp & GENMASK(15, 8)) >> 8; + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_UNCORE_P0_P1_INFO resp:%x uncore p0:%d uncore p1:%d\n", + id->cpu, config_index, resp, ctdp_level->uncore_p0, + ctdp_level->uncore_p1); +} + +static int _set_uncore_min_max(struct isst_id *id, int max, int freq) +{ + char buffer[128], freq_str[16]; + int fd, ret, len; + + if (max) + snprintf(buffer, sizeof(buffer), + "/sys/devices/system/cpu/intel_uncore_frequency/package_%02d_die_%02d/max_freq_khz", id->pkg, id->die); + else + snprintf(buffer, sizeof(buffer), + "/sys/devices/system/cpu/intel_uncore_frequency/package_%02d_die_%02d/min_freq_khz", id->pkg, id->die); + + fd = open(buffer, O_WRONLY); + if (fd < 0) + return fd; + + snprintf(freq_str, sizeof(freq_str), "%d", freq); + len = strlen(freq_str); + ret = write(fd, freq_str, len); + if (ret == -1) { + close(fd); + return ret; + } + close(fd); + + return 0; +} + +static void mbox_adjust_uncore_freq(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + _get_uncore_p0_p1_info(id, config_index, ctdp_level); + if (ctdp_level->uncore_pm) + _set_uncore_min_max(id, 0, ctdp_level->uncore_pm * 100000); + + if (ctdp_level->uncore_p0) + _set_uncore_min_max(id, 1, ctdp_level->uncore_p0 * 100000); +} + +static void _get_p1_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + ret = _send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_P1_INFO, 0, + config_index, &resp); + if (ret) { + ctdp_level->sse_p1 = 0; + ctdp_level->avx2_p1 = 0; + ctdp_level->avx512_p1 = 0; + return; + } + + ctdp_level->sse_p1 = resp & GENMASK(7, 0); + ctdp_level->avx2_p1 = (resp & GENMASK(15, 8)) >> 8; + ctdp_level->avx512_p1 = (resp & GENMASK(23, 16)) >> 16; + ctdp_level->amx_p1 = (resp & GENMASK(31, 24)) >> 24; + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_P1_INFO resp:%x sse_p1:%d avx2_p1:%d avx512_p1:%d amx_p1:%d\n", + id->cpu, config_index, resp, ctdp_level->sse_p1, + ctdp_level->avx2_p1, ctdp_level->avx512_p1, ctdp_level->amx_p1); +} + +static void _get_uncore_mem_freq(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_MEM_FREQ, + 0, config_index, &resp); + if (ret) { + ctdp_level->mem_freq = 0; + return; + } + + ctdp_level->mem_freq = resp & GENMASK(7, 0); + if (is_spr_platform() || is_emr_platform()) { + ctdp_level->mem_freq *= 200; + } else if (is_icx_platform()) { + if (ctdp_level->mem_freq < 7) { + ctdp_level->mem_freq = (12 - ctdp_level->mem_freq) * 133.33 * 2 * 10; + ctdp_level->mem_freq /= 10; + if (ctdp_level->mem_freq % 10 > 5) + ctdp_level->mem_freq++; + } else { + ctdp_level->mem_freq = 0; + } + } else { + ctdp_level->mem_freq = 0; + } + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_MEM_FREQ resp:%x uncore mem_freq:%d\n", + id->cpu, config_index, resp, ctdp_level->mem_freq); +} + +static int mbox_get_tdp_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_TDP_INFO, + 0, config_index, &resp); + if (ret) { + isst_display_error_info_message(1, "Invalid level, Can't get TDP information at level", 1, config_index); + return ret; + } + + ctdp_level->pkg_tdp = resp & GENMASK(14, 0); + ctdp_level->tdp_ratio = (resp & GENMASK(23, 16)) >> 16; + + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_TDP_INFO resp:%x tdp_ratio:%d pkg_tdp:%d\n", + id->cpu, config_index, resp, ctdp_level->tdp_ratio, + ctdp_level->pkg_tdp); + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_TJMAX_INFO, + 0, config_index, &resp); + if (ret) + return ret; + + ctdp_level->t_proc_hot = resp & GENMASK(7, 0); + + _get_uncore_p0_p1_info(id, config_index, ctdp_level); + _get_p1_info(id, config_index, ctdp_level); + _get_uncore_mem_freq(id, config_index, ctdp_level); + + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_TJMAX_INFO resp:%x t_proc_hot:%d\n", + id->cpu, config_index, resp, ctdp_level->t_proc_hot); + + return 0; +} + +static int mbox_get_pwr_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_PWR_INFO, + 0, config_index, &resp); + if (ret) + return ret; + + ctdp_level->pkg_max_power = resp & GENMASK(14, 0); + ctdp_level->pkg_min_power = (resp & GENMASK(30, 16)) >> 16; + + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_PWR_INFO resp:%x pkg_max_power:%d pkg_min_power:%d\n", + id->cpu, config_index, resp, ctdp_level->pkg_max_power, + ctdp_level->pkg_min_power); + + return 0; +} + +static int mbox_get_coremask_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int i, ret; + + ctdp_level->cpu_count = 0; + for (i = 0; i < 2; ++i) { + unsigned long long mask; + int cpu_count = 0; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_CORE_MASK, 0, + (i << 8) | config_index, &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d ctdp:%d mask:%d CONFIG_TDP_GET_CORE_MASK resp:%x\n", + id->cpu, config_index, i, resp); + + mask = (unsigned long long)resp << (32 * i); + set_cpu_mask_from_punit_coremask(id, mask, + ctdp_level->core_cpumask_size, + ctdp_level->core_cpumask, + &cpu_count); + ctdp_level->cpu_count += cpu_count; + debug_printf("cpu:%d ctdp:%d mask:%d cpu count:%d\n", id->cpu, + config_index, i, ctdp_level->cpu_count); + } + + return 0; +} + +static int mbox_get_get_trl(struct isst_id *id, int level, int avx_level, int *trl) +{ + unsigned int req, resp; + int ret; + + req = level | (avx_level << 16); + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_TURBO_LIMIT_RATIOS, 0, req, + &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d CONFIG_TDP_GET_TURBO_LIMIT_RATIOS req:%x resp:%x\n", + id->cpu, req, resp); + + trl[0] = resp & GENMASK(7, 0); + trl[1] = (resp & GENMASK(15, 8)) >> 8; + trl[2] = (resp & GENMASK(23, 16)) >> 16; + trl[3] = (resp & GENMASK(31, 24)) >> 24; + + req = level | BIT(8) | (avx_level << 16); + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_TURBO_LIMIT_RATIOS, 0, req, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_GET_TURBO_LIMIT req:%x resp:%x\n", id->cpu, + req, resp); + + trl[4] = resp & GENMASK(7, 0); + trl[5] = (resp & GENMASK(15, 8)) >> 8; + trl[6] = (resp & GENMASK(23, 16)) >> 16; + trl[7] = (resp & GENMASK(31, 24)) >> 24; + + return 0; +} + +static int mbox_get_get_trls(struct isst_id *id, int level, struct isst_pkg_ctdp_level_info *ctdp_level) +{ + int trl_max_levels = isst_get_trl_max_levels(); + int i, ret; + + for (i = 0; i < trl_max_levels; i++) { + ret = mbox_get_get_trl(id, level, i, ctdp_level->trl_ratios[i]); + if (ret) + return ret; + } + return 0; +} + +static int mbox_get_trl_bucket_info(struct isst_id *id, int level, unsigned long long *buckets_info) +{ + int ret; + + debug_printf("cpu:%d bucket info via MSR\n", id->cpu); + + *buckets_info = 0; + + ret = isst_send_msr_command(id->cpu, 0x1ae, 0, buckets_info); + if (ret) + return ret; + + debug_printf("cpu:%d bucket info via MSR successful 0x%llx\n", id->cpu, + *buckets_info); + + return 0; +} + +static int mbox_set_tdp_level(struct isst_id *id, int tdp_level) +{ + unsigned int resp; + int ret; + + + if (isst_get_config_tdp_lock_status(id)) { + isst_display_error_info_message(1, "TDP is locked", 0, 0); + return -1; + + } + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_SET_LEVEL, 0, + tdp_level, &resp); + if (ret) { + isst_display_error_info_message(1, "Set TDP level failed for level", 1, tdp_level); + return ret; + } + + return 0; +} + +static int mbox_get_pbf_info(struct isst_id *id, int level, struct isst_pbf_info *pbf_info) +{ + int max_punit_core, max_mask_index; + unsigned int req, resp; + int i, ret; + + max_punit_core = get_max_punit_core_id(id); + max_mask_index = max_punit_core > 32 ? 2 : 1; + + for (i = 0; i < max_mask_index; ++i) { + unsigned long long mask; + int count; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_PBF_GET_CORE_MASK_INFO, + 0, (i << 8) | level, &resp); + if (ret) + break; + + debug_printf( + "cpu:%d CONFIG_TDP_PBF_GET_CORE_MASK_INFO resp:%x\n", + id->cpu, resp); + + mask = (unsigned long long)resp << (32 * i); + set_cpu_mask_from_punit_coremask(id, mask, + pbf_info->core_cpumask_size, + pbf_info->core_cpumask, + &count); + } + + req = level; + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_PBF_GET_P1HI_P1LO_INFO, 0, req, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_PBF_GET_P1HI_P1LO_INFO resp:%x\n", id->cpu, + resp); + + pbf_info->p1_low = resp & 0xff; + pbf_info->p1_high = (resp & GENMASK(15, 8)) >> 8; + + req = level; + ret = _send_mbox_command( + id->cpu, CONFIG_TDP, CONFIG_TDP_PBF_GET_TDP_INFO, 0, req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_PBF_GET_TDP_INFO resp:%x\n", id->cpu, resp); + + pbf_info->tdp = resp & 0xffff; + + req = level; + ret = _send_mbox_command( + id->cpu, CONFIG_TDP, CONFIG_TDP_PBF_GET_TJ_MAX_INFO, 0, req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_PBF_GET_TJ_MAX_INFO resp:%x\n", id->cpu, + resp); + pbf_info->t_control = (resp >> 8) & 0xff; + pbf_info->t_prochot = resp & 0xff; + + return 0; +} + +static int mbox_set_pbf_fact_status(struct isst_id *id, int pbf, int enable) +{ + struct isst_pkg_ctdp pkg_dev; + struct isst_pkg_ctdp_level_info ctdp_level; + int current_level; + unsigned int req = 0, resp; + int ret; + + ret = isst_get_ctdp_levels(id, &pkg_dev); + if (ret) + debug_printf("cpu:%d No support for dynamic ISST\n", id->cpu); + + current_level = pkg_dev.current_level; + + ret = isst_get_ctdp_control(id, current_level, &ctdp_level); + if (ret) + return ret; + + if (pbf) { + if (ctdp_level.fact_enabled) + req = BIT(16); + + if (enable) + req |= BIT(17); + else + req &= ~BIT(17); + } else { + + if (enable && !ctdp_level.sst_cp_enabled) + isst_display_error_info_message(0, "Make sure to execute before: core-power enable", 0, 0); + + if (ctdp_level.pbf_enabled) + req = BIT(17); + + if (enable) + req |= BIT(16); + else + req &= ~BIT(16); + } + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_SET_TDP_CONTROL, 0, req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_SET_TDP_CONTROL pbf/fact:%d req:%x\n", + id->cpu, pbf, req); + + return 0; +} + +static int _get_fact_bucket_info(struct isst_id *id, int level, + struct isst_fact_bucket_info *bucket_info) +{ + unsigned int resp; + int i, k, ret; + + for (i = 0; i < 2; ++i) { + int j; + + ret = _send_mbox_command( + id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_NUMCORES, 0, + (i << 8) | level, &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_NUMCORES index:%d level:%d resp:%x\n", + id->cpu, i, level, resp); + + for (j = 0; j < 4; ++j) { + bucket_info[j + (i * 4)].hp_cores = + (resp >> (j * 8)) & 0xff; + } + } + + for (k = 0; k < 3; ++k) { + for (i = 0; i < 2; ++i) { + int j; + + ret = _send_mbox_command( + id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_RATIOS, 0, + (k << 16) | (i << 8) | level, &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_RATIOS index:%d level:%d avx:%d resp:%x\n", + id->cpu, i, level, k, resp); + + for (j = 0; j < 4; ++j) { + bucket_info[j + (i * 4)].hp_ratios[k] = + (resp >> (j * 8)) & 0xff; + } + } + } + + return 0; +} + +static int mbox_get_fact_info(struct isst_id *id, int level, int fact_bucket, struct isst_fact_info *fact_info) +{ + unsigned int resp; + int j, ret, print; + + ret = _send_mbox_command(id->cpu, CONFIG_TDP, + CONFIG_TDP_GET_FACT_LP_CLIPPING_RATIO, 0, + level, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_GET_FACT_LP_CLIPPING_RATIO resp:%x\n", + id->cpu, resp); + + fact_info->lp_ratios[0] = resp & 0xff; + fact_info->lp_ratios[1] = (resp >> 8) & 0xff; + fact_info->lp_ratios[2] = (resp >> 16) & 0xff; + + ret = _get_fact_bucket_info(id, level, fact_info->bucket_info); + if (ret) + return ret; + + print = 0; + for (j = 0; j < ISST_FACT_MAX_BUCKETS; ++j) { + if (fact_bucket != 0xff && fact_bucket != j) + continue; + + if (!fact_info->bucket_info[j].hp_cores) + break; + + print = 1; + } + if (!print) { + isst_display_error_info_message(1, "Invalid bucket", 0, 0); + return -1; + } + + return 0; +} + +static int mbox_get_clos_information(struct isst_id *id, int *enable, int *type) +{ + unsigned int resp; + int ret; + + ret = _send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_QOS_CONFIG, 0, 0, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PM_QOS_CONFIG resp:%x\n", id->cpu, resp); + + if (resp & BIT(1)) + *enable = 1; + else + *enable = 0; + + if (resp & BIT(2)) + *type = 1; + else + *type = 0; + + return 0; +} + +static int _write_pm_config(struct isst_id *id, int cp_state) +{ + unsigned int req, resp; + int ret; + + if (cp_state) + req = BIT(16); + else + req = 0; + + ret = _send_mbox_command(id->cpu, WRITE_PM_CONFIG, PM_FEATURE, 0, req, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d WRITE_PM_CONFIG resp:%x\n", id->cpu, resp); + + return 0; +} + +static int mbox_pm_qos_config(struct isst_id *id, int enable_clos, int priority_type) +{ + unsigned int req, resp; + int ret; + + if (!enable_clos) { + struct isst_pkg_ctdp pkg_dev; + struct isst_pkg_ctdp_level_info ctdp_level; + + ret = isst_get_ctdp_levels(id, &pkg_dev); + if (ret) { + debug_printf("isst_get_ctdp_levels\n"); + return ret; + } + + ret = isst_get_ctdp_control(id, pkg_dev.current_level, + &ctdp_level); + if (ret) + return ret; + + if (ctdp_level.fact_enabled) { + isst_display_error_info_message(1, "Ignoring request, turbo-freq feature is still enabled", 0, 0); + return -EINVAL; + } + ret = _write_pm_config(id, 0); + if (ret) + isst_display_error_info_message(0, "WRITE_PM_CONFIG command failed, ignoring error", 0, 0); + } else { + ret = _write_pm_config(id, 1); + if (ret) + isst_display_error_info_message(0, "WRITE_PM_CONFIG command failed, ignoring error", 0, 0); + } + + ret = _send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_QOS_CONFIG, 0, 0, + &resp); + if (ret) { + isst_display_error_info_message(1, "CLOS_PM_QOS_CONFIG command failed", 0, 0); + return ret; + } + + debug_printf("cpu:%d CLOS_PM_QOS_CONFIG resp:%x\n", id->cpu, resp); + + req = resp; + + if (enable_clos) + req = req | BIT(1); + else + req = req & ~BIT(1); + + if (priority_type > 1) + isst_display_error_info_message(1, "Invalid priority type: Changing type to ordered", 0, 0); + + if (priority_type) + req = req | BIT(2); + else + req = req & ~BIT(2); + + ret = _send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_QOS_CONFIG, + BIT(MBOX_CMD_WRITE_BIT), req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PM_QOS_CONFIG priority type:%d req:%x\n", id->cpu, + priority_type, req); + + return 0; +} + +static int mbox_pm_get_clos(struct isst_id *id, int clos, struct isst_clos_config *clos_config) +{ + unsigned int resp; + int ret; + + ret = _send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_CLOS, clos, 0, + &resp); + if (ret) + return ret; + + clos_config->epp = resp & 0x0f; + clos_config->clos_prop_prio = (resp >> 4) & 0x0f; + clos_config->clos_min = (resp >> 8) & 0xff; + clos_config->clos_max = (resp >> 16) & 0xff; + clos_config->clos_desired = (resp >> 24) & 0xff; + + return 0; +} + +static int mbox_set_clos(struct isst_id *id, int clos, struct isst_clos_config *clos_config) +{ + unsigned int req, resp; + unsigned int param; + int ret; + + req = clos_config->epp & 0x0f; + req |= (clos_config->clos_prop_prio & 0x0f) << 4; + req |= (clos_config->clos_min & 0xff) << 8; + req |= (clos_config->clos_max & 0xff) << 16; + req |= (clos_config->clos_desired & 0xff) << 24; + + param = BIT(MBOX_CMD_WRITE_BIT) | clos; + + ret = _send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_CLOS, param, req, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PM_CLOS param:%x req:%x\n", id->cpu, param, req); + + return 0; +} + +static int mbox_clos_get_assoc_status(struct isst_id *id, int *clos_id) +{ + unsigned int resp; + unsigned int param; + int core_id, ret; + + core_id = find_phy_core_num(id->cpu); + param = core_id; + + ret = _send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PQR_ASSOC, param, 0, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PQR_ASSOC param:%x resp:%x\n", id->cpu, param, + resp); + *clos_id = (resp >> 16) & 0x03; + + return 0; +} + +static int mbox_clos_associate(struct isst_id *id, int clos_id) +{ + unsigned int req, resp; + unsigned int param; + int core_id, ret; + + req = (clos_id & 0x03) << 16; + core_id = find_phy_core_num(id->cpu); + param = BIT(MBOX_CMD_WRITE_BIT) | core_id; + + ret = _send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PQR_ASSOC, param, + req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PQR_ASSOC param:%x req:%x\n", id->cpu, param, + req); + + return 0; +} + +static struct isst_platform_ops mbox_ops = { + .get_disp_freq_multiplier = mbox_get_disp_freq_multiplier, + .get_trl_max_levels = mbox_get_trl_max_levels, + .get_trl_level_name = mbox_get_trl_level_name, + .update_platform_param = mbox_update_platform_param, + .is_punit_valid = mbox_is_punit_valid, + .read_pm_config = mbox_read_pm_config, + .get_config_levels = mbox_get_config_levels, + .get_ctdp_control = mbox_get_ctdp_control, + .get_tdp_info = mbox_get_tdp_info, + .get_pwr_info = mbox_get_pwr_info, + .get_coremask_info = mbox_get_coremask_info, + .get_get_trl = mbox_get_get_trl, + .get_get_trls = mbox_get_get_trls, + .get_trl_bucket_info = mbox_get_trl_bucket_info, + .set_tdp_level = mbox_set_tdp_level, + .get_pbf_info = mbox_get_pbf_info, + .set_pbf_fact_status = mbox_set_pbf_fact_status, + .get_fact_info = mbox_get_fact_info, + .adjust_uncore_freq = mbox_adjust_uncore_freq, + .get_clos_information = mbox_get_clos_information, + .pm_qos_config = mbox_pm_qos_config, + .pm_get_clos = mbox_pm_get_clos, + .set_clos = mbox_set_clos, + .clos_get_assoc_status = mbox_clos_get_assoc_status, + .clos_associate = mbox_clos_associate, +}; + +struct isst_platform_ops *mbox_get_platform_ops(void) +{ + return &mbox_ops; +} diff --git a/tools/power/x86/intel-speed-select/isst-core-tpmi.c b/tools/power/x86/intel-speed-select/isst-core-tpmi.c new file mode 100644 index 000000000000..19caa9c78d41 --- /dev/null +++ b/tools/power/x86/intel-speed-select/isst-core-tpmi.c @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select -- Enumerate and control features for TPMI Interface + * Copyright (c) 2022 Intel Corporation. + */ + +#include <linux/isst_if.h> +#include "isst.h" + +int tpmi_process_ioctl(int ioctl_no, void *info) +{ + const char *pathname = "/dev/isst_interface"; + int fd; + + if (is_debug_enabled()) { + debug_printf("Issue IOCTL: "); + switch (ioctl_no) { + case ISST_IF_CORE_POWER_STATE: + debug_printf("ISST_IF_CORE_POWER_STATE\n"); + break; + case ISST_IF_CLOS_PARAM: + debug_printf("ISST_IF_CLOS_PARAM\n"); + break; + case ISST_IF_CLOS_ASSOC: + debug_printf("ISST_IF_CLOS_ASSOC\n"); + break; + case ISST_IF_PERF_LEVELS: + debug_printf("ISST_IF_PERF_LEVELS\n"); + break; + case ISST_IF_PERF_SET_LEVEL: + debug_printf("ISST_IF_PERF_SET_LEVEL\n"); + break; + case ISST_IF_PERF_SET_FEATURE: + debug_printf("ISST_IF_PERF_SET_FEATURE\n"); + break; + case ISST_IF_GET_PERF_LEVEL_INFO: + debug_printf("ISST_IF_GET_PERF_LEVEL_INFO\n"); + break; + case ISST_IF_GET_PERF_LEVEL_CPU_MASK: + debug_printf("ISST_IF_GET_PERF_LEVEL_CPU_MASK\n"); + break; + case ISST_IF_GET_BASE_FREQ_INFO: + debug_printf("ISST_IF_GET_BASE_FREQ_INFO\n"); + break; + case ISST_IF_GET_BASE_FREQ_CPU_MASK: + debug_printf("ISST_IF_GET_BASE_FREQ_CPU_MASK\n"); + break; + case ISST_IF_GET_TURBO_FREQ_INFO: + debug_printf("ISST_IF_GET_TURBO_FREQ_INFO\n"); + break; + case ISST_IF_COUNT_TPMI_INSTANCES: + debug_printf("ISST_IF_COUNT_TPMI_INSTANCES\n"); + break; + default: + debug_printf("%d\n", ioctl_no); + break; + } + } + + fd = open(pathname, O_RDWR); + if (fd < 0) + return -1; + + if (ioctl(fd, ioctl_no, info) == -1) { + debug_printf("IOCTL %d Failed\n", ioctl_no); + close(fd); + return -1; + } + + close(fd); + + return 0; +} + +static int tpmi_get_disp_freq_multiplier(void) +{ + return 1; +} + +static int tpmi_get_trl_max_levels(void) +{ + return TRL_MAX_LEVELS; +} + +static char *tpmi_get_trl_level_name(int level) +{ + switch (level) { + case 0: + return "level-0"; + case 1: + return "level-1"; + case 2: + return "level-2"; + case 3: + return "level-3"; + case 4: + return "level-4"; + case 5: + return "level-5"; + case 6: + return "level-6"; + case 7: + return "level-7"; + default: + return NULL; + } +} + + +static void tpmi_update_platform_param(enum isst_platform_param param, int value) +{ + /* No params need to be updated for now */ +} + +static int tpmi_is_punit_valid(struct isst_id *id) +{ + struct isst_tpmi_instance_count info; + int ret; + + if (id->punit < 0) + return 0; + + info.socket_id = id->pkg; + ret = tpmi_process_ioctl(ISST_IF_COUNT_TPMI_INSTANCES, &info); + if (ret == -1) + return 0; + + if (info.valid_mask & BIT(id->punit)) + return 1; + + return 0; +} + +static int tpmi_read_pm_config(struct isst_id *id, int *cp_state, int *cp_cap) +{ + struct isst_core_power info; + int ret; + + info.get_set = 0; + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info); + if (ret == -1) + return ret; + + *cp_state = info.enable; + *cp_cap = info.supported; + + return 0; +} + +int tpmi_get_config_levels(struct isst_id *id, struct isst_pkg_ctdp *pkg_dev) +{ + struct isst_perf_level_info info; + int ret; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + + ret = tpmi_process_ioctl(ISST_IF_PERF_LEVELS, &info); + if (ret == -1) + return ret; + + pkg_dev->version = info.feature_rev; + pkg_dev->levels = info.max_level; + pkg_dev->locked = info.locked; + pkg_dev->current_level = info.current_level; + pkg_dev->locked = info.locked; + pkg_dev->enabled = info.enabled; + + return 0; +} + +static int tpmi_get_ctdp_control(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + struct isst_core_power core_power_info; + struct isst_perf_level_info info; + int level_mask; + int ret; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + + ret = tpmi_process_ioctl(ISST_IF_PERF_LEVELS, &info); + if (ret == -1) + return -1; + + if (config_index != 0xff) + level_mask = 1 << config_index; + else + level_mask = config_index; + + if (!(info.level_mask & level_mask)) + return -1; + + ctdp_level->fact_support = info.sst_tf_support; + ctdp_level->pbf_support = info.sst_bf_support; + ctdp_level->fact_enabled = !!(info.feature_state & BIT(1)); + ctdp_level->pbf_enabled = !!(info.feature_state & BIT(0)); + + core_power_info.get_set = 0; + core_power_info.socket_id = id->pkg; + core_power_info.power_domain_id = id->punit; + + ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &core_power_info); + if (ret == -1) + return ret; + + ctdp_level->sst_cp_support = core_power_info.supported; + ctdp_level->sst_cp_enabled = core_power_info.enable; + + debug_printf + ("cpu:%d CONFIG_TDP_GET_TDP_CONTROL fact_support:%d pbf_support: %d fact_enabled:%d pbf_enabled:%d\n", + id->cpu, ctdp_level->fact_support, ctdp_level->pbf_support, + ctdp_level->fact_enabled, ctdp_level->pbf_enabled); + + return 0; +} + +static int tpmi_get_tdp_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + struct isst_perf_level_data_info info; + int ret; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = config_index; + + ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_INFO, &info); + if (ret == -1) + return ret; + + ctdp_level->pkg_tdp = info.thermal_design_power_w; + ctdp_level->tdp_ratio = info.tdp_ratio; + ctdp_level->sse_p1 = info.base_freq_mhz; + ctdp_level->avx2_p1 = info.base_freq_avx2_mhz; + ctdp_level->avx512_p1 = info.base_freq_avx512_mhz; + ctdp_level->amx_p1 = info.base_freq_amx_mhz; + + ctdp_level->t_proc_hot = info.tjunction_max_c; + ctdp_level->mem_freq = info.max_memory_freq_mhz; + ctdp_level->cooling_type = info.cooling_type; + + ctdp_level->uncore_p0 = info.p0_fabric_freq_mhz; + ctdp_level->uncore_p1 = info.p1_fabric_freq_mhz; + ctdp_level->uncore_pm = info.pm_fabric_freq_mhz; + + debug_printf + ("cpu:%d ctdp:%d CONFIG_TDP_GET_TDP_INFO tdp_ratio:%d pkg_tdp:%d ctdp_level->t_proc_hot:%d\n", + id->cpu, config_index, ctdp_level->tdp_ratio, ctdp_level->pkg_tdp, + ctdp_level->t_proc_hot); + + return 0; +} + +static int tpmi_get_pwr_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + /* TBD */ + ctdp_level->pkg_max_power = 0; + ctdp_level->pkg_min_power = 0; + + debug_printf + ("cpu:%d ctdp:%d CONFIG_TDP_GET_PWR_INFO pkg_max_power:%d pkg_min_power:%d\n", + id->cpu, config_index, ctdp_level->pkg_max_power, + ctdp_level->pkg_min_power); + + return 0; +} + +int tpmi_get_coremask_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + struct isst_perf_level_cpu_mask info; + int ret, cpu_count; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = config_index; + info.punit_cpu_map = 1; + + ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_CPU_MASK, &info); + if (ret == -1) + return ret; + + set_cpu_mask_from_punit_coremask(id, info.mask, + ctdp_level->core_cpumask_size, + ctdp_level->core_cpumask, &cpu_count); + ctdp_level->cpu_count = cpu_count; + + debug_printf("cpu:%d ctdp:%d core_mask ino cpu count:%d\n", + id->cpu, config_index, ctdp_level->cpu_count); + + return 0; +} + +static int tpmi_get_get_trls(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + struct isst_perf_level_data_info info; + int ret, i, j; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = config_index; + + ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_INFO, &info); + if (ret == -1) + return ret; + + if (info.max_buckets > TRL_MAX_BUCKETS) + info.max_buckets = TRL_MAX_BUCKETS; + + if (info.max_trl_levels > TRL_MAX_LEVELS) + info.max_trl_levels = TRL_MAX_LEVELS; + + for (i = 0; i < info.max_trl_levels; ++i) + for (j = 0; j < info.max_buckets; ++j) + ctdp_level->trl_ratios[i][j] = info.trl_freq_mhz[i][j]; + + return 0; +} + +static int tpmi_get_get_trl(struct isst_id *id, int level, int config_index, + int *trl) +{ + struct isst_pkg_ctdp_level_info ctdp_level; + int ret, i; + + ret = tpmi_get_get_trls(id, config_index, &ctdp_level); + if (ret) + return ret; + + /* FIX ME: Just return for level 0 */ + for (i = 0; i < 8; ++i) + trl[i] = ctdp_level.trl_ratios[0][i]; + + return 0; +} + +static int tpmi_get_trl_bucket_info(struct isst_id *id, int config_index, + unsigned long long *buckets_info) +{ + struct isst_perf_level_data_info info; + unsigned char *mask = (unsigned char *)buckets_info; + int ret, i; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = config_index; + + ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_INFO, &info); + if (ret == -1) + return ret; + + if (info.max_buckets > TRL_MAX_BUCKETS) + info.max_buckets = TRL_MAX_BUCKETS; + + for (i = 0; i < info.max_buckets; ++i) + mask[i] = info.bucket_core_counts[i]; + + debug_printf("cpu:%d TRL bucket info: 0x%llx\n", id->cpu, + *buckets_info); + + return 0; +} + +static int tpmi_set_tdp_level(struct isst_id *id, int tdp_level) +{ + struct isst_perf_level_control info; + int ret; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = tdp_level; + + ret = tpmi_process_ioctl(ISST_IF_PERF_SET_LEVEL, &info); + if (ret == -1) + return ret; + + return 0; +} + +static int _pbf_get_coremask_info(struct isst_id *id, int config_index, + struct isst_pbf_info *pbf_info) +{ + struct isst_perf_level_cpu_mask info; + int ret, cpu_count; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = config_index; + info.punit_cpu_map = 1; + + ret = tpmi_process_ioctl(ISST_IF_GET_BASE_FREQ_CPU_MASK, &info); + if (ret == -1) + return ret; + + set_cpu_mask_from_punit_coremask(id, info.mask, + pbf_info->core_cpumask_size, + pbf_info->core_cpumask, &cpu_count); + + debug_printf("cpu:%d ctdp:%d pbf core_mask info cpu count:%d\n", + id->cpu, config_index, cpu_count); + + return 0; +} + +static int tpmi_get_pbf_info(struct isst_id *id, int level, + struct isst_pbf_info *pbf_info) +{ + struct isst_base_freq_info info; + int ret; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = level; + + ret = tpmi_process_ioctl(ISST_IF_GET_BASE_FREQ_INFO, &info); + if (ret == -1) + return ret; + + pbf_info->p1_low = info.low_base_freq_mhz; + pbf_info->p1_high = info.high_base_freq_mhz; + pbf_info->tdp = info.thermal_design_power_w; + pbf_info->t_prochot = info.tjunction_max_c; + + debug_printf("cpu:%d ctdp:%d pbf info:%d:%d:%d:%d\n", + id->cpu, level, pbf_info->p1_low, pbf_info->p1_high, + pbf_info->tdp, pbf_info->t_prochot); + + return _pbf_get_coremask_info(id, level, pbf_info); +} + +static int tpmi_set_pbf_fact_status(struct isst_id *id, int pbf, int enable) +{ + struct isst_pkg_ctdp pkg_dev; + struct isst_pkg_ctdp_level_info ctdp_level; + int current_level; + struct isst_perf_feature_control info; + int ret; + + ret = isst_get_ctdp_levels(id, &pkg_dev); + if (ret) + debug_printf("cpu:%d No support for dynamic ISST\n", id->cpu); + + current_level = pkg_dev.current_level; + + ret = isst_get_ctdp_control(id, current_level, &ctdp_level); + if (ret) + return ret; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + + info.feature = 0; + + if (pbf) { + if (ctdp_level.fact_enabled) + info.feature |= BIT(1); + + if (enable) + info.feature |= BIT(0); + else + info.feature &= ~BIT(0); + } else { + + if (enable && !ctdp_level.sst_cp_enabled) + isst_display_error_info_message(0, + "Make sure to execute before: core-power enable", + 0, 0); + + if (ctdp_level.pbf_enabled) + info.feature |= BIT(0); + + if (enable) + info.feature |= BIT(1); + else + info.feature &= ~BIT(1); + } + + ret = tpmi_process_ioctl(ISST_IF_PERF_SET_FEATURE, &info); + if (ret == -1) + return ret; + + return 0; +} + +static int tpmi_get_fact_info(struct isst_id *id, int level, int fact_bucket, + struct isst_fact_info *fact_info) +{ + struct isst_turbo_freq_info info; + int i, j; + int ret; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = level; + + ret = tpmi_process_ioctl(ISST_IF_GET_TURBO_FREQ_INFO, &info); + if (ret == -1) + return ret; + + for (i = 0; i < info.max_clip_freqs; ++i) + fact_info->lp_ratios[i] = info.lp_clip_freq_mhz[i]; + + if (info.max_buckets > TRL_MAX_BUCKETS) + info.max_buckets = TRL_MAX_BUCKETS; + + if (info.max_trl_levels > TRL_MAX_LEVELS) + info.max_trl_levels = TRL_MAX_LEVELS; + + for (i = 0; i < info.max_trl_levels; ++i) { + for (j = 0; j < info.max_buckets; ++j) + fact_info->bucket_info[j].hp_ratios[i] = + info.trl_freq_mhz[i][j]; + } + + for (i = 0; i < info.max_buckets; ++i) + fact_info->bucket_info[i].hp_cores = info.bucket_core_counts[i]; + + return 0; +} + +static void _set_uncore_min_max(struct isst_id *id, int max, int freq) +{ + DIR *dir; + FILE *filep; + struct dirent *entry; + char buffer[512]; + unsigned int tmp_id; + int ret; + + dir = opendir("/sys/devices/system/cpu/intel_uncore_frequency/"); + if (!dir) + return; + + while ((entry = readdir(dir)) != NULL ) { + /* Check domain_id */ + snprintf(buffer, sizeof(buffer), + "/sys/devices/system/cpu/intel_uncore_frequency/%s/domain_id", entry->d_name); + + filep = fopen(buffer, "r"); + if (!filep) + goto end; + + ret = fscanf(filep, "%u", &tmp_id); + fclose(filep); + if (ret != 1) + goto end; + + if (tmp_id != id->punit) + continue; + + /* Check package_id */ + snprintf(buffer, sizeof(buffer), + "/sys/devices/system/cpu/intel_uncore_frequency/%s/package_id", entry->d_name); + + filep = fopen(buffer, "r"); + if (!filep) + goto end; + + ret = fscanf(filep, "%u", &tmp_id); + fclose(filep); + + if (ret != 1) + goto end; + + if (tmp_id != id->pkg) + continue; + + /* Found the right sysfs path, adjust and quit */ + if (max) + snprintf(buffer, sizeof(buffer), + "/sys/devices/system/cpu/intel_uncore_frequency/%s/max_freq_khz", entry->d_name); + else + snprintf(buffer, sizeof(buffer), + "/sys/devices/system/cpu/intel_uncore_frequency/%s/min_freq_khz", entry->d_name); + + filep = fopen(buffer, "w"); + if (!filep) + goto end; + + fprintf(filep, "%d\n", freq); + fclose(filep); + break; + } + +end: + closedir(dir); +} + +static void tpmi_adjust_uncore_freq(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + struct isst_perf_level_data_info info; + int ret; + + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.level = config_index; + + ret = tpmi_process_ioctl(ISST_IF_GET_PERF_LEVEL_INFO, &info); + if (ret == -1) + return; + + ctdp_level->uncore_p0 = info.p0_fabric_freq_mhz; + ctdp_level->uncore_p1 = info.p1_fabric_freq_mhz; + ctdp_level->uncore_pm = info.pm_fabric_freq_mhz; + + if (ctdp_level->uncore_pm) + _set_uncore_min_max(id, 0, ctdp_level->uncore_pm * 100000); + + if (ctdp_level->uncore_p0) + _set_uncore_min_max(id, 1, ctdp_level->uncore_p0 * 100000); + + return; +} + +static int tpmi_get_clos_information(struct isst_id *id, int *enable, int *type) +{ + struct isst_core_power info; + int ret; + + info.get_set = 0; + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info); + if (ret == -1) + return ret; + + *enable = info.enable; + *type = info.priority_type; + + return 0; +} + +static int tpmi_pm_qos_config(struct isst_id *id, int enable_clos, + int priority_type) +{ + struct isst_core_power info; + int ret; + + info.get_set = 1; + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.enable = enable_clos; + info.priority_type = priority_type; + ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info); + if (ret == -1) + return ret; + + return 0; +} + +int tpmi_pm_get_clos(struct isst_id *id, int clos, + struct isst_clos_config *clos_config) +{ + struct isst_clos_param info; + int ret; + + info.get_set = 0; + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.clos = clos; + + ret = tpmi_process_ioctl(ISST_IF_CLOS_PARAM, &info); + if (ret == -1) + return ret; + + clos_config->epp = 0; + clos_config->clos_prop_prio = info.prop_prio; + clos_config->clos_min = info.min_freq_mhz; + clos_config->clos_max = info.max_freq_mhz; + clos_config->clos_desired = 0; + + debug_printf("cpu:%d clos:%d min:%d max:%d\n", id->cpu, clos, + clos_config->clos_min, clos_config->clos_max); + + return 0; +} + +int tpmi_set_clos(struct isst_id *id, int clos, + struct isst_clos_config *clos_config) +{ + struct isst_clos_param info; + int ret; + + info.get_set = 1; + info.socket_id = id->pkg; + info.power_domain_id = id->punit; + info.clos = clos; + info.prop_prio = clos_config->clos_prop_prio; + + info.min_freq_mhz = clos_config->clos_min; + info.max_freq_mhz = clos_config->clos_max; + + if (info.min_freq_mhz <= 0xff) + info.min_freq_mhz *= 100; + if (info.max_freq_mhz <= 0xff) + info.max_freq_mhz *= 100; + + ret = tpmi_process_ioctl(ISST_IF_CLOS_PARAM, &info); + if (ret == -1) + return ret; + + debug_printf("set cpu:%d clos:%d min:%d max:%d\n", id->cpu, clos, + clos_config->clos_min, clos_config->clos_max); + + return 0; +} + +static int tpmi_clos_get_assoc_status(struct isst_id *id, int *clos_id) +{ + struct isst_if_clos_assoc_cmds assoc_cmds; + int ret; + + assoc_cmds.cmd_count = 1; + assoc_cmds.get_set = 0; + assoc_cmds.punit_cpu_map = 1; + assoc_cmds.assoc_info[0].logical_cpu = find_phy_core_num(id->cpu); + assoc_cmds.assoc_info[0].socket_id = id->pkg; + assoc_cmds.assoc_info[0].power_domain_id = id->punit; + + ret = tpmi_process_ioctl(ISST_IF_CLOS_ASSOC, &assoc_cmds); + if (ret == -1) + return ret; + + *clos_id = assoc_cmds.assoc_info[0].clos; + + return 0; +} + +static int tpmi_clos_associate(struct isst_id *id, int clos_id) +{ + struct isst_if_clos_assoc_cmds assoc_cmds; + int ret; + + assoc_cmds.cmd_count = 1; + assoc_cmds.get_set = 1; + assoc_cmds.punit_cpu_map = 1; + assoc_cmds.assoc_info[0].logical_cpu = find_phy_core_num(id->cpu); + assoc_cmds.assoc_info[0].clos = clos_id; + assoc_cmds.assoc_info[0].socket_id = id->pkg; + assoc_cmds.assoc_info[0].power_domain_id = id->punit; + + ret = tpmi_process_ioctl(ISST_IF_CLOS_ASSOC, &assoc_cmds); + if (ret == -1) + return ret; + + return 0; +} + +static struct isst_platform_ops tpmi_ops = { + .get_disp_freq_multiplier = tpmi_get_disp_freq_multiplier, + .get_trl_max_levels = tpmi_get_trl_max_levels, + .get_trl_level_name = tpmi_get_trl_level_name, + .update_platform_param = tpmi_update_platform_param, + .is_punit_valid = tpmi_is_punit_valid, + .read_pm_config = tpmi_read_pm_config, + .get_config_levels = tpmi_get_config_levels, + .get_ctdp_control = tpmi_get_ctdp_control, + .get_tdp_info = tpmi_get_tdp_info, + .get_pwr_info = tpmi_get_pwr_info, + .get_coremask_info = tpmi_get_coremask_info, + .get_get_trl = tpmi_get_get_trl, + .get_get_trls = tpmi_get_get_trls, + .get_trl_bucket_info = tpmi_get_trl_bucket_info, + .set_tdp_level = tpmi_set_tdp_level, + .get_pbf_info = tpmi_get_pbf_info, + .set_pbf_fact_status = tpmi_set_pbf_fact_status, + .get_fact_info = tpmi_get_fact_info, + .adjust_uncore_freq = tpmi_adjust_uncore_freq, + .get_clos_information = tpmi_get_clos_information, + .pm_qos_config = tpmi_pm_qos_config, + .pm_get_clos = tpmi_pm_get_clos, + .set_clos = tpmi_set_clos, + .clos_get_assoc_status = tpmi_clos_get_assoc_status, + .clos_associate = tpmi_clos_associate, +}; + +struct isst_platform_ops *tpmi_get_platform_ops(void) +{ + return &tpmi_ops; +} diff --git a/tools/power/x86/intel-speed-select/isst-core.c b/tools/power/x86/intel-speed-select/isst-core.c index 2bfc118c4b87..f55fef4c13a7 100644 --- a/tools/power/x86/intel-speed-select/isst-core.c +++ b/tools/power/x86/intel-speed-select/isst-core.c @@ -6,304 +6,141 @@ #include "isst.h" -int isst_write_pm_config(struct isst_id *id, int cp_state) -{ - unsigned int req, resp; - int ret; - - if (cp_state) - req = BIT(16); - else - req = 0; - - ret = isst_send_mbox_command(id->cpu, WRITE_PM_CONFIG, PM_FEATURE, 0, req, - &resp); - if (ret) - return ret; - - debug_printf("cpu:%d WRITE_PM_CONFIG resp:%x\n", id->cpu, resp); +static struct isst_platform_ops *isst_ops; + +#define CHECK_CB(_name) \ + do { \ + if (!isst_ops || !isst_ops->_name) { \ + fprintf(stderr, "Invalid ops\n"); \ + exit(0); \ + } \ + } while (0) + +int isst_set_platform_ops(int api_version) +{ + switch (api_version) { + case 1: + isst_ops = mbox_get_platform_ops(); + break; + case 2: + isst_ops = tpmi_get_platform_ops(); + break; + default: + isst_ops = NULL; + break; + } + if (!isst_ops) + return -1; return 0; } -int isst_read_pm_config(struct isst_id *id, int *cp_state, int *cp_cap) +void isst_update_platform_param(enum isst_platform_param param, int value) { - unsigned int resp; - int ret; - - ret = isst_send_mbox_command(id->cpu, READ_PM_CONFIG, PM_FEATURE, 0, 0, - &resp); - if (ret) - return ret; - - debug_printf("cpu:%d READ_PM_CONFIG resp:%x\n", id->cpu, resp); + CHECK_CB(update_platform_param); - *cp_state = resp & BIT(16); - *cp_cap = resp & BIT(0) ? 1 : 0; - - return 0; + isst_ops->update_platform_param(param, value); } -int isst_get_ctdp_levels(struct isst_id *id, struct isst_pkg_ctdp *pkg_dev) +int isst_get_disp_freq_multiplier(void) { - unsigned int resp; - int ret; - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_LEVELS_INFO, 0, 0, &resp); - if (ret) { - pkg_dev->levels = 0; - pkg_dev->locked = 1; - pkg_dev->current_level = 0; - pkg_dev->version = 0; - pkg_dev->enabled = 0; - return 0; - } + CHECK_CB(get_disp_freq_multiplier); + return isst_ops->get_disp_freq_multiplier(); +} - debug_printf("cpu:%d CONFIG_TDP_GET_LEVELS_INFO resp:%x\n", id->cpu, resp); +int isst_get_trl_max_levels(void) +{ + CHECK_CB(get_trl_max_levels); + return isst_ops->get_trl_max_levels(); +} - pkg_dev->version = resp & 0xff; - pkg_dev->levels = (resp >> 8) & 0xff; - pkg_dev->current_level = (resp >> 16) & 0xff; - pkg_dev->locked = !!(resp & BIT(24)); - pkg_dev->enabled = !!(resp & BIT(31)); +char *isst_get_trl_level_name(int level) +{ + CHECK_CB(get_trl_level_name); + return isst_ops->get_trl_level_name(level); +} - return 0; +int isst_is_punit_valid(struct isst_id *id) +{ + CHECK_CB(is_punit_valid); + return isst_ops->is_punit_valid(id); } -int isst_get_ctdp_control(struct isst_id *id, int config_index, - struct isst_pkg_ctdp_level_info *ctdp_level) +int isst_send_msr_command(unsigned int cpu, unsigned int msr, int write, + unsigned long long *req_resp) { - int cp_state, cp_cap; - unsigned int resp; - int ret; + struct isst_if_msr_cmds msr_cmds; + const char *pathname = "/dev/isst_interface"; + FILE *outf = get_output_file(); + int fd; - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_TDP_CONTROL, 0, - config_index, &resp); - if (ret) - return ret; + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); - ctdp_level->fact_support = resp & BIT(0); - ctdp_level->pbf_support = !!(resp & BIT(1)); - ctdp_level->fact_enabled = !!(resp & BIT(16)); - ctdp_level->pbf_enabled = !!(resp & BIT(17)); + msr_cmds.cmd_count = 1; + msr_cmds.msr_cmd[0].logical_cpu = cpu; + msr_cmds.msr_cmd[0].msr = msr; + msr_cmds.msr_cmd[0].read_write = write; + if (write) + msr_cmds.msr_cmd[0].data = *req_resp; - ret = isst_read_pm_config(id, &cp_state, &cp_cap); - if (ret) { - debug_printf("cpu:%d pm_config is not supported\n", id->cpu); + if (ioctl(fd, ISST_IF_MSR_COMMAND, &msr_cmds) == -1) { + perror("ISST_IF_MSR_COMMAND"); + fprintf(outf, "Error: msr_cmd cpu:%d msr:%x read_write:%d\n", + cpu, msr, write); } else { - debug_printf("cpu:%d pm_config SST-CP state:%d cap:%d\n", id->cpu, cp_state, cp_cap); - ctdp_level->sst_cp_support = cp_cap; - ctdp_level->sst_cp_enabled = cp_state; - } - - debug_printf( - "cpu:%d CONFIG_TDP_GET_TDP_CONTROL resp:%x fact_support:%d pbf_support: %d fact_enabled:%d pbf_enabled:%d\n", - id->cpu, resp, ctdp_level->fact_support, ctdp_level->pbf_support, - ctdp_level->fact_enabled, ctdp_level->pbf_enabled); - - return 0; -} - -int isst_get_tdp_info(struct isst_id *id, int config_index, - struct isst_pkg_ctdp_level_info *ctdp_level) -{ - unsigned int resp; - int ret; + if (!write) + *req_resp = msr_cmds.msr_cmd[0].data; - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_TDP_INFO, - 0, config_index, &resp); - if (ret) { - isst_display_error_info_message(1, "Invalid level, Can't get TDP information at level", 1, config_index); - return ret; + debug_printf( + "msr_cmd response: cpu:%d msr:%x rd_write:%x resp:%llx %llx\n", + cpu, msr, write, *req_resp, msr_cmds.msr_cmd[0].data); } - ctdp_level->pkg_tdp = resp & GENMASK(14, 0); - ctdp_level->tdp_ratio = (resp & GENMASK(23, 16)) >> 16; + close(fd); - debug_printf( - "cpu:%d ctdp:%d CONFIG_TDP_GET_TDP_INFO resp:%x tdp_ratio:%d pkg_tdp:%d\n", - id->cpu, config_index, resp, ctdp_level->tdp_ratio, - ctdp_level->pkg_tdp); return 0; } -int isst_get_pwr_info(struct isst_id *id, int config_index, - struct isst_pkg_ctdp_level_info *ctdp_level) +int isst_read_pm_config(struct isst_id *id, int *cp_state, int *cp_cap) { - unsigned int resp; - int ret; - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_PWR_INFO, - 0, config_index, &resp); - if (ret) - return ret; - - ctdp_level->pkg_max_power = resp & GENMASK(14, 0); - ctdp_level->pkg_min_power = (resp & GENMASK(30, 16)) >> 16; - - debug_printf( - "cpu:%d ctdp:%d CONFIG_TDP_GET_PWR_INFO resp:%x pkg_max_power:%d pkg_min_power:%d\n", - id->cpu, config_index, resp, ctdp_level->pkg_max_power, - ctdp_level->pkg_min_power); - - return 0; + CHECK_CB(read_pm_config); + return isst_ops->read_pm_config(id, cp_state, cp_cap); } -void isst_get_uncore_p0_p1_info(struct isst_id *id, int config_index, - struct isst_pkg_ctdp_level_info *ctdp_level) +int isst_get_ctdp_levels(struct isst_id *id, struct isst_pkg_ctdp *pkg_dev) { - unsigned int resp; - int ret; - - ctdp_level->uncore_pm = 0; - ctdp_level->uncore_p0 = 0; - ctdp_level->uncore_p1 = 0; - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_RATIO_INFO, 0, - (BIT(16) | config_index), &resp); - if (ret) - goto try_uncore_mbox; - - ctdp_level->uncore_p0 = resp & GENMASK(7, 0); - ctdp_level->uncore_p1 = (resp & GENMASK(15, 8)) >> 8; - ctdp_level->uncore_pm = (resp & GENMASK(31, 24)) >> 24; - - debug_printf( - "cpu:%d ctdp:%d CONFIG_TDP_GET_RATIO_INFO resp:%x uncore p0:%d uncore p1:%d uncore pm:%d\n", - id->cpu, config_index, resp, ctdp_level->uncore_p0, ctdp_level->uncore_p1, - ctdp_level->uncore_pm); - - return; - -try_uncore_mbox: - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_UNCORE_P0_P1_INFO, 0, - config_index, &resp); - if (ret) { - ctdp_level->uncore_p0 = 0; - ctdp_level->uncore_p1 = 0; - return; - } - - ctdp_level->uncore_p0 = resp & GENMASK(7, 0); - ctdp_level->uncore_p1 = (resp & GENMASK(15, 8)) >> 8; - debug_printf( - "cpu:%d ctdp:%d CONFIG_TDP_GET_UNCORE_P0_P1_INFO resp:%x uncore p0:%d uncore p1:%d\n", - id->cpu, config_index, resp, ctdp_level->uncore_p0, - ctdp_level->uncore_p1); + CHECK_CB(get_config_levels); + return isst_ops->get_config_levels(id, pkg_dev); } -void isst_get_p1_info(struct isst_id *id, int config_index, - struct isst_pkg_ctdp_level_info *ctdp_level) +int isst_get_ctdp_control(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) { - unsigned int resp; - int ret; - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_P1_INFO, 0, - config_index, &resp); - if (ret) { - ctdp_level->sse_p1 = 0; - ctdp_level->avx2_p1 = 0; - ctdp_level->avx512_p1 = 0; - return; - } - - ctdp_level->sse_p1 = resp & GENMASK(7, 0); - ctdp_level->avx2_p1 = (resp & GENMASK(15, 8)) >> 8; - ctdp_level->avx512_p1 = (resp & GENMASK(23, 16)) >> 16; - debug_printf( - "cpu:%d ctdp:%d CONFIG_TDP_GET_P1_INFO resp:%x sse_p1:%d avx2_p1:%d avx512_p1:%d\n", - id->cpu, config_index, resp, ctdp_level->sse_p1, - ctdp_level->avx2_p1, ctdp_level->avx512_p1); + CHECK_CB(get_ctdp_control); + return isst_ops->get_ctdp_control(id, config_index, ctdp_level); } -void isst_get_uncore_mem_freq(struct isst_id *id, int config_index, - struct isst_pkg_ctdp_level_info *ctdp_level) +int isst_get_tdp_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) { - unsigned int resp; - int ret; - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_MEM_FREQ, - 0, config_index, &resp); - if (ret) { - ctdp_level->mem_freq = 0; - return; - } - - ctdp_level->mem_freq = resp & GENMASK(7, 0); - if (is_spr_platform()) { - ctdp_level->mem_freq *= 200; - } else if (is_icx_platform()) { - if (ctdp_level->mem_freq < 7) { - ctdp_level->mem_freq = (12 - ctdp_level->mem_freq) * 133.33 * 2 * 10; - ctdp_level->mem_freq /= 10; - if (ctdp_level->mem_freq % 10 > 5) - ctdp_level->mem_freq++; - } else { - ctdp_level->mem_freq = 0; - } - } else { - ctdp_level->mem_freq = 0; - } - debug_printf( - "cpu:%d ctdp:%d CONFIG_TDP_GET_MEM_FREQ resp:%x uncore mem_freq:%d\n", - id->cpu, config_index, resp, ctdp_level->mem_freq); + CHECK_CB(get_tdp_info); + return isst_ops->get_tdp_info(id, config_index, ctdp_level); } -int isst_get_tjmax_info(struct isst_id *id, int config_index, - struct isst_pkg_ctdp_level_info *ctdp_level) +int isst_get_pwr_info(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) { - unsigned int resp; - int ret; - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_GET_TJMAX_INFO, - 0, config_index, &resp); - if (ret) - return ret; - - ctdp_level->t_proc_hot = resp & GENMASK(7, 0); - - debug_printf( - "cpu:%d ctdp:%d CONFIG_TDP_GET_TJMAX_INFO resp:%x t_proc_hot:%d\n", - id->cpu, config_index, resp, ctdp_level->t_proc_hot); - - return 0; + CHECK_CB(get_pwr_info); + return isst_ops->get_pwr_info(id, config_index, ctdp_level); } int isst_get_coremask_info(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level) { - unsigned int resp; - int i, ret; - - ctdp_level->cpu_count = 0; - for (i = 0; i < 2; ++i) { - unsigned long long mask; - int cpu_count = 0; - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_CORE_MASK, 0, - (i << 8) | config_index, &resp); - if (ret) - return ret; - - debug_printf( - "cpu:%d ctdp:%d mask:%d CONFIG_TDP_GET_CORE_MASK resp:%x\n", - id->cpu, config_index, i, resp); - - mask = (unsigned long long)resp << (32 * i); - set_cpu_mask_from_punit_coremask(id, mask, - ctdp_level->core_cpumask_size, - ctdp_level->core_cpumask, - &cpu_count); - ctdp_level->cpu_count += cpu_count; - debug_printf("cpu:%d ctdp:%d mask:%d cpu count:%d\n", id->cpu, - config_index, i, ctdp_level->cpu_count); - } - - return 0; + CHECK_CB(get_coremask_info); + return isst_ops->get_coremask_info(id, config_index, ctdp_level); } int isst_get_get_trl_from_msr(struct isst_id *id, int *trl) @@ -329,89 +166,33 @@ int isst_get_get_trl_from_msr(struct isst_id *id, int *trl) int isst_get_get_trl(struct isst_id *id, int level, int avx_level, int *trl) { - unsigned int req, resp; - int ret; - - req = level | (avx_level << 16); - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_TURBO_LIMIT_RATIOS, 0, req, - &resp); - if (ret) - return ret; - - debug_printf( - "cpu:%d CONFIG_TDP_GET_TURBO_LIMIT_RATIOS req:%x resp:%x\n", - id->cpu, req, resp); - - trl[0] = resp & GENMASK(7, 0); - trl[1] = (resp & GENMASK(15, 8)) >> 8; - trl[2] = (resp & GENMASK(23, 16)) >> 16; - trl[3] = (resp & GENMASK(31, 24)) >> 24; - - req = level | BIT(8) | (avx_level << 16); - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_TURBO_LIMIT_RATIOS, 0, req, - &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CONFIG_TDP_GET_TURBO_LIMIT req:%x resp:%x\n", id->cpu, - req, resp); - - trl[4] = resp & GENMASK(7, 0); - trl[5] = (resp & GENMASK(15, 8)) >> 8; - trl[6] = (resp & GENMASK(23, 16)) >> 16; - trl[7] = (resp & GENMASK(31, 24)) >> 24; - - return 0; + CHECK_CB(get_get_trl); + return isst_ops->get_get_trl(id, level, avx_level, trl); } -int isst_get_trl_bucket_info(struct isst_id *id, unsigned long long *buckets_info) +int isst_get_get_trls(struct isst_id *id, int level, struct isst_pkg_ctdp_level_info *ctdp_level) { - int ret; - - debug_printf("cpu:%d bucket info via MSR\n", id->cpu); - - *buckets_info = 0; - - ret = isst_send_msr_command(id->cpu, 0x1ae, 0, buckets_info); - if (ret) - return ret; - - debug_printf("cpu:%d bucket info via MSR successful 0x%llx\n", id->cpu, - *buckets_info); + CHECK_CB(get_get_trls); + return isst_ops->get_get_trls(id, level, ctdp_level); +} - return 0; +int isst_get_trl_bucket_info(struct isst_id *id, int level, unsigned long long *buckets_info) +{ + CHECK_CB(get_trl_bucket_info); + return isst_ops->get_trl_bucket_info(id, level, buckets_info); } int isst_set_tdp_level(struct isst_id *id, int tdp_level) { - unsigned int resp; - int ret; - - - if (isst_get_config_tdp_lock_status(id)) { - isst_display_error_info_message(1, "TDP is locked", 0, 0); - return -1; - - } - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, CONFIG_TDP_SET_LEVEL, 0, - tdp_level, &resp); - if (ret) { - isst_display_error_info_message(1, "Set TDP level failed for level", 1, tdp_level); - return ret; - } - - return 0; + CHECK_CB(set_tdp_level); + return isst_ops->set_tdp_level(id, tdp_level); } int isst_get_pbf_info(struct isst_id *id, int level, struct isst_pbf_info *pbf_info) { struct isst_pkg_ctdp_level_info ctdp_level; struct isst_pkg_ctdp pkg_dev; - int i, ret, max_punit_core, max_mask_index; - unsigned int req, resp; + int ret; ret = isst_get_ctdp_levels(id, &pkg_dev); if (ret) { @@ -435,194 +216,23 @@ int isst_get_pbf_info(struct isst_id *id, int level, struct isst_pbf_info *pbf_i pbf_info->core_cpumask_size = alloc_cpu_set(&pbf_info->core_cpumask); - max_punit_core = get_max_punit_core_id(id); - max_mask_index = max_punit_core > 32 ? 2 : 1; - - for (i = 0; i < max_mask_index; ++i) { - unsigned long long mask; - int count; - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_PBF_GET_CORE_MASK_INFO, - 0, (i << 8) | level, &resp); - if (ret) - break; - - debug_printf( - "cpu:%d CONFIG_TDP_PBF_GET_CORE_MASK_INFO resp:%x\n", - id->cpu, resp); - - mask = (unsigned long long)resp << (32 * i); - set_cpu_mask_from_punit_coremask(id, mask, - pbf_info->core_cpumask_size, - pbf_info->core_cpumask, - &count); - } - - req = level; - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_PBF_GET_P1HI_P1LO_INFO, 0, req, - &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CONFIG_TDP_PBF_GET_P1HI_P1LO_INFO resp:%x\n", id->cpu, - resp); - - pbf_info->p1_low = resp & 0xff; - pbf_info->p1_high = (resp & GENMASK(15, 8)) >> 8; - - req = level; - ret = isst_send_mbox_command( - id->cpu, CONFIG_TDP, CONFIG_TDP_PBF_GET_TDP_INFO, 0, req, &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CONFIG_TDP_PBF_GET_TDP_INFO resp:%x\n", id->cpu, resp); - - pbf_info->tdp = resp & 0xffff; - - req = level; - ret = isst_send_mbox_command( - id->cpu, CONFIG_TDP, CONFIG_TDP_PBF_GET_TJ_MAX_INFO, 0, req, &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CONFIG_TDP_PBF_GET_TJ_MAX_INFO resp:%x\n", id->cpu, - resp); - pbf_info->t_control = (resp >> 8) & 0xff; - pbf_info->t_prochot = resp & 0xff; - - return 0; -} - -void isst_get_pbf_info_complete(struct isst_pbf_info *pbf_info) -{ - free_cpu_set(pbf_info->core_cpumask); + CHECK_CB(get_pbf_info); + return isst_ops->get_pbf_info(id, level, pbf_info); } int isst_set_pbf_fact_status(struct isst_id *id, int pbf, int enable) { - struct isst_pkg_ctdp pkg_dev; - struct isst_pkg_ctdp_level_info ctdp_level; - int current_level; - unsigned int req = 0, resp; - int ret; - - ret = isst_get_ctdp_levels(id, &pkg_dev); - if (ret) - debug_printf("cpu:%d No support for dynamic ISST\n", id->cpu); - - current_level = pkg_dev.current_level; - - ret = isst_get_ctdp_control(id, current_level, &ctdp_level); - if (ret) - return ret; - - if (pbf) { - if (ctdp_level.fact_enabled) - req = BIT(16); - - if (enable) - req |= BIT(17); - else - req &= ~BIT(17); - } else { - - if (enable && !ctdp_level.sst_cp_enabled) - isst_display_error_info_message(0, "Make sure to execute before: core-power enable", 0, 0); - - if (ctdp_level.pbf_enabled) - req = BIT(17); - - if (enable) - req |= BIT(16); - else - req &= ~BIT(16); - } - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_SET_TDP_CONTROL, 0, req, &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CONFIG_TDP_SET_TDP_CONTROL pbf/fact:%d req:%x\n", - id->cpu, pbf, req); - - return 0; + CHECK_CB(set_pbf_fact_status); + return isst_ops->set_pbf_fact_status(id, pbf, enable); } -int isst_get_fact_bucket_info(struct isst_id *id, int level, - struct isst_fact_bucket_info *bucket_info) -{ - unsigned int resp; - int i, k, ret; - - for (i = 0; i < 2; ++i) { - int j; - - ret = isst_send_mbox_command( - id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_NUMCORES, 0, - (i << 8) | level, &resp); - if (ret) - return ret; - - debug_printf( - "cpu:%d CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_NUMCORES index:%d level:%d resp:%x\n", - id->cpu, i, level, resp); - - for (j = 0; j < 4; ++j) { - bucket_info[j + (i * 4)].high_priority_cores_count = - (resp >> (j * 8)) & 0xff; - } - } - - for (k = 0; k < 3; ++k) { - for (i = 0; i < 2; ++i) { - int j; - - ret = isst_send_mbox_command( - id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_RATIOS, 0, - (k << 16) | (i << 8) | level, &resp); - if (ret) - return ret; - - debug_printf( - "cpu:%d CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_RATIOS index:%d level:%d avx:%d resp:%x\n", - id->cpu, i, level, k, resp); - - for (j = 0; j < 4; ++j) { - switch (k) { - case 0: - bucket_info[j + (i * 4)].sse_trl = - (resp >> (j * 8)) & 0xff; - break; - case 1: - bucket_info[j + (i * 4)].avx_trl = - (resp >> (j * 8)) & 0xff; - break; - case 2: - bucket_info[j + (i * 4)].avx512_trl = - (resp >> (j * 8)) & 0xff; - break; - default: - break; - } - } - } - } - return 0; -} int isst_get_fact_info(struct isst_id *id, int level, int fact_bucket, struct isst_fact_info *fact_info) { struct isst_pkg_ctdp_level_info ctdp_level; struct isst_pkg_ctdp pkg_dev; - unsigned int resp; - int j, ret, print; + int ret; ret = isst_get_ctdp_levels(id, &pkg_dev); if (ret) { @@ -643,40 +253,8 @@ int isst_get_fact_info(struct isst_id *id, int level, int fact_bucket, struct is isst_display_error_info_message(1, "turbo-freq feature is not present at this level", 1, level); return -1; } - - ret = isst_send_mbox_command(id->cpu, CONFIG_TDP, - CONFIG_TDP_GET_FACT_LP_CLIPPING_RATIO, 0, - level, &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CONFIG_TDP_GET_FACT_LP_CLIPPING_RATIO resp:%x\n", - id->cpu, resp); - - fact_info->lp_clipping_ratio_license_sse = resp & 0xff; - fact_info->lp_clipping_ratio_license_avx2 = (resp >> 8) & 0xff; - fact_info->lp_clipping_ratio_license_avx512 = (resp >> 16) & 0xff; - - ret = isst_get_fact_bucket_info(id, level, fact_info->bucket_info); - if (ret) - return ret; - - print = 0; - for (j = 0; j < ISST_FACT_MAX_BUCKETS; ++j) { - if (fact_bucket != 0xff && fact_bucket != j) - continue; - - if (!fact_info->bucket_info[j].high_priority_cores_count) - break; - - print = 1; - } - if (!print) { - isst_display_error_info_message(1, "Invalid bucket", 0, 0); - return -1; - } - - return 0; + CHECK_CB(get_fact_info); + return isst_ops->get_fact_info(id, level, fact_bucket, fact_info); } int isst_get_trl(struct isst_id *id, unsigned long long *trl) @@ -709,6 +287,9 @@ int isst_set_trl_from_current_tdp(struct isst_id *id, unsigned long long trl) unsigned long long msr_trl; int ret; + if (id->cpu < 0) + return 0; + if (trl) { msr_trl = trl; } else { @@ -770,6 +351,13 @@ void isst_get_process_ctdp_complete(struct isst_id *id, struct isst_pkg_ctdp *pk } } +void isst_adjust_uncore_freq(struct isst_id *id, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + CHECK_CB(adjust_uncore_freq); + return isst_ops->adjust_uncore_freq(id, config_index, ctdp_level); +} + int isst_get_process_ctdp(struct isst_id *id, int tdp_level, struct isst_pkg_ctdp *pkg_dev) { int i, ret, valid = 0; @@ -838,8 +426,8 @@ int isst_get_process_ctdp(struct isst_id *id, int tdp_level, struct isst_pkg_ctd ctdp_level->tdp_ratio = ctdp_level->sse_p1; } - isst_get_get_trl_from_msr(id, ctdp_level->trl_sse_active_cores); - isst_get_trl_bucket_info(id, &ctdp_level->buckets_info); + isst_get_get_trl_from_msr(id, ctdp_level->trl_ratios[0]); + isst_get_trl_bucket_info(id, i, &ctdp_level->trl_cores); continue; } @@ -851,38 +439,19 @@ int isst_get_process_ctdp(struct isst_id *id, int tdp_level, struct isst_pkg_ctd if (ret) return ret; - ret = isst_get_tjmax_info(id, i, ctdp_level); - if (ret) - return ret; - ctdp_level->core_cpumask_size = alloc_cpu_set(&ctdp_level->core_cpumask); ret = isst_get_coremask_info(id, i, ctdp_level); if (ret) return ret; - ret = isst_get_trl_bucket_info(id, &ctdp_level->buckets_info); - if (ret) - return ret; - - ret = isst_get_get_trl(id, i, 0, - ctdp_level->trl_sse_active_cores); + ret = isst_get_trl_bucket_info(id, i, &ctdp_level->trl_cores); if (ret) return ret; - ret = isst_get_get_trl(id, i, 1, - ctdp_level->trl_avx_active_cores); + ret = isst_get_get_trls(id, i, ctdp_level); if (ret) return ret; - - ret = isst_get_get_trl(id, i, 2, - ctdp_level->trl_avx_512_active_cores); - if (ret) - return ret; - - isst_get_uncore_p0_p1_info(id, i, ctdp_level); - isst_get_p1_info(id, i, ctdp_level); - isst_get_uncore_mem_freq(id, i, ctdp_level); } if (!valid) @@ -893,178 +462,37 @@ int isst_get_process_ctdp(struct isst_id *id, int tdp_level, struct isst_pkg_ctd int isst_clos_get_clos_information(struct isst_id *id, int *enable, int *type) { - unsigned int resp; - int ret; - - ret = isst_send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_QOS_CONFIG, 0, 0, - &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CLOS_PM_QOS_CONFIG resp:%x\n", id->cpu, resp); - - if (resp & BIT(1)) - *enable = 1; - else - *enable = 0; - - if (resp & BIT(2)) - *type = 1; - else - *type = 0; - - return 0; + CHECK_CB(get_clos_information); + return isst_ops->get_clos_information(id, enable, type); } int isst_pm_qos_config(struct isst_id *id, int enable_clos, int priority_type) { - unsigned int req, resp; - int ret; - - if (!enable_clos) { - struct isst_pkg_ctdp pkg_dev; - struct isst_pkg_ctdp_level_info ctdp_level; - - ret = isst_get_ctdp_levels(id, &pkg_dev); - if (ret) { - debug_printf("isst_get_ctdp_levels\n"); - return ret; - } - - ret = isst_get_ctdp_control(id, pkg_dev.current_level, - &ctdp_level); - if (ret) - return ret; - - if (ctdp_level.fact_enabled) { - isst_display_error_info_message(1, "Ignoring request, turbo-freq feature is still enabled", 0, 0); - return -EINVAL; - } - ret = isst_write_pm_config(id, 0); - if (ret) - isst_display_error_info_message(0, "WRITE_PM_CONFIG command failed, ignoring error", 0, 0); - } else { - ret = isst_write_pm_config(id, 1); - if (ret) - isst_display_error_info_message(0, "WRITE_PM_CONFIG command failed, ignoring error", 0, 0); - } - - ret = isst_send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_QOS_CONFIG, 0, 0, - &resp); - if (ret) { - isst_display_error_info_message(1, "CLOS_PM_QOS_CONFIG command failed", 0, 0); - return ret; - } - - debug_printf("cpu:%d CLOS_PM_QOS_CONFIG resp:%x\n", id->cpu, resp); - - req = resp; - - if (enable_clos) - req = req | BIT(1); - else - req = req & ~BIT(1); - - if (priority_type > 1) - isst_display_error_info_message(1, "Invalid priority type: Changing type to ordered", 0, 0); - - if (priority_type) - req = req | BIT(2); - else - req = req & ~BIT(2); - - ret = isst_send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_QOS_CONFIG, - BIT(MBOX_CMD_WRITE_BIT), req, &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CLOS_PM_QOS_CONFIG priority type:%d req:%x\n", id->cpu, - priority_type, req); - - return 0; + CHECK_CB(pm_qos_config); + return isst_ops->pm_qos_config(id, enable_clos, priority_type); } int isst_pm_get_clos(struct isst_id *id, int clos, struct isst_clos_config *clos_config) { - unsigned int resp; - int ret; - - ret = isst_send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_CLOS, clos, 0, - &resp); - if (ret) - return ret; - - clos_config->epp = resp & 0x0f; - clos_config->clos_prop_prio = (resp >> 4) & 0x0f; - clos_config->clos_min = (resp >> 8) & 0xff; - clos_config->clos_max = (resp >> 16) & 0xff; - clos_config->clos_desired = (resp >> 24) & 0xff; - - return 0; + CHECK_CB(pm_get_clos); + return isst_ops->pm_get_clos(id, clos, clos_config); } int isst_set_clos(struct isst_id *id, int clos, struct isst_clos_config *clos_config) { - unsigned int req, resp; - unsigned int param; - int ret; - - req = clos_config->epp & 0x0f; - req |= (clos_config->clos_prop_prio & 0x0f) << 4; - req |= (clos_config->clos_min & 0xff) << 8; - req |= (clos_config->clos_max & 0xff) << 16; - req |= (clos_config->clos_desired & 0xff) << 24; - - param = BIT(MBOX_CMD_WRITE_BIT) | clos; - - ret = isst_send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PM_CLOS, param, req, - &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CLOS_PM_CLOS param:%x req:%x\n", id->cpu, param, req); - - return 0; + CHECK_CB(set_clos); + return isst_ops->set_clos(id, clos, clos_config); } int isst_clos_get_assoc_status(struct isst_id *id, int *clos_id) { - unsigned int resp; - unsigned int param; - int core_id, ret; - - core_id = find_phy_core_num(id->cpu); - param = core_id; - - ret = isst_send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PQR_ASSOC, param, 0, - &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CLOS_PQR_ASSOC param:%x resp:%x\n", id->cpu, param, - resp); - *clos_id = (resp >> 16) & 0x03; - - return 0; + CHECK_CB(clos_get_assoc_status); + return isst_ops->clos_get_assoc_status(id, clos_id); } int isst_clos_associate(struct isst_id *id, int clos_id) { - unsigned int req, resp; - unsigned int param; - int core_id, ret; + CHECK_CB(clos_associate); + return isst_ops->clos_associate(id, clos_id); - req = (clos_id & 0x03) << 16; - core_id = find_phy_core_num(id->cpu); - param = BIT(MBOX_CMD_WRITE_BIT) | core_id; - - ret = isst_send_mbox_command(id->cpu, CONFIG_CLOS, CLOS_PQR_ASSOC, param, - req, &resp); - if (ret) - return ret; - - debug_printf("cpu:%d CLOS_PQR_ASSOC param:%x req:%x\n", id->cpu, param, - req); - - return 0; } diff --git a/tools/power/x86/intel-speed-select/isst-daemon.c b/tools/power/x86/intel-speed-select/isst-daemon.c index c2290ef0e3af..12053fa43542 100644 --- a/tools/power/x86/intel-speed-select/isst-daemon.c +++ b/tools/power/x86/intel-speed-select/isst-daemon.c @@ -20,16 +20,17 @@ #include "isst.h" -static int per_package_levels_info[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE]; -static time_t per_package_levels_tm[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE]; +static int per_package_levels_info[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE][MAX_PUNIT_PER_DIE]; +static time_t per_package_levels_tm[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE][MAX_PUNIT_PER_DIE]; static void init_levels(void) { - int i, j; + int i, j, k; for (i = 0; i < MAX_PACKAGE_COUNT; ++i) for (j = 0; j < MAX_DIE_PER_PACKAGE; ++j) - per_package_levels_info[i][j] = -1; + for (k = 0; k < MAX_PUNIT_PER_DIE; ++k) + per_package_levels_info[i][j][k] = -1; } void process_level_change(struct isst_id *id) @@ -39,16 +40,16 @@ void process_level_change(struct isst_id *id) time_t tm; int ret; - if (id->pkg < 0 || id->die < 0) { + if (id->pkg < 0 || id->die < 0 || id->punit < 0) { debug_printf("Invalid package/die info for cpu:%d\n", id->cpu); return; } tm = time(NULL); - if (tm - per_package_levels_tm[id->pkg][id->die] < 2) + if (tm - per_package_levels_tm[id->pkg][id->die][id->punit] < 2) return; - per_package_levels_tm[id->pkg][id->die] = tm; + per_package_levels_tm[id->pkg][id->die][id->punit] = tm; ret = isst_get_ctdp_levels(id, &pkg_dev); if (ret) { @@ -64,14 +65,14 @@ void process_level_change(struct isst_id *id) return; } - if (per_package_levels_info[id->pkg][id->die] == pkg_dev.current_level) + if (per_package_levels_info[id->pkg][id->die][id->punit] == pkg_dev.current_level) return; debug_printf("**Config level change for cpu:%d pkg:%d die:%d from %d to %d\n", - id->cpu, id->pkg, id->die, per_package_levels_info[id->pkg][id->die], + id->cpu, id->pkg, id->die, per_package_levels_info[id->pkg][id->die][id->punit], pkg_dev.current_level); - per_package_levels_info[id->pkg][id->die] = pkg_dev.current_level; + per_package_levels_info[id->pkg][id->die][id->punit] = pkg_dev.current_level; ctdp_level.core_cpumask_size = alloc_cpu_set(&ctdp_level.core_cpumask); @@ -82,6 +83,19 @@ void process_level_change(struct isst_id *id) return; } + if (use_cgroupv2()) { + int ret; + + ret = enable_cpuset_controller(); + if (ret) + goto use_offline; + + isolate_cpus(id, ctdp_level.core_cpumask_size, ctdp_level.core_cpumask, pkg_dev.current_level); + + goto free_mask; + } + +use_offline: if (ctdp_level.cpu_count) { int i, max_cpus = get_topo_max_cpus(); for (i = 0; i < max_cpus; ++i) { @@ -96,7 +110,7 @@ void process_level_change(struct isst_id *id) } } } - +free_mask: free_cpu_set(ctdp_level.core_cpumask); } @@ -108,7 +122,7 @@ static void _poll_for_config_change(struct isst_id *id, void *arg1, void *arg2, static void poll_for_config_change(void) { - for_each_online_package_in_set(_poll_for_config_change, NULL, NULL, + for_each_online_power_domain_in_set(_poll_for_config_change, NULL, NULL, NULL, NULL); } diff --git a/tools/power/x86/intel-speed-select/isst-display.c b/tools/power/x86/intel-speed-select/isst-display.c index 7feadac04a6f..0403d42ab1ba 100644 --- a/tools/power/x86/intel-speed-select/isst-display.c +++ b/tools/power/x86/intel-speed-select/isst-display.c @@ -169,21 +169,30 @@ static void format_and_print(FILE *outf, int level, char *header, char *value) static int print_package_info(struct isst_id *id, FILE *outf) { char header[256]; + int level = 1; if (out_format_is_json()) { - snprintf(header, sizeof(header), "package-%d:die-%d:cpu-%d", - id->pkg, id->die, id->cpu); - format_and_print(outf, 1, header, NULL); + if (api_version() > 1) + snprintf(header, sizeof(header), "package-%d:die-%d:powerdomain-%d:cpu-%d", + id->pkg, id->die, id->punit, id->cpu); + else + snprintf(header, sizeof(header), "package-%d:die-%d:cpu-%d", + id->pkg, id->die, id->cpu); + format_and_print(outf, level, header, NULL); return 1; } snprintf(header, sizeof(header), "package-%d", id->pkg); - format_and_print(outf, 1, header, NULL); + format_and_print(outf, level++, header, NULL); snprintf(header, sizeof(header), "die-%d", id->die); - format_and_print(outf, 2, header, NULL); + format_and_print(outf, level++, header, NULL); + if (api_version() > 1) { + snprintf(header, sizeof(header), "powerdomain-%d", id->punit); + format_and_print(outf, level++, header, NULL); + } snprintf(header, sizeof(header), "cpu-%d", id->cpu); - format_and_print(outf, 3, header, NULL); + format_and_print(outf, level, header, NULL); - return 3; + return level; } static void _isst_pbf_display_information(struct isst_id *id, FILE *outf, int level, @@ -198,7 +207,7 @@ static void _isst_pbf_display_information(struct isst_id *id, FILE *outf, int le snprintf(header, sizeof(header), "high-priority-base-frequency(MHz)"); snprintf(value, sizeof(value), "%d", - pbf_info->p1_high * DISP_FREQ_MULTIPLIER); + pbf_info->p1_high * isst_get_disp_freq_multiplier()); format_and_print(outf, disp_level + 1, header, value); snprintf(header, sizeof(header), "high-priority-cpu-mask"); @@ -214,7 +223,7 @@ static void _isst_pbf_display_information(struct isst_id *id, FILE *outf, int le snprintf(header, sizeof(header), "low-priority-base-frequency(MHz)"); snprintf(value, sizeof(value), "%d", - pbf_info->p1_low * DISP_FREQ_MULTIPLIER); + pbf_info->p1_low * isst_get_disp_freq_multiplier()); format_and_print(outf, disp_level + 1, header, value); if (is_clx_n_platform()) @@ -235,6 +244,7 @@ static void _isst_fact_display_information(struct isst_id *id, FILE *outf, int l int base_level) { struct isst_fact_bucket_info *bucket_info = fact_info->bucket_info; + int trl_max_levels = isst_get_trl_max_levels(); char header[256]; char value[256]; int print = 0, j; @@ -243,7 +253,8 @@ static void _isst_fact_display_information(struct isst_id *id, FILE *outf, int l if (fact_bucket != 0xff && fact_bucket != j) continue; - if (!bucket_info[j].high_priority_cores_count) + /* core count must be valid for CPU power domain */ + if (!bucket_info[j].hp_cores && id->cpu >= 0) break; print = 1; @@ -256,10 +267,12 @@ static void _isst_fact_display_information(struct isst_id *id, FILE *outf, int l snprintf(header, sizeof(header), "speed-select-turbo-freq-properties"); format_and_print(outf, base_level, header, NULL); for (j = 0; j < ISST_FACT_MAX_BUCKETS; ++j) { + int i; + if (fact_bucket != 0xff && fact_bucket != j) continue; - if (!bucket_info[j].high_priority_cores_count) + if (!bucket_info[j].hp_cores) break; snprintf(header, sizeof(header), "bucket-%d", j); @@ -267,75 +280,49 @@ static void _isst_fact_display_information(struct isst_id *id, FILE *outf, int l snprintf(header, sizeof(header), "high-priority-cores-count"); snprintf(value, sizeof(value), "%d", - bucket_info[j].high_priority_cores_count); + bucket_info[j].hp_cores); format_and_print(outf, base_level + 2, header, value); - - if (fact_avx & 0x01) { - snprintf(header, sizeof(header), - "high-priority-max-frequency(MHz)"); - snprintf(value, sizeof(value), "%d", - bucket_info[j].sse_trl * DISP_FREQ_MULTIPLIER); - format_and_print(outf, base_level + 2, header, value); - } - - if (fact_avx & 0x02) { - snprintf(header, sizeof(header), - "high-priority-max-avx2-frequency(MHz)"); - snprintf(value, sizeof(value), "%d", - bucket_info[j].avx_trl * DISP_FREQ_MULTIPLIER); - format_and_print(outf, base_level + 2, header, value); - } - - if (fact_avx & 0x04) { - snprintf(header, sizeof(header), - "high-priority-max-avx512-frequency(MHz)"); + for (i = 0; i < trl_max_levels; i++) { + if (!bucket_info[j].hp_ratios[i] || (fact_avx != 0xFF && !(fact_avx & (1 << i)))) + continue; + if (i == 0 && api_version() == 1 && !is_emr_platform()) + snprintf(header, sizeof(header), + "high-priority-max-frequency(MHz)"); + else + snprintf(header, sizeof(header), + "high-priority-max-%s-frequency(MHz)", isst_get_trl_level_name(i)); snprintf(value, sizeof(value), "%d", - bucket_info[j].avx512_trl * - DISP_FREQ_MULTIPLIER); + bucket_info[j].hp_ratios[i] * isst_get_disp_freq_multiplier()); format_and_print(outf, base_level + 2, header, value); } } snprintf(header, sizeof(header), "speed-select-turbo-freq-clip-frequencies"); format_and_print(outf, base_level + 1, header, NULL); - snprintf(header, sizeof(header), "low-priority-max-frequency(MHz)"); - snprintf(value, sizeof(value), "%d", - fact_info->lp_clipping_ratio_license_sse * - DISP_FREQ_MULTIPLIER); - format_and_print(outf, base_level + 2, header, value); - snprintf(header, sizeof(header), - "low-priority-max-avx2-frequency(MHz)"); - snprintf(value, sizeof(value), "%d", - fact_info->lp_clipping_ratio_license_avx2 * - DISP_FREQ_MULTIPLIER); - format_and_print(outf, base_level + 2, header, value); - snprintf(header, sizeof(header), - "low-priority-max-avx512-frequency(MHz)"); - snprintf(value, sizeof(value), "%d", - fact_info->lp_clipping_ratio_license_avx512 * - DISP_FREQ_MULTIPLIER); - format_and_print(outf, base_level + 2, header, value); + + for (j = 0; j < trl_max_levels; j++) { + if (!fact_info->lp_ratios[j]) + continue; + + /* No AVX level name for SSE to be consistent with previous formatting */ + if (j == 0 && api_version() == 1 && !is_emr_platform()) + snprintf(header, sizeof(header), "low-priority-max-frequency(MHz)"); + else + snprintf(header, sizeof(header), "low-priority-max-%s-frequency(MHz)", + isst_get_trl_level_name(j)); + snprintf(value, sizeof(value), "%d", + fact_info->lp_ratios[j] * isst_get_disp_freq_multiplier()); + format_and_print(outf, base_level + 2, header, value); + } } void isst_ctdp_display_core_info(struct isst_id *id, FILE *outf, char *prefix, unsigned int val, char *str0, char *str1) { - char header[256]; char value[256]; - int level = 1; + int level = print_package_info(id, outf); - if (out_format_is_json()) { - snprintf(header, sizeof(header), "package-%d:die-%d:cpu-%d", - id->pkg, id->die, id->cpu); - format_and_print(outf, level++, header, NULL); - } else { - snprintf(header, sizeof(header), "package-%d", id->pkg); - format_and_print(outf, level++, header, NULL); - snprintf(header, sizeof(header), "die-%d", id->die); - format_and_print(outf, level++, header, NULL); - snprintf(header, sizeof(header), "cpu-%d", id->cpu); - format_and_print(outf, level++, header, NULL); - } + level++; if (str0 && !val) snprintf(value, sizeof(value), "%s", str0); @@ -354,6 +341,7 @@ void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level char header[256]; char value[512]; static int level; + int trl_max_levels = isst_get_trl_max_levels(); int i; if (pkg_dev->processed) @@ -361,7 +349,7 @@ void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level for (i = 0; i <= pkg_dev->levels; ++i) { struct isst_pkg_ctdp_level_info *ctdp_level; - int j; + int j, k; ctdp_level = &pkg_dev->ctdp_level[i]; if (!ctdp_level->processed) @@ -371,31 +359,33 @@ void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level ctdp_level->level); format_and_print(outf, level + 1, header, NULL); - snprintf(header, sizeof(header), "cpu-count"); - j = get_cpu_count(id); - snprintf(value, sizeof(value), "%d", j); - format_and_print(outf, level + 2, header, value); - - j = CPU_COUNT_S(ctdp_level->core_cpumask_size, - ctdp_level->core_cpumask); - if (j) { - snprintf(header, sizeof(header), "enable-cpu-count"); + if (id->cpu >= 0) { + snprintf(header, sizeof(header), "cpu-count"); + j = get_cpu_count(id); snprintf(value, sizeof(value), "%d", j); format_and_print(outf, level + 2, header, value); - } - if (ctdp_level->core_cpumask_size) { - snprintf(header, sizeof(header), "enable-cpu-mask"); - printcpumask(sizeof(value), value, - ctdp_level->core_cpumask_size, - ctdp_level->core_cpumask); - format_and_print(outf, level + 2, header, value); + j = CPU_COUNT_S(ctdp_level->core_cpumask_size, + ctdp_level->core_cpumask); + if (j) { + snprintf(header, sizeof(header), "enable-cpu-count"); + snprintf(value, sizeof(value), "%d", j); + format_and_print(outf, level + 2, header, value); + } - snprintf(header, sizeof(header), "enable-cpu-list"); - printcpulist(sizeof(value), value, - ctdp_level->core_cpumask_size, - ctdp_level->core_cpumask); - format_and_print(outf, level + 2, header, value); + if (ctdp_level->core_cpumask_size) { + snprintf(header, sizeof(header), "enable-cpu-mask"); + printcpumask(sizeof(value), value, + ctdp_level->core_cpumask_size, + ctdp_level->core_cpumask); + format_and_print(outf, level + 2, header, value); + + snprintf(header, sizeof(header), "enable-cpu-list"); + printcpulist(sizeof(value), value, + ctdp_level->core_cpumask_size, + ctdp_level->core_cpumask); + format_and_print(outf, level + 2, header, value); + } } snprintf(header, sizeof(header), "thermal-design-power-ratio"); @@ -406,41 +396,48 @@ void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level if (!ctdp_level->sse_p1) ctdp_level->sse_p1 = ctdp_level->tdp_ratio; snprintf(value, sizeof(value), "%d", - ctdp_level->sse_p1 * DISP_FREQ_MULTIPLIER); + ctdp_level->sse_p1 * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); if (ctdp_level->avx2_p1) { snprintf(header, sizeof(header), "base-frequency-avx2(MHz)"); snprintf(value, sizeof(value), "%d", - ctdp_level->avx2_p1 * DISP_FREQ_MULTIPLIER); + ctdp_level->avx2_p1 * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); } if (ctdp_level->avx512_p1) { snprintf(header, sizeof(header), "base-frequency-avx512(MHz)"); snprintf(value, sizeof(value), "%d", - ctdp_level->avx512_p1 * DISP_FREQ_MULTIPLIER); + ctdp_level->avx512_p1 * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); } if (ctdp_level->uncore_pm) { snprintf(header, sizeof(header), "uncore-frequency-min(MHz)"); snprintf(value, sizeof(value), "%d", - ctdp_level->uncore_pm * DISP_FREQ_MULTIPLIER); + ctdp_level->uncore_pm * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); } if (ctdp_level->uncore_p0) { snprintf(header, sizeof(header), "uncore-frequency-max(MHz)"); snprintf(value, sizeof(value), "%d", - ctdp_level->uncore_p0 * DISP_FREQ_MULTIPLIER); + ctdp_level->uncore_p0 * isst_get_disp_freq_multiplier()); + format_and_print(outf, level + 2, header, value); + } + + if (ctdp_level->amx_p1) { + snprintf(header, sizeof(header), "base-frequency-amx(MHz)"); + snprintf(value, sizeof(value), "%d", + ctdp_level->amx_p1 * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); } if (ctdp_level->uncore_p1) { snprintf(header, sizeof(header), "uncore-frequency-base(MHz)"); snprintf(value, sizeof(value), "%d", - ctdp_level->uncore_p1 * DISP_FREQ_MULTIPLIER); + ctdp_level->uncore_p1 * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); } @@ -451,6 +448,13 @@ void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level format_and_print(outf, level + 2, header, value); } + if (api_version() > 1) { + snprintf(header, sizeof(header), "cooling_type"); + snprintf(value, sizeof(value), "%d", + ctdp_level->cooling_type); + format_and_print(outf, level + 2, header, value); + } + snprintf(header, sizeof(header), "speed-select-turbo-freq"); if (ctdp_level->fact_support) { @@ -505,54 +509,24 @@ void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level format_and_print(outf, level + 2, header, value); } - snprintf(header, sizeof(header), "turbo-ratio-limits-sse"); - format_and_print(outf, level + 2, header, NULL); - for (j = 0; j < 8; ++j) { - snprintf(header, sizeof(header), "bucket-%d", j); - format_and_print(outf, level + 3, header, NULL); + for (k = 0; k < trl_max_levels; k++) { + if (!ctdp_level->trl_ratios[k][0]) + continue; - snprintf(header, sizeof(header), "core-count"); - snprintf(value, sizeof(value), "%llu", (ctdp_level->buckets_info >> (j * 8)) & 0xff); - format_and_print(outf, level + 4, header, value); - - snprintf(header, sizeof(header), - "max-turbo-frequency(MHz)"); - snprintf(value, sizeof(value), "%d", - ctdp_level->trl_sse_active_cores[j] * - DISP_FREQ_MULTIPLIER); - format_and_print(outf, level + 4, header, value); - } - - if (ctdp_level->trl_avx_active_cores[0]) { - snprintf(header, sizeof(header), "turbo-ratio-limits-avx2"); + snprintf(header, sizeof(header), "turbo-ratio-limits-%s", isst_get_trl_level_name(k)); format_and_print(outf, level + 2, header, NULL); - for (j = 0; j < 8; ++j) { - snprintf(header, sizeof(header), "bucket-%d", j); - format_and_print(outf, level + 3, header, NULL); - snprintf(header, sizeof(header), "core-count"); - snprintf(value, sizeof(value), "%llu", (ctdp_level->buckets_info >> (j * 8)) & 0xff); - format_and_print(outf, level + 4, header, value); - - snprintf(header, sizeof(header), "max-turbo-frequency(MHz)"); - snprintf(value, sizeof(value), "%d", ctdp_level->trl_avx_active_cores[j] * DISP_FREQ_MULTIPLIER); - format_and_print(outf, level + 4, header, value); - } - } - - if (ctdp_level->trl_avx_512_active_cores[0]) { - snprintf(header, sizeof(header), "turbo-ratio-limits-avx512"); - format_and_print(outf, level + 2, header, NULL); for (j = 0; j < 8; ++j) { snprintf(header, sizeof(header), "bucket-%d", j); format_and_print(outf, level + 3, header, NULL); snprintf(header, sizeof(header), "core-count"); - snprintf(value, sizeof(value), "%llu", (ctdp_level->buckets_info >> (j * 8)) & 0xff); + + snprintf(value, sizeof(value), "%llu", (ctdp_level->trl_cores >> (j * 8)) & 0xff); format_and_print(outf, level + 4, header, value); snprintf(header, sizeof(header), "max-turbo-frequency(MHz)"); - snprintf(value, sizeof(value), "%d", ctdp_level->trl_avx_512_active_cores[j] * DISP_FREQ_MULTIPLIER); + snprintf(value, sizeof(value), "%d", ctdp_level->trl_ratios[k][j] * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 4, header, value); } } @@ -631,18 +605,18 @@ void isst_clos_display_information(struct isst_id *id, FILE *outf, int clos, format_and_print(outf, level + 2, header, value); snprintf(header, sizeof(header), "clos-min"); - snprintf(value, sizeof(value), "%d MHz", clos_config->clos_min * DISP_FREQ_MULTIPLIER); + snprintf(value, sizeof(value), "%d MHz", clos_config->clos_min * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); snprintf(header, sizeof(header), "clos-max"); - if (clos_config->clos_max == 0xff) + if ((clos_config->clos_max * isst_get_disp_freq_multiplier()) == 25500) snprintf(value, sizeof(value), "Max Turbo frequency"); else - snprintf(value, sizeof(value), "%d MHz", clos_config->clos_max * DISP_FREQ_MULTIPLIER); + snprintf(value, sizeof(value), "%d MHz", clos_config->clos_max * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); snprintf(header, sizeof(header), "clos-desired"); - snprintf(value, sizeof(value), "%d MHz", clos_config->clos_desired * DISP_FREQ_MULTIPLIER); + snprintf(value, sizeof(value), "%d MHz", clos_config->clos_desired * isst_get_disp_freq_multiplier()); format_and_print(outf, level + 2, header, value); format_and_print(outf, level, NULL, NULL); @@ -717,8 +691,7 @@ void isst_display_result(struct isst_id *id, FILE *outf, char *feature, char *cm char value[256]; int level = 3; - if (id->cpu >= 0) - level = print_package_info(id, outf); + level = print_package_info(id, outf); snprintf(header, sizeof(header), "%s", feature); format_and_print(outf, level + 1, header, NULL); diff --git a/tools/power/x86/intel-speed-select/isst.h b/tools/power/x86/intel-speed-select/isst.h index 824876e31e23..54fc21575d56 100644 --- a/tools/power/x86/intel-speed-select/isst.h +++ b/tools/power/x86/intel-speed-select/isst.h @@ -28,6 +28,8 @@ #include <stdarg.h> #include <sys/ioctl.h> +#include <linux/isst_if.h> + #define BIT(x) (1 << (x)) #define BIT_ULL(nr) (1ULL << (nr)) #define GENMASK(h, l) (((~0UL) << (l)) & (~0UL >> (sizeof(long) * 8 - 1 - (h)))) @@ -77,29 +79,29 @@ #define DISP_FREQ_MULTIPLIER 100 -#define MAX_PACKAGE_COUNT 8 -#define MAX_DIE_PER_PACKAGE 2 +#define MAX_PACKAGE_COUNT 8 +#define MAX_DIE_PER_PACKAGE 2 +#define MAX_PUNIT_PER_DIE 8 /* Unified structure to specific a CPU or a Power Domain */ struct isst_id { int cpu; int pkg; int die; + int punit; }; struct isst_clos_config { + unsigned int clos_min; + unsigned int clos_max; unsigned char epp; unsigned char clos_prop_prio; - unsigned char clos_min; - unsigned char clos_max; unsigned char clos_desired; }; struct isst_fact_bucket_info { - int high_priority_cores_count; - int sse_trl; - int avx_trl; - int avx512_trl; + int hp_cores; + int hp_ratios[TRL_MAX_LEVELS]; }; struct isst_pbf_info { @@ -117,9 +119,7 @@ struct isst_pbf_info { #define ISST_TRL_MAX_ACTIVE_CORES 8 #define ISST_FACT_MAX_BUCKETS 8 struct isst_fact_info { - int lp_clipping_ratio_license_sse; - int lp_clipping_ratio_license_avx2; - int lp_clipping_ratio_license_avx512; + int lp_ratios[TRL_MAX_LEVELS]; struct isst_fact_bucket_info bucket_info[ISST_FACT_MAX_BUCKETS]; }; @@ -143,20 +143,20 @@ struct isst_pkg_ctdp_level_info { int pkg_max_power; int fact; int t_proc_hot; + int cooling_type; int uncore_p0; int uncore_p1; int uncore_pm; int sse_p1; int avx2_p1; int avx512_p1; + int amx_p1; int mem_freq; size_t core_cpumask_size; cpu_set_t *core_cpumask; int cpu_count; - unsigned long long buckets_info; - int trl_sse_active_cores[ISST_TRL_MAX_ACTIVE_CORES]; - int trl_avx_active_cores[ISST_TRL_MAX_ACTIVE_CORES]; - int trl_avx_512_active_cores[ISST_TRL_MAX_ACTIVE_CORES]; + unsigned long long trl_cores; /* Buckets info */ + int trl_ratios[TRL_MAX_LEVELS][ISST_TRL_MAX_ACTIVE_CORES]; int kobj_bucket_index; int active_bucket; int fact_max_index; @@ -178,13 +178,48 @@ struct isst_pkg_ctdp { struct isst_pkg_ctdp_level_info ctdp_level[ISST_MAX_TDP_LEVELS]; }; +enum isst_platform_param { + ISST_PARAM_MBOX_DELAY, + ISST_PARAM_MBOX_RETRIES, +}; + +struct isst_platform_ops { + int (*get_disp_freq_multiplier)(void); + int (*get_trl_max_levels)(void); + char *(*get_trl_level_name)(int level); + void (*update_platform_param)(enum isst_platform_param param, int value); + int (*is_punit_valid)(struct isst_id *id); + int (*read_pm_config)(struct isst_id *id, int *cp_state, int *cp_cap); + int (*get_config_levels)(struct isst_id *id, struct isst_pkg_ctdp *pkg_ctdp); + int (*get_ctdp_control)(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level); + int (*get_tdp_info)(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level); + int (*get_pwr_info)(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level); + int (*get_coremask_info)(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level); + int (*get_get_trl)(struct isst_id *id, int level, int avx_level, int *trl); + int (*get_get_trls)(struct isst_id *id, int level, struct isst_pkg_ctdp_level_info *ctdp_level); + int (*get_trl_bucket_info)(struct isst_id *id, int level, unsigned long long *buckets_info); + int (*set_tdp_level)(struct isst_id *id, int tdp_level); + int (*get_pbf_info)(struct isst_id *id, int level, struct isst_pbf_info *pbf_info); + int (*set_pbf_fact_status)(struct isst_id *id, int pbf, int enable); + int (*get_fact_info)(struct isst_id *id, int level, int fact_bucket, struct isst_fact_info *fact_info); + void (*adjust_uncore_freq)(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level); + int (*get_clos_information)(struct isst_id *id, int *enable, int *type); + int (*pm_qos_config)(struct isst_id *id, int enable_clos, int priority_type); + int (*pm_get_clos)(struct isst_id *id, int clos, struct isst_clos_config *clos_config); + int (*set_clos)(struct isst_id *id, int clos, struct isst_clos_config *clos_config); + int (*clos_get_assoc_status)(struct isst_id *id, int *clos_id); + int (*clos_associate)(struct isst_id *id, int clos_id); +}; + extern int is_cpu_in_power_domain(int cpu, struct isst_id *id); extern int get_topo_max_cpus(void); extern int get_cpu_count(struct isst_id *id); extern int get_max_punit_core_id(struct isst_id *id); +extern int api_version(void); /* Common interfaces */ FILE *get_output_file(void); +extern int is_debug_enabled(void); extern void debug_printf(const char *format, ...); extern int out_format_is_json(void); extern void set_isst_id(struct isst_id *id, int cpu); @@ -196,21 +231,22 @@ extern void set_cpu_mask_from_punit_coremask(struct isst_id *id, size_t core_cpumask_size, cpu_set_t *core_cpumask, int *cpu_cnt); - -extern int isst_send_mbox_command(unsigned int cpu, unsigned char command, - unsigned char sub_command, - unsigned int write, - unsigned int req_data, unsigned int *resp); - extern int isst_send_msr_command(unsigned int cpu, unsigned int command, int write, unsigned long long *req_resp); +extern int isst_set_platform_ops(int api_version); +extern void isst_update_platform_param(enum isst_platform_param, int vale); +extern int isst_get_disp_freq_multiplier(void); +extern int isst_get_trl_max_levels(void); +extern char *isst_get_trl_level_name(int level); +extern int isst_is_punit_valid(struct isst_id *id); + extern int isst_get_ctdp_levels(struct isst_id *id, struct isst_pkg_ctdp *pkg_dev); extern int isst_get_ctdp_control(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level); extern int isst_get_coremask_info(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level); -extern void isst_get_uncore_p0_p1_info(struct isst_id *id, int config_index, +extern void isst_adjust_uncore_freq(struct isst_id *id, int config_index, struct isst_pkg_ctdp_level_info *ctdp_level); extern int isst_get_process_ctdp(struct isst_id *id, int tdp_level, struct isst_pkg_ctdp *pkg_dev); @@ -228,11 +264,8 @@ extern int isst_set_tdp_level(struct isst_id *id, int tdp_level); extern int isst_set_pbf_fact_status(struct isst_id *id, int pbf, int enable); extern int isst_get_pbf_info(struct isst_id *id, int level, struct isst_pbf_info *pbf_info); -extern void isst_get_pbf_info_complete(struct isst_pbf_info *pbf_info); extern int isst_get_fact_info(struct isst_id *id, int level, int fact_bucket, struct isst_fact_info *fact_info); -extern int isst_get_fact_bucket_info(struct isst_id *id, int level, - struct isst_fact_bucket_info *bucket_info); extern void isst_fact_display_information(struct isst_id *id, FILE *outf, int level, int fact_bucket, int fact_avx, struct isst_fact_info *fact_info); @@ -265,11 +298,12 @@ extern int isst_read_pm_config(struct isst_id *id, int *cp_state, int *cp_cap); extern void isst_display_error_info_message(int error, char *msg, int arg_valid, int arg); extern int is_skx_based_platform(void); extern int is_spr_platform(void); +extern int is_emr_platform(void); extern int is_icx_platform(void); extern void isst_trl_display_information(struct isst_id *id, FILE *outf, unsigned long long trl); extern void set_cpu_online_offline(int cpu, int state); -extern void for_each_online_package_in_set(void (*callback)(struct isst_id *, void *, void *, +extern void for_each_online_power_domain_in_set(void (*callback)(struct isst_id *, void *, void *, void *, void *), void *arg1, void *arg2, void *arg3, void *arg4); @@ -277,4 +311,14 @@ extern int isst_daemon(int debug_mode, int poll_interval, int no_daemon); extern void process_level_change(struct isst_id *id); extern int hfi_main(void); extern void hfi_exit(void); + +/* Interface specific callbacks */ +extern struct isst_platform_ops *mbox_get_platform_ops(void); +extern struct isst_platform_ops *tpmi_get_platform_ops(void); + +/* Cgroup related interface */ +extern int enable_cpuset_controller(void); +extern int isolate_cpus(struct isst_id *id, int mask_size, cpu_set_t *cpu_mask, int level); +extern int use_cgroupv2(void); + #endif diff --git a/tools/rcu/extract-stall.sh b/tools/rcu/extract-stall.sh index e565697c9f90..08a39ad44320 100644..100755 --- a/tools/rcu/extract-stall.sh +++ b/tools/rcu/extract-stall.sh @@ -1,11 +1,25 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0+ -# -# Extract any RCU CPU stall warnings present in specified file. -# Filter out clocksource lines. Note that preceding-lines excludes the -# initial line of the stall warning but trailing-lines includes it. -# -# Usage: extract-stall.sh dmesg-file [ preceding-lines [ trailing-lines ] ] + +usage() { + echo Extract any RCU CPU stall warnings present in specified file. + echo Filter out clocksource lines. Note that preceding-lines excludes the + echo initial line of the stall warning but trailing-lines includes it. + echo + echo Usage: $(basename $0) dmesg-file [ preceding-lines [ trailing-lines ] ] + echo + echo Error: $1 +} + +# Terminate the script, if the argument is missing + +if test -f "$1" && test -r "$1" +then + : +else + usage "Console log file \"$1\" missing or unreadable." + exit 1 +fi echo $1 preceding_lines="${2-3}" diff --git a/tools/scripts/Makefile.arch b/tools/scripts/Makefile.arch index 1c72d07cb9fe..f6a50f06dfc4 100644 --- a/tools/scripts/Makefile.arch +++ b/tools/scripts/Makefile.arch @@ -29,11 +29,6 @@ ifeq ($(ARCH),sparc64) SRCARCH := sparc endif -# Additional ARCH settings for sh -ifeq ($(ARCH),sh64) - SRCARCH := sh -endif - # Additional ARCH settings for loongarch ifeq ($(ARCH),loongarch32) SRCARCH := loongarch diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index 741f15420467..3905c43369c3 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -123,7 +123,7 @@ def _suites_from_test_list(tests: List[str]) -> List[str]: parts = t.split('.', maxsplit=2) if len(parts) != 2: raise ValueError(f'internal KUnit error, test name should be of the form "<suite>.<test>", got "{t}"') - suite, case = parts + suite, _ = parts if not suites or suites[-1] != suite: suites.append(suite) return suites @@ -269,7 +269,7 @@ def massage_argv(argv: Sequence[str]) -> Sequence[str]: def get_default_jobs() -> int: return len(os.sched_getaffinity(0)) -def add_common_opts(parser) -> None: +def add_common_opts(parser: argparse.ArgumentParser) -> None: parser.add_argument('--build_dir', help='As in the make command, it specifies the build ' 'directory.', @@ -320,13 +320,13 @@ def add_common_opts(parser) -> None: help='Additional QEMU arguments, e.g. "-smp 8"', action='append', metavar='') -def add_build_opts(parser) -> None: +def add_build_opts(parser: argparse.ArgumentParser) -> None: parser.add_argument('--jobs', help='As in the make command, "Specifies the number of ' 'jobs (commands) to run simultaneously."', type=int, default=get_default_jobs(), metavar='N') -def add_exec_opts(parser) -> None: +def add_exec_opts(parser: argparse.ArgumentParser) -> None: parser.add_argument('--timeout', help='maximum number of seconds to allow for all tests ' 'to run. This does not include time taken to build the ' @@ -351,7 +351,7 @@ def add_exec_opts(parser) -> None: type=str, choices=['suite', 'test']) -def add_parse_opts(parser) -> None: +def add_parse_opts(parser: argparse.ArgumentParser) -> None: parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. ' 'By default, filters to just KUnit output. Use ' '--raw_output=all to show everything', @@ -386,7 +386,7 @@ def tree_from_args(cli_args: argparse.Namespace) -> kunit_kernel.LinuxSourceTree extra_qemu_args=qemu_args) -def run_handler(cli_args): +def run_handler(cli_args: argparse.Namespace) -> None: if not os.path.exists(cli_args.build_dir): os.mkdir(cli_args.build_dir) @@ -405,7 +405,7 @@ def run_handler(cli_args): sys.exit(1) -def config_handler(cli_args): +def config_handler(cli_args: argparse.Namespace) -> None: if cli_args.build_dir and ( not os.path.exists(cli_args.build_dir)): os.mkdir(cli_args.build_dir) @@ -421,7 +421,7 @@ def config_handler(cli_args): sys.exit(1) -def build_handler(cli_args): +def build_handler(cli_args: argparse.Namespace) -> None: linux = tree_from_args(cli_args) request = KunitBuildRequest(build_dir=cli_args.build_dir, make_options=cli_args.make_options, @@ -434,7 +434,7 @@ def build_handler(cli_args): sys.exit(1) -def exec_handler(cli_args): +def exec_handler(cli_args: argparse.Namespace) -> None: linux = tree_from_args(cli_args) exec_request = KunitExecRequest(raw_output=cli_args.raw_output, build_dir=cli_args.build_dir, @@ -450,10 +450,10 @@ def exec_handler(cli_args): sys.exit(1) -def parse_handler(cli_args): +def parse_handler(cli_args: argparse.Namespace) -> None: if cli_args.file is None: - sys.stdin.reconfigure(errors='backslashreplace') # pytype: disable=attribute-error - kunit_output = sys.stdin + sys.stdin.reconfigure(errors='backslashreplace') # type: ignore + kunit_output = sys.stdin # type: Iterable[str] else: with open(cli_args.file, 'r', errors='backslashreplace') as f: kunit_output = f.read().splitlines() @@ -475,7 +475,7 @@ subcommand_handlers_map = { } -def main(argv): +def main(argv: Sequence[str]) -> None: parser = argparse.ArgumentParser( description='Helps writing and running KUnit tests.') subparser = parser.add_subparsers(dest='subcommand') diff --git a/tools/testing/kunit/kunit_config.py b/tools/testing/kunit/kunit_config.py index 48b5f34b2e5d..eb5dd01210b1 100644 --- a/tools/testing/kunit/kunit_config.py +++ b/tools/testing/kunit/kunit_config.py @@ -8,7 +8,7 @@ from dataclasses import dataclass import re -from typing import Dict, Iterable, List, Set, Tuple +from typing import Any, Dict, Iterable, List, Tuple CONFIG_IS_NOT_SET_PATTERN = r'^# CONFIG_(\w+) is not set$' CONFIG_PATTERN = r'^CONFIG_(\w+)=(\S+|".*")$' @@ -34,7 +34,7 @@ class Kconfig: def __init__(self) -> None: self._entries = {} # type: Dict[str, str] - def __eq__(self, other) -> bool: + def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return False return self._entries == other._entries diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py index 53e90c335834..f01f94106129 100644 --- a/tools/testing/kunit/kunit_kernel.py +++ b/tools/testing/kunit/kunit_kernel.py @@ -16,9 +16,9 @@ import shutil import signal import threading from typing import Iterator, List, Optional, Tuple +from types import FrameType import kunit_config -from kunit_printer import stdout import qemu_config KCONFIG_PATH = '.config' @@ -57,7 +57,7 @@ class LinuxSourceTreeOperations: def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: return base_kunitconfig - def make_olddefconfig(self, build_dir: str, make_options) -> None: + def make_olddefconfig(self, build_dir: str, make_options: Optional[List[str]]) -> None: command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, 'olddefconfig'] if self._cross_compile: command += ['CROSS_COMPILE=' + self._cross_compile] @@ -71,7 +71,7 @@ class LinuxSourceTreeOperations: except subprocess.CalledProcessError as e: raise ConfigError(e.output.decode()) - def make(self, jobs, build_dir: str, make_options) -> None: + def make(self, jobs: int, build_dir: str, make_options: Optional[List[str]]) -> None: command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, '--jobs=' + str(jobs)] if make_options: command.extend(make_options) @@ -92,7 +92,7 @@ class LinuxSourceTreeOperations: if stderr: # likely only due to build warnings print(stderr.decode()) - def start(self, params: List[str], build_dir: str) -> subprocess.Popen: + def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: raise RuntimeError('not implemented!') @@ -106,13 +106,14 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): self._kernel_path = qemu_arch_params.kernel_path self._kernel_command_line = qemu_arch_params.kernel_command_line + ' kunit_shutdown=reboot' self._extra_qemu_params = qemu_arch_params.extra_qemu_params + self._serial = qemu_arch_params.serial def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: kconfig = kunit_config.parse_from_string(self._kconfig) kconfig.merge_in_entries(base_kunitconfig) return kconfig - def start(self, params: List[str], build_dir: str) -> subprocess.Popen: + def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: kernel_path = os.path.join(build_dir, self._kernel_path) qemu_command = ['qemu-system-' + self._qemu_arch, '-nodefaults', @@ -121,7 +122,7 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): '-append', ' '.join(params + [self._kernel_command_line]), '-no-reboot', '-nographic', - '-serial', 'stdio'] + self._extra_qemu_params + '-serial', self._serial] + self._extra_qemu_params # Note: shlex.join() does what we want, but requires python 3.8+. print('Running tests with:\n$', ' '.join(shlex.quote(arg) for arg in qemu_command)) return subprocess.Popen(qemu_command, @@ -133,7 +134,7 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): """An abstraction over command line operations performed on a source tree.""" - def __init__(self, cross_compile=None): + def __init__(self, cross_compile: Optional[str]=None): super().__init__(linux_arch='um', cross_compile=cross_compile) def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: @@ -141,7 +142,7 @@ class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): kconfig.merge_in_entries(base_kunitconfig) return kconfig - def start(self, params: List[str], build_dir: str) -> subprocess.Popen: + def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: """Runs the Linux UML binary. Must be named 'linux'.""" linux_bin = os.path.join(build_dir, 'linux') params.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) @@ -216,7 +217,7 @@ def _get_qemu_ops(config_path: str, if not hasattr(config, 'QEMU_ARCH'): raise ValueError('qemu_config module missing "QEMU_ARCH": ' + config_path) - params: qemu_config.QemuArchParams = config.QEMU_ARCH # type: ignore + params: qemu_config.QemuArchParams = config.QEMU_ARCH if extra_qemu_args: params.extra_qemu_params.extend(extra_qemu_args) return params.linux_arch, LinuxSourceTreeOperationsQemu( @@ -230,10 +231,10 @@ class LinuxSourceTree: build_dir: str, kunitconfig_paths: Optional[List[str]]=None, kconfig_add: Optional[List[str]]=None, - arch=None, - cross_compile=None, - qemu_config_path=None, - extra_qemu_args=None) -> None: + arch: Optional[str]=None, + cross_compile: Optional[str]=None, + qemu_config_path: Optional[str]=None, + extra_qemu_args: Optional[List[str]]=None) -> None: signal.signal(signal.SIGINT, self.signal_handler) if qemu_config_path: self._arch, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) @@ -276,7 +277,7 @@ class LinuxSourceTree: logging.error(message) return False - def build_config(self, build_dir: str, make_options) -> bool: + def build_config(self, build_dir: str, make_options: Optional[List[str]]) -> bool: kconfig_path = get_kconfig_path(build_dir) if build_dir and not os.path.exists(build_dir): os.mkdir(build_dir) @@ -304,7 +305,7 @@ class LinuxSourceTree: old_kconfig = kunit_config.parse_file(old_path) return old_kconfig != self._kconfig - def build_reconfig(self, build_dir: str, make_options) -> bool: + def build_reconfig(self, build_dir: str, make_options: Optional[List[str]]) -> bool: """Creates a new .config if it is not a subset of the .kunitconfig.""" kconfig_path = get_kconfig_path(build_dir) if not os.path.exists(kconfig_path): @@ -320,7 +321,7 @@ class LinuxSourceTree: os.remove(kconfig_path) return self.build_config(build_dir, make_options) - def build_kernel(self, jobs, build_dir: str, make_options) -> bool: + def build_kernel(self, jobs: int, build_dir: str, make_options: Optional[List[str]]) -> bool: try: self._ops.make_olddefconfig(build_dir, make_options) self._ops.make(jobs, build_dir, make_options) @@ -329,7 +330,7 @@ class LinuxSourceTree: return False return self.validate_config(build_dir) - def run_kernel(self, args=None, build_dir='', filter_glob='', timeout=None) -> Iterator[str]: + def run_kernel(self, args: Optional[List[str]]=None, build_dir: str='', filter_glob: str='', timeout: Optional[int]=None) -> Iterator[str]: if not args: args = [] if filter_glob: @@ -340,7 +341,7 @@ class LinuxSourceTree: assert process.stdout is not None # tell mypy it's set # Enforce the timeout in a background thread. - def _wait_proc(): + def _wait_proc() -> None: try: process.wait(timeout=timeout) except Exception as e: @@ -366,6 +367,6 @@ class LinuxSourceTree: waiter.join() subprocess.call(['stty', 'sane']) - def signal_handler(self, unused_sig, unused_frame) -> None: + def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None: logging.error('Build interruption occurred. Cleaning console.') subprocess.call(['stty', 'sane']) diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py index a225799f6b1b..fbc094f0567e 100644 --- a/tools/testing/kunit/kunit_parser.py +++ b/tools/testing/kunit/kunit_parser.py @@ -12,7 +12,6 @@ from __future__ import annotations from dataclasses import dataclass import re -import sys import textwrap from enum import Enum, auto diff --git a/tools/testing/kunit/kunit_printer.py b/tools/testing/kunit/kunit_printer.py index 5f1cc55ecdf5..015adf87dc2c 100644 --- a/tools/testing/kunit/kunit_printer.py +++ b/tools/testing/kunit/kunit_printer.py @@ -15,7 +15,7 @@ _RESET = '\033[0;0m' class Printer: """Wraps a file object, providing utilities for coloring output, etc.""" - def __init__(self, output: typing.IO): + def __init__(self, output: typing.IO[str]): self._output = output self._use_color = output.isatty() diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py index 0c2190514103..be35999bb84f 100755 --- a/tools/testing/kunit/kunit_tool_test.py +++ b/tools/testing/kunit/kunit_tool_test.py @@ -328,7 +328,7 @@ class KUnitParserTest(unittest.TestCase): def test_parse_subtest_header(self): ktap_log = test_data_path('test_parse_subtest_header.log') with open(ktap_log) as file: - result = kunit_parser.parse_run_tests(file.readlines()) + kunit_parser.parse_run_tests(file.readlines()) self.print_mock.assert_any_call(StrContains('suite (1 subtest)')) def test_show_test_output_on_failure(self): diff --git a/tools/testing/kunit/qemu_config.py b/tools/testing/kunit/qemu_config.py index 0b6a80398ccc..b1fba9016eed 100644 --- a/tools/testing/kunit/qemu_config.py +++ b/tools/testing/kunit/qemu_config.py @@ -17,3 +17,4 @@ class QemuArchParams: kernel_path: str kernel_command_line: str extra_qemu_params: List[str] + serial: str = 'stdio' diff --git a/tools/testing/kunit/qemu_configs/m68k.py b/tools/testing/kunit/qemu_configs/m68k.py new file mode 100644 index 000000000000..287fc386f8a7 --- /dev/null +++ b/tools/testing/kunit/qemu_configs/m68k.py @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +from ..qemu_config import QemuArchParams + +QEMU_ARCH = QemuArchParams(linux_arch='m68k', + kconfig=''' +CONFIG_VIRT=y''', + qemu_arch='m68k', + kernel_path='vmlinux', + kernel_command_line='console=hvc0', + extra_qemu_params=['-machine', 'virt']) diff --git a/tools/testing/kunit/qemu_configs/sh.py b/tools/testing/kunit/qemu_configs/sh.py new file mode 100644 index 000000000000..78a474a5b95f --- /dev/null +++ b/tools/testing/kunit/qemu_configs/sh.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only +from ..qemu_config import QemuArchParams + +QEMU_ARCH = QemuArchParams(linux_arch='sh', + kconfig=''' +CONFIG_CPU_SUBTYPE_SH7751R=y +CONFIG_MEMORY_START=0x0c000000 +CONFIG_SH_RTS7751R2D=y +CONFIG_RTS7751R2D_PLUS=y +CONFIG_SERIAL_SH_SCI=y''', + qemu_arch='sh4', + kernel_path='arch/sh/boot/zImage', + kernel_command_line='console=ttySC1', + serial='null', + extra_qemu_params=[ + '-machine', 'r2d', + '-serial', 'mon:stdio']) diff --git a/tools/testing/kunit/run_checks.py b/tools/testing/kunit/run_checks.py index 066e6f938f6d..8208c3b3135e 100755 --- a/tools/testing/kunit/run_checks.py +++ b/tools/testing/kunit/run_checks.py @@ -23,7 +23,7 @@ commands: Dict[str, Sequence[str]] = { 'kunit_tool_test.py': ['./kunit_tool_test.py'], 'kunit smoke test': ['./kunit.py', 'run', '--kunitconfig=lib/kunit', '--build_dir=kunit_run_checks'], 'pytype': ['/bin/sh', '-c', 'pytype *.py'], - 'mypy': ['/bin/sh', '-c', 'mypy *.py'], + 'mypy': ['mypy', '--strict', '--exclude', '_test.py$', '--exclude', 'qemu_configs/', '.'], } # The user might not have mypy or pytype installed, skip them if so. @@ -37,7 +37,7 @@ def main(argv: Sequence[str]) -> None: if argv: raise RuntimeError('This script takes no arguments') - future_to_name: Dict[futures.Future, str] = {} + future_to_name: Dict[futures.Future[None], str] = {} executor = futures.ThreadPoolExecutor(max_workers=len(commands)) for name, argv in commands.items(): if name in necessary_deps and shutil.which(necessary_deps[name]) is None: @@ -73,7 +73,7 @@ def main(argv: Sequence[str]) -> None: sys.exit(1) -def run_cmd(argv: Sequence[str]): +def run_cmd(argv: Sequence[str]) -> None: subprocess.check_output(argv, stderr=subprocess.STDOUT, cwd=ABS_TOOL_PATH, timeout=TIMEOUT) diff --git a/tools/testing/memblock/linux/mmzone.h b/tools/testing/memblock/linux/mmzone.h index e65f89b12f1c..134f8eab0768 100644 --- a/tools/testing/memblock/linux/mmzone.h +++ b/tools/testing/memblock/linux/mmzone.h @@ -17,10 +17,10 @@ enum zone_type { }; #define MAX_NR_ZONES __MAX_NR_ZONES -#define MAX_ORDER 11 -#define MAX_ORDER_NR_PAGES (1 << (MAX_ORDER - 1)) +#define MAX_ORDER 10 +#define MAX_ORDER_NR_PAGES (1 << MAX_ORDER) -#define pageblock_order (MAX_ORDER - 1) +#define pageblock_order MAX_ORDER #define pageblock_nr_pages BIT(pageblock_order) #define pageblock_align(pfn) ALIGN((pfn), pageblock_nr_pages) #define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages) diff --git a/tools/testing/nvdimm/test/ndtest.c b/tools/testing/nvdimm/test/ndtest.c index 01ceb98c15a0..3eba10c1e3e8 100644 --- a/tools/testing/nvdimm/test/ndtest.c +++ b/tools/testing/nvdimm/test/ndtest.c @@ -921,7 +921,7 @@ static __init int ndtest_init(void) nfit_test_setup(ndtest_resource_lookup, NULL); - ndtest_dimm_class = class_create(THIS_MODULE, "nfit_test_dimm"); + ndtest_dimm_class = class_create("nfit_test_dimm"); if (IS_ERR(ndtest_dimm_class)) { rc = PTR_ERR(ndtest_dimm_class); goto err_register; diff --git a/tools/testing/nvdimm/test/nfit.c b/tools/testing/nvdimm/test/nfit.c index c75abb497a1a..e4e2d1650dd5 100644 --- a/tools/testing/nvdimm/test/nfit.c +++ b/tools/testing/nvdimm/test/nfit.c @@ -1878,14 +1878,14 @@ static size_t sizeof_spa(struct acpi_nfit_system_address *spa) static int nfit_test0_alloc(struct nfit_test *t) { struct acpi_nfit_system_address *spa = NULL; + struct acpi_nfit_flush_address *flush; size_t nfit_size = sizeof_spa(spa) * NUM_SPA + sizeof(struct acpi_nfit_memory_map) * NUM_MEM + sizeof(struct acpi_nfit_control_region) * NUM_DCR + offsetof(struct acpi_nfit_control_region, window_size) * NUM_DCR + sizeof(struct acpi_nfit_data_region) * NUM_BDW - + (sizeof(struct acpi_nfit_flush_address) - + sizeof(u64) * NUM_HINTS) * NUM_DCR + + struct_size(flush, hint_address, NUM_HINTS) * NUM_DCR + sizeof(struct acpi_nfit_capabilities); int i; @@ -3282,7 +3282,7 @@ static __init int nfit_test_init(void) if (!nfit_wq) return -ENOMEM; - nfit_test_dimm = class_create(THIS_MODULE, "nfit_test_dimm"); + nfit_test_dimm = class_create("nfit_test_dimm"); if (IS_ERR(nfit_test_dimm)) { rc = PTR_ERR(nfit_test_dimm); goto err_register; diff --git a/tools/testing/radix-tree/maple.c b/tools/testing/radix-tree/maple.c index 4c89ff333f6f..9286d3baa12d 100644 --- a/tools/testing/radix-tree/maple.c +++ b/tools/testing/radix-tree/maple.c @@ -55,6 +55,28 @@ struct rcu_reader_struct { struct rcu_test_struct2 *test; }; +static int get_alloc_node_count(struct ma_state *mas) +{ + int count = 1; + struct maple_alloc *node = mas->alloc; + + if (!node || ((unsigned long)node & 0x1)) + return 0; + while (node->node_count) { + count += node->node_count; + node = node->slot[0]; + } + return count; +} + +static void check_mas_alloc_node_count(struct ma_state *mas) +{ + mas_node_count_gfp(mas, MAPLE_ALLOC_SLOTS + 1, GFP_KERNEL); + mas_node_count_gfp(mas, MAPLE_ALLOC_SLOTS + 3, GFP_KERNEL); + MT_BUG_ON(mas->tree, get_alloc_node_count(mas) != mas->alloc->total); + mas_destroy(mas); +} + /* * check_new_node() - Check the creation of new nodes and error path * verification. @@ -69,6 +91,8 @@ static noinline void check_new_node(struct maple_tree *mt) MA_STATE(mas, mt, 0, 0); + check_mas_alloc_node_count(&mas); + /* Try allocating 3 nodes */ mtree_lock(mt); mt_set_non_kernel(0); diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 13a6837a0c6b..90a62cf75008 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -58,11 +58,13 @@ TARGETS += nsfs TARGETS += pidfd TARGETS += pid_namespace TARGETS += powerpc +TARGETS += prctl TARGETS += proc TARGETS += pstore TARGETS += ptrace TARGETS += openat2 TARGETS += resctrl +TARGETS += riscv TARGETS += rlimits TARGETS += rseq TARGETS += rtc diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index 05f1749ae19d..c95d63e553f4 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -63,6 +63,7 @@ static void find_controls(void) struct card_data *card_data; struct ctl_data *ctl_data; snd_config_t *config; + char *card_name, *card_longname; card = -1; if (snd_card_next(&card) < 0 || card < 0) @@ -84,6 +85,15 @@ static void find_controls(void) goto next_card; } + err = snd_card_get_name(card, &card_name); + if (err != 0) + card_name = "Unknown"; + err = snd_card_get_longname(card, &card_longname); + if (err != 0) + card_longname = "Unknown"; + ksft_print_msg("Card %d - %s (%s)\n", card, + card_name, card_longname); + /* Count controls */ snd_ctl_elem_list_malloc(&card_data->ctls); snd_ctl_elem_list(card_data->handle, card_data->ctls); @@ -422,6 +432,9 @@ static void test_ctl_name(struct ctl_data *ctl) bool name_ok = true; bool check; + ksft_print_msg("%d.%d %s\n", ctl->card->card, ctl->elem, + ctl->name); + /* Only boolean controls should end in Switch */ if (strend(ctl->name, " Switch")) { if (snd_ctl_elem_info_get_type(ctl->info) != SND_CTL_ELEM_TYPE_BOOLEAN) { @@ -445,6 +458,48 @@ static void test_ctl_name(struct ctl_data *ctl) ctl->card->card, ctl->elem); } +static void show_values(struct ctl_data *ctl, snd_ctl_elem_value_t *orig_val, + snd_ctl_elem_value_t *read_val) +{ + long long orig_int, read_int; + int i; + + for (i = 0; i < snd_ctl_elem_info_get_count(ctl->info); i++) { + switch (snd_ctl_elem_info_get_type(ctl->info)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + orig_int = snd_ctl_elem_value_get_boolean(orig_val, i); + read_int = snd_ctl_elem_value_get_boolean(read_val, i); + break; + + case SND_CTL_ELEM_TYPE_INTEGER: + orig_int = snd_ctl_elem_value_get_integer(orig_val, i); + read_int = snd_ctl_elem_value_get_integer(read_val, i); + break; + + case SND_CTL_ELEM_TYPE_INTEGER64: + orig_int = snd_ctl_elem_value_get_integer64(orig_val, + i); + read_int = snd_ctl_elem_value_get_integer64(read_val, + i); + break; + + case SND_CTL_ELEM_TYPE_ENUMERATED: + orig_int = snd_ctl_elem_value_get_enumerated(orig_val, + i); + read_int = snd_ctl_elem_value_get_enumerated(read_val, + i); + break; + + default: + return; + } + + ksft_print_msg("%s.%d orig %lld read %lld, is_volatile %d\n", + ctl->name, i, orig_int, read_int, + snd_ctl_elem_info_is_volatile(ctl->info)); + } +} + static bool show_mismatch(struct ctl_data *ctl, int index, snd_ctl_elem_value_t *read_val, snd_ctl_elem_value_t *expected_val) @@ -584,12 +639,14 @@ static int write_and_verify(struct ctl_data *ctl, if (err < 1) { ksft_print_msg("No event generated for %s\n", ctl->name); + show_values(ctl, initial_val, read_val); ctl->event_missing++; } } else { if (err != 0) { ksft_print_msg("Spurious event generated for %s\n", ctl->name); + show_values(ctl, initial_val, read_val); ctl->event_spurious++; } } @@ -755,7 +812,6 @@ static bool test_ctl_write_valid_enumerated(struct ctl_data *ctl) static void test_ctl_write_valid(struct ctl_data *ctl) { bool pass; - int err; /* If the control is turned off let's be polite */ if (snd_ctl_elem_info_is_inactive(ctl->info)) { @@ -797,9 +853,7 @@ static void test_ctl_write_valid(struct ctl_data *ctl) } /* Restore the default value to minimise disruption */ - err = write_and_verify(ctl, ctl->def_val, NULL); - if (err < 0) - pass = false; + write_and_verify(ctl, ctl->def_val, NULL); ksft_test_result(pass, "write_valid.%d.%d\n", ctl->card->card, ctl->elem); @@ -1015,9 +1069,7 @@ static void test_ctl_write_invalid(struct ctl_data *ctl) } /* Restore the default value to minimise disruption */ - err = write_and_verify(ctl, ctl->def_val, NULL); - if (err < 0) - pass = false; + write_and_verify(ctl, ctl->def_val, NULL); ksft_test_result(pass, "write_invalid.%d.%d\n", ctl->card->card, ctl->elem); diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index 58b525a4a32c..3e390fe67eb9 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -149,6 +149,7 @@ static void missing_devices(int card, snd_config_t *card_config) static void find_pcms(void) { char name[32], key[64]; + char *card_name, *card_longname; int card, dev, subdev, count, direction, err; snd_pcm_stream_t stream; struct pcm_data *pcm_data; @@ -175,6 +176,15 @@ static void find_pcms(void) goto next_card; } + err = snd_card_get_name(card, &card_name); + if (err != 0) + card_name = "Unknown"; + err = snd_card_get_longname(card, &card_longname); + if (err != 0) + card_longname = "Unknown"; + ksft_print_msg("Card %d - %s (%s)\n", card, + card_name, card_longname); + card_config = conf_by_card(card); card_data = calloc(1, sizeof(*card_data)); @@ -489,17 +499,18 @@ __close: } if (!skip) - ksft_test_result(pass, "%s.%s.%d.%d.%d.%s%s%s\n", + ksft_test_result(pass, "%s.%s.%d.%d.%d.%s\n", test_class_name, test_name, data->card, data->device, data->subdevice, - snd_pcm_stream_name(data->stream), - msg[0] ? " " : "", msg); + snd_pcm_stream_name(data->stream)); else - ksft_test_result_skip("%s.%s.%d.%d.%d.%s%s%s\n", + ksft_test_result_skip("%s.%s.%d.%d.%d.%s\n", test_class_name, test_name, data->card, data->device, data->subdevice, - snd_pcm_stream_name(data->stream), - msg[0] ? " " : "", msg); + snd_pcm_stream_name(data->stream)); + + if (msg[0]) + ksft_print_msg("%s\n", msg); pthread_mutex_unlock(&results_lock); diff --git a/tools/testing/selftests/amd-pstate/gitsource.sh b/tools/testing/selftests/amd-pstate/gitsource.sh index dbc1fe45599d..5f2171f0116d 100755 --- a/tools/testing/selftests/amd-pstate/gitsource.sh +++ b/tools/testing/selftests/amd-pstate/gitsource.sh @@ -117,7 +117,7 @@ parse_gitsource() printf "Gitsource-$1-#$2 power consumption(J): $en_sum\n" | tee -a $OUTFILE_GIT.result # Permance is the number of run gitsource per second, denoted 1/t, where 1 is the number of run gitsource in t - # senconds. It is well known that P=E/t, where P is power measured in watts(W), E is energy measured in joules(J), + # seconds. It is well known that P=E/t, where P is power measured in watts(W), E is energy measured in joules(J), # and t is time measured in seconds(s). This means that performance per watt becomes # 1/t 1/t 1 # ----- = ----- = --- @@ -175,7 +175,7 @@ gather_gitsource() printf "Gitsource-$1 avg power consumption(J): $avg_en\n" | tee -a $OUTFILE_GIT.result # Permance is the number of run gitsource per second, denoted 1/t, where 1 is the number of run gitsource in t - # senconds. It is well known that P=E/t, where P is power measured in watts(W), E is energy measured in joules(J), + # seconds. It is well known that P=E/t, where P is power measured in watts(W), E is energy measured in joules(J), # and t is time measured in seconds(s). This means that performance per watt becomes # 1/t 1/t 1 # ----- = ----- = --- diff --git a/tools/testing/selftests/amd-pstate/run.sh b/tools/testing/selftests/amd-pstate/run.sh index 57cad57e59c0..de4d8e9c9565 100755 --- a/tools/testing/selftests/amd-pstate/run.sh +++ b/tools/testing/selftests/amd-pstate/run.sh @@ -244,7 +244,7 @@ prerequisite() if [ "$scaling_driver" != "$CURRENT_TEST" ]; then echo "$0 # Skipped: Test can only run on $CURRENT_TEST driver or run comparative test." echo "$0 # Please set X86_AMD_PSTATE enabled or run comparative test." - echo "$0 # Current cpufreq scaling drvier is $scaling_driver." + echo "$0 # Current cpufreq scaling driver is $scaling_driver." exit $ksft_skip fi else @@ -252,7 +252,7 @@ prerequisite() "tbench" | "gitsource") if [ "$scaling_driver" != "$COMPARATIVE_TEST" ]; then echo "$0 # Skipped: Comparison test can only run on $COMPARISON_TEST driver." - echo "$0 # Current cpufreq scaling drvier is $scaling_driver." + echo "$0 # Current cpufreq scaling driver is $scaling_driver." exit $ksft_skip fi ;; diff --git a/tools/testing/selftests/arm64/fp/Makefile b/tools/testing/selftests/arm64/fp/Makefile index 48f56c86ad45..b413b0af07f9 100644 --- a/tools/testing/selftests/arm64/fp/Makefile +++ b/tools/testing/selftests/arm64/fp/Makefile @@ -38,7 +38,7 @@ $(OUTPUT)/vec-syscfg: vec-syscfg.c $(OUTPUT)/rdvl.o $(OUTPUT)/vlset: vlset.c $(OUTPUT)/za-fork: za-fork.c $(OUTPUT)/za-fork-asm.o $(CC) -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \ - -include ../../../../include/nolibc/nolibc.h \ + -include ../../../../include/nolibc/nolibc.h -I../..\ -static -ffreestanding -Wall $^ -o $@ $(OUTPUT)/za-ptrace: za-ptrace.c $(OUTPUT)/za-test: za-test.S $(OUTPUT)/asm-utils.o diff --git a/tools/testing/selftests/arm64/fp/za-fork.c b/tools/testing/selftests/arm64/fp/za-fork.c index ff475c649e96..b86cb1049497 100644 --- a/tools/testing/selftests/arm64/fp/za-fork.c +++ b/tools/testing/selftests/arm64/fp/za-fork.c @@ -9,42 +9,9 @@ #include <linux/sched.h> #include <linux/wait.h> -#define EXPECTED_TESTS 1 - -static void putstr(const char *str) -{ - write(1, str, strlen(str)); -} - -static void putnum(unsigned int num) -{ - char c; - - if (num / 10) - putnum(num / 10); - - c = '0' + (num % 10); - write(1, &c, 1); -} +#include "kselftest.h" -static int tests_run; -static int tests_passed; -static int tests_failed; -static int tests_skipped; - -static void print_summary(void) -{ - if (tests_passed + tests_failed + tests_skipped != EXPECTED_TESTS) - putstr("# UNEXPECTED TEST COUNT: "); - - putstr("# Totals: pass:"); - putnum(tests_passed); - putstr(" fail:"); - putnum(tests_failed); - putstr(" xfail:0 xpass:0 skip:"); - putnum(tests_skipped); - putstr(" error:0\n"); -} +#define EXPECTED_TESTS 1 int fork_test(void); int verify_fork(void); @@ -63,22 +30,21 @@ int fork_test_c(void) if (newpid == 0) { /* In child */ if (!verify_fork()) { - putstr("# ZA state invalid in child\n"); + ksft_print_msg("ZA state invalid in child\n"); exit(0); } else { exit(1); } } if (newpid < 0) { - putstr("# fork() failed: -"); - putnum(-newpid); - putstr("\n"); + ksft_print_msg("fork() failed: %d\n", newpid); + return 0; } parent_result = verify_fork(); if (!parent_result) - putstr("# ZA state invalid in parent\n"); + ksft_print_msg("ZA state invalid in parent\n"); for (;;) { waiting = waitpid(newpid, &child_status, 0); @@ -86,18 +52,16 @@ int fork_test_c(void) if (waiting < 0) { if (errno == EINTR) continue; - putstr("# waitpid() failed: "); - putnum(errno); - putstr("\n"); + ksft_print_msg("waitpid() failed: %d\n", errno); return 0; } if (waiting != newpid) { - putstr("# waitpid() returned wrong PID\n"); + ksft_print_msg("waitpid() returned wrong PID\n"); return 0; } if (!WIFEXITED(child_status)) { - putstr("# child did not exit\n"); + ksft_print_msg("child did not exit\n"); return 0; } @@ -105,29 +69,14 @@ int fork_test_c(void) } } -#define run_test(name) \ - if (name()) { \ - tests_passed++; \ - } else { \ - tests_failed++; \ - putstr("not "); \ - } \ - putstr("ok "); \ - putnum(++tests_run); \ - putstr(" " #name "\n"); - int main(int argc, char **argv) { int ret, i; - putstr("TAP version 13\n"); - putstr("1.."); - putnum(EXPECTED_TESTS); - putstr("\n"); + ksft_print_header(); + ksft_set_plan(EXPECTED_TESTS); - putstr("# PID: "); - putnum(getpid()); - putstr("\n"); + ksft_print_msg("PID: %d\n", getpid()); /* * This test is run with nolibc which doesn't support hwcap and @@ -136,21 +85,16 @@ int main(int argc, char **argv) */ ret = open("/proc/sys/abi/sme_default_vector_length", O_RDONLY, 0); if (ret >= 0) { - run_test(fork_test); + ksft_test_result(fork_test(), "fork_test"); } else { - putstr("# SME support not present\n"); - + ksft_print_msg("SME not supported\n"); for (i = 0; i < EXPECTED_TESTS; i++) { - putstr("ok "); - putnum(i); - putstr(" skipped\n"); + ksft_test_result_skip("fork_test\n"); } - - tests_skipped += EXPECTED_TESTS; } - print_summary(); + ksft_finished(); return 0; } diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index 75c100de90ff..2b5215cc599f 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -15,13 +15,6 @@ skip_test() { [[ $(id -u) -eq 0 ]] || skip_test "Test must be run as root!" -# Set sched verbose flag, if available -if [[ -d /sys/kernel/debug/sched ]] -then - # Used to restore the original setting during cleanup - SCHED_DEBUG=$(cat /sys/kernel/debug/sched/verbose) - echo Y > /sys/kernel/debug/sched/verbose -fi # Get wait_inotify location WAIT_INOTIFY=$(cd $(dirname $0); pwd)/wait_inotify @@ -37,10 +30,14 @@ CPUS=$(lscpu | grep "^CPU(s):" | sed -e "s/.*:[[:space:]]*//") PROG=$1 VERBOSE= DELAY_FACTOR=1 +SCHED_DEBUG= while [[ "$1" = -* ]] do case "$1" in -v) VERBOSE=1 + # Enable sched/verbose can slow thing down + [[ $DELAY_FACTOR -eq 1 ]] && + DELAY_FACTOR=2 break ;; -d) DELAY_FACTOR=$2 @@ -54,6 +51,14 @@ do shift done +# Set sched verbose flag if available when "-v" option is specified +if [[ -n "$VERBOSE" && -d /sys/kernel/debug/sched ]] +then + # Used to restore the original setting during cleanup + SCHED_DEBUG=$(cat /sys/kernel/debug/sched/verbose) + echo Y > /sys/kernel/debug/sched/verbose +fi + cd $CGROUP2 echo +cpuset > cgroup.subtree_control [[ -d test ]] || mkdir test @@ -65,7 +70,8 @@ cleanup() rmdir A1/A2/A3 A1/A2 A1 B1 > /dev/null 2>&1 cd .. rmdir test > /dev/null 2>&1 - echo "$SCHED_DEBUG" > /sys/kernel/debug/sched/verbose + [[ -n "$SCHED_DEBUG" ]] && + echo "$SCHED_DEBUG" > /sys/kernel/debug/sched/verbose } # Pause in ms @@ -571,7 +577,6 @@ run_state_test() echo "Test $TEST[$I] failed result check!" eval echo \"\${$TEST[$I]}\" dump_states - online_cpus exit 1 } @@ -582,7 +587,6 @@ run_state_test() eval echo \"\${$TEST[$I]}\" echo dump_states - online_cpus exit 1 } } @@ -594,7 +598,6 @@ run_state_test() eval echo \"\${$TEST[$I]}\" echo dump_states - online_cpus exit 1 } } diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c index 1e616a8c6a9c..f4f7c0aef702 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -98,6 +98,11 @@ static int alloc_anon_50M_check(const char *cgroup, void *arg) int ret = -1; buf = malloc(size); + if (buf == NULL) { + fprintf(stderr, "malloc() failed\n"); + return -1; + } + for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE) *ptr = 0; @@ -211,6 +216,11 @@ static int alloc_anon_noexit(const char *cgroup, void *arg) char *buf, *ptr; buf = malloc(size); + if (buf == NULL) { + fprintf(stderr, "malloc() failed\n"); + return -1; + } + for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE) *ptr = 0; @@ -778,6 +788,11 @@ static int alloc_anon_50M_check_swap(const char *cgroup, void *arg) int ret = -1; buf = malloc(size); + if (buf == NULL) { + fprintf(stderr, "malloc() failed\n"); + return -1; + } + for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE) *ptr = 0; diff --git a/tools/testing/selftests/clone3/clone3.c b/tools/testing/selftests/clone3/clone3.c index 4fce46afe6db..e495f895a2cd 100644 --- a/tools/testing/selftests/clone3/clone3.c +++ b/tools/testing/selftests/clone3/clone3.c @@ -129,7 +129,7 @@ int main(int argc, char *argv[]) uid_t uid = getuid(); ksft_print_header(); - ksft_set_plan(17); + ksft_set_plan(18); test_clone3_supported(); /* Just a simple clone3() should return 0.*/ @@ -198,5 +198,5 @@ int main(int argc, char *argv[]) /* Do a clone3() in a new time namespace */ test_clone3(CLONE_NEWTIME, 0, 0, CLONE3_ARGS_NO_TEST); - return !ksft_get_fail_cnt() ? ksft_exit_pass() : ksft_exit_fail(); + ksft_finished(); } diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile index 83e8f87d643a..01c0491d64da 100644 --- a/tools/testing/selftests/hid/Makefile +++ b/tools/testing/selftests/hid/Makefile @@ -5,6 +5,18 @@ 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 + CXX ?= $(CROSS_COMPILE)g++ HOSTPKG_CONFIG := pkg-config diff --git a/tools/testing/selftests/hid/config b/tools/testing/selftests/hid/config index 5b5cef445b54..4f425178b56f 100644 --- a/tools/testing/selftests/hid/config +++ b/tools/testing/selftests/hid/config @@ -20,3 +20,14 @@ 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 diff --git a/tools/testing/selftests/hid/hid-apple.sh b/tools/testing/selftests/hid/hid-apple.sh new file mode 100755 index 000000000000..656f2d5ae5a9 --- /dev/null +++ b/tools/testing/selftests/hid/hid-apple.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-core.sh b/tools/testing/selftests/hid/hid-core.sh new file mode 100755 index 000000000000..5bbabc12c34f --- /dev/null +++ b/tools/testing/selftests/hid/hid-core.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-gamepad.sh b/tools/testing/selftests/hid/hid-gamepad.sh new file mode 100755 index 000000000000..1ba00c0ca95f --- /dev/null +++ b/tools/testing/selftests/hid/hid-gamepad.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-ite.sh b/tools/testing/selftests/hid/hid-ite.sh new file mode 100755 index 000000000000..52c5ccf42292 --- /dev/null +++ b/tools/testing/selftests/hid/hid-ite.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-keyboard.sh b/tools/testing/selftests/hid/hid-keyboard.sh new file mode 100755 index 000000000000..55368f17d1d5 --- /dev/null +++ b/tools/testing/selftests/hid/hid-keyboard.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-mouse.sh b/tools/testing/selftests/hid/hid-mouse.sh new file mode 100755 index 000000000000..7b4ad4f646f7 --- /dev/null +++ b/tools/testing/selftests/hid/hid-mouse.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-multitouch.sh b/tools/testing/selftests/hid/hid-multitouch.sh new file mode 100755 index 000000000000..d03a1ddbfb1f --- /dev/null +++ b/tools/testing/selftests/hid/hid-multitouch.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 diff --git a/tools/testing/selftests/hid/hid-sony.sh b/tools/testing/selftests/hid/hid-sony.sh new file mode 100755 index 000000000000..c863c442686e --- /dev/null +++ b/tools/testing/selftests/hid/hid-sony.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-tablet.sh b/tools/testing/selftests/hid/hid-tablet.sh new file mode 100755 index 000000000000..e86b3fedafd9 --- /dev/null +++ b/tools/testing/selftests/hid/hid-tablet.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-usb_crash.sh b/tools/testing/selftests/hid/hid-usb_crash.sh new file mode 100755 index 000000000000..3f0debe7e8fd --- /dev/null +++ b/tools/testing/selftests/hid/hid-usb_crash.sh @@ -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 diff --git a/tools/testing/selftests/hid/hid-wacom.sh b/tools/testing/selftests/hid/hid-wacom.sh new file mode 100755 index 000000000000..1630c22726d2 --- /dev/null +++ b/tools/testing/selftests/hid/hid-wacom.sh @@ -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 diff --git a/tools/testing/selftests/hid/run-hid-tools-tests.sh b/tools/testing/selftests/hid/run-hid-tools-tests.sh new file mode 100755 index 000000000000..bdae8464da86 --- /dev/null +++ b/tools/testing/selftests/hid/run-hid-tools-tests.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Runs tests for the HID subsystem + +if ! command -v python3 > /dev/null 2>&1; then + echo "hid-tools: [SKIP] python3 not installed" + exit 77 +fi + +if ! python3 -c "import pytest" > /dev/null 2>&1; then + echo "hid: [SKIP/ pytest module not installed" + exit 77 +fi + +if ! python3 -c "import pytest_tap" > /dev/null 2>&1; then + echo "hid: [SKIP/ pytest_tap module not installed" + exit 77 +fi + +if ! python3 -c "import hidtools" > /dev/null 2>&1; then + echo "hid: [SKIP/ hid-tools module not installed" + exit 77 +fi + +TARGET=${TARGET:=.} + +echo TAP version 13 +python3 -u -m pytest $PYTEST_XDIST ./tests/$TARGET --tap-stream --udevd diff --git a/tools/testing/selftests/hid/settings b/tools/testing/selftests/hid/settings new file mode 100644 index 000000000000..b3cbfc521b10 --- /dev/null +++ b/tools/testing/selftests/hid/settings @@ -0,0 +1,3 @@ +# HID tests can be long, so give a little bit more time +# to them +timeout=200 diff --git a/tools/testing/selftests/hid/tests/__init__.py b/tools/testing/selftests/hid/tests/__init__.py new file mode 100644 index 000000000000..c940e9275252 --- /dev/null +++ b/tools/testing/selftests/hid/tests/__init__.py @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +# Just to make sphinx-apidoc document this directory diff --git a/tools/testing/selftests/hid/tests/base.py b/tools/testing/selftests/hid/tests/base.py new file mode 100644 index 000000000000..1305cfc9646e --- /dev/null +++ b/tools/testing/selftests/hid/tests/base.py @@ -0,0 +1,345 @@ +#!/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 libevdev +import os +import pytest +import time + +import logging + +from hidtools.device.base_device import BaseDevice, EvdevMatch, SysfsFile +from pathlib import Path +from typing import Final + +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 + + +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 tuple '(kernel driver name, kernel module)', + # for example ("playstation", "hid-playstation") + kernel_modules = [] + + 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(): + import subprocess + + 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 kernel_driver, kernel_module in self.kernel_modules: + self._load_kernel_module(kernel_driver, kernel_module) + yield + + @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: + skip_cond = request.node.get_closest_marker("skip_if_uhdev") + if skip_cond: + 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.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 + 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): + import subprocess + + 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"\n' + ) + f.write( + 'KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"\n' + ) + self.rulesfile = f + + @classmethod + def instance(cls): + if not cls._instance: + cls._instance = HIDTestUdevRule() + return cls._instance diff --git a/tools/testing/selftests/hid/tests/conftest.py b/tools/testing/selftests/hid/tests/conftest.py new file mode 100644 index 000000000000..1361ec981db6 --- /dev/null +++ b/tools/testing/selftests/hid/tests/conftest.py @@ -0,0 +1,81 @@ +#!/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 platform +import pytest +import re +import resource +import subprocess +from .base import HIDTestUdevRule +from pathlib import Path + + +# 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) diff --git a/tools/testing/selftests/hid/tests/descriptors_wacom.py b/tools/testing/selftests/hid/tests/descriptors_wacom.py new file mode 100644 index 000000000000..91c16e005c12 --- /dev/null +++ b/tools/testing/selftests/hid/tests/descriptors_wacom.py @@ -0,0 +1,1360 @@ +# SPDX-License-Identifier: GPL-2.0 + +# fmt: off +wacom_pth660_v145 = [ + 0x05, 0x01, # . Usage Page (Desktop), + 0x09, 0x02, # . Usage (Mouse), + 0xA1, 0x01, # . Collection (Application), + 0x85, 0x01, # . Report ID (1), + 0x09, 0x01, # . Usage (Pointer), + 0xA1, 0x00, # . Collection (Physical), + 0x05, 0x09, # . Usage Page (Button), + 0x19, 0x01, # . Usage Minimum (01h), + 0x29, 0x03, # . Usage Maximum (03h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x03, # . Report Count (3), + 0x81, 0x02, # . Input (Variable), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x05, # . Report Count (5), + 0x81, 0x03, # . Input (Constant, Variable), + 0x05, 0x01, # . Usage Page (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 (Variable, Relative), + 0xC0, # . End Collection, + 0xC0, # . End Collection, + 0x06, 0x0D, 0xFF, # . Usage Page (FF0Dh), + 0x09, 0x01, # . Usage (01h), + 0xA1, 0x01, # . Collection (Application), + 0x85, 0x10, # . Report ID (16), + 0x09, 0x20, # . Usage (20h), + 0xA1, 0x00, # . Collection (Physical), + 0x09, 0x42, # . Usage (42h), + 0x09, 0x44, # . Usage (44h), + 0x09, 0x5A, # . Usage (5Ah), + 0x09, 0x45, # . Usage (45h), + 0x09, 0x3C, # . Usage (3Ch), + 0x09, 0x32, # . Usage (32h), + 0x09, 0x36, # . Usage (36h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x07, # . Report Count (7), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x30, 0x01, # . Usage (0130h), + 0x65, 0x11, # . Unit (Centimeter), + 0x55, 0x0D, # . Unit Exponent (13), + 0x35, 0x00, # . Physical Minimum (0), + 0x47, 0x80, 0x57, 0x00, 0x00, # . Physical Maximum (22400), + 0x15, 0x00, # . Logical Minimum (0), + 0x27, 0x00, 0xAF, 0x00, 0x00, # . Logical Maximum (44800), + 0x75, 0x18, # . Report Size (24), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x31, 0x01, # . Usage (0131h), + 0x47, 0xD0, 0x39, 0x00, 0x00, # . Physical Maximum (14800), + 0x27, 0xA0, 0x73, 0x00, 0x00, # . Logical Maximum (29600), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x30, # . Usage (30h), + 0x55, 0x00, # . Unit Exponent (0), + 0x65, 0x00, # . Unit, + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x1F, # . Logical Maximum (8191), # !!! Errata: Missing Physical Max = 0 + 0x75, 0x10, # . Report Size (16), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x3D, # . Usage (3Dh), + 0x09, 0x3E, # . Usage (3Eh), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0xC0, # . Physical Minimum (-64), + 0x45, 0x3F, # . Physical Maximum (63), + 0x15, 0xC0, # . Logical Minimum (-64), + 0x25, 0x3F, # . Logical Maximum (63), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x41, # . Usage (41h), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x36, 0x4C, 0xFF, # . Physical Minimum (-180), + 0x46, 0xB3, 0x00, # . Physical Maximum (179), + 0x16, 0x7C, 0xFC, # . Logical Minimum (-900), + 0x26, 0x83, 0x03, # . Logical Maximum (899), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x0A, # . Input (Variable, Wrap), + 0x0A, 0x03, 0x0D, # . Usage (0D03h), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x07, # . Logical Maximum (2047), # !!! Errata: Missing Physical Min/Max = 0 + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x32, 0x01, # . Usage (0132h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x3F, # . Logical Maximum (63), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x5B, # . Usage (5Bh), + 0x09, 0x5C, # . Usage (5Ch), + 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648), + 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647), + 0x75, 0x20, # . Report Size (32), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x77, # . Usage (77h), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x0F, # . Logical Maximum (4095), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0x85, 0x11, # . Report ID (17), + 0x09, 0x39, # . Usage (39h), + 0xA1, 0x00, # . Collection (Physical), + 0x1A, 0x10, 0x09, # . Usage Minimum (0910h), + 0x2A, 0x17, 0x09, # . Usage Maximum (0917h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x08, # . Report Count (8), + 0x81, 0x02, # . Input (Variable), + 0x1A, 0x40, 0x09, # . Usage Minimum (0940h), + 0x2A, 0x47, 0x09, # . Usage Maximum (0947h), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x95, 0x09, # . Usage (0995h), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x07, # . Report Count (7), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x38, 0x01, # . Usage (0138h), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0x00, # . Physical Minimum (0), + 0x46, 0x67, 0x01, # . Physical Maximum (359), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x47, # . Logical Maximum (71), + 0x75, 0x07, # . Report Size (7), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x0A, # . Input (Variable, Wrap), + 0x0A, 0x39, 0x01, # . Usage (0139h), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x25, 0x01, # . Logical Maximum (1), # !!! Errata: Missing Physical Max = 0 + 0x75, 0x01, # . Report Size (1), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x04, # . Report Count (4), + 0x81, 0x03, # . Input (Constant, Variable), + 0xC0, # . End Collection, + 0x85, 0x13, # . Report ID (19), + 0x0A, 0x13, 0x10, # . Usage (1013h), + 0xA1, 0x00, # . Collection (Physical), + 0x0A, 0x3B, 0x04, # . Usage (043Bh), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x64, # . Logical Maximum (100), + 0x75, 0x07, # . Report Size (7), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x04, 0x04, # . Usage (0404h), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x52, 0x04, # . Usage (0452h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x06, # . Report Count (6), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x54, 0x04, # . Usage (0454h), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x06, # . Report Count (6), + 0x81, 0x03, # . Input (Constant, Variable), + 0xC0, # . End Collection, + 0x09, 0x0E, # . Usage (0Eh), + 0xA1, 0x02, # . Collection (Logical), + 0x15, 0x00, # . Logical Minimum (0), + 0x85, 0x02, # . Report ID (2), + 0x09, 0x01, # . Usage (01h), + 0x75, 0x08, # . Report Size (8), + 0x25, 0x01, # . Logical Maximum (1), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x03, # . Report ID (3), + 0x0A, 0x03, 0x10, # . Usage (1003h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x04, # . Report ID (4), + 0x0A, 0x04, 0x10, # . Usage (1004h), + 0x25, 0x01, # . Logical Maximum (1), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x07, # . Report ID (7), + 0x0A, 0x09, 0x10, # . Usage (1009h), + 0x25, 0x02, # . Logical Maximum (2), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0x0A, 0x07, 0x10, # . Usage (1007h), + 0x09, 0x00, # . Usage (00h), + 0x0A, 0x08, 0x10, # . Usage (1008h), + 0x09, 0x00, # . Usage (00h), + 0x09, 0x00, # . Usage (00h), + 0x09, 0x00, # . Usage (00h), + 0x27, 0xFF, 0xFF, 0x00, 0x00, # . Logical Maximum (65535), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x06, # . Report Count (6), + 0xB1, 0x02, # . Feature (Variable), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0x85, 0x0C, # . Report ID (12), + 0x0A, 0x30, 0x0D, # . Usage (0D30h), + 0x0A, 0x31, 0x0D, # . Usage (0D31h), + 0x0A, 0x32, 0x0D, # . Usage (0D32h), + 0x0A, 0x33, 0x0D, # . Usage (0D33h), # !!! Errata: Missing Non-zero Physical Max + 0x65, 0x11, # . Unit (Centimeter), + 0x55, 0x0D, # . Unit Exponent (13), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0x85, 0x0D, # . Report ID (13), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x0A, 0x0D, 0x10, # . Usage (100Dh), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x14, # . Report ID (20), + 0x0A, 0x14, 0x10, # . Usage (1014h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x0D, # . Report Count (13), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x31, # . Report ID (49), + 0x0A, 0x31, 0x10, # . Usage (1031h), + 0x25, 0x64, # . Logical Maximum (100), + 0x95, 0x05, # . Report Count (5), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x32, # . Report ID (50), + 0x0A, 0x31, 0x10, # . Usage (1031h), + 0x25, 0x64, # . Logical Maximum (100), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x0A, 0x32, 0x10, # . Usage (1032h), + 0x25, 0x03, # . Logical Maximum (3), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x34, # . Report ID (52), + 0x0A, 0x34, 0x10, # . Usage (1034h), + 0x25, 0x01, # . Logical Maximum (1), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x35, # . Report ID (53), + 0x0A, 0x35, 0x10, # . Usage (1035h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x0A, # . Report Count (10), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x36, # . Report ID (54), + 0x0A, 0x35, 0x10, # . Usage (1035h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x96, 0x01, 0x01, # . Report Count (257), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xCC, # . Report ID (204), + 0x0A, 0xCC, 0x10, # . Usage (10CCh), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x02, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0xC0, # . End Collection, + 0x0A, 0xAC, 0x10, # . Usage (10ACh), + 0xA1, 0x02, # . Collection (Logical), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x75, 0x08, # . Report Size (8), + 0x85, 0xAC, # . Report ID (172), + 0x09, 0x00, # . Usage (00h), + 0x95, 0xBF, # . Report Count (191), + 0x81, 0x02, # . Input (Variable), + 0x85, 0x33, # . Report ID (51), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x12, # . Report Count (18), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x64, # . Report ID (100), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0C, # . Report Count (12), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x15, # . Report ID (21), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0E, # . Report Count (14), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x12, # . Report ID (18), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x16, # . Report ID (22), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0E, # . Report Count (14), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x40, # . Report ID (64), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x41, # . Report ID (65), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x42, # . Report ID (66), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x43, # . Report ID (67), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0D, # . Report Count (13), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x44, # . Report ID (68), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3F, # . Report Count (63), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x45, # . Report ID (69), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x20, # . Report Count (32), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x60, # . Report ID (96), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3F, # . Report Count (63), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x61, # . Report ID (97), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3E, # . Report Count (62), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x62, # . Report ID (98), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3E, # . Report Count (62), + 0xB1, 0x02, # . Feature (Variable), + 0xC0, # . End Collection, + 0x85, 0xD0, # . Report ID (208), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x08, 0x00, # . Report Count (8), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD1, # . Report ID (209), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x01, # . Report Count (260), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD2, # . Report ID (210), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x01, # . Report Count (260), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD3, # . Report ID (211), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD4, # . Report ID (212), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD5, # . Report ID (213), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD6, # . Report ID (214), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD7, # . Report ID (215), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x08, 0x00, # . Report Count (8), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD8, # . Report ID (216), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x0C, 0x00, # . Report Count (12), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD9, # . Report ID (217), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x00, 0x0A, # . Report Count (2560), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDA, # . Report ID (218), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x04, # . Report Count (1028), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDB, # . Report ID (219), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x06, 0x00, # . Report Count (6), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDC, # . Report ID (220), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDD, # . Report ID (221), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDE, # . Report ID (222), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDF, # . Report ID (223), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x22, 0x00, # . Report Count (34), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE0, # . Report ID (224), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x01, 0x00, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE1, # . Report ID (225), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE2, # . Report ID (226), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE3, # . Report ID (227), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE4, # . Report ID (228), + 0x09, 0x01, # . Usage (01h), + 0x96, 0xFF, 0x01, # . Report Count (511), + 0xB1, 0x02, # . Feature (Variable), + 0xC0 # . End Collection +] +# fmt: on + +# Report ID (20), Usage (1014h), Report Count (13) -> 15 +wacom_pth660_v150 = wacom_pth660_v145.copy() +wacom_pth660_v150[0x2CB] = 0x0F + +# fmt: off +wacom_pth860_v145 = [ + 0x05, 0x01, # . Usage Page (Desktop), + 0x09, 0x02, # . Usage (Mouse), + 0xA1, 0x01, # . Collection (Application), + 0x85, 0x01, # . Report ID (1), + 0x09, 0x01, # . Usage (Pointer), + 0xA1, 0x00, # . Collection (Physical), + 0x05, 0x09, # . Usage Page (Button), + 0x19, 0x01, # . Usage Minimum (01h), + 0x29, 0x03, # . Usage Maximum (03h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x03, # . Report Count (3), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x05, # . Report Count (5), + 0x81, 0x03, # . Input (Constant, Variable), + 0x05, 0x01, # . Usage Page (Desktop), + 0x09, 0x30, # . Usage (X), + 0x09, 0x31, # . Usage (Y), + 0x15, 0x80, # . Logical Minimum (-128), + 0x25, 0x7F, # . Logical Maximum (127), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x06, # . Input (Variable, Relative), + 0xC0, # . End Collection, + 0xC0, # . End Collection, + 0x06, 0x0D, 0xFF, # . Usage Page (FF0Dh), + 0x09, 0x01, # . Usage (01h), + 0xA1, 0x01, # . Collection (Application), + 0x85, 0x10, # . Report ID (16), + 0x09, 0x20, # . Usage (20h), + 0xA1, 0x00, # . Collection (Physical), + 0x09, 0x42, # . Usage (42h), + 0x09, 0x44, # . Usage (44h), + 0x09, 0x5A, # . Usage (5Ah), + 0x09, 0x45, # . Usage (45h), + 0x09, 0x3C, # . Usage (3Ch), + 0x09, 0x32, # . Usage (32h), + 0x09, 0x36, # . Usage (36h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x07, # . Report Count (7), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x30, 0x01, # . Usage (0130h), + 0x65, 0x11, # . Unit (Centimeter), + 0x55, 0x0D, # . Unit Exponent (13), + 0x35, 0x00, # . Physical Minimum (0), + 0x47, 0x7C, 0x79, 0x00, 0x00, # . Physical Maximum (31100), + 0x15, 0x00, # . Logical Minimum (0), + 0x27, 0xF8, 0xF2, 0x00, 0x00, # . Logical Maximum (62200), + 0x75, 0x18, # . Report Size (24), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x31, 0x01, # . Usage (0131h), + 0x47, 0x60, 0x54, 0x00, 0x00, # . Physical Maximum (21600), + 0x27, 0xC0, 0xA8, 0x00, 0x00, # . Logical Maximum (43200), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x30, # . Usage (30h), # !!! Errata: Missing Physical Max = 0 + 0x55, 0x00, # . Unit Exponent (0), + 0x65, 0x00, # . Unit, + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x1F, # . Logical Maximum (8191), + 0x75, 0x10, # . Report Size (16), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x3D, # . Usage (3Dh), + 0x09, 0x3E, # . Usage (3Eh), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0xC0, # . Physical Minimum (-64), + 0x45, 0x3F, # . Physical Maximum (63), + 0x15, 0xC0, # . Logical Minimum (-64), + 0x25, 0x3F, # . Logical Maximum (63), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x41, # . Usage (41h), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x36, 0x4C, 0xFF, # . Physical Minimum (-180), + 0x46, 0xB3, 0x00, # . Physical Maximum (179), + 0x16, 0x7C, 0xFC, # . Logical Minimum (-900), + 0x26, 0x83, 0x03, # . Logical Maximum (899), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x0A, # . Input (Variable, Wrap), + 0x0A, 0x03, 0x0D, # . Usage (0D03h), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x07, # . Logical Maximum (2047), # !!! Errata: Missing Physical Min/Max = 0 + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x32, 0x01, # . Usage (0132h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x3F, # . Logical Maximum (63), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x5B, # . Usage (5Bh), + 0x09, 0x5C, # . Usage (5Ch), + 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648), + 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647), + 0x75, 0x20, # . Report Size (32), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x77, # . Usage (77h), + 0x16, 0x00, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x0F, # . Logical Maximum (4095), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0x85, 0x11, # . Report ID (17), + 0x09, 0x39, # . Usage (39h), + 0xA1, 0x00, # . Collection (Physical), + 0x1A, 0x10, 0x09, # . Usage Minimum (0910h), + 0x2A, 0x17, 0x09, # . Usage Maximum (0917h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x08, # . Report Count (8), + 0x81, 0x02, # . Input (Variable), + 0x1A, 0x40, 0x09, # . Usage Minimum (0940h), + 0x2A, 0x47, 0x09, # . Usage Maximum (0947h), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x95, 0x09, # . Usage (0995h), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x07, # . Report Count (7), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x38, 0x01, # . Usage (0138h), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0x00, # . Physical Minimum (0), + 0x46, 0x67, 0x01, # . Physical Maximum (359), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x47, # . Logical Maximum (71), + 0x75, 0x07, # . Report Size (7), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x0A, # . Input (Variable, Wrap), + 0x0A, 0x39, 0x01, # . Usage (0139h), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x25, 0x01, # . Logical Maximum (1), # !!! Errata: Missing Physical Max = 0 + 0x75, 0x01, # . Report Size (1), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x04, # . Report Count (4), + 0x81, 0x03, # . Input (Constant, Variable), + 0xC0, # . End Collection, + 0x85, 0x13, # . Report ID (19), + 0x0A, 0x13, 0x10, # . Usage (1013h), + 0xA1, 0x00, # . Collection (Physical), + 0x0A, 0x3B, 0x04, # . Usage (043Bh), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x64, # . Logical Maximum (100), + 0x75, 0x07, # . Report Size (7), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x04, 0x04, # . Usage (0404h), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x52, 0x04, # . Usage (0452h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x06, # . Report Count (6), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x54, 0x04, # . Usage (0454h), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x06, # . Report Count (6), + 0x81, 0x03, # . Input (Constant, Variable), + 0xC0, # . End Collection, + 0x09, 0x0E, # . Usage (0Eh), + 0xA1, 0x02, # . Collection (Logical), + 0x15, 0x00, # . Logical Minimum (0), + 0x85, 0x02, # . Report ID (2), + 0x09, 0x01, # . Usage (01h), + 0x75, 0x08, # . Report Size (8), + 0x25, 0x01, # . Logical Maximum (1), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x03, # . Report ID (3), + 0x0A, 0x03, 0x10, # . Usage (1003h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x04, # . Report ID (4), + 0x0A, 0x04, 0x10, # . Usage (1004h), + 0x25, 0x01, # . Logical Maximum (1), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x07, # . Report ID (7), + 0x0A, 0x09, 0x10, # . Usage (1009h), + 0x25, 0x02, # . Logical Maximum (2), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0x0A, 0x07, 0x10, # . Usage (1007h), + 0x09, 0x00, # . Usage (00h), + 0x0A, 0x08, 0x10, # . Usage (1008h), + 0x09, 0x00, # . Usage (00h), + 0x09, 0x00, # . Usage (00h), + 0x09, 0x00, # . Usage (00h), + 0x27, 0xFF, 0xFF, 0x00, 0x00, # . Logical Maximum (65535), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x06, # . Report Count (6), + 0xB1, 0x02, # . Feature (Variable), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0x85, 0x0C, # . Report ID (12), + 0x0A, 0x30, 0x0D, # . Usage (0D30h), + 0x0A, 0x31, 0x0D, # . Usage (0D31h), + 0x0A, 0x32, 0x0D, # . Usage (0D32h), + 0x0A, 0x33, 0x0D, # . Usage (0D33h), # !!! Errata: Missing Non-zero Physical Max + 0x65, 0x11, # . Unit (Centimeter), + 0x55, 0x0D, # . Unit Exponent (13), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0x85, 0x0D, # . Report ID (13), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x0A, 0x0D, 0x10, # . Usage (100Dh), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x14, # . Report ID (20), + 0x0A, 0x14, 0x10, # . Usage (1014h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x0D, # . Report Count (13), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x31, # . Report ID (49), + 0x0A, 0x31, 0x10, # . Usage (1031h), + 0x25, 0x64, # . Logical Maximum (100), + 0x95, 0x05, # . Report Count (5), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x32, # . Report ID (50), + 0x0A, 0x31, 0x10, # . Usage (1031h), + 0x25, 0x64, # . Logical Maximum (100), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x0A, 0x32, 0x10, # . Usage (1032h), + 0x25, 0x03, # . Logical Maximum (3), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x34, # . Report ID (52), + 0x0A, 0x34, 0x10, # . Usage (1034h), + 0x25, 0x01, # . Logical Maximum (1), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x35, # . Report ID (53), + 0x0A, 0x35, 0x10, # . Usage (1035h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x0A, # . Report Count (10), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x36, # . Report ID (54), + 0x0A, 0x35, 0x10, # . Usage (1035h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x96, 0x01, 0x01, # . Report Count (257), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xCC, # . Report ID (204), + 0x0A, 0xCC, 0x10, # . Usage (10CCh), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x02, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0xC0, # . End Collection, + 0x0A, 0xAC, 0x10, # . Usage (10ACh), + 0xA1, 0x02, # . Collection (Logical), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x75, 0x08, # . Report Size (8), + 0x85, 0xAC, # . Report ID (172), + 0x09, 0x00, # . Usage (00h), + 0x95, 0xBF, # . Report Count (191), + 0x81, 0x02, # . Input (Variable), + 0x85, 0x33, # . Report ID (51), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x12, # . Report Count (18), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x64, # . Report ID (100), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0C, # . Report Count (12), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x15, # . Report ID (21), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0E, # . Report Count (14), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x12, # . Report ID (18), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x16, # . Report ID (22), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0E, # . Report Count (14), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x40, # . Report ID (64), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x41, # . Report ID (65), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x42, # . Report ID (66), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x43, # . Report ID (67), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0D, # . Report Count (13), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x44, # . Report ID (68), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3F, # . Report Count (63), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x45, # . Report ID (69), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x20, # . Report Count (32), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x60, # . Report ID (96), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3F, # . Report Count (63), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x61, # . Report ID (97), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3E, # . Report Count (62), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x62, # . Report ID (98), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3E, # . Report Count (62), + 0xB1, 0x02, # . Feature (Variable), + 0xC0, # . End Collection, + 0x85, 0xD0, # . Report ID (208), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x08, 0x00, # . Report Count (8), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD1, # . Report ID (209), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x01, # . Report Count (260), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD2, # . Report ID (210), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x01, # . Report Count (260), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD3, # . Report ID (211), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD4, # . Report ID (212), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD5, # . Report ID (213), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD6, # . Report ID (214), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD7, # . Report ID (215), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x08, 0x00, # . Report Count (8), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD8, # . Report ID (216), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x0C, 0x00, # . Report Count (12), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD9, # . Report ID (217), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x00, 0x0A, # . Report Count (2560), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDA, # . Report ID (218), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x04, # . Report Count (1028), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDB, # . Report ID (219), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x06, 0x00, # . Report Count (6), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDC, # . Report ID (220), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDD, # . Report ID (221), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDE, # . Report ID (222), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDF, # . Report ID (223), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x22, 0x00, # . Report Count (34), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE0, # . Report ID (224), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x01, 0x00, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE1, # . Report ID (225), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE2, # . Report ID (226), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE3, # . Report ID (227), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE4, # . Report ID (228), + 0x09, 0x01, # . Usage (01h), + 0x96, 0xFF, 0x01, # . Report Count (511), + 0xB1, 0x02, # . Feature (Variable), + 0xC0 # . End Collection +] +# fmt: on + +# Report ID (20), Usage (1014h), Report Count (13) -> 15 +wacom_pth860_v150 = wacom_pth860_v145.copy() +wacom_pth860_v150[0x2CA] = 0x0F + +# fmt: off +wacom_pth460_v105 = [ + 0x06, 0x0D, 0xFF, # . Usage Page (FF0Dh), + 0x09, 0x01, # . Usage (01h), + 0xA1, 0x01, # . Collection (Application), + 0x85, 0x10, # . Report ID (16), + 0x09, 0x20, # . Usage (20h), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0xA1, 0x00, # . Collection (Physical), + 0x09, 0x42, # . Usage (42h), + 0x09, 0x44, # . Usage (44h), + 0x09, 0x5A, # . Usage (5Ah), + 0x09, 0x45, # . Usage (45h), + 0x09, 0x3C, # . Usage (3Ch), + 0x09, 0x32, # . Usage (32h), + 0x09, 0x36, # . Usage (36h), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x07, # . Report Count (7), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x30, 0x01, # . Usage (0130h), + 0x65, 0x11, # . Unit (Centimeter), + 0x55, 0x0D, # . Unit Exponent (13), + 0x47, 0x58, 0x3E, 0x00, 0x00, # . Physical Maximum (15960), + 0x27, 0xB0, 0x7C, 0x00, 0x00, # . Logical Maximum (31920), + 0x75, 0x18, # . Report Size (24), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x31, 0x01, # . Usage (0131h), + 0x47, 0xF7, 0x26, 0x00, 0x00, # . Physical Maximum (9975), + 0x27, 0xEE, 0x4D, 0x00, 0x00, # . Logical Maximum (19950), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x30, # . Usage (30h), + 0x55, 0x00, # . Unit Exponent (0), + 0x65, 0x00, # . Unit, + 0x26, 0xFF, 0x1F, # . Logical Maximum (8191), # !!! Errata: Missing Physical Max = 0 + 0x75, 0x10, # . Report Size (16), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x3D, # . Usage (3Dh), + 0x09, 0x3E, # . Usage (3Eh), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0xC0, # . Physical Minimum (-64), + 0x45, 0x3F, # . Physical Maximum (63), + 0x15, 0xC0, # . Logical Minimum (-64), + 0x25, 0x3F, # . Logical Maximum (63), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x41, # . Usage (41h), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x36, 0x4C, 0xFF, # . Physical Minimum (-180), + 0x46, 0xB3, 0x00, # . Physical Maximum (179), + 0x16, 0x7C, 0xFC, # . Logical Minimum (-900), + 0x26, 0x83, 0x03, # . Logical Maximum (899), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x0A, # . Input (Variable, Wrap), + 0x0A, 0x03, 0x0D, # . Usage (0D03h), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x07, # . Logical Maximum (2047), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x32, 0x01, # . Usage (0132h), + 0x25, 0x3F, # . Logical Maximum (63), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x5B, # . Usage (5Bh), + 0x09, 0x5C, # . Usage (5Ch), + 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648), + 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647), + 0x75, 0x20, # . Report Size (32), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x77, # . Usage (77h), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x0F, # . Logical Maximum (4095), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0x85, 0x11, # . Report ID (17), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x09, 0x39, # . Usage (39h), + 0xA1, 0x00, # . Collection (Physical), + 0x09, 0x39, # . Usage (39h), + 0xA1, 0x00, # . Collection (Physical), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x1A, 0x10, 0x09, # . Usage Minimum (0910h), + 0x2A, 0x15, 0x09, # . Usage Maximum (0915h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x06, # . Report Count (6), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x03, # . Input (Constant, Variable), + 0xC0, # . End Collection, + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x03, # . Input (Constant, Variable), + 0x09, 0x39, # . Usage (39h), + 0xA1, 0x00, # . Collection (Physical), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x0A, 0x95, 0x09, # . Usage (0995h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x07, # . Report Count (7), + 0x81, 0x03, # . Input (Constant, Variable), + 0xC0, # . End Collection, + 0x09, 0x39, # . Usage (39h), + 0xA1, 0x00, # . Collection (Physical), + 0x35, 0x00, # . Physical Minimum (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x0A, 0x38, 0x01, # . Usage (0138h), + 0x65, 0x14, # . Unit (Degrees), + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0x00, # . Physical Minimum (0), + 0x46, 0x67, 0x01, # . Physical Maximum (359), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x47, # . Logical Maximum (71), + 0x75, 0x07, # . Report Size (7), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x4A, # . Input (Variable, Wrap, Null State), + 0x0A, 0x39, 0x01, # . Usage (0139h), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0x75, 0x08, # . Report Size (8), + 0x95, 0x04, # . Report Count (4), + 0x81, 0x03, # . Input (Constant, Variable), + 0xC0, # . End Collection, + 0x85, 0x13, # . Report ID (19), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x0A, 0x13, 0x10, # . Usage (1013h), + 0xA1, 0x00, # . Collection (Physical), + 0x0A, 0x13, 0x10, # . Usage (1013h), + 0xA1, 0x00, # . Collection (Physical), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x0A, 0x3B, 0x04, # . Usage (043Bh), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x64, # . Logical Maximum (100), + 0x75, 0x07, # . Report Size (7), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x04, 0x04, # . Usage (0404h), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0x0A, 0x13, 0x10, # . Usage (1013h), + 0xA1, 0x00, # . Collection (Physical), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x0A, 0x52, 0x04, # . Usage (0452h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x41, 0x04, # . Usage (0441h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x07, # . Logical Maximum (7), + 0x75, 0x03, # . Report Size (3), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x54, 0x04, # . Usage (0454h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0x0A, 0x13, 0x10, # . Usage (1013h), + 0xA1, 0x00, # . Collection (Physical), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x0A, 0x3C, 0x04, # . Usage (043Ch), + 0x55, 0x00, # . Unit Exponent (0), + 0x65, 0x00, # . Unit, + 0x15, 0xFB, # . Logical Minimum (-5), + 0x25, 0x32, # . Logical Maximum (50), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0x0A, 0x13, 0x10, # . Usage (1013h), + 0xA1, 0x00, # . Collection (Physical), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x0A, 0x3D, 0x04, # . Usage (043Dh), + 0x55, 0x00, # . Unit Exponent (0), + 0x65, 0x00, # . Unit, + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x0F, # . Logical Maximum (4095), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0x75, 0x08, # . Report Size (8), + 0x95, 0x03, # . Report Count (3), + 0x81, 0x03, # . Input (Constant, Variable), + 0xC0, # . End Collection, + 0x09, 0x0E, # . Usage (0Eh), + 0xA1, 0x02, # . Collection (Logical), + 0x85, 0x02, # . Report ID (2), + 0x0A, 0x02, 0x10, # . Usage (1002h), + 0x15, 0x02, # . Logical Minimum (2), + 0x25, 0x02, # . Logical Maximum (2), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x03, # . Report ID (3), + 0x0A, 0x03, 0x10, # . Usage (1003h), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x04, # . Report ID (4), + 0x0A, 0x04, 0x10, # . Usage (1004h), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x07, # . Report ID (7), + 0x0A, 0x09, 0x10, # . Usage (1009h), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0x0A, 0x07, 0x10, # . Usage (1007h), + 0x09, 0x00, # . Usage (00h), + 0x0A, 0x08, 0x10, # . Usage (1008h), + 0x09, 0x00, # . Usage (00h), + 0x09, 0x00, # . Usage (00h), + 0x09, 0x00, # . Usage (00h), + 0x27, 0xFF, 0xFF, 0x00, 0x00, # . Logical Maximum (65535), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x06, # . Report Count (6), + 0xB1, 0x02, # . Feature (Variable), + 0x09, 0x00, # . Usage (00h), + 0x25, 0x00, # . Logical Maximum (0), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0x85, 0x0C, # . Report ID (12), + 0x0A, 0x30, 0x0D, # . Usage (0D30h), + 0x0A, 0x31, 0x0D, # . Usage (0D31h), + 0x0A, 0x32, 0x0D, # . Usage (0D32h), + 0x0A, 0x33, 0x0D, # . Usage (0D33h), + 0x65, 0x11, # . Unit (Centimeter), + 0x55, 0x0D, # . Unit Exponent (13), + 0x35, 0x00, # . Physical Minimum (0), + 0x46, 0xC8, 0x00, # . Physical Maximum (200), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0x90, 0x01, # . Logical Maximum (400), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x0D, # . Report ID (13), + 0x0A, 0x0D, 0x10, # . Usage (100Dh), + 0x65, 0x00, # . Unit, + 0x55, 0x00, # . Unit Exponent (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x14, # . Report ID (20), + 0x0A, 0x14, 0x10, # . Usage (1014h), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x95, 0x0D, # . Report Count (13), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xCC, # . Report ID (204), + 0x0A, 0xCC, 0x10, # . Usage (10CCh), + 0x95, 0x02, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0xC0, # . End Collection, + 0x09, 0x0E, # . Usage (0Eh), + 0xA1, 0x02, # . Collection (Logical), + 0x85, 0x31, # . Report ID (49), + 0x0A, 0x31, 0x10, # . Usage (1031h), + 0x25, 0x64, # . Logical Maximum (100), + 0x95, 0x03, # . Report Count (3), + 0xB1, 0x02, # . Feature (Variable), + 0x95, 0x02, # . Report Count (2), + 0xB1, 0x03, # . Feature (Constant, Variable), + 0xC0, # . End Collection, + 0x0A, 0xAC, 0x10, # . Usage (10ACh), + 0xA1, 0x02, # . Collection (Logical), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x00, # . Logical Maximum (255), + 0x75, 0x08, # . Report Size (8), + 0x85, 0xAC, # . Report ID (172), + 0x09, 0x00, # . Usage (00h), + 0x96, 0xBF, 0x00, # . Report Count (191), + 0x81, 0x02, # . Input (Variable), + 0x85, 0x15, # . Report ID (21), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0E, # . Report Count (14), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x33, # . Report ID (51), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x12, # . Report Count (18), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x44, # . Report ID (68), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x45, # . Report ID (69), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x20, # . Report Count (32), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x60, # . Report ID (96), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3F, # . Report Count (63), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x61, # . Report ID (97), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3E, # . Report Count (62), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x62, # . Report ID (98), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3E, # . Report Count (62), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x65, # . Report ID (101), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x66, # . Report ID (102), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x67, # . Report ID (103), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x04, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x68, # . Report ID (104), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x11, # . Report Count (17), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x6F, # . Report ID (111), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x3E, # . Report Count (62), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xCD, # . Report ID (205), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x02, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x16, # . Report ID (22), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0E, # . Report Count (14), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0x35, # . Report ID (53), + 0x09, 0x00, # . Usage (00h), + 0x95, 0x0A, # . Report Count (10), + 0xB1, 0x02, # . Feature (Variable), + 0xC0, # . End Collection, + 0x85, 0xD1, # . Report ID (209), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x01, # . Report Count (260), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD2, # . Report ID (210), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x01, # . Report Count (260), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD3, # . Report ID (211), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD4, # . Report ID (212), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD5, # . Report ID (213), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD6, # . Report ID (214), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD7, # . Report ID (215), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x08, 0x00, # . Report Count (8), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD8, # . Report ID (216), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x0C, 0x00, # . Report Count (12), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xD9, # . Report ID (217), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x00, 0x0A, # . Report Count (2560), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDA, # . Report ID (218), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x04, # . Report Count (1028), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDB, # . Report ID (219), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x06, 0x00, # . Report Count (6), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDC, # . Report ID (220), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDD, # . Report ID (221), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDE, # . Report ID (222), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x04, 0x00, # . Report Count (4), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xDF, # . Report ID (223), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x22, 0x00, # . Report Count (34), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE0, # . Report ID (224), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x01, 0x00, # . Report Count (1), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE1, # . Report ID (225), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE2, # . Report ID (226), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE3, # . Report ID (227), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x02, 0x00, # . Report Count (2), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xE4, # . Report ID (228), + 0x09, 0x01, # . Usage (01h), + 0x96, 0xFF, 0x01, # . Report Count (511), + 0xB1, 0x02, # . Feature (Variable), + 0x85, 0xCB, # . Report ID (203), + 0x09, 0x01, # . Usage (01h), + 0x96, 0x1F, 0x00, # . Report Count (31), + 0xB1, 0x02, # . Feature (Variable), + 0xC0 # . End Collection +] +# fmt: on diff --git a/tools/testing/selftests/hid/tests/test_apple_keyboard.py b/tools/testing/selftests/hid/tests/test_apple_keyboard.py new file mode 100644 index 000000000000..f81071d46166 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_apple_keyboard.py @@ -0,0 +1,440 @@ +#!/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 + +import libevdev +import logging + +logger = logging.getLogger("hidtools.test.apple-keyboard") + +KERNEL_MODULE = ("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 diff --git a/tools/testing/selftests/hid/tests/test_gamepad.py b/tools/testing/selftests/hid/tests/test_gamepad.py new file mode 100644 index 000000000000..26c74040b796 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_gamepad.py @@ -0,0 +1,209 @@ +#!/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 hidtools.device.base_gamepad import AsusGamepad, SaitekGamepad + +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 TestSaitekGamepad(BaseTest.TestGamepad): + def create_device(self): + return SaitekGamepad() + + +class TestAsusGamepad(BaseTest.TestGamepad): + def create_device(self): + return AsusGamepad() diff --git a/tools/testing/selftests/hid/tests/test_hid_core.py b/tools/testing/selftests/hid/tests/test_hid_core.py new file mode 100644 index 000000000000..9a7fe40020d2 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_hid_core.py @@ -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 diff --git a/tools/testing/selftests/hid/tests/test_ite_keyboard.py b/tools/testing/selftests/hid/tests/test_ite_keyboard.py new file mode 100644 index 000000000000..38550c167bae --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_ite_keyboard.py @@ -0,0 +1,166 @@ +#!/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 + +logger = logging.getLogger("hidtools.test.ite-keyboard") + +KERNEL_MODULE = ("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) diff --git a/tools/testing/selftests/hid/tests/test_keyboard.py b/tools/testing/selftests/hid/tests/test_keyboard.py new file mode 100644 index 000000000000..b3b2bdbf63b7 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_keyboard.py @@ -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() diff --git a/tools/testing/selftests/hid/tests/test_mouse.py b/tools/testing/selftests/hid/tests/test_mouse.py new file mode 100644 index 000000000000..fd2ba62e783a --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_mouse.py @@ -0,0 +1,977 @@ +#!/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 . import base +import hidtools.hid +from hidtools.util import BusType +import libevdev +import logging +import pytest + +logger = logging.getLogger("hidtools.test.mouse") + +# workaround https://gitlab.freedesktop.org/libevdev/python-libevdev/issues/6 +try: + libevdev.EV_REL.REL_WHEEL_HI_RES +except AttributeError: + libevdev.EV_REL.REL_WHEEL_HI_RES = libevdev.EV_REL.REL_0B + libevdev.EV_REL.REL_HWHEEL_HI_RES = libevdev.EV_REL.REL_0C + + +class InvalidHIDCommunication(Exception): + pass + + +class MouseData(object): + pass + + +class BaseMouse(base.UHIDTestDevice): + def __init__(self, rdesc, name=None, input_info=None): + assert rdesc is not None + super().__init__(name, "Mouse", input_info=input_info, rdesc=rdesc) + self.left = False + self.right = False + self.middle = False + + def create_report(self, x, y, buttons=None, wheels=None, reportID=None): + """ + Return an input report for this device. + + :param x: relative x + :param y: relative y + :param buttons: a (l, r, m) tuple of bools for the button states, + where ``None`` is "leave unchanged" + :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for + the two wheels + :param reportID: the numeric report ID for this report, if needed + """ + if buttons is not None: + l, r, m = buttons + if l is not None: + self.left = l + if r is not None: + self.right = r + if m is not None: + self.middle = m + left = self.left + right = self.right + middle = self.middle + # Note: the BaseMouse doesn't actually have a wheel but the + # create_report magic only fills in those fields exist, so let's + # make this generic here. + wheel, acpan = 0, 0 + if wheels is not None: + if isinstance(wheels, tuple): + wheel = wheels[0] + acpan = wheels[1] + else: + wheel = wheels + + reportID = reportID or self.default_reportID + + mouse = MouseData() + mouse.b1 = int(left) + mouse.b2 = int(right) + mouse.b3 = int(middle) + mouse.x = x + mouse.y = y + mouse.wheel = wheel + mouse.acpan = acpan + return super().create_report(mouse, reportID=reportID) + + def event(self, x, y, buttons=None, wheels=None): + """ + Send an input event on the default report ID. + + :param x: relative x + :param y: relative y + :param buttons: a (l, r, m) tuple of bools for the button states, + where ``None`` is "leave unchanged" + :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for + the two wheels + """ + r = self.create_report(x, y, buttons, wheels) + self.call_input_event(r) + return [r] + + +class ButtonMouse(BaseMouse): + # 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, rdesc=report_descriptor, name=None, input_info=None): + super().__init__(rdesc, name, input_info) + + def fake_report(self, x, y, buttons): + if buttons is not None: + left, right, middle = buttons + if left is None: + left = self.left + if right is None: + right = self.right + if middle is None: + middle = self.middle + else: + left = self.left + right = self.right + middle = self.middle + + button_mask = sum(1 << i for i, b in enumerate([left, right, middle]) if b) + x = max(-127, min(127, x)) + y = max(-127, min(127, y)) + x = hidtools.util.to_twos_comp(x, 8) + y = hidtools.util.to_twos_comp(y, 8) + return [button_mask, x, y] + + +class WheelMouse(ButtonMouse): + # fmt: off + report_descriptor = [ + 0x05, 0x01, # Usage Page (Generic Desktop) 0 + 0x09, 0x02, # Usage (Mouse) 2 + 0xa1, 0x01, # Collection (Application) 4 + 0x05, 0x09, # .Usage Page (Button) 6 + 0x19, 0x01, # .Usage Minimum (1) 8 + 0x29, 0x03, # .Usage Maximum (3) 10 + 0x15, 0x00, # .Logical Minimum (0) 12 + 0x25, 0x01, # .Logical Maximum (1) 14 + 0x95, 0x03, # .Report Count (3) 16 + 0x75, 0x01, # .Report Size (1) 18 + 0x81, 0x02, # .Input (Data,Var,Abs) 20 + 0x95, 0x01, # .Report Count (1) 22 + 0x75, 0x05, # .Report Size (5) 24 + 0x81, 0x03, # .Input (Cnst,Var,Abs) 26 + 0x05, 0x01, # .Usage Page (Generic Desktop) 28 + 0x09, 0x01, # .Usage (Pointer) 30 + 0xa1, 0x00, # .Collection (Physical) 32 + 0x09, 0x30, # ..Usage (X) 34 + 0x09, 0x31, # ..Usage (Y) 36 + 0x15, 0x81, # ..Logical Minimum (-127) 38 + 0x25, 0x7f, # ..Logical Maximum (127) 40 + 0x75, 0x08, # ..Report Size (8) 42 + 0x95, 0x02, # ..Report Count (2) 44 + 0x81, 0x06, # ..Input (Data,Var,Rel) 46 + 0xc0, # .End Collection 48 + 0x09, 0x38, # .Usage (Wheel) 49 + 0x15, 0x81, # .Logical Minimum (-127) 51 + 0x25, 0x7f, # .Logical Maximum (127) 53 + 0x75, 0x08, # .Report Size (8) 55 + 0x95, 0x01, # .Report Count (1) 57 + 0x81, 0x06, # .Input (Data,Var,Rel) 59 + 0xc0, # End Collection 61 + ] + # fmt: on + + def __init__(self, rdesc=report_descriptor, name=None, input_info=None): + super().__init__(rdesc, name, input_info) + self.wheel_multiplier = 1 + + +class TwoWheelMouse(WheelMouse): + # fmt: off + report_descriptor = [ + 0x05, 0x01, # Usage Page (Generic Desktop) 0 + 0x09, 0x02, # Usage (Mouse) 2 + 0xa1, 0x01, # Collection (Application) 4 + 0x09, 0x01, # .Usage (Pointer) 6 + 0xa1, 0x00, # .Collection (Physical) 8 + 0x05, 0x09, # ..Usage Page (Button) 10 + 0x19, 0x01, # ..Usage Minimum (1) 12 + 0x29, 0x10, # ..Usage Maximum (16) 14 + 0x15, 0x00, # ..Logical Minimum (0) 16 + 0x25, 0x01, # ..Logical Maximum (1) 18 + 0x95, 0x10, # ..Report Count (16) 20 + 0x75, 0x01, # ..Report Size (1) 22 + 0x81, 0x02, # ..Input (Data,Var,Abs) 24 + 0x05, 0x01, # ..Usage Page (Generic Desktop) 26 + 0x16, 0x01, 0x80, # ..Logical Minimum (-32767) 28 + 0x26, 0xff, 0x7f, # ..Logical Maximum (32767) 31 + 0x75, 0x10, # ..Report Size (16) 34 + 0x95, 0x02, # ..Report Count (2) 36 + 0x09, 0x30, # ..Usage (X) 38 + 0x09, 0x31, # ..Usage (Y) 40 + 0x81, 0x06, # ..Input (Data,Var,Rel) 42 + 0x15, 0x81, # ..Logical Minimum (-127) 44 + 0x25, 0x7f, # ..Logical Maximum (127) 46 + 0x75, 0x08, # ..Report Size (8) 48 + 0x95, 0x01, # ..Report Count (1) 50 + 0x09, 0x38, # ..Usage (Wheel) 52 + 0x81, 0x06, # ..Input (Data,Var,Rel) 54 + 0x05, 0x0c, # ..Usage Page (Consumer Devices) 56 + 0x0a, 0x38, 0x02, # ..Usage (AC Pan) 58 + 0x95, 0x01, # ..Report Count (1) 61 + 0x81, 0x06, # ..Input (Data,Var,Rel) 63 + 0xc0, # .End Collection 65 + 0xc0, # End Collection 66 + ] + # fmt: on + + def __init__(self, rdesc=report_descriptor, name=None, input_info=None): + super().__init__(rdesc, name, input_info) + self.hwheel_multiplier = 1 + + +class MIDongleMIWirelessMouse(TwoWheelMouse): + # fmt: off + report_descriptor = [ + 0x05, 0x01, # Usage Page (Generic Desktop) + 0x09, 0x02, # Usage (Mouse) + 0xa1, 0x01, # Collection (Application) + 0x85, 0x01, # .Report ID (1) + 0x09, 0x01, # .Usage (Pointer) + 0xa1, 0x00, # .Collection (Physical) + 0x95, 0x05, # ..Report Count (5) + 0x75, 0x01, # ..Report Size (1) + 0x05, 0x09, # ..Usage Page (Button) + 0x19, 0x01, # ..Usage Minimum (1) + 0x29, 0x05, # ..Usage Maximum (5) + 0x15, 0x00, # ..Logical Minimum (0) + 0x25, 0x01, # ..Logical Maximum (1) + 0x81, 0x02, # ..Input (Data,Var,Abs) + 0x95, 0x01, # ..Report Count (1) + 0x75, 0x03, # ..Report Size (3) + 0x81, 0x01, # ..Input (Cnst,Arr,Abs) + 0x75, 0x08, # ..Report Size (8) + 0x95, 0x01, # ..Report Count (1) + 0x05, 0x01, # ..Usage Page (Generic Desktop) + 0x09, 0x38, # ..Usage (Wheel) + 0x15, 0x81, # ..Logical Minimum (-127) + 0x25, 0x7f, # ..Logical Maximum (127) + 0x81, 0x06, # ..Input (Data,Var,Rel) + 0x05, 0x0c, # ..Usage Page (Consumer Devices) + 0x0a, 0x38, 0x02, # ..Usage (AC Pan) + 0x95, 0x01, # ..Report Count (1) + 0x81, 0x06, # ..Input (Data,Var,Rel) + 0xc0, # .End Collection + 0x85, 0x02, # .Report ID (2) + 0x09, 0x01, # .Usage (Consumer Control) + 0xa1, 0x00, # .Collection (Physical) + 0x75, 0x0c, # ..Report Size (12) + 0x95, 0x02, # ..Report Count (2) + 0x05, 0x01, # ..Usage Page (Generic Desktop) + 0x09, 0x30, # ..Usage (X) + 0x09, 0x31, # ..Usage (Y) + 0x16, 0x01, 0xf8, # ..Logical Minimum (-2047) + 0x26, 0xff, 0x07, # ..Logical Maximum (2047) + 0x81, 0x06, # ..Input (Data,Var,Rel) + 0xc0, # .End Collection + 0xc0, # End Collection + 0x05, 0x0c, # Usage Page (Consumer Devices) + 0x09, 0x01, # Usage (Consumer Control) + 0xa1, 0x01, # Collection (Application) + 0x85, 0x03, # .Report ID (3) + 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, 0x06, # .Input (Data,Var,Rel) + 0x0a, 0x83, 0x01, # .Usage (AL Consumer Control Config) + 0x81, 0x06, # .Input (Data,Var,Rel) + 0x09, 0xb5, # .Usage (Scan Next Track) + 0x81, 0x06, # .Input (Data,Var,Rel) + 0x09, 0xb6, # .Usage (Scan Previous Track) + 0x81, 0x06, # .Input (Data,Var,Rel) + 0x09, 0xea, # .Usage (Volume Down) + 0x81, 0x06, # .Input (Data,Var,Rel) + 0x09, 0xe9, # .Usage (Volume Up) + 0x81, 0x06, # .Input (Data,Var,Rel) + 0x0a, 0x25, 0x02, # .Usage (AC Forward) + 0x81, 0x06, # .Input (Data,Var,Rel) + 0x0a, 0x24, 0x02, # .Usage (AC Back) + 0x81, 0x06, # .Input (Data,Var,Rel) + 0xc0, # End Collection + ] + # fmt: on + device_input_info = (BusType.USB, 0x2717, 0x003B) + device_name = "uhid test MI Dongle MI Wireless Mouse" + + def __init__( + self, rdesc=report_descriptor, name=device_name, input_info=device_input_info + ): + super().__init__(rdesc, name, input_info) + + def event(self, x, y, buttons=None, wheels=None): + # this mouse spreads the relative pointer and the mouse buttons + # onto 2 distinct reports + rs = [] + r = self.create_report(x, y, buttons, wheels, reportID=1) + self.call_input_event(r) + rs.append(r) + r = self.create_report(x, y, buttons, reportID=2) + self.call_input_event(r) + rs.append(r) + return rs + + +class ResolutionMultiplierMouse(TwoWheelMouse): + # fmt: off + report_descriptor = [ + 0x05, 0x01, # Usage Page (Generic Desktop) 83 + 0x09, 0x02, # Usage (Mouse) 85 + 0xa1, 0x01, # Collection (Application) 87 + 0x05, 0x01, # .Usage Page (Generic Desktop) 89 + 0x09, 0x02, # .Usage (Mouse) 91 + 0xa1, 0x02, # .Collection (Logical) 93 + 0x85, 0x11, # ..Report ID (17) 95 + 0x09, 0x01, # ..Usage (Pointer) 97 + 0xa1, 0x00, # ..Collection (Physical) 99 + 0x05, 0x09, # ...Usage Page (Button) 101 + 0x19, 0x01, # ...Usage Minimum (1) 103 + 0x29, 0x03, # ...Usage Maximum (3) 105 + 0x95, 0x03, # ...Report Count (3) 107 + 0x75, 0x01, # ...Report Size (1) 109 + 0x25, 0x01, # ...Logical Maximum (1) 111 + 0x81, 0x02, # ...Input (Data,Var,Abs) 113 + 0x95, 0x01, # ...Report Count (1) 115 + 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 117 + 0x09, 0x05, # ...Usage (Vendor Usage 0x05) 119 + 0x81, 0x02, # ...Input (Data,Var,Abs) 121 + 0x95, 0x03, # ...Report Count (3) 123 + 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 125 + 0x05, 0x01, # ...Usage Page (Generic Desktop) 127 + 0x09, 0x30, # ...Usage (X) 129 + 0x09, 0x31, # ...Usage (Y) 131 + 0x95, 0x02, # ...Report Count (2) 133 + 0x75, 0x08, # ...Report Size (8) 135 + 0x15, 0x81, # ...Logical Minimum (-127) 137 + 0x25, 0x7f, # ...Logical Maximum (127) 139 + 0x81, 0x06, # ...Input (Data,Var,Rel) 141 + 0xa1, 0x02, # ...Collection (Logical) 143 + 0x85, 0x12, # ....Report ID (18) 145 + 0x09, 0x48, # ....Usage (Resolution Multiplier) 147 + 0x95, 0x01, # ....Report Count (1) 149 + 0x75, 0x02, # ....Report Size (2) 151 + 0x15, 0x00, # ....Logical Minimum (0) 153 + 0x25, 0x01, # ....Logical Maximum (1) 155 + 0x35, 0x01, # ....Physical Minimum (1) 157 + 0x45, 0x04, # ....Physical Maximum (4) 159 + 0xb1, 0x02, # ....Feature (Data,Var,Abs) 161 + 0x35, 0x00, # ....Physical Minimum (0) 163 + 0x45, 0x00, # ....Physical Maximum (0) 165 + 0x75, 0x06, # ....Report Size (6) 167 + 0xb1, 0x01, # ....Feature (Cnst,Arr,Abs) 169 + 0x85, 0x11, # ....Report ID (17) 171 + 0x09, 0x38, # ....Usage (Wheel) 173 + 0x15, 0x81, # ....Logical Minimum (-127) 175 + 0x25, 0x7f, # ....Logical Maximum (127) 177 + 0x75, 0x08, # ....Report Size (8) 179 + 0x81, 0x06, # ....Input (Data,Var,Rel) 181 + 0xc0, # ...End Collection 183 + 0x05, 0x0c, # ...Usage Page (Consumer Devices) 184 + 0x75, 0x08, # ...Report Size (8) 186 + 0x0a, 0x38, 0x02, # ...Usage (AC Pan) 188 + 0x81, 0x06, # ...Input (Data,Var,Rel) 191 + 0xc0, # ..End Collection 193 + 0xc0, # .End Collection 194 + 0xc0, # End Collection 195 + ] + # fmt: on + + def __init__(self, rdesc=report_descriptor, name=None, input_info=None): + super().__init__(rdesc, name, input_info) + self.default_reportID = 0x11 + + # Feature Report 12, multiplier Feature value must be set to 0b01, + # i.e. 1. We should extract that from the descriptor instead + # of hardcoding it here, but meanwhile this will do. + self.set_feature_report = [0x12, 0x1] + + def set_report(self, req, rnum, rtype, data): + if rtype != self.UHID_FEATURE_REPORT: + raise InvalidHIDCommunication(f"Unexpected report type: {rtype}") + if rnum != 0x12: + raise InvalidHIDCommunication(f"Unexpected report number: {rnum}") + + if data != self.set_feature_report: + raise InvalidHIDCommunication( + f"Unexpected data: {data}, expected {self.set_feature_report}" + ) + + self.wheel_multiplier = 4 + + return 0 + + +class BadResolutionMultiplierMouse(ResolutionMultiplierMouse): + def set_report(self, req, rnum, rtype, data): + super().set_report(req, rnum, rtype, data) + + self.wheel_multiplier = 1 + self.hwheel_multiplier = 1 + return 32 # EPIPE + + +class ResolutionMultiplierHWheelMouse(TwoWheelMouse): + # fmt: off + report_descriptor = [ + 0x05, 0x01, # Usage Page (Generic Desktop) 0 + 0x09, 0x02, # Usage (Mouse) 2 + 0xa1, 0x01, # Collection (Application) 4 + 0x05, 0x01, # .Usage Page (Generic Desktop) 6 + 0x09, 0x02, # .Usage (Mouse) 8 + 0xa1, 0x02, # .Collection (Logical) 10 + 0x85, 0x1a, # ..Report ID (26) 12 + 0x09, 0x01, # ..Usage (Pointer) 14 + 0xa1, 0x00, # ..Collection (Physical) 16 + 0x05, 0x09, # ...Usage Page (Button) 18 + 0x19, 0x01, # ...Usage Minimum (1) 20 + 0x29, 0x05, # ...Usage Maximum (5) 22 + 0x95, 0x05, # ...Report Count (5) 24 + 0x75, 0x01, # ...Report Size (1) 26 + 0x15, 0x00, # ...Logical Minimum (0) 28 + 0x25, 0x01, # ...Logical Maximum (1) 30 + 0x81, 0x02, # ...Input (Data,Var,Abs) 32 + 0x75, 0x03, # ...Report Size (3) 34 + 0x95, 0x01, # ...Report Count (1) 36 + 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 38 + 0x05, 0x01, # ...Usage Page (Generic Desktop) 40 + 0x09, 0x30, # ...Usage (X) 42 + 0x09, 0x31, # ...Usage (Y) 44 + 0x95, 0x02, # ...Report Count (2) 46 + 0x75, 0x10, # ...Report Size (16) 48 + 0x16, 0x01, 0x80, # ...Logical Minimum (-32767) 50 + 0x26, 0xff, 0x7f, # ...Logical Maximum (32767) 53 + 0x81, 0x06, # ...Input (Data,Var,Rel) 56 + 0xa1, 0x02, # ...Collection (Logical) 58 + 0x85, 0x12, # ....Report ID (18) 60 + 0x09, 0x48, # ....Usage (Resolution Multiplier) 62 + 0x95, 0x01, # ....Report Count (1) 64 + 0x75, 0x02, # ....Report Size (2) 66 + 0x15, 0x00, # ....Logical Minimum (0) 68 + 0x25, 0x01, # ....Logical Maximum (1) 70 + 0x35, 0x01, # ....Physical Minimum (1) 72 + 0x45, 0x0c, # ....Physical Maximum (12) 74 + 0xb1, 0x02, # ....Feature (Data,Var,Abs) 76 + 0x85, 0x1a, # ....Report ID (26) 78 + 0x09, 0x38, # ....Usage (Wheel) 80 + 0x35, 0x00, # ....Physical Minimum (0) 82 + 0x45, 0x00, # ....Physical Maximum (0) 84 + 0x95, 0x01, # ....Report Count (1) 86 + 0x75, 0x10, # ....Report Size (16) 88 + 0x16, 0x01, 0x80, # ....Logical Minimum (-32767) 90 + 0x26, 0xff, 0x7f, # ....Logical Maximum (32767) 93 + 0x81, 0x06, # ....Input (Data,Var,Rel) 96 + 0xc0, # ...End Collection 98 + 0xa1, 0x02, # ...Collection (Logical) 99 + 0x85, 0x12, # ....Report ID (18) 101 + 0x09, 0x48, # ....Usage (Resolution Multiplier) 103 + 0x75, 0x02, # ....Report Size (2) 105 + 0x15, 0x00, # ....Logical Minimum (0) 107 + 0x25, 0x01, # ....Logical Maximum (1) 109 + 0x35, 0x01, # ....Physical Minimum (1) 111 + 0x45, 0x0c, # ....Physical Maximum (12) 113 + 0xb1, 0x02, # ....Feature (Data,Var,Abs) 115 + 0x35, 0x00, # ....Physical Minimum (0) 117 + 0x45, 0x00, # ....Physical Maximum (0) 119 + 0x75, 0x04, # ....Report Size (4) 121 + 0xb1, 0x01, # ....Feature (Cnst,Arr,Abs) 123 + 0x85, 0x1a, # ....Report ID (26) 125 + 0x05, 0x0c, # ....Usage Page (Consumer Devices) 127 + 0x95, 0x01, # ....Report Count (1) 129 + 0x75, 0x10, # ....Report Size (16) 131 + 0x16, 0x01, 0x80, # ....Logical Minimum (-32767) 133 + 0x26, 0xff, 0x7f, # ....Logical Maximum (32767) 136 + 0x0a, 0x38, 0x02, # ....Usage (AC Pan) 139 + 0x81, 0x06, # ....Input (Data,Var,Rel) 142 + 0xc0, # ...End Collection 144 + 0xc0, # ..End Collection 145 + 0xc0, # .End Collection 146 + 0xc0, # End Collection 147 + ] + # fmt: on + + def __init__(self, rdesc=report_descriptor, name=None, input_info=None): + super().__init__(rdesc, name, input_info) + self.default_reportID = 0x1A + + # Feature Report 12, multiplier Feature value must be set to 0b0101, + # i.e. 5. We should extract that from the descriptor instead + # of hardcoding it here, but meanwhile this will do. + self.set_feature_report = [0x12, 0x5] + + def set_report(self, req, rnum, rtype, data): + super().set_report(req, rnum, rtype, data) + + self.wheel_multiplier = 12 + self.hwheel_multiplier = 12 + + return 0 + + +class BaseTest: + class TestMouse(base.BaseTestCase.TestUhid): + def test_buttons(self): + """check for button reliability.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + syn_event = self.syn_event + + r = uhdev.event(0, 0, (None, True, None)) + expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn((syn_event, expected_event), events) + assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1 + + r = uhdev.event(0, 0, (None, False, None)) + expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn((syn_event, expected_event), events) + assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0 + + r = uhdev.event(0, 0, (None, None, True)) + expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 1) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn((syn_event, expected_event), events) + assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 1 + + r = uhdev.event(0, 0, (None, None, False)) + expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 0) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn((syn_event, expected_event), events) + assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 0 + + r = uhdev.event(0, 0, (True, None, None)) + expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn((syn_event, expected_event), events) + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1 + + r = uhdev.event(0, 0, (False, None, None)) + expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn((syn_event, expected_event), events) + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 + + r = uhdev.event(0, 0, (True, True, None)) + expected_event0 = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) + expected_event1 = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn( + (syn_event, expected_event0, expected_event1), events + ) + assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1 + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1 + + r = uhdev.event(0, 0, (False, None, None)) + expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn((syn_event, expected_event), events) + assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1 + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 + + r = uhdev.event(0, 0, (None, False, None)) + expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEventsIn((syn_event, expected_event), events) + assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0 + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 + + def test_relative(self): + """Check for relative events.""" + uhdev = self.uhdev + + syn_event = self.syn_event + + r = uhdev.event(0, -1) + expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_Y, -1) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents((syn_event, expected_event), events) + + r = uhdev.event(1, 0) + expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_X, 1) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents((syn_event, expected_event), events) + + r = uhdev.event(-1, 2) + expected_event0 = libevdev.InputEvent(libevdev.EV_REL.REL_X, -1) + expected_event1 = libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents( + (syn_event, expected_event0, expected_event1), events + ) + + +class TestSimpleMouse(BaseTest.TestMouse): + def create_device(self): + return ButtonMouse() + + def test_rdesc(self): + """Check that the testsuite actually manages to format the + reports according to the report descriptors. + No kernel device is used here""" + uhdev = self.uhdev + + event = (0, 0, (None, None, None)) + assert uhdev.fake_report(*event) == uhdev.create_report(*event) + + event = (0, 0, (None, True, None)) + assert uhdev.fake_report(*event) == uhdev.create_report(*event) + + event = (0, 0, (True, True, None)) + assert uhdev.fake_report(*event) == uhdev.create_report(*event) + + event = (0, 0, (False, False, False)) + assert uhdev.fake_report(*event) == uhdev.create_report(*event) + + event = (1, 0, (True, False, True)) + assert uhdev.fake_report(*event) == uhdev.create_report(*event) + + event = (-1, 0, (True, False, True)) + assert uhdev.fake_report(*event) == uhdev.create_report(*event) + + event = (-5, 5, (True, False, True)) + assert uhdev.fake_report(*event) == uhdev.create_report(*event) + + event = (-127, 127, (True, False, True)) + assert uhdev.fake_report(*event) == uhdev.create_report(*event) + + event = (0, -128, (True, False, True)) + with pytest.raises(hidtools.hid.RangeError): + uhdev.create_report(*event) + + +class TestWheelMouse(BaseTest.TestMouse): + def create_device(self): + return WheelMouse() + + def is_wheel_highres(self, uhdev): + evdev = uhdev.get_evdev() + assert evdev.has(libevdev.EV_REL.REL_WHEEL) + return evdev.has(libevdev.EV_REL.REL_WHEEL_HI_RES) + + def test_wheel(self): + uhdev = self.uhdev + + # check if the kernel is high res wheel compatible + high_res_wheel = self.is_wheel_highres(uhdev) + + syn_event = self.syn_event + # The Resolution Multiplier is applied to the HID reports, so we + # need to pre-multiply too. + mult = uhdev.wheel_multiplier + + r = uhdev.event(0, 0, wheels=1 * mult) + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1)) + if high_res_wheel: + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(0, 0, wheels=-1 * mult) + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -1)) + if high_res_wheel: + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(-1, 2, wheels=3 * mult) + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 3)) + if high_res_wheel: + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 360)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + +class TestTwoWheelMouse(TestWheelMouse): + def create_device(self): + return TwoWheelMouse() + + def is_hwheel_highres(self, uhdev): + evdev = uhdev.get_evdev() + assert evdev.has(libevdev.EV_REL.REL_HWHEEL) + return evdev.has(libevdev.EV_REL.REL_HWHEEL_HI_RES) + + def test_ac_pan(self): + uhdev = self.uhdev + + # check if the kernel is high res wheel compatible + high_res_wheel = self.is_wheel_highres(uhdev) + high_res_hwheel = self.is_hwheel_highres(uhdev) + assert high_res_wheel == high_res_hwheel + + syn_event = self.syn_event + # The Resolution Multiplier is applied to the HID reports, so we + # need to pre-multiply too. + hmult = uhdev.hwheel_multiplier + vmult = uhdev.wheel_multiplier + + r = uhdev.event(0, 0, wheels=(0, 1 * hmult)) + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1)) + if high_res_hwheel: + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(0, 0, wheels=(0, -1 * hmult)) + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, -1)) + if high_res_hwheel: + expected.append( + libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120) + ) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(-1, 2, wheels=(0, 3 * hmult)) + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 3)) + if high_res_hwheel: + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 360)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(-1, 2, wheels=(-3 * vmult, 4 * hmult)) + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -3)) + if high_res_wheel: + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -360)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 4)) + if high_res_wheel: + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 480)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + +class TestResolutionMultiplierMouse(TestTwoWheelMouse): + def create_device(self): + return ResolutionMultiplierMouse() + + def is_wheel_highres(self, uhdev): + high_res = super().is_wheel_highres(uhdev) + + if not high_res: + # the kernel doesn't seem to support the high res wheel mice, + # make sure we haven't triggered the feature + assert uhdev.wheel_multiplier == 1 + + return high_res + + def test_resolution_multiplier_wheel(self): + uhdev = self.uhdev + + if not self.is_wheel_highres(uhdev): + pytest.skip("Kernel not compatible, we can not trigger the conditions") + + assert uhdev.wheel_multiplier > 1 + assert 120 % uhdev.wheel_multiplier == 0 + + def test_wheel_with_multiplier(self): + uhdev = self.uhdev + + if not self.is_wheel_highres(uhdev): + pytest.skip("Kernel not compatible, we can not trigger the conditions") + + assert uhdev.wheel_multiplier > 1 + + syn_event = self.syn_event + mult = uhdev.wheel_multiplier + + r = uhdev.event(0, 0, wheels=1) + expected = [syn_event] + expected.append( + libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult) + ) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(0, 0, wheels=-1) + expected = [syn_event] + expected.append( + libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120 / mult) + ) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2)) + expected.append( + libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult) + ) + + for _ in range(mult - 1): + r = uhdev.event(1, -2, wheels=1) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(1, -2, wheels=1) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + +class TestBadResolutionMultiplierMouse(TestTwoWheelMouse): + def create_device(self): + return BadResolutionMultiplierMouse() + + def is_wheel_highres(self, uhdev): + high_res = super().is_wheel_highres(uhdev) + + assert uhdev.wheel_multiplier == 1 + + return high_res + + def test_resolution_multiplier_wheel(self): + uhdev = self.uhdev + + assert uhdev.wheel_multiplier == 1 + + +class TestResolutionMultiplierHWheelMouse(TestResolutionMultiplierMouse): + def create_device(self): + return ResolutionMultiplierHWheelMouse() + + def is_hwheel_highres(self, uhdev): + high_res = super().is_hwheel_highres(uhdev) + + if not high_res: + # the kernel doesn't seem to support the high res wheel mice, + # make sure we haven't triggered the feature + assert uhdev.hwheel_multiplier == 1 + + return high_res + + def test_resolution_multiplier_ac_pan(self): + uhdev = self.uhdev + + if not self.is_hwheel_highres(uhdev): + pytest.skip("Kernel not compatible, we can not trigger the conditions") + + assert uhdev.hwheel_multiplier > 1 + assert 120 % uhdev.hwheel_multiplier == 0 + + def test_ac_pan_with_multiplier(self): + uhdev = self.uhdev + + if not self.is_hwheel_highres(uhdev): + pytest.skip("Kernel not compatible, we can not trigger the conditions") + + assert uhdev.hwheel_multiplier > 1 + + syn_event = self.syn_event + hmult = uhdev.hwheel_multiplier + + r = uhdev.event(0, 0, wheels=(0, 1)) + expected = [syn_event] + expected.append( + libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult) + ) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(0, 0, wheels=(0, -1)) + expected = [syn_event] + expected.append( + libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120 / hmult) + ) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + expected = [syn_event] + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2)) + expected.append( + libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult) + ) + + for _ in range(hmult - 1): + r = uhdev.event(1, -2, wheels=(0, 1)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + r = uhdev.event(1, -2, wheels=(0, 1)) + expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1)) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + self.assertInputEvents(expected, events) + + +class TestMiMouse(TestWheelMouse): + def create_device(self): + return MIDongleMIWirelessMouse() + + def assertInputEvents(self, expected_events, effective_events): + # Buttons and x/y are spread over two HID reports, so we can get two + # event frames for this device. + remaining = self.assertInputEventsIn(expected_events, effective_events) + try: + remaining.remove(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0)) + except ValueError: + # If there's no SYN_REPORT in the list, continue and let the + # assert below print out the real error + pass + assert remaining == [] diff --git a/tools/testing/selftests/hid/tests/test_multitouch.py b/tools/testing/selftests/hid/tests/test_multitouch.py new file mode 100644 index 000000000000..4265012231c6 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_multitouch.py @@ -0,0 +1,2088 @@ +#!/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 . import base +from hidtools.hut import HUT +from hidtools.util import BusType +import libevdev +import logging +import pytest +import sys +import time + +logger = logging.getLogger("hidtools.test.multitouch") + +KERNEL_MODULE = ("hid-multitouch", "hid_multitouch") + + +def BIT(x): + return 1 << x + + +mt_quirks = { + "NOT_SEEN_MEANS_UP": BIT(0), + "SLOT_IS_CONTACTID": BIT(1), + "CYPRESS": BIT(2), + "SLOT_IS_CONTACTNUMBER": BIT(3), + "ALWAYS_VALID": BIT(4), + "VALID_IS_INRANGE": BIT(5), + "VALID_IS_CONFIDENCE": BIT(6), + "CONFIDENCE": BIT(7), + "SLOT_IS_CONTACTID_MINUS_ONE": BIT(8), + "NO_AREA": BIT(9), + "IGNORE_DUPLICATES": BIT(10), + "HOVERING": BIT(11), + "CONTACT_CNT_ACCURATE": BIT(12), + "FORCE_GET_FEATURE": BIT(13), + "FIX_CONST_CONTACT_ID": BIT(14), + "TOUCH_SIZE_SCALING": BIT(15), + "STICKY_FINGERS": BIT(16), + "ASUS_CUSTOM_UP": BIT(17), + "WIN8_PTP_BUTTONS": BIT(18), + "SEPARATE_APP_REPORT": BIT(19), + "MT_QUIRK_FORCE_MULTI_INPUT": BIT(20), +} + + +class Data(object): + pass + + +class Touch(object): + def __init__(self, id, x, y): + self.contactid = id + self.x = x + self.y = y + self.cx = x + self.cy = y + self.tipswitch = True + self.confidence = True + self.tippressure = 15 + self.azimuth = 0 + self.inrange = True + self.width = 10 + self.height = 10 + + +class Pen(Touch): + def __init__(self, x, y): + super().__init__(0, x, y) + self.barrel = False + self.invert = False + self.eraser = False + self.x_tilt = False + self.y_tilt = False + self.twist = 0 + + +class Digitizer(base.UHIDTestDevice): + @classmethod + def msCertificationBlob(cls, reportID): + return f""" + Usage Page (Digitizers) + Usage (Touch Screen) + Collection (Application) + Report ID ({reportID}) + Usage Page (0xff00) + Usage (0xc5) + Logical Minimum (0) + Logical Maximum (255) + Report Size (8) + Report Count (256) + Feature (Data,Var,Abs) + End Collection + """ + + def __init__( + self, + name, + rdesc_str=None, + rdesc=None, + application="Touch Screen", + physical="Finger", + max_contacts=None, + input_info=(BusType.USB, 1, 2), + quirks=None, + ): + super().__init__(name, application, rdesc_str, rdesc, input_info) + self.scantime = 0 + self.quirks = quirks + if max_contacts is None: + self.max_contacts = sys.maxsize + for features in self.parsed_rdesc.feature_reports.values(): + for feature in features: + if feature.usage_name in ["Contact Max"]: + self.max_contacts = feature.logical_max + for inputs in self.parsed_rdesc.input_reports.values(): + for i in inputs: + if ( + i.usage_name in ["Contact Count"] + and i.logical_max > 0 + and self.max_contacts > i.logical_max + ): + self.max_contacts = i.logical_max + if self.max_contacts == sys.maxsize: + self.max_contacts = 1 + else: + self.max_contacts = max_contacts + self.physical = physical + self.cur_application = application + + for features in self.parsed_rdesc.feature_reports.values(): + for feature in features: + if feature.usage_name == "Inputmode": + self.cur_application = "Mouse" + + self.fields = [] + for r in self.parsed_rdesc.input_reports.values(): + if r.application_name == self.application: + physicals = [f.physical_name for f in r] + if self.physical not in physicals and None not in physicals: + continue + self.fields = [f.usage_name for f in r] + + @property + def touches_in_a_report(self): + return self.fields.count("Contact Id") + + def event(self, slots, global_data=None, contact_count=None, incr_scantime=True): + if incr_scantime: + self.scantime += 1 + rs = [] + # make sure we have only the required number of available slots + slots = slots[: self.max_contacts] + + if global_data is None: + global_data = Data() + if contact_count is None: + global_data.contactcount = len(slots) + else: + global_data.contactcount = contact_count + global_data.scantime = self.scantime + + while len(slots): + r = self.create_report( + application=self.cur_application, data=slots, global_data=global_data + ) + self.call_input_event(r) + rs.append(r) + global_data.contactcount = 0 + return rs + + def get_report(self, req, rnum, rtype): + if rtype != self.UHID_FEATURE_REPORT: + return (1, []) + + rdesc = None + for v in self.parsed_rdesc.feature_reports.values(): + if v.report_ID == rnum: + rdesc = v + + if rdesc is None: + return (1, []) + + if "Contact Max" not in [f.usage_name for f in rdesc]: + return (1, []) + + self.contactmax = self.max_contacts + r = rdesc.create_report([self], None) + return (0, r) + + def set_report(self, req, rnum, rtype, data): + if rtype != self.UHID_FEATURE_REPORT: + return 1 + + rdesc = None + for v in self.parsed_rdesc.feature_reports.values(): + if v.report_ID == rnum: + rdesc = v + + if rdesc is None: + return 1 + + if "Inputmode" not in [f.usage_name for f in rdesc]: + return 0 + + Inputmode_seen = False + for f in rdesc: + if "Inputmode" == f.usage_name: + values = f.get_values(data) + assert len(values) == 1 + value = values[0] + + if not Inputmode_seen: + Inputmode_seen = True + if value == 0: + self.cur_application = "Mouse" + elif value == 2: + self.cur_application = "Touch Screen" + elif value == 3: + self.cur_application = "Touch Pad" + else: + if value != 0: + # Elan bug where the device doesn't work properly + # if we set twice an Input Mode in the same Feature + self.cur_application = "Mouse" + + return 0 + + +class PTP(Digitizer): + def __init__( + self, + name, + type="Click Pad", + rdesc_str=None, + rdesc=None, + application="Touch Pad", + physical="Pointer", + max_contacts=None, + input_info=None, + ): + self.type = type.lower().replace(" ", "") + if self.type == "clickpad": + self.buttontype = 0 + else: # pressurepad + self.buttontype = 1 + self.clickpad_state = False + self.left_state = False + self.right_state = False + super().__init__( + name, rdesc_str, rdesc, application, physical, max_contacts, input_info + ) + + def event( + self, + slots=None, + click=None, + left=None, + right=None, + contact_count=None, + incr_scantime=True, + ): + # update our internal state + if click is not None: + self.clickpad_state = click + if left is not None: + self.left_state = left + if right is not None: + self.right_state = right + + # now create the global data + global_data = Data() + global_data.b1 = 1 if self.clickpad_state else 0 + global_data.b2 = 1 if self.left_state else 0 + global_data.b3 = 1 if self.right_state else 0 + + if slots is None: + slots = [Data()] + + return super().event(slots, global_data, contact_count, incr_scantime) + + +class MinWin8TSParallel(Digitizer): + def __init__(self, max_slots): + self.max_slots = max_slots + self.phys_max = 120, 90 + rdesc_finger_str = f""" + Usage Page (Digitizers) + Usage (Finger) + Collection (Logical) + Report Size (1) + Report Count (1) + Logical Minimum (0) + Logical Maximum (1) + Usage (Tip Switch) + Input (Data,Var,Abs) + Report Size (7) + Logical Maximum (127) + Input (Cnst,Var,Abs) + Report Size (8) + Logical Maximum (255) + Usage (Contact Id) + Input (Data,Var,Abs) + Report Size (16) + Unit Exponent (-1) + Unit (SILinear: cm) + Logical Maximum (4095) + Physical Minimum (0) + Physical Maximum ({self.phys_max[0]}) + Usage Page (Generic Desktop) + Usage (X) + Input (Data,Var,Abs) + Physical Maximum ({self.phys_max[1]}) + Usage (Y) + Input (Data,Var,Abs) + Usage Page (Digitizers) + Usage (Azimuth) + Logical Maximum (360) + Unit (SILinear: deg) + Report Size (16) + Input (Data,Var,Abs) + End Collection +""" + rdesc_str = f""" + Usage Page (Digitizers) + Usage (Touch Screen) + Collection (Application) + Report ID (1) + {rdesc_finger_str * self.max_slots} + Unit Exponent (-4) + Unit (SILinear: s) + Logical Maximum (65535) + Physical Maximum (65535) + Usage Page (Digitizers) + Usage (Scan Time) + Input (Data,Var,Abs) + Report Size (8) + Logical Maximum (255) + Usage (Contact Count) + Input (Data,Var,Abs) + Report ID (2) + Logical Maximum ({self.max_slots}) + Usage (Contact Max) + Feature (Data,Var,Abs) + End Collection + {Digitizer.msCertificationBlob(68)} +""" + super().__init__(f"uhid test parallel {self.max_slots}", rdesc_str) + + +class MinWin8TSHybrid(Digitizer): + def __init__(self): + self.max_slots = 10 + self.phys_max = 120, 90 + rdesc_finger_str = f""" + Usage Page (Digitizers) + Usage (Finger) + Collection (Logical) + Report Size (1) + Report Count (1) + Logical Minimum (0) + Logical Maximum (1) + Usage (Tip Switch) + Input (Data,Var,Abs) + Report Size (7) + Logical Maximum (127) + Input (Cnst,Var,Abs) + Report Size (8) + Logical Maximum (255) + Usage (Contact Id) + Input (Data,Var,Abs) + Report Size (16) + Unit Exponent (-1) + Unit (SILinear: cm) + Logical Maximum (4095) + Physical Minimum (0) + Physical Maximum ({self.phys_max[0]}) + Usage Page (Generic Desktop) + Usage (X) + Input (Data,Var,Abs) + Physical Maximum ({self.phys_max[1]}) + Usage (Y) + Input (Data,Var,Abs) + End Collection +""" + rdesc_str = f""" + Usage Page (Digitizers) + Usage (Touch Screen) + Collection (Application) + Report ID (1) + {rdesc_finger_str * 2} + Unit Exponent (-4) + Unit (SILinear: s) + Logical Maximum (65535) + Physical Maximum (65535) + Usage Page (Digitizers) + Usage (Scan Time) + Input (Data,Var,Abs) + Report Size (8) + Logical Maximum (255) + Usage (Contact Count) + Input (Data,Var,Abs) + Report ID (2) + Logical Maximum ({self.max_slots}) + Usage (Contact Max) + Feature (Data,Var,Abs) + End Collection + {Digitizer.msCertificationBlob(68)} +""" + super().__init__("uhid test hybrid", rdesc_str) + + +class Win8TSConfidence(Digitizer): + def __init__(self, max_slots): + self.max_slots = max_slots + self.phys_max = 120, 90 + rdesc_finger_str = f""" + Usage Page (Digitizers) + Usage (Finger) + Collection (Logical) + Report Size (1) + Report Count (1) + Logical Minimum (0) + Logical Maximum (1) + Usage (Tip Switch) + Input (Data,Var,Abs) + Usage (Confidence) + Input (Data,Var,Abs) + Report Size (6) + Logical Maximum (127) + Input (Cnst,Var,Abs) + Report Size (8) + Logical Maximum (255) + Usage (Contact Id) + Input (Data,Var,Abs) + Report Size (16) + Unit Exponent (-1) + Unit (SILinear: cm) + Logical Maximum (4095) + Physical Minimum (0) + Physical Maximum ({self.phys_max[0]}) + Usage Page (Generic Desktop) + Usage (X) + Input (Data,Var,Abs) + Physical Maximum ({self.phys_max[1]}) + Usage (Y) + Input (Data,Var,Abs) + Usage Page (Digitizers) + Usage (Azimuth) + Logical Maximum (360) + Unit (SILinear: deg) + Report Size (16) + Input (Data,Var,Abs) + End Collection +""" + rdesc_str = f""" + Usage Page (Digitizers) + Usage (Touch Screen) + Collection (Application) + Report ID (1) + {rdesc_finger_str * self.max_slots} + Unit Exponent (-4) + Unit (SILinear: s) + Logical Maximum (65535) + Physical Maximum (65535) + Usage Page (Digitizers) + Usage (Scan Time) + Input (Data,Var,Abs) + Report Size (8) + Logical Maximum (255) + Usage (Contact Count) + Input (Data,Var,Abs) + Report ID (2) + Logical Maximum ({self.max_slots}) + Usage (Contact Max) + Feature (Data,Var,Abs) + End Collection + {Digitizer.msCertificationBlob(68)} +""" + super().__init__(f"uhid test confidence {self.max_slots}", rdesc_str) + + +class SmartTechDigitizer(Digitizer): + def __init__(self, name, input_info): + super().__init__( + name, + rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 05 81 03 05 01 15 00 26 ff 0f 55 0e 65 11 75 10 95 01 35 00 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 06 15 00 26 ff 00 a1 01 85 02 75 08 95 3f 09 00 82 02 01 95 3f 09 00 92 02 01 c0 05 0d 09 04 a1 01 85 05 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 75 08 95 01 15 00 25 0a 09 54 81 02 09 55 b1 02 c0 05 0d 09 0e a1 01 85 04 09 23 a1 02 15 00 25 02 75 08 95 02 09 52 09 53 b1 02 c0 c0 05 0d 09 04 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 75 08 95 01 15 00 25 0a 09 54 81 02 09 55 b1 02 c0 05 0d 09 04 a1 01 85 06 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 06 81 03 95 01 75 10 65 11 55 0e 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 07 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 08 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 09 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 0a 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0", + input_info=input_info, + ) + + def create_report(self, data, global_data=None, reportID=None, application=None): + # this device has *a lot* of different reports, and most of them + # have the Touch Screen application. But the first one is a stylus + # report (as stated in the physical type), so we simply override + # the report ID to use what the device sends + return super().create_report(data, global_data=global_data, reportID=3) + + def match_evdev_rule(self, application, evdev): + # we need to select the correct evdev node, as the device has multiple + # Touch Screen application collections + if application != "Touch Screen": + return True + absinfo = evdev.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X] + return absinfo is not None and absinfo.resolution == 3 + + +class BaseTest: + class TestMultitouch(base.BaseTestCase.TestUhid): + kernel_modules = [KERNEL_MODULE] + + def create_device(self): + raise Exception("please reimplement me in subclasses") + + def get_slot(self, uhdev, t, default): + if uhdev.quirks is None: + return default + + if "SLOT_IS_CONTACTID" in uhdev.quirks: + return t.contactid + + if "SLOT_IS_CONTACTID_MINUS_ONE" in uhdev.quirks: + return t.contactid - 1 + + return default + + 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.""" + super().test_creation() + + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + # some sanity checking for the quirks + if uhdev.quirks is not None: + for q in uhdev.quirks: + assert q in mt_quirks + + assert evdev.num_slots == uhdev.max_contacts + + if uhdev.max_contacts > 1: + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + if uhdev.max_contacts > 2: + assert evdev.slots[2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + def test_required_usages(self): + """Make sure the device exports the correct required features and + inputs.""" + uhdev = self.uhdev + rdesc = uhdev.parsed_rdesc + for feature in rdesc.feature_reports.values(): + for field in feature: + page_id = field.usage >> 16 + value = field.usage & 0xFF + try: + if HUT[page_id][value] == "Contact Max": + assert HUT[page_id][field.application] in [ + "Touch Screen", + "Touch Pad", + "System Multi-Axis Controller", + ] + except KeyError: + pass + + try: + if HUT[page_id][value] == "Inputmode": + assert HUT[page_id][field.application] in [ + "Touch Screen", + "Touch Pad", + "Device Configuration", + ] + except KeyError: + pass + + 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() + + t0 = Touch(1, 50, 100) + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + slot = self.get_slot(uhdev, t0, 0) + + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + + t0.tipswitch = False + if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks: + t0.inrange = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events + assert evdev.slots[slot][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() + + t0 = Touch(1, 50, 100) + t1 = Touch(2, 150, 200) + + if uhdev.quirks is not None and ( + "SLOT_IS_CONTACTID" in uhdev.quirks + or "SLOT_IS_CONTACTNUMBER" in uhdev.quirks + ): + t1.contactid = 0 + + slot0 = self.get_slot(uhdev, t0, 0) + slot1 = self.get_slot(uhdev, t1, 1) + + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + 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[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + r = uhdev.event([t0, t1]) + events = uhdev.next_sync_events() + 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[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200 + + t0.tipswitch = False + if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks: + t0.inrange = False + r = uhdev.event([t0, t1]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[slot1][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 + if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks: + t1.inrange = False + + if uhdev.quirks is not None and "SLOT_IS_CONTACTNUMBER" in uhdev.quirks: + r = uhdev.event([t0, t1]) + else: + r = uhdev.event([t1]) + + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + @pytest.mark.skip_if_uhdev( + lambda uhdev: uhdev.max_contacts <= 2, "Device not compatible" + ) + def test_mt_triple_tap(self): + """Send 3 touches in the first 3 slots. + Make sure the kernel sees this as a triple touch. + Release and check + + Note: PTP will send here BTN_TRIPLETAP emulation""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = Touch(1, 50, 100) + t1 = Touch(2, 150, 200) + t2 = Touch(3, 250, 300) + r = uhdev.event([t0, t1, t2]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + slot0 = self.get_slot(uhdev, t0, 0) + slot1 = self.get_slot(uhdev, t1, 1) + slot2 = self.get_slot(uhdev, t2, 2) + + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200 + assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 2 + assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_POSITION_X] == 250 + assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 300 + + t0.tipswitch = False + t1.tipswitch = False + t2.tipswitch = False + if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks: + t0.inrange = False + t1.inrange = False + t2.inrange = False + r = uhdev.event([t0, t1, t2]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + @pytest.mark.skip_if_uhdev( + lambda uhdev: uhdev.max_contacts <= 2, "Device not compatible" + ) + def test_mt_max_contact(self): + """send the maximum number of contact as reported by the device. + Make sure all contacts are forwarded and that there is no miss. + Release and check.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + touches = [ + Touch(i, (i + 3) * 20, (i + 3) * 20 + 5) + for i in range(uhdev.max_contacts) + ] + if ( + uhdev.quirks is not None + and "SLOT_IS_CONTACTID_MINUS_ONE" in uhdev.quirks + ): + for t in touches: + t.contactid += 1 + r = uhdev.event(touches) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + for i, t in enumerate(touches): + slot = self.get_slot(uhdev, t, i) + + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == i + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == t.x + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == t.y + + for t in touches: + t.tipswitch = False + if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks: + t.inrange = False + + r = uhdev.event(touches) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + for i, t in enumerate(touches): + slot = self.get_slot(uhdev, t, i) + + assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + @pytest.mark.skip_if_uhdev( + lambda uhdev: ( + uhdev.touches_in_a_report == 1 + or uhdev.quirks is not None + and "CONTACT_CNT_ACCURATE" not in uhdev.quirks + ), + "Device not compatible, we can not trigger the conditions", + ) + def test_mt_contact_count_accurate(self): + """Test the MT_QUIRK_CONTACT_CNT_ACCURATE from the kernel. + A report should forward an accurate contact count and the kernel + should ignore any data provided after we have reached this + contact count.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = Touch(1, 50, 100) + t1 = Touch(2, 150, 200) + + slot0 = self.get_slot(uhdev, t0, 0) + slot1 = self.get_slot(uhdev, t1, 1) + + r = uhdev.event([t0, t1], contact_count=1) + events = uhdev.next_sync_events() + 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 libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) in events + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + class TestWin8Multitouch(TestMultitouch): + def test_required_usages8(self): + """Make sure the device exports the correct required features and + inputs.""" + uhdev = self.uhdev + rdesc = uhdev.parsed_rdesc + for feature in rdesc.feature_reports.values(): + for field in feature: + page_id = field.usage >> 16 + value = field.usage & 0xFF + try: + if HUT[page_id][value] == "Inputmode": + assert HUT[field.application] not in ["Touch Screen"] + except KeyError: + pass + + @pytest.mark.skip_if_uhdev( + lambda uhdev: uhdev.fields.count("X") == uhdev.touches_in_a_report, + "Device not compatible, we can not trigger the conditions", + ) + def test_mt_tx_cx(self): + """send a single touch in the first slot of the device, with + different values of Tx and Cx. Make sure the kernel reports Tx.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = Touch(1, 5, 10) + t0.cx = 50 + t0.cy = 100 + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + 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] == 5 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TOOL_X] == 50 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 10 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TOOL_Y] == 100 + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "In Range" not in uhdev.fields, + "Device not compatible, missing In Range usage", + ) + def test_mt_inrange(self): + """Send one contact that has the InRange bit set before/after + tipswitch. + Kernel is supposed to mark the contact with a distance > 0 + when inrange is set but not tipswitch. + + This tests the hovering capability of devices (MT_QUIRK_HOVERING). + + Make sure the contact is only released from the kernel POV + when the inrange bit is set to 0.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = Touch(1, 150, 200) + t0.tipswitch = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + 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 libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) in events + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE) in events + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] > 0 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + t0.tipswitch = True + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE, 0) in events + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] == 0 + + t0.tipswitch = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE) in events + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] > 0 + + t0.inrange = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + 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_duplicates(self): + """Test the MT_QUIRK_IGNORE_DUPLICATES from the kernel. + If a touch is reported more than once with the same Contact ID, + we should only handle the first touch. + + Note: this is not in MS spec, but the current kernel behaves + like that""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = Touch(1, 5, 10) + t1 = Touch(1, 15, 20) + t2 = Touch(2, 50, 100) + + r = uhdev.event([t0, t1, t2], contact_count=2) + events = uhdev.next_sync_events() + 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 libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) 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] == 5 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 10 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + + def test_mt_release_miss(self): + """send a single touch in the first slot of the device, and + forget to release it. The kernel is supposed to release by itself + the touch in 100ms. + Make sure that we are dealing with a new touch by resending the + same touch after the timeout expired, and check that the kernel + considers it as a separate touch (different tracking ID)""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = Touch(1, 5, 10) + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + + time.sleep(0.2) + events = uhdev.next_sync_events() + 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 + + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1 + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Azimuth" not in uhdev.fields, + "Device not compatible, missing Azimuth usage", + ) + def test_mt_azimuth(self): + """Check for the azimtuh information bit. + When azimuth is presented by the device, it should be exported + as ABS_MT_ORIENTATION and the exported value should report a quarter + of circle.""" + uhdev = self.uhdev + + t0 = Touch(1, 5, 10) + t0.azimuth = 270 + + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + # orientation is clockwise, while Azimuth is counter clockwise + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_ORIENTATION, 90) in events + + class TestPTP(TestWin8Multitouch): + def test_ptp_buttons(self): + """check for button reliability. + There are 2 types of touchpads: the click pads and the pressure pads. + Each should reliably report the BTN_LEFT events. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + if uhdev.type == "clickpad": + r = uhdev.event(click=True) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1 + + r = uhdev.event(click=False) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 + else: + r = uhdev.event(left=True) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1 + + r = uhdev.event(left=False) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 + + r = uhdev.event(right=True) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1) in events + assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1 + + r = uhdev.event(right=False) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0) in events + assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0 + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Confidence" not in uhdev.fields, + "Device not compatible, missing Confidence usage", + ) + def test_ptp_confidence(self): + """Check for the validity of the confidence bit. + When a contact is marked as not confident, it should be detected + as a palm from the kernel POV and released. + + Note: if the kernel exports ABS_MT_TOOL_TYPE, it shouldn't release + the touch but instead convert it to ABS_MT_TOOL_PALM.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = Touch(1, 150, 200) + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + t0.confidence = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + if evdev.absinfo[libevdev.EV_ABS.ABS_MT_TOOL_TYPE] is not None: + # the kernel exports MT_TOOL_PALM + assert ( + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TOOL_TYPE, 2) in events + ) + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] != -1 + + t0.tipswitch = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + 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 + + @pytest.mark.skip_if_uhdev( + lambda uhdev: uhdev.touches_in_a_report >= uhdev.max_contacts, + "Device not compatible, we can not trigger the conditions", + ) + def test_ptp_non_touch_data(self): + """Some single finger hybrid touchpads might not provide the + button information in subsequent reports (only in the first report). + + Emulate this and make sure we do not release the buttons in the + middle of the event.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + touches = [Touch(i, i * 10, i * 10 + 5) for i in range(uhdev.max_contacts)] + contact_count = uhdev.max_contacts + incr_scantime = True + btn_state = True + events = None + while touches: + t = touches[: uhdev.touches_in_a_report] + touches = touches[uhdev.touches_in_a_report :] + r = uhdev.event( + t, + click=btn_state, + left=btn_state, + contact_count=contact_count, + incr_scantime=incr_scantime, + ) + contact_count = 0 + incr_scantime = False + btn_state = False + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + if touches: + assert len(events) == 0 + + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) not in events + assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1 + + +################################################################################ +# +# Windows 7 compatible devices +# +################################################################################ +class Test3m_0596_0500(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test 3m_0596_0500", + rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 01 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 10 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 85 12 09 55 95 01 75 08 15 00 25 0a b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 07 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 c0", + input_info=(BusType.USB, 0x0596, 0x0500), + max_contacts=60, + quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID", "TOUCH_SIZE_SCALING"), + ) + + +class Test3m_0596_0506(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test 3m_0596_0506", + rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 02 81 03 05 0d 85 12 09 55 95 01 75 08 15 00 25 3c b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 73 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 85 0f 09 01 75 08 96 07 02 b1 02 c0", + input_info=(BusType.USB, 0x0596, 0x0506), + max_contacts=60, + quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID", "TOUCH_SIZE_SCALING"), + ) + + +class TestActionStar_2101_1011(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test ActionStar_2101_1011", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4d 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4d 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0", + input_info=(BusType.USB, 0x2101, 0x1011), + ) + + def test_mt_actionstar_inrange(self): + """Special sequence that might not be handled properly""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + # fmt: off + sequence = [ + # t0 = Touch(1, 6999, 2441) | t1 = Touch(2, 15227, 2026) + '01 ff 01 57 1b 89 09 ff 02 7b 3b ea 07 02', + # t0.xy = (6996, 2450) | t1.y = 2028 + '01 ff 01 54 1b 92 09 ff 02 7b 3b ec 07 02', + # t1.xy = (15233, 2040) | t0.tipswitch = False + '01 ff 02 81 3b f8 07 fe 01 54 1b 92 09 02', + # t1 | t0.inrange = False + '01 ff 02 81 3b f8 07 fc 01 54 1b 92 09 02', + ] + # fmt: on + + for num, r_str in enumerate(sequence): + r = [int(i, 16) for i in r_str.split()] + uhdev.call_input_event(r) + events = uhdev.next_sync_events() + self.debug_reports([r], uhdev) + for e in events: + print(e) + if num == 2: + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + +class TestAsus_computers_0486_0185(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test asus-computers_0486_0185", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 95 01 75 01 81 02 09 32 81 02 09 47 81 02 75 05 81 03 09 30 26 ff 00 75 08 81 02 09 51 25 02 81 02 26 96 0d 05 01 75 10 55 0d 65 33 09 30 35 00 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 09 22 a1 02 05 0d 35 00 45 00 55 00 65 00 09 42 25 01 75 01 81 02 09 32 81 02 09 47 81 02 75 05 81 03 09 30 26 ff 00 75 08 81 02 09 51 25 02 81 02 26 96 0d 05 01 75 10 55 0d 65 33 09 30 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 35 00 45 00 55 00 65 00 05 0d 09 54 75 08 25 02 81 02 85 08 09 55 b1 02 c0 09 0e a1 01 85 07 09 22 a1 00 09 52 25 0a b1 02 c0 05 0c 09 01 a1 01 85 06 09 01 26 ff 00 95 08 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 25 01 75 01 95 02 81 02 95 06 81 03 26 96 0d 05 01 75 10 95 01 55 0d 65 33 09 30 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 c0 06 ff 01 09 01 a1 01 26 ff 00 35 00 45 00 55 00 65 00 85 05 75 08 95 3f 09 00 81 02 c0", + input_info=(BusType.USB, 0x0486, 0x0185), + quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"), + ) + + +class TestAtmel_03eb_201c(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test atmel_03eb_201c", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0", + input_info=(BusType.USB, 0x03EB, 0x201C), + ) + + +class TestAtmel_03eb_211c(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test atmel_03eb_211c", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 56 0a 26 ff 0f 09 30 81 02 46 b2 05 26 ff 0f 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0", + input_info=(BusType.USB, 0x03EB, 0x211C), + ) + + +class TestCando_2087_0a02(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test cando_2087_0a02", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 6d 03 81 02 46 ec 01 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 6d 03 81 02 46 ec 01 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 02 81 02 85 02 09 55 b1 02 c0 06 00 ff 09 01 a1 01 85 a6 95 22 75 08 26 ff 00 15 00 09 01 81 02 85 a5 95 06 75 08 26 ff 00 15 00 09 01 91 02 c0", + input_info=(BusType.USB, 0x2087, 0x0A02), + ) + + +class TestCando_2087_0b03(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test cando_2087_0b03", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 49 46 f2 03 81 02 09 31 26 ff 29 46 39 02 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 49 46 f2 03 81 02 09 31 26 ff 29 46 39 02 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0", + input_info=(BusType.USB, 0x2087, 0x0B03), + ) + + +class TestCVTouch_1ff7_0013(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test cvtouch_1ff7_0013", + rdesc="06 00 ff 09 00 a1 01 85 fd 06 00 ff 09 01 09 02 09 03 09 04 09 05 09 06 15 00 26 ff 00 75 08 95 06 81 02 85 fe 06 00 ff 09 01 09 02 09 03 09 04 15 00 26 ff 00 75 08 95 04 b1 02 c0 05 01 09 02 a1 01 09 01 a1 00 85 01 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 ff 7f 75 10 95 02 81 02 05 0d 09 33 15 00 26 ff 00 35 00 46 ff 00 75 08 95 01 81 02 05 01 09 38 15 81 25 7f 35 81 45 7f 95 01 81 06 c0 c0 06 00 ff 09 00 a1 01 85 fc 15 00 26 ff 00 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 06 00 ff 09 00 a1 01 85 fb 15 00 26 ff 00 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 85 03 09 55 15 00 25 02 b1 02 c0 09 0e a1 01 85 04 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x1FF7, 0x0013), + quirks=("NOT_SEEN_MEANS_UP",), + ) + + +class TestCvtouch_1ff7_0017(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test cvtouch_1ff7_0017", + rdesc="06 00 ff 09 00 a1 01 85 fd 06 00 ff 09 01 09 02 09 03 09 04 09 05 09 06 15 00 26 ff 00 75 08 95 06 81 02 85 fe 06 00 ff 09 01 09 02 09 03 09 04 15 00 26 ff 00 75 08 95 04 b1 02 c0 05 01 09 02 a1 01 09 01 a1 00 85 01 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 03 05 01 09 30 09 31 15 00 26 ff 0f 35 00 46 ff 0f 75 10 95 02 81 02 09 00 15 00 25 ff 35 00 45 ff 75 08 95 01 81 02 09 38 15 81 25 7f 95 01 81 06 c0 c0 06 00 ff 09 00 a1 01 85 fc 15 00 25 ff 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 06 00 ff 09 00 a1 01 85 fb 15 00 25 ff 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 05 0d 09 54 95 01 75 08 81 02 85 03 09 55 25 02 b1 02 c0 09 0e a1 01 85 04 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x1FF7, 0x0017), + ) + + +class TestCypress_04b4_c001(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test cypress_04b4_c001", + rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 04 a1 01 85 02 09 22 09 53 95 01 75 08 81 02 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 09 55 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x04B4, 0xC001), + ) + + +class TestData_modul_7374_1232(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test data-modul_7374_1232", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 d0 07 26 ff 0f 09 30 81 02 46 40 06 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0", + input_info=(BusType.USB, 0x7374, 0x1232), + ) + + +class TestData_modul_7374_1252(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test data-modul_7374_1252", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 d0 07 26 ff 0f 09 30 81 02 46 40 06 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0", + input_info=(BusType.USB, 0x7374, 0x1252), + ) + + +class TestE4_2219_044c(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test e4_2219_044c", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 09 55 b1 02 c0 09 0e a1 01 85 02 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 05 01 09 38 15 81 25 7f 75 08 95 01 81 06 c0 c0", + input_info=(BusType.USB, 0x2219, 0x044C), + ) + + +class TestEgalax_capacitive_0eef_7224(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test egalax-capacitive_0eef_7224", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x0EEF, 0x7224), + quirks=("SLOT_IS_CONTACTID", "ALWAYS_VALID"), + ) + + +class TestEgalax_capacitive_0eef_72fa(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test egalax-capacitive_0eef_72fa", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 72 22 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 87 13 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 72 22 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 87 13 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x0EEF, 0x72FA), + quirks=("SLOT_IS_CONTACTID", "VALID_IS_INRANGE"), + ) + + +class TestEgalax_capacitive_0eef_7336(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test egalax-capacitive_0eef_7336", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 c1 20 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c2 18 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 c1 20 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c2 18 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x0EEF, 0x7336), + ) + + +class TestEgalax_capacitive_0eef_7337(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test egalax-capacitive_0eef_7337", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 ae 17 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c3 0e 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 ae 17 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c3 0e 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x0EEF, 0x7337), + ) + + +class TestEgalax_capacitive_0eef_7349(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test egalax-capacitive_0eef_7349", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x0EEF, 0x7349), + quirks=("SLOT_IS_CONTACTID", "ALWAYS_VALID"), + ) + + +class TestEgalax_capacitive_0eef_73f4(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test egalax-capacitive_0eef_73f4", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 96 4e 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 23 2c 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 96 4e 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 23 2c 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x0EEF, 0x73F4), + ) + + +class TestEgalax_capacitive_0eef_a001(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test egalax-capacitive_0eef_a001", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 23 28 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 11 19 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x0EEF, 0xA001), + quirks=("SLOT_IS_CONTACTID", "VALID_IS_INRANGE"), + ) + + +class TestElo_touchsystems_04e7_0022(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test elo-touchsystems_04e7_0022", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 ff 0f 81 02 46 ff 0f 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 46 ff 0f 09 31 81 02 c0 05 0d 09 54 25 10 95 01 75 08 81 02 85 08 09 55 25 02 b1 02 c0 09 0e a1 01 85 07 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 06 00 ff 09 55 85 80 15 00 26 ff 00 75 08 95 01 b1 82 c0 05 01 09 02 a1 01 85 54 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 15 00 26 ff 0f 75 10 95 01 81 02 09 31 75 10 95 01 81 02 09 3b 16 00 00 26 00 01 36 00 00 46 00 01 66 00 00 75 10 95 01 81 62 c0 c0", + input_info=(BusType.USB, 0x04E7, 0x0022), + ) + + +class TestElo_touchsystems_04e7_0080(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test elo-touchsystems_04e7_0080", + rdesc="05 0d 09 04 a1 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 05 0d 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 c0", + input_info=(BusType.USB, 0x04E7, 0x0080), + ) + + +class TestFlatfrog_25b5_0002(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test flatfrog_25b5_0002", + rdesc="05 0d 09 04 a1 01 85 05 09 22 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 65 00 55 00 05 0d 55 0c 66 01 10 75 20 95 01 27 ff ff ff 7f 45 00 09 56 81 02 75 08 95 01 15 00 25 28 09 54 81 02 09 55 85 06 25 28 b1 02 c0 65 00 55 00 45 00 09 0e a1 01 85 03 09 23 a1 02 09 52 15 02 25 02 75 08 95 01 b1 02 09 53 15 00 25 0a 75 08 95 01 b1 02 c0 c0", + input_info=(BusType.USB, 0x25B5, 0x0002), + quirks=("NOT_SEEN_MEANS_UP", "NO_AREA"), + max_contacts=40, + ) + + +class TestFocaltech_10c4_81b9(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test focaltech_10c4_81b9", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 02 09 55 75 08 95 01 b1 02 c0", + input_info=(BusType.USB, 0x10C4, 0x81B9), + quirks=("ALWAYS_VALID",), + max_contacts=5, + ) + + +class TestHanvon_20b3_0a18(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test hanvon_20b3_0a18", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0", + input_info=(BusType.USB, 0x20B3, 0x0A18), + ) + + +class TestHuitoo_03f7_0003(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test huitoo_03f7_0003", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 09 55 b1 02 c0 09 0e a1 01 85 02 09 23 a1 02 09 52 09 53 15 00 25 10 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 00 26 ff 0f 35 00 46 ff 0f 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 3f 09 02 81 02 95 3f 09 02 91 02 c0", + input_info=(BusType.USB, 0x03F7, 0x0003), + ) + + +class TestIdeacom_1cb6_6650(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test ideacom_1cb6_6650", + rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 26 ff 1f 75 10 95 01 55 0d 65 33 09 31 35 00 46 61 13 81 02 09 30 46 73 22 81 02 05 0d 75 08 95 01 09 30 26 ff 00 81 02 09 51 81 02 85 0c 09 55 25 02 95 01 b1 02 c0 06 00 ff 85 02 09 01 75 08 95 07 b1 02 85 03 09 02 75 08 95 07 b1 02 85 04 09 03 75 08 95 07 b1 02 85 05 09 04 75 08 95 07 b1 02 85 06 09 05 75 08 96 27 00 b1 02 85 07 09 06 75 08 96 27 00 b1 02 85 08 09 07 75 08 95 07 b1 02 85 09 09 08 75 08 95 07 b1 02 85 0b 09 09 75 08 96 07 00 b1 02 85 0d 09 0a 75 08 96 27 00 b1 02 c0 09 0e a1 01 85 0e 09 52 09 53 95 07 b1 02 c0 05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 75 06 95 01 81 01 05 01 09 31 09 30 15 00 27 ff 1f 00 00 75 10 95 02 81 02 c0 09 01 a1 02 15 00 26 ff 00 95 02 75 08 81 03 c0 c0", + input_info=(BusType.USB, 0x1CB6, 0x6650), + ) + + +class TestIdeacom_1cb6_6651(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test ideacom_1cb6_6651", + rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 02 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 26 ff 1f 75 10 95 01 55 0d 65 33 09 31 35 00 46 39 13 81 02 09 30 46 24 22 81 02 05 0d 75 08 95 01 09 30 26 ff 00 81 02 09 51 81 02 85 0c 09 55 25 02 95 01 b1 02 c0 06 00 ff 85 02 09 01 75 08 95 07 b1 02 85 03 09 02 75 08 95 07 b1 02 85 04 09 03 75 08 95 07 b1 02 85 05 09 04 75 08 95 07 b1 02 85 06 09 05 75 08 95 1f b1 02 85 07 09 06 75 08 96 1f 00 b1 02 85 08 09 07 75 08 95 07 b1 02 85 09 09 08 75 08 95 07 b1 02 85 0b 09 09 75 08 95 07 b1 02 85 0d 09 0a 75 08 96 1f 00 b1 02 c0 09 0e a1 01 85 0e 09 52 09 53 95 07 b1 02 c0 05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 75 06 95 01 81 01 05 01 09 31 09 30 15 00 27 ff 1f 00 00 75 10 95 02 81 02 c0 09 01 a1 02 15 00 26 ff 00 95 02 75 08 81 03 c0 c0", + input_info=(BusType.USB, 0x1CB6, 0x6651), + ) + + +class TestIkaist_2793_0001(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test ikaist_2793_0001", + rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 02 81 03 05 0d 85 12 09 55 95 01 75 08 15 00 25 3c b1 02 06 00 ff 15 00 26 ff 00 85 1e 09 01 75 08 95 80 b1 02 85 1f 09 01 75 08 96 3f 01 b1 02 c0", + input_info=(BusType.USB, 0x2793, 0x0001), + ) + + +class TestIrmtouch_23c9_5666(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test irmtouch_23c9_5666", + rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 0c 09 23 a1 02 09 52 15 00 25 06 75 08 95 01 b1 02 c0 c0", + input_info=(BusType.USB, 0x23C9, 0x5666), + ) + + +class TestIrtouch_6615_0070(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test irtouch_6615_0070", + rdesc="05 01 09 02 a1 01 85 10 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 30 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 05 0d 09 54 15 00 26 02 00 75 08 95 01 81 02 85 03 09 55 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 05 0d 09 02 a1 01 85 20 09 20 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 85 01 06 00 ff 09 01 75 08 95 01 b1 02 c0 c0", + input_info=(BusType.USB, 0x6615, 0x0070), + ) + + +class TestIrtouch_6615_0081(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test irtouch_6615_0081", + rdesc="05 0d 09 04 a1 01 85 30 09 22 09 00 15 00 26 ff 00 75 08 95 05 81 02 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 54 15 00 25 1f 75 05 95 01 81 02 09 00 15 00 25 07 75 03 95 01 81 02 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 09 55 85 03 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 06 00 ff 09 00 a1 01 09 02 a1 00 85 aa 09 06 15 00 26 ff 00 35 00 46 ff 00 75 08 95 3f b1 02 c0 c0 05 01 09 02 a1 01 85 10 09 01 a1 00 05 01 09 00 15 00 26 ff 00 75 08 95 05 81 02 09 30 09 31 09 32 15 00 26 ff 7f 75 10 95 03 81 02 05 09 19 01 29 08 15 00 25 01 95 08 75 01 81 02 09 00 15 00 26 ff 00 75 08 95 02 81 02 c0 c0 06 00 ff 09 00 a1 01 85 40 09 00 15 00 26 ff 00 75 08 95 2e 81 02 c0", + input_info=(BusType.USB, 0x6615, 0x0081), + ) + + +class TestLG_043e_9aa1(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test lg_043e_9aa1", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 25 0a 09 55 b1 02 c0 09 0e a1 01 85 03 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 04 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 75 10 95 01 15 00 26 7f 07 81 02 09 31 26 37 04 81 02 c0 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 75 08 95 19 09 01 b1 02 c0 05 14 09 2b a1 02 85 07 09 2b 15 00 25 0a 75 08 95 40 b1 02 09 4b 15 00 25 0a 75 08 95 02 91 02 c0 05 14 09 2c a1 02 85 08 09 2b 15 00 25 0a 75 08 95 05 81 02 09 4b 15 00 25 0a 75 08 95 47 91 02 c0", + input_info=(BusType.USB, 0x043E, 0x9AA1), + ) + + +class TestLG_043e_9aa3(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test lg_043e_9aa3", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 25 0a 09 55 b1 02 c0 09 0e a1 01 85 03 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 04 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 75 10 95 01 15 00 26 7f 07 81 02 09 31 26 37 04 81 02 c0 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 75 08 95 19 09 01 b1 02 c0 05 14 09 2b a1 02 85 07 09 2b 15 00 25 0a 75 08 95 40 b1 02 09 4b 15 00 25 0a 75 08 95 02 91 02 c0 05 14 09 2c a1 02 85 08 09 2b 15 00 25 0a 75 08 95 05 81 02 09 4b 15 00 25 0a 75 08 95 47 91 02 c0", + input_info=(BusType.USB, 0x043E, 0x9AA3), + ) + + +class TestLG_1fd2_0064(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test lg_1fd2_0064", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 a1 00 05 01 26 80 07 75 10 55 0e 65 33 09 30 35 00 46 53 07 81 02 26 38 04 46 20 04 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 a1 00 05 01 26 80 07 75 10 55 0e 65 33 09 30 35 00 46 53 07 81 02 26 38 04 46 20 04 09 31 81 02 45 00 c0 c0 05 0d 09 54 95 01 75 08 81 02 85 08 09 55 95 01 25 02 b1 02 c0 09 0e a1 01 85 07 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 75 10 95 02 15 00 26 ff 7f 81 02 c0 c0", + input_info=(BusType.USB, 0x1FD2, 0x0064), + ) + + +class TestLumio_202e_0006(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test lumio_202e_0006", + rdesc="05 0d 09 04 a1 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 05 0d 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 c0", + input_info=(BusType.USB, 0x202E, 0x0006), + quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"), + ) + + +class TestLumio_202e_0007(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test lumio_202e_0007", + rdesc="05 0d 09 04 a1 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 0a 81 03 05 01 26 ff 7f 65 11 55 0e 46 ba 0e 75 10 95 01 09 30 81 02 09 31 46 ea 0b 81 02 05 0d 09 51 75 10 95 01 81 02 09 55 15 00 25 08 75 08 95 01 b1 02 c0 c0", + input_info=(BusType.USB, 0x202E, 0x0007), + quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"), + ) + + +class TestNexio_1870_0100(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test nexio_1870_0100", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 40 81 00 19 01 29 40 91 00 c0", + input_info=(BusType.USB, 0x1870, 0x0100), + ) + + +class TestNexio_1870_010d(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test nexio_1870_010d", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0", + input_info=(BusType.USB, 0x1870, 0x010D), + ) + + +class TestNexio_1870_0119(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test nexio_1870_0119", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0", + input_info=(BusType.USB, 0x1870, 0x0119), + ) + + +class TestPenmount_14e1_3500(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test penmount_14e1_3500", + rdesc="05 0d 09 04 a1 01 09 22 a1 00 09 51 15 00 25 0f 75 04 95 01 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 81 01 05 01 75 10 95 01 09 30 26 ff 07 81 02 09 31 26 ff 07 81 02 05 0d 09 55 75 08 95 05 b1 02 c0 c0", + input_info=(BusType.USB, 0x14E1, 0x3500), + quirks=("VALID_IS_CONFIDENCE",), + max_contacts=10, + ) + + +class TestPixart_093a_8002(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test pixart_093a_8002", + rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 5a 14 26 ff 7f 09 30 81 22 46 72 0b 26 ff 7f 09 31 81 22 95 08 75 08 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 5a 14 26 ff 7f 81 02 09 31 46 72 0b 26 ff 7f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 5a 14 26 ff 7f 81 02 46 72 0b 26 ff 7f 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0", + input_info=(BusType.USB, 0x093A, 0x8002), + quirks=("VALID_IS_INRANGE", "SLOT_IS_CONTACTNUMBER"), + ) + + +class TestPqlabs_1ef1_0001(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test pqlabs_1ef1_0001", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 3f 81 02 c0 c0 05 8c 09 07 a1 01 85 11 09 02 15 00 26 ff 00 75 08 95 3f 81 02 85 10 09 10 91 02 c0", + input_info=(BusType.USB, 0x1EF1, 0x0001), + ) + + +class TestQuanta_0408_3000(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test quanta_0408_3000", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 e3 13 26 7f 07 81 02 09 31 46 2f 0b 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 e3 13 26 7f 07 81 02 46 2f 0b 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 06 00 ff 09 01 26 ff 00 75 08 95 2f 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 c0", + input_info=(BusType.USB, 0x0408, 0x3000), + ) + + +class TestQuanta_0408_3001(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test quanta_0408_3001", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 0f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 c0", + input_info=(BusType.USB, 0x0408, 0x3001), + quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID"), + ) + + +class TestQuanta_0408_3008_1(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test quanta_0408_3008_1", + rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 4c 11 26 7f 07 09 30 81 22 46 bb 09 26 37 04 09 31 81 22 95 08 75 08 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 4c 11 26 7f 07 81 02 09 31 46 bb 09 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 4c 11 26 7f 07 81 02 46 bb 09 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0", + input_info=(BusType.USB, 0x0408, 0x3008), + ) + + +class TestQuanta_0408_3008(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test quanta_0408_3008", + rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 98 12 26 7f 07 09 30 81 22 46 78 0a 26 37 04 09 31 81 22 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 09 31 46 78 0a 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 46 78 0a 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0", + input_info=(BusType.USB, 0x0408, 0x3008), + ) + + +class TestRafi_05bd_0107(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test rafi_05bd_0107", + rdesc="05 0d 09 04 a1 01 85 01 09 22 65 00 55 00 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 09 81 02 05 0d 85 02 95 01 75 08 09 55 25 0a b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 05 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 65 11 55 0f 09 30 26 ff 03 35 00 46 9c 01 75 10 95 01 81 02 09 31 26 ff 03 35 00 46 e7 00 81 02 c0 c0", + input_info=(BusType.USB, 0x05BD, 0x0107), + ) + + +class TestRndplus_2512_5003(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test rndplus_2512_5003", + rdesc="05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 08 09 55 b1 02 c0 09 0e a1 01 85 07 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 16 00 00 26 ff 3f 36 00 00 46 ff 3f 66 00 00 75 10 95 02 81 62 c0 c0", + input_info=(BusType.USB, 0x2512, 0x5003), + ) + + +class TestRndplus_2512_5004(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test rndplus_2512_5004", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 05 09 55 b1 02 c0 09 0e a1 01 85 06 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 16 00 00 26 ff 3f 36 00 00 46 ff 3f 66 00 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 01 09 01 15 00 26 ff 00 75 08 95 3f 82 00 01 85 02 09 01 15 00 26 ff 00 75 08 95 3f 92 00 01 c0", + input_info=(BusType.USB, 0x2512, 0x5004), + ) + + +class TestSitronix_1403_5001(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test sitronix_1403_5001", + rdesc="05 0d 09 04 a1 01 85 01 09 54 95 01 75 08 81 02 09 22 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 05 01 26 90 04 75 0c 95 01 55 0f 65 11 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 04 09 48 09 49 81 02 c0 85 02 09 55 26 ff 00 75 08 95 01 b1 02 09 04 15 00 25 ff 75 08 95 07 91 02 c0 09 0e a1 01 85 03 09 23 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + input_info=(BusType.USB, 0x1403, 0x5001), + max_contacts=10, + ) + + +class TestSmart_0b8c_0092(BaseTest.TestMultitouch): + def create_device(self): + return SmartTechDigitizer( + "uhid test smart_0b8c_0092", input_info=(BusType.USB, 0x0B8C, 0x0092) + ) + + +class TestStantum_1f87_0002(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test stantum_1f87_0002", + rdesc="05 0d 09 04 a1 01 85 03 05 0d 09 54 95 01 75 08 81 02 06 00 ff 75 02 09 01 81 01 75 0e 09 02 81 02 05 0d 09 22 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 85 08 05 0d 09 55 95 01 75 08 25 0a b1 02 c0", + input_info=(BusType.USB, 0x1F87, 0x0002), + ) + + +class TestTopseed_1784_0016(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test topseed_1784_0016", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 04 75 10 55 00 65 00 09 30 35 00 46 ff 04 81 02 09 31 46 ff 04 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 09 55 b1 02 c0 05 0c 09 01 a1 01 85 03 a1 02 09 b5 15 00 25 01 75 01 95 01 81 02 09 b6 81 02 09 b7 81 02 09 cd 81 02 09 e2 81 02 09 e9 81 02 09 ea 81 02 05 01 09 82 81 02 c0 c0", + input_info=(BusType.USB, 0x1784, 0x0016), + max_contacts=2, + ) + + +class TestTpv_25aa_8883(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test tpv_25aa_8883", + rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 05 0d 09 32 95 01 75 01 81 02 95 01 75 05 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 98 12 26 7f 07 09 30 81 22 46 78 0a 26 37 04 09 31 81 22 35 00 45 00 15 81 25 7f 75 08 95 01 09 38 81 06 09 00 75 08 95 07 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 09 31 46 78 0a 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 46 78 0a 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0", + input_info=(BusType.USB, 0x25AA, 0x8883), + ) + + +class TestTrs_star_238f_0001(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test trs-star_238f_0001", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 95 01 81 03 09 37 95 01 81 03 95 01 81 03 15 00 25 0f 75 04 09 51 95 01 81 02 09 54 95 01 81 02 09 55 95 01 81 02 05 01 26 ff 03 15 00 75 10 65 00 09 30 95 01 81 02 09 31 81 02 c0 05 0d 09 0e 85 02 09 23 a1 02 15 00 25 0a 09 52 75 08 95 01 b1 02 09 53 95 01 b1 02 09 55 95 01 b1 02 c0 c0", + input_info=(BusType.USB, 0x238F, 0x0001), + ) + + +class TestUnitec_227d_0103(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test unitec_227d_0103", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 03 09 55 25 05 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 53 15 00 25 05 75 08 95 01 b1 02 c0", + input_info=(BusType.USB, 0x227D, 0x0103), + ) + + +class TestZytronic_14c8_0005(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test zytronic_14c8_0005", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 95 01 81 02 95 06 81 01 05 01 26 00 10 75 10 95 01 65 00 09 30 81 02 09 31 46 00 10 81 02 05 0d 09 51 26 ff 00 75 08 95 01 81 02 c0 85 02 09 55 15 00 25 08 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 03 a1 02 09 23 09 52 09 53 15 00 25 08 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 15 00 26 00 10 35 00 46 00 10 65 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 05 09 00 15 00 26 ff 00 75 08 95 3f b1 02 c0 06 00 ff 09 01 a1 01 85 06 09 00 15 00 26 ff 00 75 08 95 3f 81 02 c0", + input_info=(BusType.USB, 0x14C8, 0x0005), + ) + + +class TestZytronic_14c8_0006(BaseTest.TestMultitouch): + def create_device(self): + return Digitizer( + "uhid test zytronic_14c8_0006", + rdesc="05 0d 09 04 a1 01 85 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 05 0d 85 02 09 55 95 01 75 08 15 00 25 3c b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 15 00 26 00 10 35 00 46 00 10 65 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 05 09 00 15 00 26 ff 00 75 08 95 3f b1 02 c0 06 00 ff 09 01 a1 01 85 06 09 00 15 00 26 ff 00 75 08 95 3f 81 02 c0", + input_info=(BusType.USB, 0x14C8, 0x0006), + ) + + +################################################################################ +# +# Windows 8 compatible devices +# +################################################################################ + + +class TestMinWin8TSParallelTriple(BaseTest.TestWin8Multitouch): + def create_device(self): + return MinWin8TSParallel(3) + + +class TestMinWin8TSParallel(BaseTest.TestWin8Multitouch): + def create_device(self): + return MinWin8TSParallel(10) + + +class TestMinWin8TSHybrid(BaseTest.TestWin8Multitouch): + def create_device(self): + return MinWin8TSHybrid() + + +class TestWin8TSConfidence(BaseTest.TestWin8Multitouch): + def create_device(self): + return Win8TSConfidence(5) + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Confidence" not in uhdev.fields, + "Device not compatible, missing Confidence usage", + ) + def test_mt_confidence_bad_release(self): + """Check for the validity of the confidence bit. + When a contact is marked as not confident, it should be detected + as a palm from the kernel POV and released. + + Note: if the kernel exports ABS_MT_TOOL_TYPE, it shouldn't release + the touch but instead convert it to ABS_MT_TOOL_PALM.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + t0 = Touch(1, 150, 200) + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + t0.confidence = False + t0.tipswitch = False + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + if evdev.absinfo[libevdev.EV_ABS.ABS_MT_TOOL_TYPE] is not None: + # the kernel exports MT_TOOL_PALM + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TOOL_TYPE, 2) in events + + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + +class TestElanXPS9360(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test ElanXPS9360", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0", + ) + + +class TestTouchpadXPS9360(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test TouchpadXPS9360", + max_contacts=5, + rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 3d 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 3d 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 03 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0", + ) + + +class TestSurfaceBook2(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test SurfaceBook2", + max_contacts=5, + rdesc="05 01 09 06 A1 01 85 01 14 25 01 75 01 95 08 05 07 19 E0 29 E7 81 02 75 08 95 0A 18 29 91 26 FF 00 80 05 0C 0A C0 02 A1 02 1A C1 02 2A C6 02 95 06 B1 03 C0 05 08 19 01 29 03 75 01 95 03 25 01 91 02 95 05 91 01 C0 05 01 09 02 A1 01 85 02 05 09 19 01 29 05 81 02 95 01 75 03 81 03 15 81 25 7F 75 08 95 02 05 01 09 30 09 31 81 06 A1 02 09 48 14 25 01 35 01 45 10 75 02 95 01 A4 B1 02 09 38 15 81 25 7F 34 44 75 08 81 06 C0 A1 02 09 48 B4 B1 02 34 44 75 04 B1 03 05 0C 0A 38 02 15 81 25 7F 75 08 81 06 C0 C0 05 0C 09 01 A1 01 85 03 75 10 14 26 FF 03 18 2A FF 03 80 C0 06 05 FF 09 01 A1 01 85 0D 25 FF 95 02 75 08 09 20 81 02 09 22 91 02 15 81 25 7F 95 20 75 08 09 21 81 02 09 23 91 02 C0 09 02 A1 01 85 0C 14 25 FF 95 01 08 91 02 C0 05 0D 09 05 A1 01 85 04 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 C0 05 0D 55 0C 66 01 10 47 FF FF 00 00 27 FF FF 00 00 09 56 81 02 09 54 25 7F 75 08 81 02 05 09 09 01 25 01 75 01 81 02 95 07 81 03 05 0D 85 04 09 55 09 59 75 04 95 02 25 0F B1 02 06 00 FF 09 C6 85 05 14 25 08 75 08 95 01 B1 02 09 C7 26 FF 00 75 08 95 20 B1 02 C0 05 0D 09 0E A1 01 85 07 09 22 A1 02 09 52 14 25 0A 75 08 95 01 B1 02 C0 09 22 A0 85 08 09 57 09 58 75 01 95 02 25 01 B1 02 95 06 B1 03 C0 C0 06 07 FF 09 01 A1 01 85 0A 09 02 26 FF 00 75 08 95 14 91 02 85 09 09 03 91 02 85 0A 09 04 95 26 81 02 85 09 09 05 81 02 85 09 09 06 95 01 B1 02 85 0B 09 07 B1 02 C0 06 05 FF 09 04 A1 01 85 0E 09 31 91 02 09 31 81 03 09 30 91 02 09 30 81 02 95 39 09 32 92 02 01 09 32 82 02 01 C0 06 05 FF 09 50 A1 01 85 20 14 25 FF 75 08 95 3C 09 60 82 02 01 09 61 92 02 01 09 62 B2 02 01 85 21 09 63 82 02 01 09 64 92 02 01 09 65 B2 02 01 85 22 25 FF 75 20 95 04 19 66 29 69 81 02 19 6A 29 6D 91 02 19 6E 29 71 B1 02 85 23 19 72 29 75 81 02 19 76 29 79 91 02 19 7A 29 7D B1 02 85 24 19 7E 29 81 81 02 19 82 29 85 91 02 19 86 29 89 B1 02 85 25 19 8A 29 8D 81 02 19 8E 29 91 91 02 19 92 29 95 B1 02 85 26 19 96 29 99 81 02 19 9A 29 9D 91 02 19 9E 29 A1 B1 02 85 27 19 A2 29 A5 81 02 19 A6 29 A9 91 02 19 AA 29 AD B1 02 85 28 19 AE 29 B1 81 02 19 B2 29 B5 91 02 19 B6 29 B9 B1 02 85 29 19 BA 29 BD 81 02 19 BE 29 C1 91 02 19 C2 29 C5 B1 02 C0 06 00 FF 0A 00 F9 A1 01 85 32 75 10 95 02 14 27 FF FF 00 00 0A 01 F9 B1 02 75 20 95 01 25 FF 0A 02 F9 B1 02 75 08 95 08 26 FF 00 0A 03 F9 B2 02 01 95 10 0A 04 F9 B2 02 01 0A 05 F9 B2 02 01 95 01 75 10 27 FF FF 00 00 0A 06 F9 81 02 C0", + ) + + +class Test3m_0596_051c(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test 3m_0596_051c", + rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 ff 7f 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 14 81 02 05 0d 55 0c 66 01 10 35 00 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 05 0d 09 55 85 12 15 00 25 14 75 08 95 01 b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 73 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 85 0f 09 01 75 08 96 07 02 b1 02 c0", + ) + + +class Testadvanced_silicon_04e8_2084(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test advanced_silicon_04e8_2084", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0", + ) + + +class Testadvanced_silicon_2149_2306(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test advanced_silicon_2149_2306", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0", + ) + + +class Testadvanced_silicon_2149_230a(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test advanced_silicon_2149_230a", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0", + ) + + +class Testadvanced_silicon_2149_231c(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test advanced_silicon_2149_231c", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0", + ) + + +class Testadvanced_silicon_2149_2703(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test advanced_silicon_2149_2703", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0", + ) + + +class Testadvanced_silicon_2149_270b(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test advanced_silicon_2149_270b", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0", + ) + + +class Testadvanced_silicon_2575_0204(BaseTest.TestWin8Multitouch): + """found on the Dell Canvas 27""" + + def create_device(self): + return Digitizer( + "uhid test advanced_silicon_2575_0204", + rdesc="05 0d 09 04 a1 01 85 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 42 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 c0 05 01 09 0e a1 01 85 05 05 01 09 08 a1 00 09 30 55 0e 65 11 15 00 26 ff 7f 35 00 46 4f 17 75 10 95 01 81 42 09 31 46 1d 0d 81 42 06 00 ff 09 01 75 20 81 03 05 01 09 37 55 00 65 14 16 98 fe 26 68 01 36 98 fe 46 68 01 75 0f 81 06 05 09 09 01 65 00 15 00 25 01 35 00 45 00 75 01 81 02 05 0d 09 42 81 02 09 51 75 07 25 7f 81 02 05 0d 09 48 55 0e 65 11 15 00 26 ff 7f 35 00 46 ff 7f 75 10 81 02 09 49 81 02 09 3f 55 00 65 14 15 00 26 67 01 35 00 46 67 01 81 0a c0 65 00 35 00 45 00 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 05 75 08 09 54 81 02 85 47 09 55 25 05 b1 02 c0 06 00 ff 09 04 a1 01 85 f0 09 01 75 08 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 85 c0 09 01 95 03 b1 02 85 c2 09 01 95 0f b1 02 85 c4 09 01 95 3e b1 02 85 c5 09 01 95 7e b1 02 85 c6 09 01 95 fe b1 02 85 c8 09 01 96 fe 03 b1 02 85 0a 09 01 95 3f b1 02 c0", + ) + + +class Testadvanced_silicon_2619_5610(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test advanced_silicon_2619_5610", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 c0", + ) + + +class Testatmel_03eb_8409(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test atmel_03eb_8409", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 00 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 46 18 06 26 77 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0", + ) + + +class Testatmel_03eb_840b(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test atmel_03eb_840b", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 00 0a 26 ff 0f 09 30 81 02 46 a0 05 26 ff 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0", + ) + + +class Testdell_044e_1220(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test dell_044e_1220", + type="pressurepad", + rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 09 38 95 01 81 06 05 0c 0a 38 02 81 06 c0 c0 05 0d 09 05 a1 01 85 08 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 af 04 75 10 55 0e 65 11 09 30 35 00 46 e8 03 95 01 81 02 26 7b 02 46 12 02 09 31 81 02 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 05 0d 09 56 81 02 09 54 25 05 95 01 75 08 81 02 05 09 19 01 29 03 25 01 75 01 95 03 81 02 95 05 81 03 05 0d 85 09 09 55 75 08 95 01 25 05 b1 02 06 00 ff 85 0a 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 01 ff 09 01 a1 01 85 03 09 01 15 00 26 ff 00 95 1b 81 02 85 04 09 02 95 50 81 02 85 05 09 03 95 07 b1 02 85 06 09 04 81 02 c0 06 02 ff 09 01 a1 01 85 07 09 02 95 86 75 08 b1 02 c0 05 0d 09 0e a1 01 85 0b 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 0c 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0", + ) + + +class Testdell_06cb_75db(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test dell_06cb_75db", + max_contacts=3, + rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 1a 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 1a 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0", + ) + + +class Testegalax_capacitive_0eef_790a(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test egalax_capacitive_0eef_790a", + max_contacts=10, + rdesc="05 0d 09 04 a1 01 85 06 05 0d 09 54 75 08 15 00 25 0c 95 01 81 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 17 00 00 00 00 27 ff ff ff 7f 75 20 95 01 55 00 65 00 09 56 81 02 09 55 09 53 75 08 95 02 26 ff 00 b1 02 06 00 ff 09 c5 85 07 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 01 a1 01 85 01 09 01 a1 02 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 0e a1 01 85 05 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0", + ) + + +class Testelan_04f3_000a(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test elan_04f3_000a", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0", + ) + + +class Testelan_04f3_000c(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test elan_04f3_000c", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0", + ) + + +class Testelan_04f3_010c(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test elan_04f3_010c", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0", + ) + + +class Testelan_04f3_0125(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test elan_04f3_0125", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0", + ) + + +class Testelan_04f3_016f(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test elan_04f3_016f", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0", + ) + + +class Testelan_04f3_0732(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test elan_04f3_0732", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff 00 00 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 25 ff 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0", + ) + + +class Testelan_04f3_200a(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test elan_04f3_200a", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff 00 00 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 0e 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0", + ) + + +class Testelan_04f3_300b(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test elan_04f3_300b", + max_contacts=3, + rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 05 0c 0a 38 02 95 01 81 06 75 08 95 03 81 03 c0 06 00 ff 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 76 02 75 08 b1 02 85 0b 09 c7 95 42 75 08 b1 02 09 01 85 5d 95 1f 75 08 81 06 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 02 25 02 09 51 81 02 75 01 95 04 81 03 05 01 15 00 26 a7 0c 75 10 55 0e 65 13 09 30 35 00 46 9d 01 95 01 81 02 46 25 01 26 2b 09 26 2b 09 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 08 95 02 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 15 00 75 01 95 02 25 03 b1 02 95 0e b1 03 c0 c0", + ) + + +class Testelan_04f3_3045(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test elan_04f3_3045", + rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 05 0c 0a 38 02 95 01 81 06 75 08 95 03 81 03 c0 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 75 01 95 02 81 03 95 01 75 04 25 0f 09 51 81 02 05 01 15 00 26 80 0c 75 10 55 0e 65 13 09 30 35 00 46 90 01 95 01 81 02 46 13 01 26 96 08 26 96 08 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 09 c5 75 08 95 04 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 8a 02 75 08 b1 02 85 0b 09 c7 95 80 75 08 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 08 95 02 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 15 00 75 01 95 02 25 03 b1 02 95 0e b1 03 c0 c0", + ) + + +class Testelan_04f3_313a(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test elan_04f3_313a", + type="touchpad", + input_info=(BusType.I2C, 0x04F3, 0x313A), + rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 75 08 95 05 81 03 c0 06 00 ff 09 01 85 0e 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0a 09 c6 15 00 26 ff 00 75 08 95 04 b1 02 c0 06 00 ff 09 01 a1 01 85 5c 09 01 95 0b 75 08 81 06 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 80 03 75 08 b1 02 85 0b 09 c7 95 82 75 08 b1 02 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 05 09 09 02 09 03 15 00 25 01 75 01 95 02 81 02 05 0d 95 01 75 04 25 0f 09 51 81 02 05 01 15 00 26 d7 0e 75 10 55 0d 65 11 09 30 35 00 46 44 2f 95 01 81 02 46 12 16 26 eb 06 26 eb 06 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 25 01 75 01 95 08 81 03 09 c5 75 08 95 02 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 10 95 01 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 75 01 95 02 25 01 b1 02 95 0e b1 03 c0 c0 05 01 09 02 a1 01 85 2a 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 03 05 01 09 30 09 31 15 81 25 7f 35 81 45 7f 55 00 65 13 75 08 95 02 81 06 75 08 95 05 81 03 c0 c0", + ) + + +class Testelo_04e7_0080(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test elo_04e7_0080", + rdesc="05 0d 09 04 a1 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0", + ) + + +class Testilitek_222a_0015(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test ilitek_222a_0015", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 02 09 55 25 0a b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 03 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0", + ) + + +class Testilitek_222a_001c(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test ilitek_222a_001c", + rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 02 09 55 25 0a b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 03 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0", + ) + + +class Testite_06cb_2968(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test ite_06cb_2968", + rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 1b 04 75 10 55 0e 65 11 09 30 35 00 46 6c 03 95 01 81 02 46 db 01 26 3b 02 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 1a 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 1a 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0", + max_contacts=5, + input_info=(0x3, 0x06CB, 0x2968), + ) + + +class Testn_trig_1b96_0c01(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test n_trig_1b96_0c01", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testn_trig_1b96_0c03(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test n_trig_1b96_0c03", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testn_trig_1b96_0f00(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test n_trig_1b96_0f00", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testn_trig_1b96_0f04(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test n_trig_1b96_0f04", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testn_trig_1b96_1000(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test n_trig_1b96_1000", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testsharp_04dd_9681(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test sharp_04dd_9681", + rdesc="06 00 ff 09 01 a1 01 75 08 26 ff 00 15 00 85 06 95 3f 09 01 91 02 85 05 95 3f 09 01 81 02 c0 05 0d 09 04 a1 01 85 81 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 56 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 81 02 09 54 95 01 75 08 15 00 25 0a 81 02 85 84 09 55 b1 02 85 87 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 09 0e a1 01 85 83 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 80 05 09 19 01 29 01 15 00 25 01 95 01 75 01 81 02 95 01 75 07 81 01 05 01 65 11 55 0f 09 30 26 80 07 35 00 46 66 00 75 10 95 01 81 02 09 31 26 38 04 35 00 46 4d 00 81 02 c0 c0", + ) + + +class Testsipodev_0603_0002(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test sipodev_0603_0002", + type="clickpad", + rdesc="05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 80 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 75 01 95 02 81 03 95 01 75 04 25 05 09 51 81 02 05 01 15 00 26 44 0a 75 0c 55 0e 65 11 09 30 35 00 46 ac 03 95 01 81 02 46 fe 01 26 34 05 75 0c 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 0a 95 01 75 04 81 02 75 01 95 03 81 03 05 09 09 01 25 01 75 01 95 01 81 02 05 0d 85 0a 09 55 09 59 75 04 95 02 25 0f b1 02 85 0b 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 09 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 06 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 07 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 05 01 09 0c a1 01 85 08 15 00 25 01 09 c6 75 01 95 01 81 06 75 07 81 03 c0 05 01 09 80 a1 01 85 01 15 00 25 01 75 01 0a 81 00 0a 82 00 0a 83 00 95 03 81 06 95 05 81 01 c0 06 0c 00 09 01 a1 01 85 02 25 01 15 00 75 01 0a b5 00 0a b6 00 0a b7 00 0a cd 00 0a e2 00 0a a2 00 0a e9 00 0a ea 00 95 08 81 02 0a 83 01 0a 6f 00 0a 70 00 0a 88 01 0a 8a 01 0a 92 01 0a a8 02 0a 24 02 95 08 81 02 0a 21 02 0a 23 02 0a 96 01 0a 25 02 0a 26 02 0a 27 02 0a 23 02 0a b1 02 95 08 81 02 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 19 01 29 02 75 08 95 05 b1 02 c0", + ) + + +class Testsynaptics_06cb_1d10(BaseTest.TestWin8Multitouch): + def create_device(self): + return Digitizer( + "uhid test synaptics_06cb_1d10", + rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 75 08 95 02 15 81 25 7f 35 81 45 7f 55 0e 65 11 81 06 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 95 01 75 08 15 00 25 0f 81 02 85 08 09 55 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 3f 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 05 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 3d 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 01 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 c0", + ) + + +class Testsynaptics_06cb_ce08(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test synaptics_06cb_ce08", + max_contacts=5, + physical="Vendor Usage 1", + input_info=(BusType.I2C, 0x06CB, 0xCE08), + rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 01 09 02 a1 01 85 18 09 01 a1 00 05 09 19 01 29 03 46 00 00 15 00 25 01 75 01 95 03 81 02 95 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 06 00 ff 09 02 a1 01 85 20 09 01 a1 00 09 03 15 00 26 ff 00 35 00 46 ff 00 75 08 95 05 81 02 c0 c0 05 0d 09 05 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 45 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 45 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 03 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0", + ) diff --git a/tools/testing/selftests/hid/tests/test_sony.py b/tools/testing/selftests/hid/tests/test_sony.py new file mode 100644 index 000000000000..7e52c28e59c5 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_sony.py @@ -0,0 +1,342 @@ +#!/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 .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 = ("sony", "hid_sony") +PS4_MODULE = ("playstation", "hid_playstation") +PS5_MODULE = ("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 diff --git a/tools/testing/selftests/hid/tests/test_tablet.py b/tools/testing/selftests/hid/tests/test_tablet.py new file mode 100644 index 000000000000..303ffff9ee8d --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_tablet.py @@ -0,0 +1,872 @@ +#!/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. +# + +from . import base +import copy +from enum import Enum +from hidtools.util import BusType +import libevdev +import logging +import pytest +from typing import Dict, Tuple + +logger = logging.getLogger("hidtools.test.tablet") + + +class PenState(Enum): + """Pen states according to Microsoft reference: + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + + PEN_IS_OUT_OF_RANGE = (False, None) + PEN_IS_IN_RANGE = (False, libevdev.EV_KEY.BTN_TOOL_PEN) + PEN_IS_IN_CONTACT = (True, libevdev.EV_KEY.BTN_TOOL_PEN) + PEN_IS_IN_RANGE_WITH_ERASING_INTENT = (False, libevdev.EV_KEY.BTN_TOOL_RUBBER) + PEN_IS_ERASING = (True, libevdev.EV_KEY.BTN_TOOL_RUBBER) + + def __init__(self, touch, tool): + self.touch = touch + self.tool = tool + + @classmethod + def from_evdev(cls, evdev) -> "PenState": + touch = bool(evdev.value[libevdev.EV_KEY.BTN_TOUCH]) + tool = None + if ( + evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] + and not evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN] + ): + tool = libevdev.EV_KEY.BTN_TOOL_RUBBER + elif ( + evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN] + and not evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] + ): + tool = libevdev.EV_KEY.BTN_TOOL_PEN + elif ( + evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN] + or evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] + ): + raise ValueError("2 tools are not allowed") + + return cls((touch, tool)) + + def apply(self, events) -> "PenState": + if libevdev.EV_SYN.SYN_REPORT in events: + raise ValueError("EV_SYN is in the event sequence") + touch = self.touch + touch_found = False + tool = self.tool + tool_found = False + + for ev in events: + if ev == libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH): + if touch_found: + raise ValueError(f"duplicated BTN_TOUCH in {events}") + touch_found = True + touch = bool(ev.value) + elif ev in ( + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN), + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_RUBBER), + ): + if tool_found: + raise ValueError(f"duplicated BTN_TOOL_* in {events}") + tool_found = True + if ev.value: + tool = ev.code + else: + tool = None + + new_state = PenState((touch, tool)) + assert ( + new_state in self.valid_transitions() + ), f"moving from {self} to {new_state} is forbidden" + + return new_state + + def valid_transitions(self) -> Tuple["PenState", ...]: + """Following the state machine in the URL above, with a couple of addition + for skipping the in-range state, due to historical reasons. + + Note that those transitions are from the evdev point of view, not HID""" + if self == PenState.PEN_IS_OUT_OF_RANGE: + return ( + PenState.PEN_IS_OUT_OF_RANGE, + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_ERASING, + ) + + if self == PenState.PEN_IS_IN_RANGE: + return ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_OUT_OF_RANGE, + PenState.PEN_IS_IN_CONTACT, + ) + + if self == PenState.PEN_IS_IN_CONTACT: + return ( + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_OUT_OF_RANGE, + ) + + if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT: + return ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_OUT_OF_RANGE, + PenState.PEN_IS_ERASING, + ) + + if self == PenState.PEN_IS_ERASING: + return ( + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_OUT_OF_RANGE, + ) + + return tuple() + + +class Data(object): + pass + + +class Pen(object): + def __init__(self, x, y): + self.x = x + self.y = y + self.tipswitch = False + self.tippressure = 15 + self.azimuth = 0 + self.inrange = False + self.width = 10 + self.height = 10 + self.barrelswitch = False + self.invert = False + self.eraser = False + self.x_tilt = 0 + self.y_tilt = 0 + self.twist = 0 + self._old_values = None + self.current_state = None + + def _restore(self): + if self._old_values is not None: + for i in [ + "x", + "y", + "tippressure", + "azimuth", + "width", + "height", + "twist", + "x_tilt", + "y_tilt", + ]: + setattr(self, i, getattr(self._old_values, i)) + + def move_to(self, state): + # fill in the previous values + if self.current_state == PenState.PEN_IS_OUT_OF_RANGE: + self._restore() + + print(f"\n *** pen is moving to {state} ***") + + if state == PenState.PEN_IS_OUT_OF_RANGE: + self._old_values = copy.copy(self) + self.x = 0 + self.y = 0 + self.tipswitch = False + self.tippressure = 0 + self.azimuth = 0 + self.inrange = False + self.width = 0 + self.height = 0 + self.invert = False + self.eraser = False + self.x_tilt = 0 + self.y_tilt = 0 + self.twist = 0 + elif state == PenState.PEN_IS_IN_RANGE: + self.tipswitch = False + self.inrange = True + self.invert = False + self.eraser = False + elif state == PenState.PEN_IS_IN_CONTACT: + self.tipswitch = True + self.inrange = True + self.invert = False + self.eraser = False + elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT: + self.tipswitch = False + self.inrange = True + self.invert = True + self.eraser = False + elif state == PenState.PEN_IS_ERASING: + self.tipswitch = False + self.inrange = True + self.invert = True + self.eraser = True + + self.current_state = state + + def __assert_axis(self, evdev, axis, value): + if ( + axis == libevdev.EV_KEY.BTN_TOOL_RUBBER + and evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] is None + ): + return + + assert ( + evdev.value[axis] == value + ), f"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}" + + def assert_expected_input_events(self, evdev): + assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x + assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y + assert self.current_state == PenState.from_evdev(evdev) + + @staticmethod + def legal_transitions() -> Dict[str, Tuple[PenState, ...]]: + """This is the first half of the Windows Pen Implementation state machine: + we don't have Invert nor Erase bits, so just move in/out-of-range or proximity. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + return { + "in-range": (PenState.PEN_IS_IN_RANGE,), + "in-range -> out-of-range": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_OUT_OF_RANGE, + ), + "in-range -> touch": (PenState.PEN_IS_IN_RANGE, PenState.PEN_IS_IN_CONTACT), + "in-range -> touch -> release": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + ), + "in-range -> touch -> release -> out-of-range": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_OUT_OF_RANGE, + ), + } + + @staticmethod + def legal_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]: + """This is the second half of the Windows Pen Implementation state machine: + we now have Invert and Erase bits, so move in/out or proximity with the intend + to erase. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + return { + "hover-erasing": (PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,), + "hover-erasing -> out-of-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_OUT_OF_RANGE, + ), + "hover-erasing -> erase": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_ERASING, + ), + "hover-erasing -> erase -> release": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + ), + "hover-erasing -> erase -> release -> out-of-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_OUT_OF_RANGE, + ), + "hover-erasing -> in-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_IN_RANGE, + ), + "in-range -> hover-erasing": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + ), + } + + @staticmethod + def tolerated_transitions() -> Dict[str, Tuple[PenState, ...]]: + """This is not adhering to the Windows Pen Implementation state machine + but we should expect the kernel to behave properly, mostly for historical + reasons.""" + return { + "direct-in-contact": (PenState.PEN_IS_IN_CONTACT,), + "direct-in-contact -> out-of-range": ( + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_OUT_OF_RANGE, + ), + } + + @staticmethod + def tolerated_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]: + """This is the second half of the Windows Pen Implementation state machine: + we now have Invert and Erase bits, so move in/out or proximity with the intend + to erase. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + return { + "direct-erase": (PenState.PEN_IS_ERASING,), + "direct-erase -> out-of-range": ( + PenState.PEN_IS_ERASING, + PenState.PEN_IS_OUT_OF_RANGE, + ), + } + + @staticmethod + def broken_transitions() -> Dict[str, Tuple[PenState, ...]]: + """Those tests are definitely not part of the Windows specification. + However, a half broken device might export those transitions. + For example, a pen that has the eraser button might wobble between + touching and erasing if the tablet doesn't enforce the Windows + state machine.""" + return { + "in-range -> touch -> erase -> hover-erase": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + ), + "in-range -> erase -> hover-erase": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + ), + "hover-erase -> erase -> touch -> in-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + ), + "hover-erase -> touch -> in-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + ), + "touch -> erase -> touch -> erase": ( + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_ERASING, + ), + } + + +class PenDigitizer(base.UHIDTestDevice): + def __init__( + self, + name, + rdesc_str=None, + rdesc=None, + application="Pen", + physical="Stylus", + input_info=(BusType.USB, 1, 2), + evdev_name_suffix=None, + ): + super().__init__(name, application, rdesc_str, rdesc, input_info) + self.physical = physical + self.cur_application = application + if evdev_name_suffix is not None: + self.name += evdev_name_suffix + + self.fields = [] + for r in self.parsed_rdesc.input_reports.values(): + if r.application_name == self.application: + physicals = [f.physical_name for f in r] + if self.physical not in physicals and None not in physicals: + continue + self.fields = [f.usage_name for f in r] + + def event(self, pen): + rs = [] + r = self.create_report(application=self.cur_application, data=pen) + self.call_input_event(r) + rs.append(r) + return rs + + def get_report(self, req, rnum, rtype): + if rtype != self.UHID_FEATURE_REPORT: + return (1, []) + + rdesc = None + for v in self.parsed_rdesc.feature_reports.values(): + if v.report_ID == rnum: + rdesc = v + + if rdesc is None: + return (1, []) + + return (1, []) + + def set_report(self, req, rnum, rtype, data): + if rtype != self.UHID_FEATURE_REPORT: + return 1 + + rdesc = None + for v in self.parsed_rdesc.feature_reports.values(): + if v.report_ID == rnum: + rdesc = v + + if rdesc is None: + return 1 + + return 1 + + +class BaseTest: + class TestTablet(base.BaseTestCase.TestUhid): + def create_device(self): + raise Exception("please reimplement me in subclasses") + + def post(self, uhdev, pen): + r = uhdev.event(pen) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + return events + + def validate_transitions(self, from_state, pen, evdev, events): + # check that the final state is correct + pen.assert_expected_input_events(evdev) + + # check that the transitions are valid + sync_events = [] + while libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) in events: + # split the first EV_SYN from the list + idx = events.index(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT)) + sync_events = events[:idx] + events = events[idx + 1 :] + + # now check for a valid transition + from_state = from_state.apply(sync_events) + + if events: + from_state = from_state.apply(sync_events) + + def _test_states(self, state_list, scribble): + """Internal method to test against a list of + transition between states. + state_list is a list of PenState objects + scribble is a boolean which tells if we need + to wobble a little the X,Y coordinates of the pen + between each state transition.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + cur_state = PenState.PEN_IS_OUT_OF_RANGE + + p = Pen(50, 60) + p.move_to(PenState.PEN_IS_OUT_OF_RANGE) + events = self.post(uhdev, p) + self.validate_transitions(cur_state, p, evdev, events) + + cur_state = p.current_state + + for state in state_list: + if scribble and cur_state != PenState.PEN_IS_OUT_OF_RANGE: + p.x += 1 + p.y -= 1 + events = self.post(uhdev, p) + self.validate_transitions(cur_state, p, evdev, events) + assert len(events) >= 3 # X, Y, SYN + p.move_to(state) + if scribble and state != PenState.PEN_IS_OUT_OF_RANGE: + p.x += 1 + p.y -= 1 + events = self.post(uhdev, p) + self.validate_transitions(cur_state, p, evdev, events) + cur_state = p.current_state + + @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) + @pytest.mark.parametrize( + "state_list", + [pytest.param(v, id=k) for k, v in Pen.legal_transitions().items()], + ) + def test_valid_pen_states(self, state_list, scribble): + """This is the first half of the Windows Pen Implementation state machine: + we don't have Invert nor Erase bits, so just move in/out-of-range or proximity. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + self._test_states(state_list, scribble) + + @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) + @pytest.mark.parametrize( + "state_list", + [pytest.param(v, id=k) for k, v in Pen.tolerated_transitions().items()], + ) + def test_tolerated_pen_states(self, state_list, scribble): + """This is not adhering to the Windows Pen Implementation state machine + but we should expect the kernel to behave properly, mostly for historical + reasons.""" + self._test_states(state_list, scribble) + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Invert" not in uhdev.fields, + "Device not compatible, missing Invert usage", + ) + @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) + @pytest.mark.parametrize( + "state_list", + [ + pytest.param(v, id=k) + for k, v in Pen.legal_transitions_with_invert().items() + ], + ) + def test_valid_invert_pen_states(self, state_list, scribble): + """This is the second half of the Windows Pen Implementation state machine: + we now have Invert and Erase bits, so move in/out or proximity with the intend + to erase. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + self._test_states(state_list, scribble) + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Invert" not in uhdev.fields, + "Device not compatible, missing Invert usage", + ) + @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) + @pytest.mark.parametrize( + "state_list", + [ + pytest.param(v, id=k) + for k, v in Pen.tolerated_transitions_with_invert().items() + ], + ) + def test_tolerated_invert_pen_states(self, state_list, scribble): + """This is the second half of the Windows Pen Implementation state machine: + we now have Invert and Erase bits, so move in/out or proximity with the intend + to erase. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + self._test_states(state_list, scribble) + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Invert" not in uhdev.fields, + "Device not compatible, missing Invert usage", + ) + @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) + @pytest.mark.parametrize( + "state_list", + [pytest.param(v, id=k) for k, v in Pen.broken_transitions().items()], + ) + def test_tolerated_broken_pen_states(self, state_list, scribble): + """Those tests are definitely not part of the Windows specification. + However, a half broken device might export those transitions. + For example, a pen that has the eraser button might wobble between + touching and erasing if the tablet doesn't enforce the Windows + state machine.""" + self._test_states(state_list, scribble) + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Barrel Switch" not in uhdev.fields, + "Device not compatible, missing Barrel Switch usage", + ) + def test_primary_button(self): + """Primary button (stylus) pressed, reports as pressed even while hovering. + Actual reporting from the device: hid=TIPSWITCH,BARRELSWITCH,INRANGE (code=TOUCH,STYLUS,PEN): + { 0, 0, 1 } <- hover + { 0, 1, 1 } <- primary button pressed + { 0, 1, 1 } <- liftoff + { 0, 0, 0 } <- leaves + """ + + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + p = Pen(50, 60) + p.inrange = True + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1) in events + assert evdev.value[libevdev.EV_ABS.ABS_X] == 50 + assert evdev.value[libevdev.EV_ABS.ABS_Y] == 60 + assert not evdev.value[libevdev.EV_KEY.BTN_STYLUS] + + p.barrelswitch = True + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1) in events + + p.x += 1 + p.y -= 1 + events = self.post(uhdev, p) + assert len(events) == 3 # X, Y, SYN + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 51) in events + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 59) in events + + p.barrelswitch = False + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0) in events + + p.inrange = False + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0) in events + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Barrel Switch" not in uhdev.fields, + "Device not compatible, missing Barrel Switch usage", + ) + def test_contact_primary_button(self): + """Primary button (stylus) pressed, reports as pressed even while hovering. + Actual reporting from the device: hid=TIPSWITCH,BARRELSWITCH,INRANGE (code=TOUCH,STYLUS,PEN): + { 0, 0, 1 } <- hover + { 0, 1, 1 } <- primary button pressed + { 1, 1, 1 } <- touch-down + { 1, 1, 1 } <- still touch, scribble on the screen + { 0, 1, 1 } <- liftoff + { 0, 0, 0 } <- leaves + """ + + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + p = Pen(50, 60) + p.inrange = True + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1) in events + assert evdev.value[libevdev.EV_ABS.ABS_X] == 50 + assert evdev.value[libevdev.EV_ABS.ABS_Y] == 60 + assert not evdev.value[libevdev.EV_KEY.BTN_STYLUS] + + p.barrelswitch = True + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1) in events + + p.tipswitch = True + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events + assert evdev.value[libevdev.EV_KEY.BTN_STYLUS] + + p.x += 1 + p.y -= 1 + events = self.post(uhdev, p) + assert len(events) == 3 # X, Y, SYN + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 51) in events + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 59) in events + + p.tipswitch = False + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events + + p.barrelswitch = False + p.inrange = False + events = self.post(uhdev, p) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0) in events + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0) in events + + +class GXTP_pen(PenDigitizer): + def event(self, pen): + if not hasattr(self, "prev_tip_state"): + self.prev_tip_state = False + + internal_pen = copy.copy(pen) + + # bug in the controller: when the pen touches the + # surface, in-range stays to 1, but when + # the pen moves in-range gets reverted to 0 + if pen.tipswitch and self.prev_tip_state: + internal_pen.inrange = False + + self.prev_tip_state = pen.tipswitch + + # another bug in the controller: when the pen is + # inverted, invert is set to 1, but as soon as + # the pen touches the surface, eraser is correctly + # set to 1 but invert is released + if pen.eraser: + internal_pen.invert = False + + return super().event(internal_pen) + + +class USIPen(PenDigitizer): + pass + + +################################################################################ +# +# Windows 7 compatible devices +# +################################################################################ +# class TestEgalax_capacitive_0eef_7224(BaseTest.TestTablet): +# def create_device(self): +# return PenDigitizer('uhid test egalax-capacitive_0eef_7224', +# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0', +# input_info=(BusType.USB, 0x0eef, 0x7224), +# evdev_name_suffix=' Touchscreen') +# +# +# class TestEgalax_capacitive_0eef_72fa(BaseTest.TestTablet): +# def create_device(self): +# return PenDigitizer('uhid test egalax-capacitive_0eef_72fa', +# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 72 22 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 87 13 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 72 22 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 87 13 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0', +# input_info=(BusType.USB, 0x0eef, 0x72fa), +# evdev_name_suffix=' Touchscreen') +# +# +# class TestEgalax_capacitive_0eef_7336(BaseTest.TestTablet): +# def create_device(self): +# return PenDigitizer('uhid test egalax-capacitive_0eef_7336', +# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 c1 20 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c2 18 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 c1 20 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c2 18 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0', +# input_info=(BusType.USB, 0x0eef, 0x7336), +# evdev_name_suffix=' Touchscreen') +# +# +# class TestEgalax_capacitive_0eef_7337(BaseTest.TestTablet): +# def create_device(self): +# return PenDigitizer('uhid test egalax-capacitive_0eef_7337', +# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 ae 17 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c3 0e 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 ae 17 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c3 0e 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0', +# input_info=(BusType.USB, 0x0eef, 0x7337), +# evdev_name_suffix=' Touchscreen') +# +# +# class TestEgalax_capacitive_0eef_7349(BaseTest.TestTablet): +# def create_device(self): +# return PenDigitizer('uhid test egalax-capacitive_0eef_7349', +# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0', +# input_info=(BusType.USB, 0x0eef, 0x7349), +# evdev_name_suffix=' Touchscreen') +# +# +# class TestEgalax_capacitive_0eef_73f4(BaseTest.TestTablet): +# def create_device(self): +# return PenDigitizer('uhid test egalax-capacitive_0eef_73f4', +# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 96 4e 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 23 2c 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 96 4e 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 23 2c 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0', +# input_info=(BusType.USB, 0x0eef, 0x73f4), +# evdev_name_suffix=' Touchscreen') +# +# bogus: BTN_TOOL_PEN is not emitted +# class TestIrtouch_6615_0070(BaseTest.TestTablet): +# def create_device(self): +# return PenDigitizer('uhid test irtouch_6615_0070', +# rdesc='05 01 09 02 a1 01 85 10 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 30 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 05 0d 09 54 15 00 26 02 00 75 08 95 01 81 02 85 03 09 55 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 05 0d 09 02 a1 01 85 20 09 20 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 85 01 06 00 ff 09 01 75 08 95 01 b1 02 c0 c0', +# input_info=(BusType.USB, 0x6615, 0x0070)) + + +class TestNexio_1870_0100(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test nexio_1870_0100", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 40 81 00 19 01 29 40 91 00 c0", + input_info=(BusType.USB, 0x1870, 0x0100), + ) + + +class TestNexio_1870_010d(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test nexio_1870_010d", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0", + input_info=(BusType.USB, 0x1870, 0x010D), + ) + + +class TestNexio_1870_0119(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test nexio_1870_0119", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0", + input_info=(BusType.USB, 0x1870, 0x0119), + ) + + +################################################################################ +# +# Windows 8 compatible devices +# +################################################################################ + +# bogus: application is 'undefined' +# class Testatmel_03eb_8409(BaseTest.TestTablet): +# def create_device(self): +# return PenDigitizer('uhid test atmel_03eb_8409', rdesc='05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 00 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 46 18 06 26 77 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0') + + +class Testatmel_03eb_840b(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test atmel_03eb_840b", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 00 0a 26 ff 0f 09 30 81 02 46 a0 05 26 ff 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0", + ) + + +class Testn_trig_1b96_0c01(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test n_trig_1b96_0c01", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testn_trig_1b96_0c03(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test n_trig_1b96_0c03", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testn_trig_1b96_0f00(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test n_trig_1b96_0f00", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testn_trig_1b96_0f04(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test n_trig_1b96_0f04", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class Testn_trig_1b96_1000(BaseTest.TestTablet): + def create_device(self): + return PenDigitizer( + "uhid test n_trig_1b96_1000", + rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0", + ) + + +class TestGXTP_27c6_0113(BaseTest.TestTablet): + def create_device(self): + return GXTP_pen( + "uhid test GXTP_27c6_0113", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 14 75 10 55 0e 65 11 09 30 35 00 46 1f 07 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 14 75 10 55 0e 65 11 09 30 35 00 46 1f 07 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 08 09 20 a1 00 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 04 81 02 95 01 81 03 09 32 81 02 95 02 81 03 95 01 75 08 09 51 81 02 05 01 09 30 75 10 95 01 a4 55 0e 65 11 35 00 26 00 14 46 1f 07 81 42 09 31 26 80 0c 46 77 04 81 42 b4 05 0d 09 30 26 ff 0f 81 02 09 3d 65 14 55 0e 36 d8 dc 46 28 23 16 d8 dc 26 28 23 81 02 09 3e 81 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0 05 01 09 06 a1 01 85 04 05 07 09 e3 15 00 25 01 75 01 95 01 81 02 95 07 81 03 c0", + ) + + +################################################################################ +# +# Windows 8 compatible devices with USI Pen +# +################################################################################ + + +class TestElan_04f3_2A49(BaseTest.TestTablet): + def create_device(self): + return USIPen( + "uhid test Elan_04f3_2A49", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 54 25 7f 96 01 00 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 16 00 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 16 00 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 20 09 01 91 02 c0 06 00 ff 09 01 a1 01 85 06 09 03 75 08 95 12 91 02 09 04 75 08 95 03 b1 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0 05 0d 09 02 a1 01 85 07 35 00 09 20 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0f 65 11 46 26 01 26 1c 48 81 42 09 31 46 a6 00 26 bc 2f 81 42 b4 05 0d 09 30 26 00 10 81 02 75 08 95 01 09 3b 25 64 81 42 09 38 15 00 25 02 81 02 09 5c 26 ff 00 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 09 5b 25 ff 75 40 81 02 c0 06 00 ff 75 08 95 02 09 01 81 02 c0 05 0d 85 60 09 81 a1 02 09 38 75 08 95 01 15 00 25 02 81 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 61 09 5c a1 02 15 00 26 ff 00 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 62 09 5e a1 02 09 38 15 00 25 02 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 63 09 70 a1 02 75 08 95 01 15 00 25 02 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 64 09 80 15 00 25 ff 75 40 95 01 b1 02 85 65 09 44 a1 02 09 38 75 08 95 01 25 02 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 66 75 08 95 01 05 0d 09 90 a1 02 09 38 25 02 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 67 05 06 09 2b a1 02 05 0d 25 02 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 68 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 02 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 69 05 0d 09 38 75 08 95 01 15 00 25 02 b1 02 c0 06 00 ff 09 81 a1 01 85 17 75 08 95 1f 09 05 81 02 c0", + input_info=(BusType.I2C, 0x04F3, 0x2A49), + ) + + +class TestGoodix_27c6_0e00(BaseTest.TestTablet): + def create_device(self): + return USIPen( + "uhid test Elan_04f3_2A49", + rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 09 20 a1 00 85 08 05 01 a4 09 30 35 00 46 e6 09 15 00 26 04 20 55 0d 65 13 75 10 95 01 81 02 09 31 46 9a 06 26 60 15 81 02 b4 05 0d 09 38 95 01 75 08 15 00 25 01 81 02 09 30 75 10 26 ff 0f 81 02 09 31 81 02 09 42 09 44 09 5a 09 3c 09 45 09 32 75 01 95 06 25 01 81 02 95 02 81 03 09 3d 55 0e 65 14 36 d8 dc 46 28 23 16 d8 dc 26 28 23 95 01 75 10 81 02 09 3e 81 02 09 41 15 00 27 a0 8c 00 00 35 00 47 a0 8c 00 00 81 02 05 20 0a 53 04 65 00 16 01 f8 26 ff 07 75 10 95 01 81 02 0a 54 04 81 02 0a 55 04 81 02 0a 57 04 81 02 0a 58 04 81 02 0a 59 04 81 02 0a 72 04 81 02 0a 73 04 81 02 0a 74 04 81 02 05 0d 09 3b 15 00 25 64 75 08 81 02 09 5b 25 ff 75 40 81 02 06 00 ff 09 5b 75 20 81 02 05 0d 09 5c 26 ff 00 75 08 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 c0 06 00 ff 09 01 15 00 27 ff ff 00 00 75 10 95 01 81 02 85 09 09 81 a1 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 10 09 5c a1 02 15 00 25 01 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 11 09 5e a1 02 09 38 15 00 25 01 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 12 09 70 a1 02 75 08 95 01 15 00 25 01 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 13 09 80 15 00 25 ff 75 40 95 01 b1 02 85 14 09 44 a1 02 09 38 75 08 95 01 25 01 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 15 75 08 95 01 05 0d 09 90 a1 02 09 38 25 01 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 16 05 06 09 2b a1 02 05 0d 25 01 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 17 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 01 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 18 05 0d 09 38 75 08 95 01 15 00 25 01 b1 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0", + input_info=(BusType.I2C, 0x27C6, 0x0E00), + ) diff --git a/tools/testing/selftests/hid/tests/test_usb_crash.py b/tools/testing/selftests/hid/tests/test_usb_crash.py new file mode 100644 index 000000000000..e98bff9197c7 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_usb_crash.py @@ -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 diff --git a/tools/testing/selftests/hid/tests/test_wacom_generic.py b/tools/testing/selftests/hid/tests/test_wacom_generic.py new file mode 100644 index 000000000000..b1eb2bc787fc --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_wacom_generic.py @@ -0,0 +1,844 @@ +#!/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. +# Copyright (c) 2020 Wacom Technology Corp. +# +# Authors: +# Jason Gerecke <jason.gerecke@wacom.com> + +""" +Tests for the Wacom driver generic codepath. + +This module tests the function of the Wacom driver's generic codepath. +The generic codepath is used by devices which are not explicitly listed +in the driver's device table. It uses the device's HID descriptor to +decode reports sent by the device. +""" + +from .descriptors_wacom import ( + wacom_pth660_v145, + wacom_pth660_v150, + wacom_pth860_v145, + wacom_pth860_v150, + wacom_pth460_v105, +) + +import attr +from enum import Enum +from hidtools.hut import HUT +from hidtools.hid import HidUnit +from . import base +import libevdev +import pytest + +import logging + +logger = logging.getLogger("hidtools.test.wacom") + +KERNEL_MODULE = ("wacom", "wacom") + + +class ProximityState(Enum): + """ + Enumeration of allowed proximity states. + """ + + # Tool is not able to be sensed by the device + OUT = 0 + + # Tool is close enough to be sensed, but some data may be invalid + # or inaccurate + IN_PROXIMITY = 1 + + # Tool is close enough to be sensed with high accuracy. All data + # valid. + IN_RANGE = 2 + + def fill(self, reportdata): + """Fill a report with approrpiate HID properties/values.""" + reportdata.inrange = self in [ProximityState.IN_RANGE] + reportdata.wacomsense = self in [ + ProximityState.IN_PROXIMITY, + ProximityState.IN_RANGE, + ] + + +class ReportData: + """ + Placeholder for HID report values. + """ + + pass + + +@attr.s +class Buttons: + """ + Stylus button state. + + Describes the state of each of the buttons / "side switches" that + may be present on a stylus. Buttons set to 'None' indicate the + state is "unchanged" since the previous event. + """ + + primary = attr.ib(default=None) + secondary = attr.ib(default=None) + tertiary = attr.ib(default=None) + + @staticmethod + def clear(): + """Button object with all states cleared.""" + return Buttons(False, False, False) + + def fill(self, reportdata): + """Fill a report with approrpiate HID properties/values.""" + reportdata.barrelswitch = int(self.primary or 0) + reportdata.secondarybarrelswitch = int(self.secondary or 0) + reportdata.b3 = int(self.tertiary or 0) + + +@attr.s +class ToolID: + """ + Stylus tool identifiers. + + Contains values used to identify a specific stylus, e.g. its serial + number and tool-type identifier. Values of ``0`` may sometimes be + used for the out-of-range condition. + """ + + serial = attr.ib() + tooltype = attr.ib() + + @staticmethod + def clear(): + """ToolID object with all fields cleared.""" + return ToolID(0, 0) + + def fill(self, reportdata): + """Fill a report with approrpiate HID properties/values.""" + reportdata.transducerserialnumber = self.serial & 0xFFFFFFFF + reportdata.serialhi = (self.serial >> 32) & 0xFFFFFFFF + reportdata.tooltype = self.tooltype + + +@attr.s +class PhysRange: + """ + Range of HID physical values, with units. + """ + + unit = attr.ib() + min_size = attr.ib() + max_size = attr.ib() + + CENTIMETER = HidUnit.from_string("SILinear: cm") + DEGREE = HidUnit.from_string("EnglishRotation: deg") + + def contains(self, field): + """ + Check if the physical size of the provided field is in range. + + Compare the physical size described by the provided HID field + against the range of sizes described by this object. This is + an exclusive range comparison (e.g. 0 cm is not within the + range 0 cm - 5 cm) and exact unit comparison (e.g. 1 inch is + not within the range 0 cm - 5 cm). + """ + phys_size = (field.physical_max - field.physical_min) * 10 ** (field.unit_exp) + return ( + field.unit == self.unit.value + and phys_size > self.min_size + and phys_size < self.max_size + ) + + +class BaseTablet(base.UHIDTestDevice): + """ + Skeleton object for all kinds of tablet devices. + """ + + def __init__(self, rdesc, name=None, info=None): + assert rdesc is not None + super().__init__(name, "Pen", input_info=info, rdesc=rdesc) + self.buttons = Buttons.clear() + self.toolid = ToolID.clear() + self.proximity = ProximityState.OUT + self.offset = 0 + self.ring = -1 + self.ek0 = False + + def match_evdev_rule(self, application, evdev): + """ + Filter out evdev nodes based on the requested application. + + The Wacom driver may create several device nodes for each USB + interface device. It is crucial that we run tests with the + expected device node or things will obviously go off the rails. + Use the Wacom driver's usual naming conventions to apply a + sensible default filter. + """ + if application in ["Pen", "Pad"]: + return evdev.name.endswith(application) + else: + return True + + def create_report( + self, x, y, pressure, buttons=None, toolid=None, proximity=None, reportID=None + ): + """ + Return an input report for this device. + + :param x: absolute x + :param y: absolute y + :param pressure: pressure + :param buttons: stylus button state. Use ``None`` for unchanged. + :param toolid: tool identifiers. Use ``None`` for unchanged. + :param proximity: a ProximityState indicating the sensor's ability + to detect and report attributes of this tool. Use ``None`` + for unchanged. + :param reportID: the numeric report ID for this report, if needed + """ + if buttons is not None: + self.buttons = buttons + buttons = self.buttons + + if toolid is not None: + self.toolid = toolid + toolid = self.toolid + + if proximity is not None: + self.proximity = proximity + proximity = self.proximity + + reportID = reportID or self.default_reportID + + report = ReportData() + report.x = x + report.y = y + report.tippressure = pressure + report.tipswitch = pressure > 0 + buttons.fill(report) + proximity.fill(report) + toolid.fill(report) + + return super().create_report(report, reportID=reportID) + + def create_report_heartbeat(self, reportID): + """ + Return a heartbeat input report for this device. + + Heartbeat reports generally contain battery status information, + among other things. + """ + report = ReportData() + report.wacombatterycharging = 1 + return super().create_report(report, reportID=reportID) + + def create_report_pad(self, reportID, ring, ek0): + report = ReportData() + + if ring is not None: + self.ring = ring + ring = self.ring + + if ek0 is not None: + self.ek0 = ek0 + ek0 = self.ek0 + + if ring >= 0: + report.wacomtouchring = ring + report.wacomtouchringstatus = 1 + else: + report.wacomtouchring = 0x7F + report.wacomtouchringstatus = 0 + + report.wacomexpresskey00 = ek0 + return super().create_report(report, reportID=reportID) + + def event(self, x, y, pressure, buttons=None, toolid=None, proximity=None): + """ + Send an input event on the default report ID. + + :param x: absolute x + :param y: absolute y + :param buttons: stylus button state. Use ``None`` for unchanged. + :param toolid: tool identifiers. Use ``None`` for unchanged. + :param proximity: a ProximityState indicating the sensor's ability + to detect and report attributes of this tool. Use ``None`` + for unchanged. + """ + r = self.create_report(x, y, pressure, buttons, toolid, proximity) + self.call_input_event(r) + return [r] + + def event_heartbeat(self, reportID): + """ + Send a heartbeat event on the requested report ID. + """ + r = self.create_report_heartbeat(reportID) + self.call_input_event(r) + return [r] + + def event_pad(self, reportID, ring=None, ek0=None): + """ + Send a pad event on the requested report ID. + """ + r = self.create_report_pad(reportID, ring, ek0) + self.call_input_event(r) + return [r] + + def get_report(self, req, rnum, rtype): + if rtype != self.UHID_FEATURE_REPORT: + return (1, []) + + rdesc = None + for v in self.parsed_rdesc.feature_reports.values(): + if v.report_ID == rnum: + rdesc = v + + if rdesc is None: + return (1, []) + + result = (1, []) + result = self.create_report_offset(rdesc) or result + return result + + def create_report_offset(self, rdesc): + require = [ + "Wacom Offset Left", + "Wacom Offset Top", + "Wacom Offset Right", + "Wacom Offset Bottom", + ] + if not set(require).issubset(set([f.usage_name for f in rdesc])): + return None + + report = ReportData() + report.wacomoffsetleft = self.offset + report.wacomoffsettop = self.offset + report.wacomoffsetright = self.offset + report.wacomoffsetbottom = self.offset + r = rdesc.create_report([report], None) + return (0, r) + + +class OpaqueTablet(BaseTablet): + """ + Bare-bones opaque tablet with a minimum of features. + + A tablet stripped down to its absolute core. It is capable of + reporting X/Y position and if the pen is in contact. No pressure, + no barrel switches, no eraser. Notably it *does* report an "In + Range" flag, but this is only because the Wacom driver expects + one to function properly. The device uses only standard HID usages, + not any of Wacom's vendor-defined pages. + """ + + # fmt: off + report_descriptor = [ + 0x05, 0x0D, # . Usage Page (Digitizer), + 0x09, 0x01, # . Usage (Digitizer), + 0xA1, 0x01, # . Collection (Application), + 0x85, 0x01, # . Report ID (1), + 0x09, 0x20, # . Usage (Stylus), + 0xA1, 0x00, # . Collection (Physical), + 0x09, 0x42, # . Usage (Tip Switch), + 0x09, 0x32, # . Usage (In Range), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x06, # . Report Count (6), + 0x81, 0x03, # . Input (Constant, Variable), + 0x05, 0x01, # . Usage Page (Desktop), + 0x09, 0x30, # . Usage (X), + 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000), + 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000), + 0x65, 0x11, # . Unit (Centimeter), + 0x55, 0x0D, # . Unit Exponent (13), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x31, # . Usage (Y), + 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000), + 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0xC0, # . End Collection, + ] + # fmt: on + + def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)): + super().__init__(rdesc, name, info) + self.default_reportID = 1 + + +class OpaqueCTLTablet(BaseTablet): + """ + Opaque tablet similar to something in the CTL product line. + + A pen-only tablet with most basic features you would expect from + an actual device. Position, eraser, pressure, barrel buttons. + Uses the Wacom vendor-defined usage page. + """ + + # fmt: off + report_descriptor = [ + 0x06, 0x0D, 0xFF, # . Usage Page (Vnd Wacom Emr), + 0x09, 0x01, # . Usage (Digitizer), + 0xA1, 0x01, # . Collection (Application), + 0x85, 0x10, # . Report ID (16), + 0x09, 0x20, # . Usage (Stylus), + 0x35, 0x00, # . Physical Minimum (0), + 0x45, 0x00, # . Physical Maximum (0), + 0x15, 0x00, # . Logical Minimum (0), + 0x25, 0x01, # . Logical Maximum (1), + 0xA1, 0x00, # . Collection (Physical), + 0x09, 0x42, # . Usage (Tip Switch), + 0x09, 0x44, # . Usage (Barrel Switch), + 0x09, 0x5A, # . Usage (Secondary Barrel Switch), + 0x09, 0x45, # . Usage (Eraser), + 0x09, 0x3C, # . Usage (Invert), + 0x09, 0x32, # . Usage (In Range), + 0x09, 0x36, # . Usage (In Proximity), + 0x25, 0x01, # . Logical Maximum (1), + 0x75, 0x01, # . Report Size (1), + 0x95, 0x07, # . Report Count (7), + 0x81, 0x02, # . Input (Variable), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x30, 0x01, # . Usage (X), + 0x65, 0x11, # . Unit (Centimeter), + 0x55, 0x0D, # . Unit Exponent (13), + 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000), + 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000), + 0x75, 0x18, # . Report Size (24), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x0A, 0x31, 0x01, # . Usage (Y), + 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000), + 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x30, # . Usage (Tip Pressure), + 0x55, 0x00, # . Unit Exponent (0), + 0x65, 0x00, # . Unit, + 0x47, 0x00, 0x00, 0x00, 0x00, # . Physical Maximum (0), + 0x26, 0xFF, 0x0F, # . Logical Maximum (4095), + 0x75, 0x10, # . Report Size (16), + 0x81, 0x02, # . Input (Variable), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x06, # . Report Count (6), + 0x81, 0x03, # . Input (Constant, Variable), + 0x0A, 0x32, 0x01, # . Usage (Z), + 0x25, 0x3F, # . Logical Maximum (63), + 0x75, 0x08, # . Report Size (8), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x5B, # . Usage (Transducer Serial Number), + 0x09, 0x5C, # . Usage (Transducer Serial Number Hi), + 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648), + 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647), + 0x75, 0x20, # . Report Size (32), + 0x95, 0x02, # . Report Count (2), + 0x81, 0x02, # . Input (Variable), + 0x09, 0x77, # . Usage (Tool Type), + 0x15, 0x00, # . Logical Minimum (0), + 0x26, 0xFF, 0x0F, # . Logical Maximum (4095), + 0x75, 0x10, # . Report Size (16), + 0x95, 0x01, # . Report Count (1), + 0x81, 0x02, # . Input (Variable), + 0xC0, # . End Collection, + 0xC0 # . End Collection + ] + # fmt: on + + def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)): + super().__init__(rdesc, name, info) + self.default_reportID = 16 + + +class PTHX60_Pen(BaseTablet): + """ + Pen interface of a PTH-660 / PTH-860 / PTH-460 tablet. + + This generation of devices are nearly identical to each other, though + the PTH-460 uses a slightly different descriptor construction (splits + the pad among several physical collections) + """ + + def __init__(self, rdesc=None, name=None, info=None): + super().__init__(rdesc, name, info) + self.default_reportID = 16 + + +class BaseTest: + class TestTablet(base.BaseTestCase.TestUhid): + kernel_modules = [KERNEL_MODULE] + + def sync_and_assert_events( + self, report, expected_events, auto_syn=True, strict=False + ): + """ + Assert we see the expected events in response to a report. + """ + uhdev = self.uhdev + syn_event = self.syn_event + if auto_syn: + expected_events.append(syn_event) + actual_events = uhdev.next_sync_events() + self.debug_reports(report, uhdev, actual_events) + if strict: + self.assertInputEvents(expected_events, actual_events) + else: + self.assertInputEventsIn(expected_events, actual_events) + + def get_usages(self, uhdev): + def get_report_usages(report): + application = report.application + for field in report.fields: + if field.usages is not None: + for usage in field.usages: + yield (field, usage, application) + else: + yield (field, field.usage, application) + + desc = uhdev.parsed_rdesc + reports = [ + *desc.input_reports.values(), + *desc.feature_reports.values(), + *desc.output_reports.values(), + ] + for report in reports: + for usage in get_report_usages(report): + yield usage + + def assertName(self, uhdev): + """ + Assert that the name is as we expect. + + The Wacom driver applies a number of decorations to the name + provided by the hardware. We cannot rely on the definition of + this assertion from the base class to work properly. + """ + evdev = uhdev.get_evdev() + expected_name = uhdev.name + " Pen" + if "wacom" not in expected_name.lower(): + expected_name = "Wacom " + expected_name + assert evdev.name == expected_name + + def test_descriptor_physicals(self): + """ + Verify that all HID usages which should have a physical range + actually do, and those which shouldn't don't. Also verify that + the associated unit is correct and within a sensible range. + """ + + def usage_id(page_name, usage_name): + page = HUT.usage_page_from_name(page_name) + return (page.page_id << 16) | page[usage_name].usage + + required = { + usage_id("Generic Desktop", "X"): PhysRange( + PhysRange.CENTIMETER, 5, 150 + ), + usage_id("Generic Desktop", "Y"): PhysRange( + PhysRange.CENTIMETER, 5, 150 + ), + usage_id("Digitizers", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), + usage_id("Digitizers", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), + usage_id("Digitizers", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360), + usage_id("Wacom", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), + usage_id("Wacom", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180), + usage_id("Wacom", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360), + usage_id("Wacom", "X"): PhysRange(PhysRange.CENTIMETER, 5, 150), + usage_id("Wacom", "Y"): PhysRange(PhysRange.CENTIMETER, 5, 150), + usage_id("Wacom", "Wacom TouchRing"): PhysRange( + PhysRange.DEGREE, 358, 360 + ), + usage_id("Wacom", "Wacom Offset Left"): PhysRange( + PhysRange.CENTIMETER, 0, 0.5 + ), + usage_id("Wacom", "Wacom Offset Top"): PhysRange( + PhysRange.CENTIMETER, 0, 0.5 + ), + usage_id("Wacom", "Wacom Offset Right"): PhysRange( + PhysRange.CENTIMETER, 0, 0.5 + ), + usage_id("Wacom", "Wacom Offset Bottom"): PhysRange( + PhysRange.CENTIMETER, 0, 0.5 + ), + } + for field, usage, application in self.get_usages(self.uhdev): + if application == usage_id("Generic Desktop", "Mouse"): + # Ignore the vestigial Mouse collection which exists + # on Wacom tablets only for backwards compatibility. + continue + + expect_physical = usage in required + + phys_set = field.physical_min != 0 or field.physical_max != 0 + assert phys_set == expect_physical + + unit_set = field.unit != 0 + assert unit_set == expect_physical + + if unit_set: + assert required[usage].contains(field) + + def test_prop_direct(self): + """ + Todo: Verify that INPUT_PROP_DIRECT is set on display devices. + """ + pass + + def test_prop_pointer(self): + """ + Todo: Verify that INPUT_PROP_POINTER is set on opaque devices. + """ + pass + + +class TestOpaqueTablet(BaseTest.TestTablet): + def create_device(self): + return OpaqueTablet() + + def test_sanity(self): + """ + Bring a pen into contact with the tablet, then remove it. + + Ensure that we get the basic tool/touch/motion events that should + be sent by the driver. + """ + uhdev = self.uhdev + + self.sync_and_assert_events( + uhdev.event( + 100, + 200, + pressure=300, + buttons=Buttons.clear(), + toolid=ToolID(serial=1, tooltype=1), + proximity=ProximityState.IN_RANGE, + ), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), + libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), + libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1), + ], + ) + + self.sync_and_assert_events( + uhdev.event(110, 220, pressure=0), + [ + libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110), + libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 220), + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0), + ], + ) + + self.sync_and_assert_events( + uhdev.event( + 120, + 230, + pressure=0, + toolid=ToolID.clear(), + proximity=ProximityState.OUT, + ), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0), + ], + ) + + self.sync_and_assert_events( + uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True + ) + + +class TestOpaqueCTLTablet(TestOpaqueTablet): + def create_device(self): + return OpaqueCTLTablet() + + def test_buttons(self): + """ + Test that the barrel buttons (side switches) work as expected. + + Press and release each button individually to verify that we get + the expected events. + """ + uhdev = self.uhdev + + self.sync_and_assert_events( + uhdev.event( + 100, + 200, + pressure=0, + buttons=Buttons.clear(), + toolid=ToolID(serial=1, tooltype=1), + proximity=ProximityState.IN_RANGE, + ), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), + libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), + libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), + libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), + ], + ) + + self.sync_and_assert_events( + uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=True)), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1), + libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), + ], + ) + + self.sync_and_assert_events( + uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=False)), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0), + libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), + ], + ) + + self.sync_and_assert_events( + uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=True)), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 1), + libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), + ], + ) + + self.sync_and_assert_events( + uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=False)), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 0), + libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1), + ], + ) + + +PTHX60_Devices = [ + {"rdesc": wacom_pth660_v145, "info": (0x3, 0x056A, 0x0357)}, + {"rdesc": wacom_pth660_v150, "info": (0x3, 0x056A, 0x0357)}, + {"rdesc": wacom_pth860_v145, "info": (0x3, 0x056A, 0x0358)}, + {"rdesc": wacom_pth860_v150, "info": (0x3, 0x056A, 0x0358)}, + {"rdesc": wacom_pth460_v105, "info": (0x3, 0x056A, 0x0392)}, +] + +PTHX60_Names = [ + "PTH-660/v145", + "PTH-660/v150", + "PTH-860/v145", + "PTH-860/v150", + "PTH-460/v105", +] + + +class TestPTHX60_Pen(TestOpaqueCTLTablet): + @pytest.fixture( + autouse=True, scope="class", params=PTHX60_Devices, ids=PTHX60_Names + ) + def set_device_params(self, request): + request.cls.device_params = request.param + + def create_device(self): + return PTHX60_Pen(**self.device_params) + + @pytest.mark.xfail + def test_descriptor_physicals(self): + # XFAIL: Various documented errata + super().test_descriptor_physicals() + + def test_heartbeat_spurious(self): + """ + Test that the heartbeat report does not send spurious events. + """ + uhdev = self.uhdev + + self.sync_and_assert_events( + uhdev.event( + 100, + 200, + pressure=300, + buttons=Buttons.clear(), + toolid=ToolID(serial=1, tooltype=0x822), + proximity=ProximityState.IN_RANGE, + ), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1), + libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100), + libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200), + libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1), + ], + ) + + # Exactly zero events: not even a SYN + self.sync_and_assert_events( + uhdev.event_heartbeat(19), [], auto_syn=False, strict=True + ) + + self.sync_and_assert_events( + uhdev.event(110, 200, pressure=300), + [ + libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110), + ], + ) + + def test_empty_pad_sync(self): + self.empty_pad_sync(num=3, denom=16, reverse=True) + + def empty_pad_sync(self, num, denom, reverse): + """ + Test that multiple pad collections do not trigger empty syncs. + """ + + def offset_rotation(value): + """ + Offset touchring rotation values by the same factor as the + Linux kernel. Tablets historically don't use the same origin + as HID, and it sometimes changes from tablet to tablet... + """ + evdev = self.uhdev.get_evdev() + info = evdev.absinfo[libevdev.EV_ABS.ABS_WHEEL] + delta = info.maximum - info.minimum + 1 + if reverse: + value = info.maximum - value + value += num * delta // denom + if value > info.maximum: + value -= delta + elif value < info.minimum: + value += delta + return value + + uhdev = self.uhdev + uhdev.application = "Pad" + evdev = uhdev.get_evdev() + + print(evdev.name) + self.sync_and_assert_events( + uhdev.event_pad(reportID=17, ring=0, ek0=1), + [ + libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 1), + libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(0)), + libevdev.InputEvent(libevdev.EV_ABS.ABS_MISC, 15), + ], + ) + + self.sync_and_assert_events( + uhdev.event_pad(reportID=17, ring=1, ek0=1), + [libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(1))], + ) + + self.sync_and_assert_events( + uhdev.event_pad(reportID=17, ring=2, ek0=0), + [ + libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(2)), + libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0), + ], + ) diff --git a/tools/testing/selftests/hid/vmtest.sh b/tools/testing/selftests/hid/vmtest.sh index 90f34150f257..681b906b4853 100755 --- a/tools/testing/selftests/hid/vmtest.sh +++ b/tools/testing/selftests/hid/vmtest.sh @@ -16,7 +16,6 @@ x86_64) exit 1 ;; esac -DEFAULT_COMMAND="./hid_bpf" SCRIPT_DIR="$(dirname $(realpath $0))" OUTPUT_DIR="$SCRIPT_DIR/results" KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}") @@ -25,7 +24,10 @@ NUM_COMPILE_JOBS="$(nproc)" LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")" LOG_FILE="${LOG_FILE_BASE}.log" EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status" -CONTAINER_IMAGE="registry.fedoraproject.org/fedora:36" +CONTAINER_IMAGE="registry.freedesktop.org/libevdev/hid-tools/fedora/37:2023-02-17.1" + +TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}" +DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests" usage() { @@ -33,9 +35,9 @@ usage() Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>] <command> is the command you would normally run when you are in -tools/testing/selftests/bpf. e.g: +the source kernel direcory. e.g: - $0 -- ./hid_bpf + $0 -- ./tools/testing/selftests/hid/hid_bpf If no command is specified and a debug shell (-s) is not requested, "${DEFAULT_COMMAND}" will be run by default. @@ -43,11 +45,11 @@ If no command is specified and a debug shell (-s) is not requested, If you build your kernel using KBUILD_OUTPUT= or O= options, these can be passed as environment variables to the script: - O=<kernel_build_path> $0 -- ./hid_bpf + O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf or - KBUILD_OUTPUT=<kernel_build_path> $0 -- ./hid_bpf + KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf Options: @@ -91,11 +93,14 @@ update_selftests() run_vm() { - local b2c="$1" - local kernel_bzimage="$2" - local command="$3" + local run_dir="$1" + local b2c="$2" + local kernel_bzimage="$3" + local command="$4" local post_command="" + cd "${run_dir}" + if ! which "${QEMU_BINARY}" &> /dev/null; then cat <<EOF Could not find ${QEMU_BINARY} @@ -273,7 +278,7 @@ main() fi update_selftests "${kernel_checkout}" "${make_command}" - run_vm $b2c "${kernel_bzimage}" "${command}" + run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}" if [[ "${debug_shell}" != "yes" ]]; then echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}" fi diff --git a/tools/testing/selftests/iommu/iommufd.c b/tools/testing/selftests/iommu/iommufd.c index fa08209268c4..e4a6b33cfde4 100644 --- a/tools/testing/selftests/iommu/iommufd.c +++ b/tools/testing/selftests/iommu/iommufd.c @@ -186,7 +186,8 @@ FIXTURE(iommufd_ioas) { int fd; uint32_t ioas_id; - uint32_t domain_id; + uint32_t stdev_id; + uint32_t hwpt_id; uint64_t base_iova; }; @@ -212,7 +213,8 @@ FIXTURE_SETUP(iommufd_ioas) } for (i = 0; i != variant->mock_domains; i++) { - test_cmd_mock_domain(self->ioas_id, NULL, &self->domain_id); + test_cmd_mock_domain(self->ioas_id, &self->stdev_id, + &self->hwpt_id); self->base_iova = MOCK_APERTURE_START; } } @@ -249,8 +251,8 @@ TEST_F(iommufd_ioas, ioas_auto_destroy) TEST_F(iommufd_ioas, ioas_destroy) { - if (self->domain_id) { - /* IOAS cannot be freed while a domain is on it */ + if (self->stdev_id) { + /* IOAS cannot be freed while a device has a HWPT using it */ EXPECT_ERRNO(EBUSY, _test_ioctl_destroy(self->fd, self->ioas_id)); } else { @@ -259,11 +261,21 @@ TEST_F(iommufd_ioas, ioas_destroy) } } +TEST_F(iommufd_ioas, hwpt_attach) +{ + /* Create a device attached directly to a hwpt */ + if (self->stdev_id) { + test_cmd_mock_domain(self->hwpt_id, NULL, NULL); + } else { + test_err_mock_domain(ENOENT, self->hwpt_id, NULL, NULL); + } +} + TEST_F(iommufd_ioas, ioas_area_destroy) { /* Adding an area does not change ability to destroy */ test_ioctl_ioas_map_fixed(buffer, PAGE_SIZE, self->base_iova); - if (self->domain_id) + if (self->stdev_id) EXPECT_ERRNO(EBUSY, _test_ioctl_destroy(self->fd, self->ioas_id)); else @@ -382,7 +394,7 @@ TEST_F(iommufd_ioas, area_auto_iova) for (i = 0; i != 10; i++) { size_t length = PAGE_SIZE * (i + 1); - if (self->domain_id) { + if (self->stdev_id) { test_ioctl_ioas_map(buffer, length, &iovas[i]); } else { test_ioctl_ioas_map((void *)(1UL << 31), length, @@ -418,7 +430,7 @@ TEST_F(iommufd_ioas, area_auto_iova) ioctl(self->fd, IOMMU_IOAS_ALLOW_IOVAS, &allow_cmd)); /* Allocate from an allowed region */ - if (self->domain_id) { + if (self->stdev_id) { ranges[0].start = MOCK_APERTURE_START + PAGE_SIZE; ranges[0].last = MOCK_APERTURE_START + PAGE_SIZE * 600 - 1; } else { @@ -525,7 +537,7 @@ TEST_F(iommufd_ioas, iova_ranges) /* Range can be read */ ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_IOVA_RANGES, &ranges_cmd)); EXPECT_EQ(1, ranges_cmd.num_iovas); - if (!self->domain_id) { + if (!self->stdev_id) { EXPECT_EQ(0, ranges[0].start); EXPECT_EQ(SIZE_MAX, ranges[0].last); EXPECT_EQ(1, ranges_cmd.out_iova_alignment); @@ -550,7 +562,7 @@ TEST_F(iommufd_ioas, iova_ranges) &test_cmd)); ranges_cmd.num_iovas = BUFFER_SIZE / sizeof(*ranges); ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_IOVA_RANGES, &ranges_cmd)); - if (!self->domain_id) { + if (!self->stdev_id) { EXPECT_EQ(2, ranges_cmd.num_iovas); EXPECT_EQ(0, ranges[0].start); EXPECT_EQ(PAGE_SIZE - 1, ranges[0].last); @@ -565,7 +577,7 @@ TEST_F(iommufd_ioas, iova_ranges) /* Buffer too small */ memset(ranges, 0, BUFFER_SIZE); ranges_cmd.num_iovas = 1; - if (!self->domain_id) { + if (!self->stdev_id) { EXPECT_ERRNO(EMSGSIZE, ioctl(self->fd, IOMMU_IOAS_IOVA_RANGES, &ranges_cmd)); EXPECT_EQ(2, ranges_cmd.num_iovas); @@ -582,6 +594,40 @@ TEST_F(iommufd_ioas, iova_ranges) EXPECT_EQ(0, ranges[1].last); } +TEST_F(iommufd_ioas, access_domain_destory) +{ + struct iommu_test_cmd access_cmd = { + .size = sizeof(access_cmd), + .op = IOMMU_TEST_OP_ACCESS_PAGES, + .access_pages = { .iova = self->base_iova + PAGE_SIZE, + .length = PAGE_SIZE}, + }; + size_t buf_size = 2 * HUGEPAGE_SIZE; + uint8_t *buf; + + buf = mmap(0, buf_size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE, -1, + 0); + ASSERT_NE(MAP_FAILED, buf); + test_ioctl_ioas_map_fixed(buf, buf_size, self->base_iova); + + test_cmd_create_access(self->ioas_id, &access_cmd.id, + MOCK_FLAGS_ACCESS_CREATE_NEEDS_PIN_PAGES); + access_cmd.access_pages.uptr = (uintptr_t)buf + PAGE_SIZE; + ASSERT_EQ(0, + ioctl(self->fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_PAGES), + &access_cmd)); + + /* Causes a complicated unpin across a huge page boundary */ + if (self->stdev_id) + test_ioctl_destroy(self->stdev_id); + + test_cmd_destroy_access_pages( + access_cmd.id, access_cmd.access_pages.out_access_pages_id); + test_cmd_destroy_access(access_cmd.id); + ASSERT_EQ(0, munmap(buf, buf_size)); +} + TEST_F(iommufd_ioas, access_pin) { struct iommu_test_cmd access_cmd = { @@ -605,7 +651,7 @@ TEST_F(iommufd_ioas, access_pin) MOCK_FLAGS_ACCESS_CREATE_NEEDS_PIN_PAGES); for (npages = 1; npages < BUFFER_SIZE / PAGE_SIZE; npages++) { - uint32_t mock_device_id; + uint32_t mock_stdev_id; uint32_t mock_hwpt_id; access_cmd.access_pages.length = npages * PAGE_SIZE; @@ -637,15 +683,14 @@ TEST_F(iommufd_ioas, access_pin) ASSERT_EQ(0, ioctl(self->fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_PAGES), &access_cmd)); - test_cmd_mock_domain(self->ioas_id, &mock_device_id, + test_cmd_mock_domain(self->ioas_id, &mock_stdev_id, &mock_hwpt_id); check_map_cmd.id = mock_hwpt_id; ASSERT_EQ(0, ioctl(self->fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_MD_CHECK_MAP), &check_map_cmd)); - test_ioctl_destroy(mock_device_id); - test_ioctl_destroy(mock_hwpt_id); + test_ioctl_destroy(mock_stdev_id); test_cmd_destroy_access_pages( access_cmd.id, access_cmd.access_pages.out_access_pages_id); @@ -789,7 +834,7 @@ TEST_F(iommufd_ioas, fork_gone) ASSERT_NE(-1, child); ASSERT_EQ(child, waitpid(child, NULL, 0)); - if (self->domain_id) { + if (self->stdev_id) { /* * If a domain already existed then everything was pinned within * the fork, so this copies from one domain to another. @@ -988,8 +1033,8 @@ FIXTURE(iommufd_mock_domain) { int fd; uint32_t ioas_id; - uint32_t domain_id; - uint32_t domain_ids[2]; + uint32_t hwpt_id; + uint32_t hwpt_ids[2]; int mmap_flags; size_t mmap_buf_size; }; @@ -1008,11 +1053,11 @@ FIXTURE_SETUP(iommufd_mock_domain) ASSERT_NE(-1, self->fd); test_ioctl_ioas_alloc(&self->ioas_id); - ASSERT_GE(ARRAY_SIZE(self->domain_ids), variant->mock_domains); + ASSERT_GE(ARRAY_SIZE(self->hwpt_ids), variant->mock_domains); for (i = 0; i != variant->mock_domains; i++) - test_cmd_mock_domain(self->ioas_id, NULL, &self->domain_ids[i]); - self->domain_id = self->domain_ids[0]; + test_cmd_mock_domain(self->ioas_id, NULL, &self->hwpt_ids[i]); + self->hwpt_id = self->hwpt_ids[0]; self->mmap_flags = MAP_SHARED | MAP_ANONYMOUS; self->mmap_buf_size = PAGE_SIZE * 8; @@ -1061,7 +1106,7 @@ FIXTURE_VARIANT_ADD(iommufd_mock_domain, two_domains_hugepage) struct iommu_test_cmd check_map_cmd = { \ .size = sizeof(check_map_cmd), \ .op = IOMMU_TEST_OP_MD_CHECK_MAP, \ - .id = self->domain_id, \ + .id = self->hwpt_id, \ .check_map = { .iova = _iova, \ .length = _length, \ .uptr = (uintptr_t)(_ptr) }, \ @@ -1070,8 +1115,8 @@ FIXTURE_VARIANT_ADD(iommufd_mock_domain, two_domains_hugepage) ioctl(self->fd, \ _IOMMU_TEST_CMD(IOMMU_TEST_OP_MD_CHECK_MAP), \ &check_map_cmd)); \ - if (self->domain_ids[1]) { \ - check_map_cmd.id = self->domain_ids[1]; \ + if (self->hwpt_ids[1]) { \ + check_map_cmd.id = self->hwpt_ids[1]; \ ASSERT_EQ(0, \ ioctl(self->fd, \ _IOMMU_TEST_CMD( \ @@ -1197,15 +1242,15 @@ TEST_F(iommufd_mock_domain, all_aligns_copy) for (; end < buf_size; end += MOCK_PAGE_SIZE) { size_t length = end - start; unsigned int old_id; - uint32_t mock_device_id; + uint32_t mock_stdev_id; __u64 iova; test_ioctl_ioas_map(buf + start, length, &iova); /* Add and destroy a domain while the area exists */ - old_id = self->domain_ids[1]; - test_cmd_mock_domain(self->ioas_id, &mock_device_id, - &self->domain_ids[1]); + old_id = self->hwpt_ids[1]; + test_cmd_mock_domain(self->ioas_id, &mock_stdev_id, + &self->hwpt_ids[1]); check_mock_iova(buf + start, iova, length); check_refs(buf + start / PAGE_SIZE * PAGE_SIZE, @@ -1213,9 +1258,8 @@ TEST_F(iommufd_mock_domain, all_aligns_copy) start / PAGE_SIZE * PAGE_SIZE, 1); - test_ioctl_destroy(mock_device_id); - test_ioctl_destroy(self->domain_ids[1]); - self->domain_ids[1] = old_id; + test_ioctl_destroy(mock_stdev_id); + self->hwpt_ids[1] = old_id; test_ioctl_ioas_unmap(iova, length); } diff --git a/tools/testing/selftests/iommu/iommufd_fail_nth.c b/tools/testing/selftests/iommu/iommufd_fail_nth.c index 9713111b820d..d9afcb23810e 100644 --- a/tools/testing/selftests/iommu/iommufd_fail_nth.c +++ b/tools/testing/selftests/iommu/iommufd_fail_nth.c @@ -297,7 +297,7 @@ TEST_FAIL_NTH(basic_fail_nth, basic) TEST_FAIL_NTH(basic_fail_nth, map_domain) { uint32_t ioas_id; - __u32 device_id; + __u32 stdev_id; __u32 hwpt_id; __u64 iova; @@ -313,7 +313,7 @@ TEST_FAIL_NTH(basic_fail_nth, map_domain) fail_nth_enable(); - if (_test_cmd_mock_domain(self->fd, ioas_id, &device_id, &hwpt_id)) + if (_test_cmd_mock_domain(self->fd, ioas_id, &stdev_id, &hwpt_id)) return -1; if (_test_ioctl_ioas_map(self->fd, ioas_id, buffer, 262144, &iova, @@ -321,12 +321,10 @@ TEST_FAIL_NTH(basic_fail_nth, map_domain) IOMMU_IOAS_MAP_READABLE)) return -1; - if (_test_ioctl_destroy(self->fd, device_id)) - return -1; - if (_test_ioctl_destroy(self->fd, hwpt_id)) + if (_test_ioctl_destroy(self->fd, stdev_id)) return -1; - if (_test_cmd_mock_domain(self->fd, ioas_id, &device_id, &hwpt_id)) + if (_test_cmd_mock_domain(self->fd, ioas_id, &stdev_id, &hwpt_id)) return -1; return 0; } @@ -334,8 +332,8 @@ TEST_FAIL_NTH(basic_fail_nth, map_domain) TEST_FAIL_NTH(basic_fail_nth, map_two_domains) { uint32_t ioas_id; - __u32 device_id2; - __u32 device_id; + __u32 stdev_id2; + __u32 stdev_id; __u32 hwpt_id2; __u32 hwpt_id; __u64 iova; @@ -350,12 +348,12 @@ TEST_FAIL_NTH(basic_fail_nth, map_two_domains) if (_test_ioctl_set_temp_memory_limit(self->fd, 32)) return -1; - if (_test_cmd_mock_domain(self->fd, ioas_id, &device_id, &hwpt_id)) + if (_test_cmd_mock_domain(self->fd, ioas_id, &stdev_id, &hwpt_id)) return -1; fail_nth_enable(); - if (_test_cmd_mock_domain(self->fd, ioas_id, &device_id2, &hwpt_id2)) + if (_test_cmd_mock_domain(self->fd, ioas_id, &stdev_id2, &hwpt_id2)) return -1; if (_test_ioctl_ioas_map(self->fd, ioas_id, buffer, 262144, &iova, @@ -363,19 +361,15 @@ TEST_FAIL_NTH(basic_fail_nth, map_two_domains) IOMMU_IOAS_MAP_READABLE)) return -1; - if (_test_ioctl_destroy(self->fd, device_id)) - return -1; - if (_test_ioctl_destroy(self->fd, hwpt_id)) + if (_test_ioctl_destroy(self->fd, stdev_id)) return -1; - if (_test_ioctl_destroy(self->fd, device_id2)) - return -1; - if (_test_ioctl_destroy(self->fd, hwpt_id2)) + if (_test_ioctl_destroy(self->fd, stdev_id2)) return -1; - if (_test_cmd_mock_domain(self->fd, ioas_id, &device_id, &hwpt_id)) + if (_test_cmd_mock_domain(self->fd, ioas_id, &stdev_id, &hwpt_id)) return -1; - if (_test_cmd_mock_domain(self->fd, ioas_id, &device_id2, &hwpt_id2)) + if (_test_cmd_mock_domain(self->fd, ioas_id, &stdev_id2, &hwpt_id2)) return -1; return 0; } @@ -518,7 +512,7 @@ TEST_FAIL_NTH(basic_fail_nth, access_pin_domain) { uint32_t access_pages_id; uint32_t ioas_id; - __u32 device_id; + __u32 stdev_id; __u32 hwpt_id; __u64 iova; @@ -532,7 +526,7 @@ TEST_FAIL_NTH(basic_fail_nth, access_pin_domain) if (_test_ioctl_set_temp_memory_limit(self->fd, 32)) return -1; - if (_test_cmd_mock_domain(self->fd, ioas_id, &device_id, &hwpt_id)) + if (_test_cmd_mock_domain(self->fd, ioas_id, &stdev_id, &hwpt_id)) return -1; if (_test_ioctl_ioas_map(self->fd, ioas_id, buffer, BUFFER_SIZE, &iova, @@ -570,9 +564,7 @@ TEST_FAIL_NTH(basic_fail_nth, access_pin_domain) return -1; self->access_id = 0; - if (_test_ioctl_destroy(self->fd, device_id)) - return -1; - if (_test_ioctl_destroy(self->fd, hwpt_id)) + if (_test_ioctl_destroy(self->fd, stdev_id)) return -1; return 0; } diff --git a/tools/testing/selftests/iommu/iommufd_utils.h b/tools/testing/selftests/iommu/iommufd_utils.h index 0d1f46369c2a..85d6662ef8e8 100644 --- a/tools/testing/selftests/iommu/iommufd_utils.h +++ b/tools/testing/selftests/iommu/iommufd_utils.h @@ -38,7 +38,7 @@ static unsigned long BUFFER_SIZE; &test_cmd)); \ }) -static int _test_cmd_mock_domain(int fd, unsigned int ioas_id, __u32 *device_id, +static int _test_cmd_mock_domain(int fd, unsigned int ioas_id, __u32 *stdev_id, __u32 *hwpt_id) { struct iommu_test_cmd cmd = { @@ -52,19 +52,19 @@ static int _test_cmd_mock_domain(int fd, unsigned int ioas_id, __u32 *device_id, ret = ioctl(fd, IOMMU_TEST_CMD, &cmd); if (ret) return ret; - if (device_id) - *device_id = cmd.mock_domain.out_device_id; + if (stdev_id) + *stdev_id = cmd.mock_domain.out_stdev_id; assert(cmd.id != 0); if (hwpt_id) *hwpt_id = cmd.mock_domain.out_hwpt_id; return 0; } -#define test_cmd_mock_domain(ioas_id, device_id, hwpt_id) \ - ASSERT_EQ(0, _test_cmd_mock_domain(self->fd, ioas_id, device_id, \ - hwpt_id)) -#define test_err_mock_domain(_errno, ioas_id, device_id, hwpt_id) \ +#define test_cmd_mock_domain(ioas_id, stdev_id, hwpt_id) \ + ASSERT_EQ(0, \ + _test_cmd_mock_domain(self->fd, ioas_id, stdev_id, hwpt_id)) +#define test_err_mock_domain(_errno, ioas_id, stdev_id, hwpt_id) \ EXPECT_ERRNO(_errno, _test_cmd_mock_domain(self->fd, ioas_id, \ - device_id, hwpt_id)) + stdev_id, hwpt_id)) static int _test_cmd_create_access(int fd, unsigned int ioas_id, __u32 *access_id, unsigned int flags) diff --git a/tools/testing/selftests/kselftest.h b/tools/testing/selftests/kselftest.h index 33a0dbd26bd3..829be379545a 100644 --- a/tools/testing/selftests/kselftest.h +++ b/tools/testing/selftests/kselftest.h @@ -43,11 +43,13 @@ #ifndef __KSELFTEST_H #define __KSELFTEST_H +#ifndef NOLIBC #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <stdarg.h> #include <stdio.h> +#endif #ifndef ARRAY_SIZE #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) diff --git a/tools/testing/selftests/memfd/memfd_test.c b/tools/testing/selftests/memfd/memfd_test.c index ae71f15f790d..dba0e8ba002f 100644 --- a/tools/testing/selftests/memfd/memfd_test.c +++ b/tools/testing/selftests/memfd/memfd_test.c @@ -43,6 +43,9 @@ */ static size_t mfd_def_size = MFD_DEF_SIZE; static const char *memfd_str = MEMFD_STR; +static pid_t spawn_newpid_thread(unsigned int flags, int (*fn)(void *)); +static int newpid_thread_fn2(void *arg); +static void join_newpid_thread(pid_t pid); static ssize_t fd2name(int fd, char *buf, size_t bufsize) { @@ -1111,6 +1114,7 @@ static void test_noexec_seal(void) static void test_sysctl_child(void) { int fd; + int pid; printf("%s sysctl 0\n", memfd_str); sysctl_assert_write("0"); @@ -1129,6 +1133,10 @@ static void test_sysctl_child(void) mfd_def_size, MFD_CLOEXEC | MFD_ALLOW_SEALING); + printf("%s child ns\n", memfd_str); + pid = spawn_newpid_thread(CLONE_NEWPID, newpid_thread_fn2); + join_newpid_thread(pid); + mfd_assert_mode(fd, 0666); mfd_assert_has_seals(fd, F_SEAL_EXEC); mfd_fail_chmod(fd, 0777); @@ -1206,12 +1214,6 @@ static void test_sysctl(void) int pid = spawn_newpid_thread(CLONE_NEWPID, newpid_thread_fn); join_newpid_thread(pid); - - printf("%s child ns\n", memfd_str); - sysctl_assert_write("1"); - - pid = spawn_newpid_thread(CLONE_NEWPID, newpid_thread_fn2); - join_newpid_thread(pid); } /* diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore index 1f8c36a9fa10..8917455f4f51 100644 --- a/tools/testing/selftests/mm/.gitignore +++ b/tools/testing/selftests/mm/.gitignore @@ -21,7 +21,8 @@ protection_keys protection_keys_32 protection_keys_64 madv_populate -userfaultfd +uffd-stress +uffd-unit-tests mlock-intersect-test mlock-random-test virtual_address_range @@ -36,3 +37,5 @@ split_huge_page_test ksm_tests local_config.h local_config.mk +ksm_functional_tests +mdwe_test diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index c31d952cff68..23af4633f0f4 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -20,7 +20,7 @@ MACHINE ?= $(shell echo $(uname_M) | sed -e 's/aarch64.*/arm64/' -e 's/ppc64.*/p # Avoid accidental wrong builds, due to built-in rules working just a little # bit too well--but not quite as well as required for our situation here. # -# In other words, "make userfaultfd" is supposed to fail to build at all, +# In other words, "make $SOME_TEST" is supposed to fail to build at all, # because this Makefile only supports either "make" (all), or "make /full/path". # However, the built-in rules, if not suppressed, will pick up CFLAGS and the # initial LDLIBS (but not the target-specific LDLIBS, because those are only @@ -29,36 +29,39 @@ MACHINE ?= $(shell echo $(uname_M) | sed -e 's/aarch64.*/arm64/' -e 's/ppc64.*/p # LDLIBS. MAKEFLAGS += --no-builtin-rules -CFLAGS = -Wall -I $(top_srcdir) $(EXTRA_CFLAGS) $(KHDR_INCLUDES) +CFLAGS = -Wall -I $(top_srcdir) -I $(top_srcdir)/tools/include/uapi $(EXTRA_CFLAGS) $(KHDR_INCLUDES) LDLIBS = -lrt -lpthread -TEST_GEN_FILES = cow -TEST_GEN_FILES += compaction_test -TEST_GEN_FILES += gup_test -TEST_GEN_FILES += hmm-tests -TEST_GEN_FILES += hugetlb-madvise -TEST_GEN_FILES += hugepage-mmap -TEST_GEN_FILES += hugepage-mremap -TEST_GEN_FILES += hugepage-shm -TEST_GEN_FILES += hugepage-vmemmap -TEST_GEN_FILES += khugepaged -TEST_GEN_PROGS = madv_populate -TEST_GEN_FILES += map_fixed_noreplace -TEST_GEN_FILES += map_hugetlb -TEST_GEN_FILES += map_populate -TEST_GEN_FILES += memfd_secret -TEST_GEN_FILES += migration -TEST_GEN_FILES += mlock-random-test -TEST_GEN_FILES += mlock2-tests -TEST_GEN_FILES += mrelease_test -TEST_GEN_FILES += mremap_dontunmap -TEST_GEN_FILES += mremap_test -TEST_GEN_FILES += on-fault-limit -TEST_GEN_FILES += thuge-gen -TEST_GEN_FILES += transhuge-stress -TEST_GEN_FILES += userfaultfd + +TEST_GEN_PROGS = cow +TEST_GEN_PROGS += compaction_test +TEST_GEN_PROGS += gup_test +TEST_GEN_PROGS += hmm-tests +TEST_GEN_PROGS += hugetlb-madvise +TEST_GEN_PROGS += hugepage-mmap +TEST_GEN_PROGS += hugepage-mremap +TEST_GEN_PROGS += hugepage-shm +TEST_GEN_PROGS += hugepage-vmemmap +TEST_GEN_PROGS += khugepaged +TEST_GEN_PROGS += madv_populate +TEST_GEN_PROGS += map_fixed_noreplace +TEST_GEN_PROGS += map_hugetlb +TEST_GEN_PROGS += map_populate +TEST_GEN_PROGS += memfd_secret +TEST_GEN_PROGS += migration +TEST_GEN_PROGS += mkdirty +TEST_GEN_PROGS += mlock-random-test +TEST_GEN_PROGS += mlock2-tests +TEST_GEN_PROGS += mrelease_test +TEST_GEN_PROGS += mremap_dontunmap +TEST_GEN_PROGS += mremap_test +TEST_GEN_PROGS += on-fault-limit +TEST_GEN_PROGS += thuge-gen +TEST_GEN_PROGS += transhuge-stress +TEST_GEN_PROGS += uffd-stress +TEST_GEN_PROGS += uffd-unit-tests TEST_GEN_PROGS += soft-dirty TEST_GEN_PROGS += split_huge_page_test -TEST_GEN_FILES += ksm_tests +TEST_GEN_PROGS += ksm_tests TEST_GEN_PROGS += ksm_functional_tests TEST_GEN_PROGS += mdwe_test @@ -76,41 +79,38 @@ CFLAGS += -no-pie endif ifeq ($(CAN_BUILD_I386),1) -TEST_GEN_FILES += $(BINARIES_32) +TEST_GEN_PROGS += $(BINARIES_32) endif ifeq ($(CAN_BUILD_X86_64),1) -TEST_GEN_FILES += $(BINARIES_64) +TEST_GEN_PROGS += $(BINARIES_64) endif else ifneq (,$(findstring $(MACHINE),ppc64)) -TEST_GEN_FILES += protection_keys +TEST_GEN_PROGS += protection_keys endif endif -ifneq (,$(filter $(MACHINE),arm64 ia64 mips64 parisc64 ppc64 riscv64 s390x sh64 sparc64 x86_64)) -TEST_GEN_FILES += va_128TBswitch -TEST_GEN_FILES += virtual_address_range -TEST_GEN_FILES += write_to_hugetlbfs +ifneq (,$(filter $(MACHINE),arm64 ia64 mips64 parisc64 ppc64 riscv64 s390x sparc64 x86_64)) +TEST_GEN_PROGS += va_high_addr_switch +TEST_GEN_PROGS += virtual_address_range +TEST_GEN_PROGS += write_to_hugetlbfs endif TEST_PROGS := run_vmtests.sh TEST_FILES := test_vmalloc.sh TEST_FILES += test_hmm.sh -TEST_FILES += va_128TBswitch.sh +TEST_FILES += va_high_addr_switch.sh include ../lib.mk -$(OUTPUT)/cow: vm_util.c -$(OUTPUT)/khugepaged: vm_util.c -$(OUTPUT)/ksm_functional_tests: vm_util.c -$(OUTPUT)/madv_populate: vm_util.c -$(OUTPUT)/soft-dirty: vm_util.c -$(OUTPUT)/split_huge_page_test: vm_util.c -$(OUTPUT)/userfaultfd: vm_util.c +$(TEST_GEN_PROGS): vm_util.c + +$(OUTPUT)/uffd-stress: uffd-common.c +$(OUTPUT)/uffd-unit-tests: uffd-common.c ifeq ($(MACHINE),x86_64) BINARIES_32 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_32)) @@ -161,8 +161,8 @@ warn_32bit_failure: endif endif -# cow_EXTRA_LIBS may get set in local_config.mk, or it may be left empty. -$(OUTPUT)/cow: LDLIBS += $(COW_EXTRA_LIBS) +# IOURING_EXTRA_LIBS may get set in local_config.mk, or it may be left empty. +$(OUTPUT)/cow: LDLIBS += $(IOURING_EXTRA_LIBS) $(OUTPUT)/mlock-random-test $(OUTPUT)/memfd_secret: LDLIBS += -lcap @@ -175,11 +175,11 @@ local_config.mk local_config.h: check_config.sh EXTRA_CLEAN += local_config.mk local_config.h -ifeq ($(COW_EXTRA_LIBS),) +ifeq ($(IOURING_EXTRA_LIBS),) all: warn_missing_liburing warn_missing_liburing: @echo ; \ - echo "Warning: missing liburing support. Some COW tests will be skipped." ; \ + echo "Warning: missing liburing support. Some tests will be skipped." ; \ echo endif diff --git a/tools/testing/selftests/mm/check_config.sh b/tools/testing/selftests/mm/check_config.sh index bcba3af0acea..3954f4746161 100644 --- a/tools/testing/selftests/mm/check_config.sh +++ b/tools/testing/selftests/mm/check_config.sh @@ -21,11 +21,11 @@ $CC -c $tmpfile_c -o $tmpfile_o >/dev/null 2>&1 if [ -f $tmpfile_o ]; then echo "#define LOCAL_CONFIG_HAVE_LIBURING 1" > $OUTPUT_H_FILE - echo "COW_EXTRA_LIBS = -luring" > $OUTPUT_MKFILE + echo "IOURING_EXTRA_LIBS = -luring" > $OUTPUT_MKFILE else echo "// No liburing support found" > $OUTPUT_H_FILE echo "# No liburing support found, so:" > $OUTPUT_MKFILE - echo "COW_EXTRA_LIBS = " >> $OUTPUT_MKFILE + echo "IOURING_EXTRA_LIBS = " >> $OUTPUT_MKFILE fi rm ${tmpname}.* diff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c index 0eb2e8180aa5..dc9d6fe86028 100644 --- a/tools/testing/selftests/mm/cow.c +++ b/tools/testing/selftests/mm/cow.c @@ -45,34 +45,6 @@ static size_t hugetlbsizes[10]; static int gup_fd; static bool has_huge_zeropage; -static void detect_thpsize(void) -{ - int fd = open("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", - O_RDONLY); - size_t size = 0; - char buf[15]; - int ret; - - if (fd < 0) - return; - - ret = pread(fd, buf, sizeof(buf), 0); - if (ret > 0 && ret < sizeof(buf)) { - buf[ret] = 0; - - size = strtoul(buf, NULL, 10); - if (size < pagesize) - size = 0; - if (size > 0) { - thpsize = size; - ksft_print_msg("[INFO] detected THP size: %zu KiB\n", - thpsize / 1024); - } - } - - close(fd); -} - static void detect_huge_zeropage(void) { int fd = open("/sys/kernel/mm/transparent_hugepage/use_zero_page", @@ -1741,7 +1713,10 @@ int main(int argc, char **argv) int err; pagesize = getpagesize(); - detect_thpsize(); + thpsize = read_pmd_pagesize(); + if (thpsize) + ksft_print_msg("[INFO] detected THP size: %zu KiB\n", + thpsize / 1024); detect_hugetlbsizes(); detect_huge_zeropage(); diff --git a/tools/testing/selftests/mm/gup_test.c b/tools/testing/selftests/mm/gup_test.c index e43879291dac..ec2229136384 100644 --- a/tools/testing/selftests/mm/gup_test.c +++ b/tools/testing/selftests/mm/gup_test.c @@ -12,8 +12,7 @@ #include <assert.h> #include <mm/gup_test.h> #include "../kselftest.h" - -#include "util.h" +#include "vm_util.h" #define MB (1UL << 20) @@ -251,7 +250,7 @@ int main(int argc, char **argv) if (touch) { gup.gup_flags |= FOLL_TOUCH; } else { - for (; (unsigned long)p < gup.addr + size; p += PAGE_SIZE) + for (; (unsigned long)p < gup.addr + size; p += psize()) p[0] = 0; } diff --git a/tools/testing/selftests/mm/hugepage-mremap.c b/tools/testing/selftests/mm/hugepage-mremap.c index e53b5eaa8fce..cabd0084f57b 100644 --- a/tools/testing/selftests/mm/hugepage-mremap.c +++ b/tools/testing/selftests/mm/hugepage-mremap.c @@ -23,6 +23,8 @@ #include <linux/userfaultfd.h> #include <sys/ioctl.h> #include <string.h> +#include <stdbool.h> +#include "vm_util.h" #define DEFAULT_LENGTH_MB 10UL #define MB_TO_BYTES(x) (x * 1024 * 1024) @@ -60,7 +62,6 @@ static void register_region_with_uffd(char *addr, size_t len) { long uffd; /* userfaultfd file descriptor */ struct uffdio_api uffdio_api; - struct uffdio_register uffdio_register; /* Create and enable userfaultfd object. */ @@ -96,11 +97,7 @@ static void register_region_with_uffd(char *addr, size_t len) * handling by the userfaultfd object. In mode, we request to track * missing pages (i.e., pages that have not yet been faulted in). */ - - uffdio_register.range.start = (unsigned long)addr; - uffdio_register.range.len = len; - uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) { + if (uffd_register(uffd, addr, len, true, false, false)) { perror("ioctl-UFFDIO_REGISTER"); exit(1); } diff --git a/tools/testing/selftests/mm/hugetlb-madvise.c b/tools/testing/selftests/mm/hugetlb-madvise.c index 9a127a8fe176..28426e30d9bc 100644 --- a/tools/testing/selftests/mm/hugetlb-madvise.c +++ b/tools/testing/selftests/mm/hugetlb-madvise.c @@ -18,6 +18,7 @@ #include <unistd.h> #include <sys/mman.h> #include <fcntl.h> +#include "vm_util.h" #define MIN_FREE_PAGES 20 #define NR_HUGE_PAGES 10 /* common number of pages to map/allocate */ @@ -35,30 +36,6 @@ unsigned long huge_page_size; unsigned long base_page_size; -/* - * default_huge_page_size copied from mlock2-tests.c - */ -unsigned long default_huge_page_size(void) -{ - unsigned long hps = 0; - char *line = NULL; - size_t linelen = 0; - FILE *f = fopen("/proc/meminfo", "r"); - - if (!f) - return 0; - while (getline(&line, &linelen, f) > 0) { - if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) { - hps <<= 10; - break; - } - } - - free(line); - fclose(f); - return hps; -} - unsigned long get_free_hugepages(void) { unsigned long fhp = 0; diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index 64126c8cd561..97adc0f34f9c 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -1476,6 +1476,10 @@ int main(int argc, const char **argv) page_size = getpagesize(); hpage_pmd_size = read_pmd_pagesize(); + if (!hpage_pmd_size) { + printf("Reading PMD pagesize failed"); + exit(EXIT_FAILURE); + } hpage_pmd_nr = hpage_pmd_size / page_size; default_settings.khugepaged.max_ptes_none = hpage_pmd_nr - 1; diff --git a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c index d8b5b4930412..7bc9fc17c9f0 100644 --- a/tools/testing/selftests/mm/ksm_functional_tests.c +++ b/tools/testing/selftests/mm/ksm_functional_tests.c @@ -15,8 +15,10 @@ #include <errno.h> #include <fcntl.h> #include <sys/mman.h> +#include <sys/prctl.h> #include <sys/syscall.h> #include <sys/ioctl.h> +#include <sys/wait.h> #include <linux/userfaultfd.h> #include "../kselftest.h" @@ -178,7 +180,6 @@ unmap: static void test_unmerge_uffd_wp(void) { struct uffdio_writeprotect uffd_writeprotect; - struct uffdio_register uffdio_register; const unsigned int size = 2 * MiB; struct uffdio_api uffdio_api; char *map; @@ -210,10 +211,7 @@ static void test_unmerge_uffd_wp(void) } /* Register UFFD-WP, no need for an actual handler. */ - uffdio_register.range.start = (unsigned long) map; - uffdio_register.range.len = size; - uffdio_register.mode = UFFDIO_REGISTER_MODE_WP; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) < 0) { + if (uffd_register(uffd, map, size, false, true, false)) { ksft_test_result_fail("UFFDIO_REGISTER_MODE_WP failed\n"); goto close_uffd; } @@ -241,9 +239,93 @@ unmap: } #endif +/* Verify that KSM can be enabled / queried with prctl. */ +static void test_prctl(void) +{ + int ret; + + ksft_print_msg("[RUN] %s\n", __func__); + + ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0); + if (ret < 0 && errno == EINVAL) { + ksft_test_result_skip("PR_SET_MEMORY_MERGE not supported\n"); + return; + } else if (ret) { + ksft_test_result_fail("PR_SET_MEMORY_MERGE=1 failed\n"); + return; + } + + ret = prctl(PR_GET_MEMORY_MERGE, 0, 0, 0, 0); + if (ret < 0) { + ksft_test_result_fail("PR_GET_MEMORY_MERGE failed\n"); + return; + } else if (ret != 1) { + ksft_test_result_fail("PR_SET_MEMORY_MERGE=1 not effective\n"); + return; + } + + ret = prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0); + if (ret) { + ksft_test_result_fail("PR_SET_MEMORY_MERGE=0 failed\n"); + return; + } + + ret = prctl(PR_GET_MEMORY_MERGE, 0, 0, 0, 0); + if (ret < 0) { + ksft_test_result_fail("PR_GET_MEMORY_MERGE failed\n"); + return; + } else if (ret != 0) { + ksft_test_result_fail("PR_SET_MEMORY_MERGE=0 not effective\n"); + return; + } + + ksft_test_result_pass("Setting/clearing PR_SET_MEMORY_MERGE works\n"); +} + +/* Verify that prctl ksm flag is inherited. */ +static void test_prctl_fork(void) +{ + int ret, status; + pid_t child_pid; + + ksft_print_msg("[RUN] %s\n", __func__); + + ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0); + if (ret < 0 && errno == EINVAL) { + ksft_test_result_skip("PR_SET_MEMORY_MERGE not supported\n"); + return; + } else if (ret) { + ksft_test_result_fail("PR_SET_MEMORY_MERGE=1 failed\n"); + return; + } + + child_pid = fork(); + if (!child_pid) { + exit(prctl(PR_GET_MEMORY_MERGE, 0, 0, 0, 0)); + } else if (child_pid < 0) { + ksft_test_result_fail("fork() failed\n"); + return; + } + + if (waitpid(child_pid, &status, 0) < 0) { + ksft_test_result_fail("waitpid() failed\n"); + return; + } else if (WEXITSTATUS(status) != 1) { + ksft_test_result_fail("unexpected PR_GET_MEMORY_MERGE result in child\n"); + return; + } + + if (prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0)) { + ksft_test_result_fail("PR_SET_MEMORY_MERGE=0 failed\n"); + return; + } + + ksft_test_result_pass("PR_SET_MEMORY_MERGE value is inherited\n"); +} + int main(int argc, char **argv) { - unsigned int tests = 2; + unsigned int tests = 4; int err; #ifdef __NR_userfaultfd @@ -271,6 +353,9 @@ int main(int argc, char **argv) test_unmerge_uffd_wp(); #endif + test_prctl(); + test_prctl_fork(); + err = ksft_get_fail_cnt(); if (err) ksft_exit_fail_msg("%d out of %d tests failed\n", diff --git a/tools/testing/selftests/mm/ksm_tests.c b/tools/testing/selftests/mm/ksm_tests.c index f9eb4d67e0dd..435acebdc325 100644 --- a/tools/testing/selftests/mm/ksm_tests.c +++ b/tools/testing/selftests/mm/ksm_tests.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 #include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/wait.h> #include <stdbool.h> #include <time.h> #include <string.h> @@ -12,7 +14,7 @@ #include "../kselftest.h" #include <include/vdso/time64.h> -#include "util.h" +#include "vm_util.h" #define KSM_SYSFS_PATH "/sys/kernel/mm/ksm/" #define KSM_FP(s) (KSM_SYSFS_PATH s) @@ -21,6 +23,7 @@ #define KSM_PROT_STR_DEFAULT "rw" #define KSM_USE_ZERO_PAGES_DEFAULT false #define KSM_MERGE_ACROSS_NODES_DEFAULT true +#define KSM_MERGE_TYPE_DEFAULT 0 #define MB (1ul << 20) struct ksm_sysfs { @@ -33,9 +36,16 @@ struct ksm_sysfs { unsigned long use_zero_pages; }; +enum ksm_merge_type { + KSM_MERGE_MADVISE, + KSM_MERGE_PRCTL, + KSM_MERGE_LAST = KSM_MERGE_PRCTL +}; + enum ksm_test_name { CHECK_KSM_MERGE, CHECK_KSM_UNMERGE, + CHECK_KSM_GET_MERGE_TYPE, CHECK_KSM_ZERO_PAGE_MERGE, CHECK_KSM_NUMA_MERGE, KSM_MERGE_TIME, @@ -44,6 +54,8 @@ enum ksm_test_name { KSM_COW_TIME }; +int debug; + static int ksm_write_sysfs(const char *file_path, unsigned long val) { FILE *f = fopen(file_path, "w"); @@ -82,6 +94,53 @@ static int ksm_read_sysfs(const char *file_path, unsigned long *val) return 0; } +static void ksm_print_sysfs(void) +{ + unsigned long max_page_sharing, pages_sharing, pages_shared; + unsigned long full_scans, pages_unshared, pages_volatile; + unsigned long stable_node_chains, stable_node_dups; + long general_profit; + + if (ksm_read_sysfs(KSM_FP("pages_shared"), &pages_shared) || + ksm_read_sysfs(KSM_FP("pages_sharing"), &pages_sharing) || + ksm_read_sysfs(KSM_FP("max_page_sharing"), &max_page_sharing) || + ksm_read_sysfs(KSM_FP("full_scans"), &full_scans) || + ksm_read_sysfs(KSM_FP("pages_unshared"), &pages_unshared) || + ksm_read_sysfs(KSM_FP("pages_volatile"), &pages_volatile) || + ksm_read_sysfs(KSM_FP("stable_node_chains"), &stable_node_chains) || + ksm_read_sysfs(KSM_FP("stable_node_dups"), &stable_node_dups) || + ksm_read_sysfs(KSM_FP("general_profit"), (unsigned long *)&general_profit)) + return; + + printf("pages_shared : %lu\n", pages_shared); + printf("pages_sharing : %lu\n", pages_sharing); + printf("max_page_sharing : %lu\n", max_page_sharing); + printf("full_scans : %lu\n", full_scans); + printf("pages_unshared : %lu\n", pages_unshared); + printf("pages_volatile : %lu\n", pages_volatile); + printf("stable_node_chains: %lu\n", stable_node_chains); + printf("stable_node_dups : %lu\n", stable_node_dups); + printf("general_profit : %ld\n", general_profit); +} + +static void ksm_print_procfs(void) +{ + const char *file_name = "/proc/self/ksm_stat"; + char buffer[512]; + FILE *f = fopen(file_name, "r"); + + if (!f) { + fprintf(stderr, "f %s\n", file_name); + perror("fopen"); + return; + } + + while (fgets(buffer, sizeof(buffer), f)) + printf("%s", buffer); + + fclose(f); +} + static int str_to_prot(char *prot_str) { int prot = 0; @@ -128,7 +187,12 @@ static void print_help(void) " Default: %d\n", KSM_USE_ZERO_PAGES_DEFAULT); printf(" -m: change merge_across_nodes tunable\n" " Default: %d\n", KSM_MERGE_ACROSS_NODES_DEFAULT); + printf(" -d: turn debugging output on\n"); printf(" -s: the size of duplicated memory area (in MiB)\n"); + printf(" -t: KSM merge type\n" + " Default: 0\n" + " 0: madvise merging\n" + " 1: prctl merging\n"); exit(0); } @@ -176,12 +240,21 @@ static int ksm_do_scan(int scan_count, struct timespec start_time, int timeout) return 0; } -static int ksm_merge_pages(void *addr, size_t size, struct timespec start_time, int timeout) +static int ksm_merge_pages(int merge_type, void *addr, size_t size, + struct timespec start_time, int timeout) { - if (madvise(addr, size, MADV_MERGEABLE)) { - perror("madvise"); - return 1; + if (merge_type == KSM_MERGE_MADVISE) { + if (madvise(addr, size, MADV_MERGEABLE)) { + perror("madvise"); + return 1; + } + } else if (merge_type == KSM_MERGE_PRCTL) { + if (prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0)) { + perror("prctl"); + return 1; + } } + if (ksm_write_sysfs(KSM_FP("run"), 1)) return 1; @@ -211,6 +284,11 @@ static bool assert_ksm_pages_count(long dupl_page_count) ksm_read_sysfs(KSM_FP("max_page_sharing"), &max_page_sharing)) return false; + if (debug) { + ksm_print_sysfs(); + ksm_print_procfs(); + } + /* * Since there must be at least 2 pages for merging and 1 page can be * shared with the limited number of pages (max_page_sharing), sometimes @@ -266,7 +344,8 @@ static int ksm_restore(struct ksm_sysfs *ksm_sysfs) return 0; } -static int check_ksm_merge(int mapping, int prot, long page_count, int timeout, size_t page_size) +static int check_ksm_merge(int merge_type, int mapping, int prot, + long page_count, int timeout, size_t page_size) { void *map_ptr; struct timespec start_time; @@ -281,13 +360,15 @@ static int check_ksm_merge(int mapping, int prot, long page_count, int timeout, if (!map_ptr) return KSFT_FAIL; - if (ksm_merge_pages(map_ptr, page_size * page_count, start_time, timeout)) + if (ksm_merge_pages(merge_type, map_ptr, page_size * page_count, start_time, timeout)) goto err_out; /* verify that the right number of pages are merged */ if (assert_ksm_pages_count(page_count)) { printf("OK\n"); munmap(map_ptr, page_size * page_count); + if (merge_type == KSM_MERGE_PRCTL) + prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0); return KSFT_PASS; } @@ -297,7 +378,7 @@ err_out: return KSFT_FAIL; } -static int check_ksm_unmerge(int mapping, int prot, int timeout, size_t page_size) +static int check_ksm_unmerge(int merge_type, int mapping, int prot, int timeout, size_t page_size) { void *map_ptr; struct timespec start_time; @@ -313,7 +394,7 @@ static int check_ksm_unmerge(int mapping, int prot, int timeout, size_t page_siz if (!map_ptr) return KSFT_FAIL; - if (ksm_merge_pages(map_ptr, page_size * page_count, start_time, timeout)) + if (ksm_merge_pages(merge_type, map_ptr, page_size * page_count, start_time, timeout)) goto err_out; /* change 1 byte in each of the 2 pages -- KSM must automatically unmerge them */ @@ -337,8 +418,8 @@ err_out: return KSFT_FAIL; } -static int check_ksm_zero_page_merge(int mapping, int prot, long page_count, int timeout, - bool use_zero_pages, size_t page_size) +static int check_ksm_zero_page_merge(int merge_type, int mapping, int prot, long page_count, + int timeout, bool use_zero_pages, size_t page_size) { void *map_ptr; struct timespec start_time; @@ -356,7 +437,7 @@ static int check_ksm_zero_page_merge(int mapping, int prot, long page_count, int if (!map_ptr) return KSFT_FAIL; - if (ksm_merge_pages(map_ptr, page_size * page_count, start_time, timeout)) + if (ksm_merge_pages(merge_type, map_ptr, page_size * page_count, start_time, timeout)) goto err_out; /* @@ -402,8 +483,8 @@ static int get_first_mem_node(void) return get_next_mem_node(numa_max_node()); } -static int check_ksm_numa_merge(int mapping, int prot, int timeout, bool merge_across_nodes, - size_t page_size) +static int check_ksm_numa_merge(int merge_type, int mapping, int prot, int timeout, + bool merge_across_nodes, size_t page_size) { void *numa1_map_ptr, *numa2_map_ptr; struct timespec start_time; @@ -439,8 +520,8 @@ static int check_ksm_numa_merge(int mapping, int prot, int timeout, bool merge_a memset(numa2_map_ptr, '*', page_size); /* try to merge the pages */ - if (ksm_merge_pages(numa1_map_ptr, page_size, start_time, timeout) || - ksm_merge_pages(numa2_map_ptr, page_size, start_time, timeout)) + if (ksm_merge_pages(merge_type, numa1_map_ptr, page_size, start_time, timeout) || + ksm_merge_pages(merge_type, numa2_map_ptr, page_size, start_time, timeout)) goto err_out; /* @@ -466,7 +547,8 @@ err_out: return KSFT_FAIL; } -static int ksm_merge_hugepages_time(int mapping, int prot, int timeout, size_t map_size) +static int ksm_merge_hugepages_time(int merge_type, int mapping, int prot, + int timeout, size_t map_size) { void *map_ptr, *map_ptr_orig; struct timespec start_time, end_time; @@ -508,7 +590,7 @@ static int ksm_merge_hugepages_time(int mapping, int prot, int timeout, size_t m perror("clock_gettime"); goto err_out; } - if (ksm_merge_pages(map_ptr, map_size, start_time, timeout)) + if (ksm_merge_pages(merge_type, map_ptr, map_size, start_time, timeout)) goto err_out; if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) { perror("clock_gettime"); @@ -533,7 +615,7 @@ err_out: return KSFT_FAIL; } -static int ksm_merge_time(int mapping, int prot, int timeout, size_t map_size) +static int ksm_merge_time(int merge_type, int mapping, int prot, int timeout, size_t map_size) { void *map_ptr; struct timespec start_time, end_time; @@ -549,7 +631,7 @@ static int ksm_merge_time(int mapping, int prot, int timeout, size_t map_size) perror("clock_gettime"); goto err_out; } - if (ksm_merge_pages(map_ptr, map_size, start_time, timeout)) + if (ksm_merge_pages(merge_type, map_ptr, map_size, start_time, timeout)) goto err_out; if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) { perror("clock_gettime"); @@ -574,7 +656,7 @@ err_out: return KSFT_FAIL; } -static int ksm_unmerge_time(int mapping, int prot, int timeout, size_t map_size) +static int ksm_unmerge_time(int merge_type, int mapping, int prot, int timeout, size_t map_size) { void *map_ptr; struct timespec start_time, end_time; @@ -589,7 +671,7 @@ static int ksm_unmerge_time(int mapping, int prot, int timeout, size_t map_size) perror("clock_gettime"); goto err_out; } - if (ksm_merge_pages(map_ptr, map_size, start_time, timeout)) + if (ksm_merge_pages(merge_type, map_ptr, map_size, start_time, timeout)) goto err_out; if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) { @@ -621,7 +703,7 @@ err_out: return KSFT_FAIL; } -static int ksm_cow_time(int mapping, int prot, int timeout, size_t page_size) +static int ksm_cow_time(int merge_type, int mapping, int prot, int timeout, size_t page_size) { void *map_ptr; struct timespec start_time, end_time; @@ -660,7 +742,7 @@ static int ksm_cow_time(int mapping, int prot, int timeout, size_t page_size) memset(map_ptr + page_size * i, '+', i / 2 + 1); memset(map_ptr + page_size * (i + 1), '+', i / 2 + 1); } - if (ksm_merge_pages(map_ptr, page_size * page_count, start_time, timeout)) + if (ksm_merge_pages(merge_type, map_ptr, page_size * page_count, start_time, timeout)) goto err_out; if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) { @@ -697,6 +779,7 @@ int main(int argc, char *argv[]) int ret, opt; int prot = 0; int ksm_scan_limit_sec = KSM_SCAN_LIMIT_SEC_DEFAULT; + int merge_type = KSM_MERGE_TYPE_DEFAULT; long page_count = KSM_PAGE_COUNT_DEFAULT; size_t page_size = sysconf(_SC_PAGESIZE); struct ksm_sysfs ksm_sysfs_old; @@ -705,7 +788,7 @@ int main(int argc, char *argv[]) bool merge_across_nodes = KSM_MERGE_ACROSS_NODES_DEFAULT; long size_MB = 0; - while ((opt = getopt(argc, argv, "ha:p:l:z:m:s:MUZNPCHD")) != -1) { + while ((opt = getopt(argc, argv, "dha:p:l:z:m:s:t:MUZNPCHD")) != -1) { switch (opt) { case 'a': prot = str_to_prot(optarg); @@ -739,12 +822,26 @@ int main(int argc, char *argv[]) else merge_across_nodes = 1; break; + case 'd': + debug = 1; + break; case 's': size_MB = atoi(optarg); if (size_MB <= 0) { printf("Size must be greater than 0\n"); return KSFT_FAIL; } + case 't': + { + int tmp = atoi(optarg); + + if (tmp < 0 || tmp > KSM_MERGE_LAST) { + printf("Invalid merge type\n"); + return KSFT_FAIL; + } + merge_type = tmp; + } + break; case 'M': break; case 'U': @@ -795,35 +892,36 @@ int main(int argc, char *argv[]) switch (test_name) { case CHECK_KSM_MERGE: - ret = check_ksm_merge(MAP_PRIVATE | MAP_ANONYMOUS, prot, page_count, + ret = check_ksm_merge(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, page_count, ksm_scan_limit_sec, page_size); break; case CHECK_KSM_UNMERGE: - ret = check_ksm_unmerge(MAP_PRIVATE | MAP_ANONYMOUS, prot, ksm_scan_limit_sec, - page_size); + ret = check_ksm_unmerge(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, + ksm_scan_limit_sec, page_size); break; case CHECK_KSM_ZERO_PAGE_MERGE: - ret = check_ksm_zero_page_merge(MAP_PRIVATE | MAP_ANONYMOUS, prot, page_count, - ksm_scan_limit_sec, use_zero_pages, page_size); + ret = check_ksm_zero_page_merge(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, + page_count, ksm_scan_limit_sec, use_zero_pages, + page_size); break; case CHECK_KSM_NUMA_MERGE: - ret = check_ksm_numa_merge(MAP_PRIVATE | MAP_ANONYMOUS, prot, ksm_scan_limit_sec, - merge_across_nodes, page_size); + ret = check_ksm_numa_merge(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, + ksm_scan_limit_sec, merge_across_nodes, page_size); break; case KSM_MERGE_TIME: if (size_MB == 0) { printf("Option '-s' is required.\n"); return KSFT_FAIL; } - ret = ksm_merge_time(MAP_PRIVATE | MAP_ANONYMOUS, prot, ksm_scan_limit_sec, - size_MB); + ret = ksm_merge_time(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, + ksm_scan_limit_sec, size_MB); break; case KSM_MERGE_TIME_HUGE_PAGES: if (size_MB == 0) { printf("Option '-s' is required.\n"); return KSFT_FAIL; } - ret = ksm_merge_hugepages_time(MAP_PRIVATE | MAP_ANONYMOUS, prot, + ret = ksm_merge_hugepages_time(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, ksm_scan_limit_sec, size_MB); break; case KSM_UNMERGE_TIME: @@ -831,12 +929,12 @@ int main(int argc, char *argv[]) printf("Option '-s' is required.\n"); return KSFT_FAIL; } - ret = ksm_unmerge_time(MAP_PRIVATE | MAP_ANONYMOUS, prot, + ret = ksm_unmerge_time(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, ksm_scan_limit_sec, size_MB); break; case KSM_COW_TIME: - ret = ksm_cow_time(MAP_PRIVATE | MAP_ANONYMOUS, prot, ksm_scan_limit_sec, - page_size); + ret = ksm_cow_time(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, + ksm_scan_limit_sec, page_size); break; } diff --git a/tools/testing/selftests/mm/mkdirty.c b/tools/testing/selftests/mm/mkdirty.c new file mode 100644 index 000000000000..6d71d972997b --- /dev/null +++ b/tools/testing/selftests/mm/mkdirty.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test handling of code that might set PTE/PMD dirty in read-only VMAs. + * Setting a PTE/PMD dirty must not accidentally set the PTE/PMD writable. + * + * Copyright 2023, Red Hat, Inc. + * + * Author(s): David Hildenbrand <david@redhat.com> + */ +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include <sys/mman.h> +#include <setjmp.h> +#include <sys/syscall.h> +#include <sys/ioctl.h> +#include <linux/userfaultfd.h> +#include <linux/mempolicy.h> + +#include "../kselftest.h" +#include "vm_util.h" + +static size_t pagesize; +static size_t thpsize; +static int mem_fd; +static int pagemap_fd; +static sigjmp_buf env; + +static void signal_handler(int sig) +{ + if (sig == SIGSEGV) + siglongjmp(env, 1); + siglongjmp(env, 2); +} + +static void do_test_write_sigsegv(char *mem) +{ + char orig = *mem; + int ret; + + if (signal(SIGSEGV, signal_handler) == SIG_ERR) { + ksft_test_result_fail("signal() failed\n"); + return; + } + + ret = sigsetjmp(env, 1); + if (!ret) + *mem = orig + 1; + + if (signal(SIGSEGV, SIG_DFL) == SIG_ERR) + ksft_test_result_fail("signal() failed\n"); + + ksft_test_result(ret == 1 && *mem == orig, + "SIGSEGV generated, page not modified\n"); +} + +static char *mmap_thp_range(int prot, char **_mmap_mem, size_t *_mmap_size) +{ + const size_t mmap_size = 2 * thpsize; + char *mem, *mmap_mem; + + mmap_mem = mmap(NULL, mmap_size, prot, MAP_PRIVATE|MAP_ANON, + -1, 0); + if (mmap_mem == MAP_FAILED) { + ksft_test_result_fail("mmap() failed\n"); + return MAP_FAILED; + } + mem = (char *)(((uintptr_t)mmap_mem + thpsize) & ~(thpsize - 1)); + + if (madvise(mem, thpsize, MADV_HUGEPAGE)) { + ksft_test_result_skip("MADV_HUGEPAGE failed\n"); + munmap(mmap_mem, mmap_size); + return MAP_FAILED; + } + + *_mmap_mem = mmap_mem; + *_mmap_size = mmap_size; + return mem; +} + +static void test_ptrace_write(void) +{ + char data = 1; + char *mem; + int ret; + + ksft_print_msg("[INFO] PTRACE write access\n"); + + mem = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE|MAP_ANON, -1, 0); + if (mem == MAP_FAILED) { + ksft_test_result_fail("mmap() failed\n"); + return; + } + + /* Fault in the shared zeropage. */ + if (*mem != 0) { + ksft_test_result_fail("Memory not zero\n"); + goto munmap; + } + + /* + * Unshare the page (populating a fresh anon page that might be set + * dirty in the PTE) in the read-only VMA using ptrace (FOLL_FORCE). + */ + lseek(mem_fd, (uintptr_t) mem, SEEK_SET); + ret = write(mem_fd, &data, 1); + if (ret != 1 || *mem != data) { + ksft_test_result_fail("write() failed\n"); + goto munmap; + } + + do_test_write_sigsegv(mem); +munmap: + munmap(mem, pagesize); +} + +static void test_ptrace_write_thp(void) +{ + char *mem, *mmap_mem; + size_t mmap_size; + char data = 1; + int ret; + + ksft_print_msg("[INFO] PTRACE write access to THP\n"); + + mem = mmap_thp_range(PROT_READ, &mmap_mem, &mmap_size); + if (mem == MAP_FAILED) + return; + + /* + * Write to the first subpage in the read-only VMA using + * ptrace(FOLL_FORCE), eventually placing a fresh THP that is marked + * dirty in the PMD. + */ + lseek(mem_fd, (uintptr_t) mem, SEEK_SET); + ret = write(mem_fd, &data, 1); + if (ret != 1 || *mem != data) { + ksft_test_result_fail("write() failed\n"); + goto munmap; + } + + /* MM populated a THP if we got the last subpage populated as well. */ + if (!pagemap_is_populated(pagemap_fd, mem + thpsize - pagesize)) { + ksft_test_result_skip("Did not get a THP populated\n"); + goto munmap; + } + + do_test_write_sigsegv(mem); +munmap: + munmap(mmap_mem, mmap_size); +} + +static void test_page_migration(void) +{ + char *mem; + + ksft_print_msg("[INFO] Page migration\n"); + + mem = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, + -1, 0); + if (mem == MAP_FAILED) { + ksft_test_result_fail("mmap() failed\n"); + return; + } + + /* Populate a fresh page and dirty it. */ + memset(mem, 1, pagesize); + if (mprotect(mem, pagesize, PROT_READ)) { + ksft_test_result_fail("mprotect() failed\n"); + goto munmap; + } + + /* Trigger page migration. Might not be available or fail. */ + if (syscall(__NR_mbind, mem, pagesize, MPOL_LOCAL, NULL, 0x7fful, + MPOL_MF_MOVE)) { + ksft_test_result_skip("mbind() failed\n"); + goto munmap; + } + + do_test_write_sigsegv(mem); +munmap: + munmap(mem, pagesize); +} + +static void test_page_migration_thp(void) +{ + char *mem, *mmap_mem; + size_t mmap_size; + + ksft_print_msg("[INFO] Page migration of THP\n"); + + mem = mmap_thp_range(PROT_READ|PROT_WRITE, &mmap_mem, &mmap_size); + if (mem == MAP_FAILED) + return; + + /* + * Write to the first page, which might populate a fresh anon THP + * and dirty it. + */ + memset(mem, 1, pagesize); + if (mprotect(mem, thpsize, PROT_READ)) { + ksft_test_result_fail("mprotect() failed\n"); + goto munmap; + } + + /* MM populated a THP if we got the last subpage populated as well. */ + if (!pagemap_is_populated(pagemap_fd, mem + thpsize - pagesize)) { + ksft_test_result_skip("Did not get a THP populated\n"); + goto munmap; + } + + /* Trigger page migration. Might not be available or fail. */ + if (syscall(__NR_mbind, mem, thpsize, MPOL_LOCAL, NULL, 0x7fful, + MPOL_MF_MOVE)) { + ksft_test_result_skip("mbind() failed\n"); + goto munmap; + } + + do_test_write_sigsegv(mem); +munmap: + munmap(mmap_mem, mmap_size); +} + +static void test_pte_mapped_thp(void) +{ + char *mem, *mmap_mem; + size_t mmap_size; + + ksft_print_msg("[INFO] PTE-mapping a THP\n"); + + mem = mmap_thp_range(PROT_READ|PROT_WRITE, &mmap_mem, &mmap_size); + if (mem == MAP_FAILED) + return; + + /* + * Write to the first page, which might populate a fresh anon THP + * and dirty it. + */ + memset(mem, 1, pagesize); + if (mprotect(mem, thpsize, PROT_READ)) { + ksft_test_result_fail("mprotect() failed\n"); + goto munmap; + } + + /* MM populated a THP if we got the last subpage populated as well. */ + if (!pagemap_is_populated(pagemap_fd, mem + thpsize - pagesize)) { + ksft_test_result_skip("Did not get a THP populated\n"); + goto munmap; + } + + /* Trigger PTE-mapping the THP by mprotect'ing the last subpage. */ + if (mprotect(mem + thpsize - pagesize, pagesize, + PROT_READ|PROT_WRITE)) { + ksft_test_result_fail("mprotect() failed\n"); + goto munmap; + } + + do_test_write_sigsegv(mem); +munmap: + munmap(mmap_mem, mmap_size); +} + +#ifdef __NR_userfaultfd +static void test_uffdio_copy(void) +{ + struct uffdio_register uffdio_register; + struct uffdio_copy uffdio_copy; + struct uffdio_api uffdio_api; + char *dst, *src; + int uffd; + + ksft_print_msg("[INFO] UFFDIO_COPY\n"); + + src = malloc(pagesize); + memset(src, 1, pagesize); + dst = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE|MAP_ANON, -1, 0); + if (dst == MAP_FAILED) { + ksft_test_result_fail("mmap() failed\n"); + return; + } + + uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); + if (uffd < 0) { + ksft_test_result_skip("__NR_userfaultfd failed\n"); + goto munmap; + } + + uffdio_api.api = UFFD_API; + uffdio_api.features = 0; + if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) { + ksft_test_result_fail("UFFDIO_API failed\n"); + goto close_uffd; + } + + uffdio_register.range.start = (unsigned long) dst; + uffdio_register.range.len = pagesize; + uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; + if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) { + ksft_test_result_fail("UFFDIO_REGISTER failed\n"); + goto close_uffd; + } + + /* Place a page in a read-only VMA, which might set the PTE dirty. */ + uffdio_copy.dst = (unsigned long) dst; + uffdio_copy.src = (unsigned long) src; + uffdio_copy.len = pagesize; + uffdio_copy.mode = 0; + if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy)) { + ksft_test_result_fail("UFFDIO_COPY failed\n"); + goto close_uffd; + } + + do_test_write_sigsegv(dst); +close_uffd: + close(uffd); +munmap: + munmap(dst, pagesize); + free(src); +#endif /* __NR_userfaultfd */ +} + +int main(void) +{ + int err, tests = 2; + + pagesize = getpagesize(); + thpsize = read_pmd_pagesize(); + if (thpsize) { + ksft_print_msg("[INFO] detected THP size: %zu KiB\n", + thpsize / 1024); + tests += 3; + } +#ifdef __NR_userfaultfd + tests += 1; +#endif /* __NR_userfaultfd */ + + ksft_print_header(); + ksft_set_plan(tests); + + mem_fd = open("/proc/self/mem", O_RDWR); + if (mem_fd < 0) + ksft_exit_fail_msg("opening /proc/self/mem failed\n"); + pagemap_fd = open("/proc/self/pagemap", O_RDONLY); + if (pagemap_fd < 0) + ksft_exit_fail_msg("opening /proc/self/pagemap failed\n"); + + /* + * On some ptrace(FOLL_FORCE) write access via /proc/self/mem in + * read-only VMAs, the kernel may set the PTE/PMD dirty. + */ + test_ptrace_write(); + if (thpsize) + test_ptrace_write_thp(); + /* + * On page migration, the kernel may set the PTE/PMD dirty when + * remapping the page. + */ + test_page_migration(); + if (thpsize) + test_page_migration_thp(); + /* PTE-mapping a THP might propagate the dirty PMD bit to the PTEs. */ + if (thpsize) + test_pte_mapped_thp(); + /* Placing a fresh page via userfaultfd may set the PTE dirty. */ +#ifdef __NR_userfaultfd + test_uffdio_copy(); +#endif /* __NR_userfaultfd */ + + err = ksft_get_fail_cnt(); + if (err) + ksft_exit_fail_msg("%d out of %d tests failed\n", + err, ksft_test_num()); + return ksft_exit_pass(); +} diff --git a/tools/testing/selftests/mm/mrelease_test.c b/tools/testing/selftests/mm/mrelease_test.c index 6c62966ab5db..37b6d33b9e84 100644 --- a/tools/testing/selftests/mm/mrelease_test.c +++ b/tools/testing/selftests/mm/mrelease_test.c @@ -9,8 +9,7 @@ #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> - -#include "util.h" +#include "vm_util.h" #include "../kselftest.h" @@ -32,7 +31,7 @@ static int alloc_noexit(unsigned long nr_pages, int pipefd) unsigned long i; char *buf; - buf = (char *)mmap(NULL, nr_pages * PAGE_SIZE, PROT_READ | PROT_WRITE, + buf = (char *)mmap(NULL, nr_pages * psize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 0, 0); if (buf == MAP_FAILED) { perror("mmap failed, halting the test"); @@ -40,7 +39,7 @@ static int alloc_noexit(unsigned long nr_pages, int pipefd) } for (i = 0; i < nr_pages; i++) - *((unsigned long *)(buf + (i * PAGE_SIZE))) = i; + *((unsigned long *)(buf + (i * psize()))) = i; /* Signal the parent that the child is ready */ if (write(pipefd, "", 1) < 0) { @@ -54,7 +53,7 @@ static int alloc_noexit(unsigned long nr_pages, int pipefd) timeout--; } - munmap(buf, nr_pages * PAGE_SIZE); + munmap(buf, nr_pages * psize()); return (timeout > 0) ? KSFT_PASS : KSFT_FAIL; } @@ -87,7 +86,7 @@ static int child_main(int pipefd[], size_t size) /* Allocate and fault-in memory and wait to be killed */ close(pipefd[0]); - res = alloc_noexit(MB(size) / PAGE_SIZE, pipefd[1]); + res = alloc_noexit(MB(size) / psize(), pipefd[1]); close(pipefd[1]); return res; } diff --git a/tools/testing/selftests/mm/protection_keys.c b/tools/testing/selftests/mm/protection_keys.c index 95f403a0c46d..0381c34fdd56 100644 --- a/tools/testing/selftests/mm/protection_keys.c +++ b/tools/testing/selftests/mm/protection_keys.c @@ -98,7 +98,7 @@ int tracing_root_ok(void) void tracing_on(void) { #if CONTROL_TRACING > 0 -#define TRACEDIR "/sys/kernel/debug/tracing" +#define TRACEDIR "/sys/kernel/tracing" char pidstr[32]; if (!tracing_root_ok()) @@ -124,7 +124,7 @@ void tracing_off(void) #if CONTROL_TRACING > 0 if (!tracing_root_ok()) return; - cat_into_file("0", "/sys/kernel/debug/tracing/tracing_on"); + cat_into_file("0", "/sys/kernel/tracing/tracing_on"); #endif } diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh index 8984e0bb58c7..4893eb60d96d 100644 --- a/tools/testing/selftests/mm/run_vmtests.sh +++ b/tools/testing/selftests/mm/run_vmtests.sh @@ -5,6 +5,9 @@ # Kselftest framework requirement - SKIP code is 4. ksft_skip=4 +count_pass=0 +count_fail=0 +count_skip=0 exitcode=0 usage() { @@ -132,7 +135,7 @@ else fi # filter 64bit architectures -ARCH64STR="arm64 ia64 mips64 parisc64 ppc64 ppc64le riscv64 s390x sh64 sparc64 x86_64" +ARCH64STR="arm64 ia64 mips64 parisc64 ppc64 ppc64le riscv64 s390x sparc64 x86_64" if [ -z "$ARCH" ]; then ARCH=$(uname -m 2>/dev/null | sed -e 's/aarch64.*/arm64/') fi @@ -149,11 +152,14 @@ run_test() { "$@" local ret=$? if [ $ret -eq 0 ]; then + count_pass=$(( count_pass + 1 )) echo "[PASS]" elif [ $ret -eq $ksft_skip ]; then + count_skip=$(( count_skip + 1 )) echo "[SKIP]" exitcode=$ksft_skip else + count_fail=$(( count_fail + 1 )) echo "[FAIL]" exitcode=1 fi @@ -190,15 +196,15 @@ CATEGORY="gup_test" run_test ./gup_test -a # Dump pages 0, 19, and 4096, using pin_user_pages: CATEGORY="gup_test" run_test ./gup_test -ct -F 0x1 0 19 0x1000 -uffd_mods=("" ":dev") -for mod in "${uffd_mods[@]}"; do - CATEGORY="userfaultfd" run_test ./userfaultfd anon${mod} 20 16 - # Hugetlb tests require source and destination huge pages. Pass in half - # the size ($half_ufd_size_MB), which is used for *each*. - CATEGORY="userfaultfd" run_test ./userfaultfd hugetlb${mod} "$half_ufd_size_MB" 32 - CATEGORY="userfaultfd" run_test ./userfaultfd hugetlb_shared${mod} "$half_ufd_size_MB" 32 - CATEGORY="userfaultfd" run_test ./userfaultfd shmem${mod} 20 16 -done +CATEGORY="userfaultfd" run_test ./uffd-unit-tests +uffd_stress_bin=./uffd-stress +CATEGORY="userfaultfd" run_test ${uffd_stress_bin} anon 20 16 +# Hugetlb tests require source and destination huge pages. Pass in half +# the size ($half_ufd_size_MB), which is used for *each*. +CATEGORY="userfaultfd" run_test ${uffd_stress_bin} hugetlb "$half_ufd_size_MB" 32 +CATEGORY="userfaultfd" run_test ${uffd_stress_bin} hugetlb-private "$half_ufd_size_MB" 32 +CATEGORY="userfaultfd" run_test ${uffd_stress_bin} shmem 20 16 +CATEGORY="userfaultfd" run_test ${uffd_stress_bin} shmem-private 20 16 #cleanup echo "$nr_hugepgs" > /proc/sys/vm/nr_hugepages @@ -220,10 +226,26 @@ CATEGORY="mremap" run_test ./mremap_test CATEGORY="hugetlb" run_test ./thuge-gen if [ $VADDR64 -ne 0 ]; then + + # set overcommit_policy as OVERCOMMIT_ALWAYS so that kernel + # allows high virtual address allocation requests independent + # of platform's physical memory. + + prev_policy=$(cat /proc/sys/vm/overcommit_memory) + echo 1 > /proc/sys/vm/overcommit_memory CATEGORY="hugevm" run_test ./virtual_address_range + echo $prev_policy > /proc/sys/vm/overcommit_memory - # virtual address 128TB switch test - CATEGORY="hugevm" run_test ./va_128TBswitch.sh + # va high address boundary switch test + ARCH_ARM64="arm64" + prev_nr_hugepages=$(cat /proc/sys/vm/nr_hugepages) + if [ "$ARCH" == "$ARCH_ARM64" ]; then + echo 6 > /proc/sys/vm/nr_hugepages + fi + CATEGORY="hugevm" run_test ./va_high_addr_switch.sh + if [ "$ARCH" == "$ARCH_ARM64" ]; then + echo $prev_nr_hugepages > /proc/sys/vm/nr_hugepages + fi fi # VADDR64 # vmalloc stability smoke test @@ -271,4 +293,6 @@ CATEGORY="soft_dirty" run_test ./soft-dirty # COW tests CATEGORY="cow" run_test ./cow +echo "SUMMARY: PASS=${count_pass} SKIP=${count_skip} FAIL=${count_fail}" + exit $exitcode diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c index 21d8830c5f24..cc5f144430d4 100644 --- a/tools/testing/selftests/mm/soft-dirty.c +++ b/tools/testing/selftests/mm/soft-dirty.c @@ -80,6 +80,9 @@ static void test_hugepage(int pagemap_fd, int pagesize) int i, ret; size_t hpage_len = read_pmd_pagesize(); + if (!hpage_len) + ksft_exit_fail_msg("Reading PMD pagesize failed"); + map = memalign(hpage_len, hpage_len); if (!map) ksft_exit_fail_msg("memalign failed\n"); diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c index 76e1c36dd9e5..0e74635c8c3d 100644 --- a/tools/testing/selftests/mm/split_huge_page_test.c +++ b/tools/testing/selftests/mm/split_huge_page_test.c @@ -106,7 +106,7 @@ void split_pmd_thp(void) for (i = 0; i < len; i++) one_page[i] = (char)i; - if (!check_huge_anon(one_page, 1, pmd_pagesize)) { + if (!check_huge_anon(one_page, 4, pmd_pagesize)) { printf("No THP is allocated\n"); exit(EXIT_FAILURE); } @@ -122,7 +122,7 @@ void split_pmd_thp(void) } - if (check_huge_anon(one_page, 0, pmd_pagesize)) { + if (!check_huge_anon(one_page, 0, pmd_pagesize)) { printf("Still AnonHugePages not split\n"); exit(EXIT_FAILURE); } @@ -169,7 +169,7 @@ void split_pte_mapped_thp(void) for (i = 0; i < len; i++) one_page[i] = (char)i; - if (!check_huge_anon(one_page, 1, pmd_pagesize)) { + if (!check_huge_anon(one_page, 4, pmd_pagesize)) { printf("No THP is allocated\n"); exit(EXIT_FAILURE); } @@ -300,6 +300,10 @@ int main(int argc, char **argv) pagesize = getpagesize(); pageshift = ffs(pagesize) - 1; pmd_pagesize = read_pmd_pagesize(); + if (!pmd_pagesize) { + printf("Reading PMD pagesize failed\n"); + exit(EXIT_FAILURE); + } split_pmd_thp(); split_pte_mapped_thp(); diff --git a/tools/testing/selftests/mm/thuge-gen.c b/tools/testing/selftests/mm/thuge-gen.c index 361ef7192cc6..380ab5f0a534 100644 --- a/tools/testing/selftests/mm/thuge-gen.c +++ b/tools/testing/selftests/mm/thuge-gen.c @@ -24,6 +24,7 @@ #include <unistd.h> #include <stdarg.h> #include <string.h> +#include "vm_util.h" #define err(x) perror(x), exit(1) @@ -74,24 +75,6 @@ void find_pagesizes(void) globfree(&g); } -unsigned long default_huge_page_size(void) -{ - unsigned long hps = 0; - char *line = NULL; - size_t linelen = 0; - FILE *f = fopen("/proc/meminfo", "r"); - if (!f) - return 0; - while (getline(&line, &linelen, f) > 0) { - if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) { - hps <<= 10; - break; - } - } - free(line); - return hps; -} - void show(unsigned long ps) { char buf[100]; diff --git a/tools/testing/selftests/mm/transhuge-stress.c b/tools/testing/selftests/mm/transhuge-stress.c index e3f00adb1b82..ba9d37ad3a89 100644 --- a/tools/testing/selftests/mm/transhuge-stress.c +++ b/tools/testing/selftests/mm/transhuge-stress.c @@ -15,7 +15,7 @@ #include <fcntl.h> #include <string.h> #include <sys/mman.h> -#include "util.h" +#include "vm_util.h" int backing_fd = -1; int mmap_flags = MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE; @@ -34,10 +34,10 @@ int main(int argc, char **argv) int pagemap_fd; ram = sysconf(_SC_PHYS_PAGES); - if (ram > SIZE_MAX / sysconf(_SC_PAGESIZE) / 4) + if (ram > SIZE_MAX / psize() / 4) ram = SIZE_MAX / 4; else - ram *= sysconf(_SC_PAGESIZE); + ram *= psize(); len = ram; while (++i < argc) { @@ -58,7 +58,7 @@ int main(int argc, char **argv) warnx("allocate %zd transhuge pages, using %zd MiB virtual memory" " and %zd MiB of ram", len >> HPAGE_SHIFT, len >> 20, - ram >> (20 + HPAGE_SHIFT - PAGE_SHIFT - 1)); + ram >> (20 + HPAGE_SHIFT - pshift() - 1)); pagemap_fd = open("/proc/self/pagemap", O_RDONLY); if (pagemap_fd < 0) @@ -92,7 +92,7 @@ int main(int argc, char **argv) if (pfn < 0) { nr_failed++; } else { - size_t idx = pfn >> (HPAGE_SHIFT - PAGE_SHIFT); + size_t idx = pfn >> (HPAGE_SHIFT - pshift()); nr_succeed++; if (idx >= map_len) { @@ -108,7 +108,7 @@ int main(int argc, char **argv) } /* split transhuge page, keep last page */ - if (madvise(p, HPAGE_SIZE - PAGE_SIZE, MADV_DONTNEED)) + if (madvise(p, HPAGE_SIZE - psize(), MADV_DONTNEED)) err(2, "MADV_DONTNEED"); } clock_gettime(CLOCK_MONOTONIC, &b); diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selftests/mm/uffd-common.c new file mode 100644 index 000000000000..61c6250adf93 --- /dev/null +++ b/tools/testing/selftests/mm/uffd-common.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Userfaultfd tests util functions + * + * Copyright (C) 2015-2023 Red Hat, Inc. + */ + +#include "uffd-common.h" + +#define BASE_PMD_ADDR ((void *)(1UL << 30)) + +volatile bool test_uffdio_copy_eexist = true; +unsigned long nr_cpus, nr_pages, nr_pages_per_cpu, page_size; +char *area_src, *area_src_alias, *area_dst, *area_dst_alias, *area_remap; +int uffd = -1, uffd_flags, finished, *pipefd, test_type; +bool map_shared; +bool test_uffdio_wp = true; +unsigned long long *count_verify; +uffd_test_ops_t *uffd_test_ops; + +static int uffd_mem_fd_create(off_t mem_size, bool hugetlb) +{ + unsigned int memfd_flags = 0; + int mem_fd; + + if (hugetlb) + memfd_flags = MFD_HUGETLB; + mem_fd = memfd_create("uffd-test", memfd_flags); + if (mem_fd < 0) + err("memfd_create"); + if (ftruncate(mem_fd, mem_size)) + err("ftruncate"); + if (fallocate(mem_fd, + FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, + mem_size)) + err("fallocate"); + + return mem_fd; +} + +static void anon_release_pages(char *rel_area) +{ + if (madvise(rel_area, nr_pages * page_size, MADV_DONTNEED)) + err("madvise(MADV_DONTNEED) failed"); +} + +static int anon_allocate_area(void **alloc_area, bool is_src) +{ + *alloc_area = mmap(NULL, nr_pages * page_size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (*alloc_area == MAP_FAILED) { + *alloc_area = NULL; + return -errno; + } + return 0; +} + +static void noop_alias_mapping(__u64 *start, size_t len, unsigned long offset) +{ +} + +static void hugetlb_release_pages(char *rel_area) +{ + if (!map_shared) { + if (madvise(rel_area, nr_pages * page_size, MADV_DONTNEED)) + err("madvise(MADV_DONTNEED) failed"); + } else { + if (madvise(rel_area, nr_pages * page_size, MADV_REMOVE)) + err("madvise(MADV_REMOVE) failed"); + } +} + +static int hugetlb_allocate_area(void **alloc_area, bool is_src) +{ + off_t size = nr_pages * page_size; + off_t offset = is_src ? 0 : size; + void *area_alias = NULL; + char **alloc_area_alias; + int mem_fd = uffd_mem_fd_create(size * 2, true); + + *alloc_area = mmap(NULL, size, PROT_READ | PROT_WRITE, + (map_shared ? MAP_SHARED : MAP_PRIVATE) | + (is_src ? 0 : MAP_NORESERVE), + mem_fd, offset); + if (*alloc_area == MAP_FAILED) { + *alloc_area = NULL; + return -errno; + } + + if (map_shared) { + area_alias = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_SHARED, mem_fd, offset); + if (area_alias == MAP_FAILED) + return -errno; + } + + if (is_src) { + alloc_area_alias = &area_src_alias; + } else { + alloc_area_alias = &area_dst_alias; + } + if (area_alias) + *alloc_area_alias = area_alias; + + close(mem_fd); + return 0; +} + +static void hugetlb_alias_mapping(__u64 *start, size_t len, unsigned long offset) +{ + if (!map_shared) + return; + + *start = (unsigned long) area_dst_alias + offset; +} + +static void shmem_release_pages(char *rel_area) +{ + if (madvise(rel_area, nr_pages * page_size, MADV_REMOVE)) + err("madvise(MADV_REMOVE) failed"); +} + +static int shmem_allocate_area(void **alloc_area, bool is_src) +{ + void *area_alias = NULL; + size_t bytes = nr_pages * page_size, hpage_size = read_pmd_pagesize(); + unsigned long offset = is_src ? 0 : bytes; + char *p = NULL, *p_alias = NULL; + int mem_fd = uffd_mem_fd_create(bytes * 2, false); + + /* TODO: clean this up. Use a static addr is ugly */ + p = BASE_PMD_ADDR; + if (!is_src) + /* src map + alias + interleaved hpages */ + p += 2 * (bytes + hpage_size); + p_alias = p; + p_alias += bytes; + p_alias += hpage_size; /* Prevent src/dst VMA merge */ + + *alloc_area = mmap(p, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, + mem_fd, offset); + if (*alloc_area == MAP_FAILED) { + *alloc_area = NULL; + return -errno; + } + if (*alloc_area != p) + err("mmap of memfd failed at %p", p); + + area_alias = mmap(p_alias, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, + mem_fd, offset); + if (area_alias == MAP_FAILED) { + munmap(*alloc_area, bytes); + *alloc_area = NULL; + return -errno; + } + if (area_alias != p_alias) + err("mmap of anonymous memory failed at %p", p_alias); + + if (is_src) + area_src_alias = area_alias; + else + area_dst_alias = area_alias; + + close(mem_fd); + return 0; +} + +static void shmem_alias_mapping(__u64 *start, size_t len, unsigned long offset) +{ + *start = (unsigned long)area_dst_alias + offset; +} + +static void shmem_check_pmd_mapping(void *p, int expect_nr_hpages) +{ + if (!check_huge_shmem(area_dst_alias, expect_nr_hpages, + read_pmd_pagesize())) + err("Did not find expected %d number of hugepages", + expect_nr_hpages); +} + +struct uffd_test_ops anon_uffd_test_ops = { + .allocate_area = anon_allocate_area, + .release_pages = anon_release_pages, + .alias_mapping = noop_alias_mapping, + .check_pmd_mapping = NULL, +}; + +struct uffd_test_ops shmem_uffd_test_ops = { + .allocate_area = shmem_allocate_area, + .release_pages = shmem_release_pages, + .alias_mapping = shmem_alias_mapping, + .check_pmd_mapping = shmem_check_pmd_mapping, +}; + +struct uffd_test_ops hugetlb_uffd_test_ops = { + .allocate_area = hugetlb_allocate_area, + .release_pages = hugetlb_release_pages, + .alias_mapping = hugetlb_alias_mapping, + .check_pmd_mapping = NULL, +}; + +void uffd_stats_report(struct uffd_args *args, int n_cpus) +{ + int i; + unsigned long long miss_total = 0, wp_total = 0, minor_total = 0; + + for (i = 0; i < n_cpus; i++) { + miss_total += args[i].missing_faults; + wp_total += args[i].wp_faults; + minor_total += args[i].minor_faults; + } + + printf("userfaults: "); + if (miss_total) { + printf("%llu missing (", miss_total); + for (i = 0; i < n_cpus; i++) + printf("%lu+", args[i].missing_faults); + printf("\b) "); + } + if (wp_total) { + printf("%llu wp (", wp_total); + for (i = 0; i < n_cpus; i++) + printf("%lu+", args[i].wp_faults); + printf("\b) "); + } + if (minor_total) { + printf("%llu minor (", minor_total); + for (i = 0; i < n_cpus; i++) + printf("%lu+", args[i].minor_faults); + printf("\b)"); + } + printf("\n"); +} + +int userfaultfd_open(uint64_t *features) +{ + struct uffdio_api uffdio_api; + + uffd = uffd_open(UFFD_FLAGS); + if (uffd < 0) + return -1; + uffd_flags = fcntl(uffd, F_GETFD, NULL); + + uffdio_api.api = UFFD_API; + uffdio_api.features = *features; + if (ioctl(uffd, UFFDIO_API, &uffdio_api)) + /* Probably lack of CAP_PTRACE? */ + return -1; + if (uffdio_api.api != UFFD_API) + err("UFFDIO_API error: %" PRIu64, (uint64_t)uffdio_api.api); + + *features = uffdio_api.features; + return 0; +} + +static inline void munmap_area(void **area) +{ + if (*area) + if (munmap(*area, nr_pages * page_size)) + err("munmap"); + + *area = NULL; +} + +static void uffd_test_ctx_clear(void) +{ + size_t i; + + if (pipefd) { + for (i = 0; i < nr_cpus * 2; ++i) { + if (close(pipefd[i])) + err("close pipefd"); + } + free(pipefd); + pipefd = NULL; + } + + if (count_verify) { + free(count_verify); + count_verify = NULL; + } + + if (uffd != -1) { + if (close(uffd)) + err("close uffd"); + uffd = -1; + } + + munmap_area((void **)&area_src); + munmap_area((void **)&area_src_alias); + munmap_area((void **)&area_dst); + munmap_area((void **)&area_dst_alias); + munmap_area((void **)&area_remap); +} + +int uffd_test_ctx_init(uint64_t features, const char **errmsg) +{ + unsigned long nr, cpu; + int ret; + + uffd_test_ctx_clear(); + + ret = uffd_test_ops->allocate_area((void **)&area_src, true); + ret |= uffd_test_ops->allocate_area((void **)&area_dst, false); + if (ret) { + if (errmsg) + *errmsg = "memory allocation failed"; + return ret; + } + + ret = userfaultfd_open(&features); + if (ret) { + if (errmsg) + *errmsg = "possible lack of priviledge"; + return ret; + } + + count_verify = malloc(nr_pages * sizeof(unsigned long long)); + if (!count_verify) + err("count_verify"); + + for (nr = 0; nr < nr_pages; nr++) { + *area_mutex(area_src, nr) = + (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + count_verify[nr] = *area_count(area_src, nr) = 1; + /* + * In the transition between 255 to 256, powerpc will + * read out of order in my_bcmp and see both bytes as + * zero, so leave a placeholder below always non-zero + * after the count, to avoid my_bcmp to trigger false + * positives. + */ + *(area_count(area_src, nr) + 1) = 1; + } + + /* + * After initialization of area_src, we must explicitly release pages + * for area_dst to make sure it's fully empty. Otherwise we could have + * some area_dst pages be errornously initialized with zero pages, + * hence we could hit memory corruption later in the test. + * + * One example is when THP is globally enabled, above allocate_area() + * calls could have the two areas merged into a single VMA (as they + * will have the same VMA flags so they're mergeable). When we + * initialize the area_src above, it's possible that some part of + * area_dst could have been faulted in via one huge THP that will be + * shared between area_src and area_dst. It could cause some of the + * area_dst won't be trapped by missing userfaults. + * + * This release_pages() will guarantee even if that happened, we'll + * proactively split the thp and drop any accidentally initialized + * pages within area_dst. + */ + uffd_test_ops->release_pages(area_dst); + + pipefd = malloc(sizeof(int) * nr_cpus * 2); + if (!pipefd) + err("pipefd"); + for (cpu = 0; cpu < nr_cpus; cpu++) + if (pipe2(&pipefd[cpu * 2], O_CLOEXEC | O_NONBLOCK)) + err("pipe"); + + return 0; +} + +void wp_range(int ufd, __u64 start, __u64 len, bool wp) +{ + struct uffdio_writeprotect prms; + + /* Write protection page faults */ + prms.range.start = start; + prms.range.len = len; + /* Undo write-protect, do wakeup after that */ + prms.mode = wp ? UFFDIO_WRITEPROTECT_MODE_WP : 0; + + if (ioctl(ufd, UFFDIO_WRITEPROTECT, &prms)) + err("clear WP failed: address=0x%"PRIx64, (uint64_t)start); +} + +static void continue_range(int ufd, __u64 start, __u64 len, bool wp) +{ + struct uffdio_continue req; + int ret; + + req.range.start = start; + req.range.len = len; + req.mode = 0; + if (wp) + req.mode |= UFFDIO_CONTINUE_MODE_WP; + + if (ioctl(ufd, UFFDIO_CONTINUE, &req)) + err("UFFDIO_CONTINUE failed for address 0x%" PRIx64, + (uint64_t)start); + + /* + * Error handling within the kernel for continue is subtly different + * from copy or zeropage, so it may be a source of bugs. Trigger an + * error (-EEXIST) on purpose, to verify doing so doesn't cause a BUG. + */ + req.mapped = 0; + ret = ioctl(ufd, UFFDIO_CONTINUE, &req); + if (ret >= 0 || req.mapped != -EEXIST) + err("failed to exercise UFFDIO_CONTINUE error handling, ret=%d, mapped=%" PRId64, + ret, (int64_t) req.mapped); +} + +int uffd_read_msg(int ufd, struct uffd_msg *msg) +{ + int ret = read(uffd, msg, sizeof(*msg)); + + if (ret != sizeof(*msg)) { + if (ret < 0) { + if (errno == EAGAIN || errno == EINTR) + return 1; + err("blocking read error"); + } else { + err("short read"); + } + } + + return 0; +} + +void uffd_handle_page_fault(struct uffd_msg *msg, struct uffd_args *args) +{ + unsigned long offset; + + if (msg->event != UFFD_EVENT_PAGEFAULT) + err("unexpected msg event %u", msg->event); + + if (msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) { + /* Write protect page faults */ + wp_range(uffd, msg->arg.pagefault.address, page_size, false); + args->wp_faults++; + } else if (msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_MINOR) { + uint8_t *area; + int b; + + /* + * Minor page faults + * + * To prove we can modify the original range for testing + * purposes, we're going to bit flip this range before + * continuing. + * + * Note that this requires all minor page fault tests operate on + * area_dst (non-UFFD-registered) and area_dst_alias + * (UFFD-registered). + */ + + area = (uint8_t *)(area_dst + + ((char *)msg->arg.pagefault.address - + area_dst_alias)); + for (b = 0; b < page_size; ++b) + area[b] = ~area[b]; + continue_range(uffd, msg->arg.pagefault.address, page_size, + args->apply_wp); + args->minor_faults++; + } else { + /* + * Missing page faults. + * + * Here we force a write check for each of the missing mode + * faults. It's guaranteed because the only threads that + * will trigger uffd faults are the locking threads, and + * their first instruction to touch the missing page will + * always be pthread_mutex_lock(). + * + * Note that here we relied on an NPTL glibc impl detail to + * always read the lock type at the entry of the lock op + * (pthread_mutex_t.__data.__type, offset 0x10) before + * doing any locking operations to guarantee that. It's + * actually not good to rely on this impl detail because + * logically a pthread-compatible lib can implement the + * locks without types and we can fail when linking with + * them. However since we used to find bugs with this + * strict check we still keep it around. Hopefully this + * could be a good hint when it fails again. If one day + * it'll break on some other impl of glibc we'll revisit. + */ + if (msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE) + err("unexpected write fault"); + + offset = (char *)(unsigned long)msg->arg.pagefault.address - area_dst; + offset &= ~(page_size-1); + + if (copy_page(uffd, offset, args->apply_wp)) + args->missing_faults++; + } +} + +void *uffd_poll_thread(void *arg) +{ + struct uffd_args *args = (struct uffd_args *)arg; + unsigned long cpu = args->cpu; + struct pollfd pollfd[2]; + struct uffd_msg msg; + struct uffdio_register uffd_reg; + int ret; + char tmp_chr; + + pollfd[0].fd = uffd; + pollfd[0].events = POLLIN; + pollfd[1].fd = pipefd[cpu*2]; + pollfd[1].events = POLLIN; + + for (;;) { + ret = poll(pollfd, 2, -1); + if (ret <= 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + err("poll error: %d", ret); + } + if (pollfd[1].revents) { + if (!(pollfd[1].revents & POLLIN)) + err("pollfd[1].revents %d", pollfd[1].revents); + if (read(pollfd[1].fd, &tmp_chr, 1) != 1) + err("read pipefd error"); + break; + } + if (!(pollfd[0].revents & POLLIN)) + err("pollfd[0].revents %d", pollfd[0].revents); + if (uffd_read_msg(uffd, &msg)) + continue; + switch (msg.event) { + default: + err("unexpected msg event %u\n", msg.event); + break; + case UFFD_EVENT_PAGEFAULT: + uffd_handle_page_fault(&msg, args); + break; + case UFFD_EVENT_FORK: + close(uffd); + uffd = msg.arg.fork.ufd; + pollfd[0].fd = uffd; + break; + case UFFD_EVENT_REMOVE: + uffd_reg.range.start = msg.arg.remove.start; + uffd_reg.range.len = msg.arg.remove.end - + msg.arg.remove.start; + if (ioctl(uffd, UFFDIO_UNREGISTER, &uffd_reg.range)) + err("remove failure"); + break; + case UFFD_EVENT_REMAP: + area_remap = area_dst; /* save for later unmap */ + area_dst = (char *)(unsigned long)msg.arg.remap.to; + break; + } + } + + return NULL; +} + +static void retry_copy_page(int ufd, struct uffdio_copy *uffdio_copy, + unsigned long offset) +{ + uffd_test_ops->alias_mapping(&uffdio_copy->dst, + uffdio_copy->len, + offset); + if (ioctl(ufd, UFFDIO_COPY, uffdio_copy)) { + /* real retval in ufdio_copy.copy */ + if (uffdio_copy->copy != -EEXIST) + err("UFFDIO_COPY retry error: %"PRId64, + (int64_t)uffdio_copy->copy); + } else { + err("UFFDIO_COPY retry unexpected: %"PRId64, + (int64_t)uffdio_copy->copy); + } +} + +static void wake_range(int ufd, unsigned long addr, unsigned long len) +{ + struct uffdio_range uffdio_wake; + + uffdio_wake.start = addr; + uffdio_wake.len = len; + + if (ioctl(ufd, UFFDIO_WAKE, &uffdio_wake)) + fprintf(stderr, "error waking %lu\n", + addr), exit(1); +} + +int __copy_page(int ufd, unsigned long offset, bool retry, bool wp) +{ + struct uffdio_copy uffdio_copy; + + if (offset >= nr_pages * page_size) + err("unexpected offset %lu\n", offset); + uffdio_copy.dst = (unsigned long) area_dst + offset; + uffdio_copy.src = (unsigned long) area_src + offset; + uffdio_copy.len = page_size; + if (wp) + uffdio_copy.mode = UFFDIO_COPY_MODE_WP; + else + uffdio_copy.mode = 0; + uffdio_copy.copy = 0; + if (ioctl(ufd, UFFDIO_COPY, &uffdio_copy)) { + /* real retval in ufdio_copy.copy */ + if (uffdio_copy.copy != -EEXIST) + err("UFFDIO_COPY error: %"PRId64, + (int64_t)uffdio_copy.copy); + wake_range(ufd, uffdio_copy.dst, page_size); + } else if (uffdio_copy.copy != page_size) { + err("UFFDIO_COPY error: %"PRId64, (int64_t)uffdio_copy.copy); + } else { + if (test_uffdio_copy_eexist && retry) { + test_uffdio_copy_eexist = false; + retry_copy_page(ufd, &uffdio_copy, offset); + } + return 1; + } + return 0; +} + +int copy_page(int ufd, unsigned long offset, bool wp) +{ + return __copy_page(ufd, offset, false, wp); +} diff --git a/tools/testing/selftests/mm/uffd-common.h b/tools/testing/selftests/mm/uffd-common.h new file mode 100644 index 000000000000..6068f2346b86 --- /dev/null +++ b/tools/testing/selftests/mm/uffd-common.h @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Userfaultfd tests common header + * + * Copyright (C) 2015-2023 Red Hat, Inc. + */ +#ifndef __UFFD_COMMON_H__ +#define __UFFD_COMMON_H__ + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> +#include <signal.h> +#include <poll.h> +#include <string.h> +#include <linux/mman.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <pthread.h> +#include <linux/userfaultfd.h> +#include <setjmp.h> +#include <stdbool.h> +#include <assert.h> +#include <inttypes.h> +#include <stdint.h> +#include <sys/random.h> + +#include "../kselftest.h" +#include "vm_util.h" + +#define UFFD_FLAGS (O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY) + +#define _err(fmt, ...) \ + do { \ + int ret = errno; \ + fprintf(stderr, "ERROR: " fmt, ##__VA_ARGS__); \ + fprintf(stderr, " (errno=%d, @%s:%d)\n", \ + ret, __FILE__, __LINE__); \ + } while (0) + +#define errexit(exitcode, fmt, ...) \ + do { \ + _err(fmt, ##__VA_ARGS__); \ + exit(exitcode); \ + } while (0) + +#define err(fmt, ...) errexit(1, fmt, ##__VA_ARGS__) + +/* pthread_mutex_t starts at page offset 0 */ +#define area_mutex(___area, ___nr) \ + ((pthread_mutex_t *) ((___area) + (___nr)*page_size)) +/* + * count is placed in the page after pthread_mutex_t naturally aligned + * to avoid non alignment faults on non-x86 archs. + */ +#define area_count(___area, ___nr) \ + ((volatile unsigned long long *) ((unsigned long) \ + ((___area) + (___nr)*page_size + \ + sizeof(pthread_mutex_t) + \ + sizeof(unsigned long long) - 1) & \ + ~(unsigned long)(sizeof(unsigned long long) \ + - 1))) + +/* Userfaultfd test statistics */ +struct uffd_args { + int cpu; + /* Whether apply wr-protects when installing pages */ + bool apply_wp; + unsigned long missing_faults; + unsigned long wp_faults; + unsigned long minor_faults; +}; + +struct uffd_test_ops { + int (*allocate_area)(void **alloc_area, bool is_src); + void (*release_pages)(char *rel_area); + void (*alias_mapping)(__u64 *start, size_t len, unsigned long offset); + void (*check_pmd_mapping)(void *p, int expect_nr_hpages); +}; +typedef struct uffd_test_ops uffd_test_ops_t; + +extern unsigned long nr_cpus, nr_pages, nr_pages_per_cpu, page_size; +extern char *area_src, *area_src_alias, *area_dst, *area_dst_alias, *area_remap; +extern int uffd, uffd_flags, finished, *pipefd, test_type; +extern bool map_shared; +extern bool test_uffdio_wp; +extern unsigned long long *count_verify; +extern volatile bool test_uffdio_copy_eexist; + +extern uffd_test_ops_t anon_uffd_test_ops; +extern uffd_test_ops_t shmem_uffd_test_ops; +extern uffd_test_ops_t hugetlb_uffd_test_ops; +extern uffd_test_ops_t *uffd_test_ops; + +void uffd_stats_report(struct uffd_args *args, int n_cpus); +int uffd_test_ctx_init(uint64_t features, const char **errmsg); +int userfaultfd_open(uint64_t *features); +int uffd_read_msg(int ufd, struct uffd_msg *msg); +void wp_range(int ufd, __u64 start, __u64 len, bool wp); +void uffd_handle_page_fault(struct uffd_msg *msg, struct uffd_args *args); +int __copy_page(int ufd, unsigned long offset, bool retry, bool wp); +int copy_page(int ufd, unsigned long offset, bool wp); +void *uffd_poll_thread(void *arg); + +#define TEST_ANON 1 +#define TEST_HUGETLB 2 +#define TEST_SHMEM 3 + +#endif diff --git a/tools/testing/selftests/mm/uffd-stress.c b/tools/testing/selftests/mm/uffd-stress.c new file mode 100644 index 000000000000..f1ad9eef1c3a --- /dev/null +++ b/tools/testing/selftests/mm/uffd-stress.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Stress userfaultfd syscall. + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This test allocates two virtual areas and bounces the physical + * memory across the two virtual areas (from area_src to area_dst) + * using userfaultfd. + * + * There are three threads running per CPU: + * + * 1) one per-CPU thread takes a per-page pthread_mutex in a random + * page of the area_dst (while the physical page may still be in + * area_src), and increments a per-page counter in the same page, + * and checks its value against a verification region. + * + * 2) another per-CPU thread handles the userfaults generated by + * thread 1 above. userfaultfd blocking reads or poll() modes are + * exercised interleaved. + * + * 3) one last per-CPU thread transfers the memory in the background + * at maximum bandwidth (if not already transferred by thread + * 2). Each cpu thread takes cares of transferring a portion of the + * area. + * + * When all threads of type 3 completed the transfer, one bounce is + * complete. area_src and area_dst are then swapped. All threads are + * respawned and so the bounce is immediately restarted in the + * opposite direction. + * + * per-CPU threads 1 by triggering userfaults inside + * pthread_mutex_lock will also verify the atomicity of the memory + * transfer (UFFDIO_COPY). + */ + +#include "uffd-common.h" + +#ifdef __NR_userfaultfd + +#define BOUNCE_RANDOM (1<<0) +#define BOUNCE_RACINGFAULTS (1<<1) +#define BOUNCE_VERIFY (1<<2) +#define BOUNCE_POLL (1<<3) +static int bounces; + +/* exercise the test_uffdio_*_eexist every ALARM_INTERVAL_SECS */ +#define ALARM_INTERVAL_SECS 10 +static char *zeropage; +pthread_attr_t attr; + +#define swap(a, b) \ + do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0) + +const char *examples = + "# Run anonymous memory test on 100MiB region with 99999 bounces:\n" + "./userfaultfd anon 100 99999\n\n" + "# Run share memory test on 1GiB region with 99 bounces:\n" + "./userfaultfd shmem 1000 99\n\n" + "# Run hugetlb memory test on 256MiB region with 50 bounces:\n" + "./userfaultfd hugetlb 256 50\n\n" + "# Run the same hugetlb test but using private file:\n" + "./userfaultfd hugetlb-private 256 50\n\n" + "# 10MiB-~6GiB 999 bounces anonymous test, " + "continue forever unless an error triggers\n" + "while ./userfaultfd anon $[RANDOM % 6000 + 10] 999; do true; done\n\n"; + +static void usage(void) +{ + fprintf(stderr, "\nUsage: ./userfaultfd <test type> <MiB> <bounces>\n\n"); + fprintf(stderr, "Supported <test type>: anon, hugetlb, " + "hugetlb-private, shmem, shmem-private\n\n"); + fprintf(stderr, "Examples:\n\n"); + fprintf(stderr, "%s", examples); + exit(1); +} + +static void uffd_stats_reset(struct uffd_args *args, unsigned long n_cpus) +{ + int i; + + for (i = 0; i < n_cpus; i++) { + args[i].cpu = i; + args[i].apply_wp = test_uffdio_wp; + args[i].missing_faults = 0; + args[i].wp_faults = 0; + args[i].minor_faults = 0; + } +} + +static inline uint64_t uffd_minor_feature(void) +{ + if (test_type == TEST_HUGETLB && map_shared) + return UFFD_FEATURE_MINOR_HUGETLBFS; + else if (test_type == TEST_SHMEM) + return UFFD_FEATURE_MINOR_SHMEM; + else + return 0; +} + +static void *locking_thread(void *arg) +{ + unsigned long cpu = (unsigned long) arg; + unsigned long page_nr; + unsigned long long count; + + if (!(bounces & BOUNCE_RANDOM)) { + page_nr = -bounces; + if (!(bounces & BOUNCE_RACINGFAULTS)) + page_nr += cpu * nr_pages_per_cpu; + } + + while (!finished) { + if (bounces & BOUNCE_RANDOM) { + if (getrandom(&page_nr, sizeof(page_nr), 0) != sizeof(page_nr)) + err("getrandom failed"); + } else + page_nr += 1; + page_nr %= nr_pages; + pthread_mutex_lock(area_mutex(area_dst, page_nr)); + count = *area_count(area_dst, page_nr); + if (count != count_verify[page_nr]) + err("page_nr %lu memory corruption %llu %llu", + page_nr, count, count_verify[page_nr]); + count++; + *area_count(area_dst, page_nr) = count_verify[page_nr] = count; + pthread_mutex_unlock(area_mutex(area_dst, page_nr)); + } + + return NULL; +} + +static int copy_page_retry(int ufd, unsigned long offset) +{ + return __copy_page(ufd, offset, true, test_uffdio_wp); +} + +pthread_mutex_t uffd_read_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void *uffd_read_thread(void *arg) +{ + struct uffd_args *args = (struct uffd_args *)arg; + struct uffd_msg msg; + + pthread_mutex_unlock(&uffd_read_mutex); + /* from here cancellation is ok */ + + for (;;) { + if (uffd_read_msg(uffd, &msg)) + continue; + uffd_handle_page_fault(&msg, args); + } + + return NULL; +} + +static void *background_thread(void *arg) +{ + unsigned long cpu = (unsigned long) arg; + unsigned long page_nr, start_nr, mid_nr, end_nr; + + start_nr = cpu * nr_pages_per_cpu; + end_nr = (cpu+1) * nr_pages_per_cpu; + mid_nr = (start_nr + end_nr) / 2; + + /* Copy the first half of the pages */ + for (page_nr = start_nr; page_nr < mid_nr; page_nr++) + copy_page_retry(uffd, page_nr * page_size); + + /* + * If we need to test uffd-wp, set it up now. Then we'll have + * at least the first half of the pages mapped already which + * can be write-protected for testing + */ + if (test_uffdio_wp) + wp_range(uffd, (unsigned long)area_dst + start_nr * page_size, + nr_pages_per_cpu * page_size, true); + + /* + * Continue the 2nd half of the page copying, handling write + * protection faults if any + */ + for (page_nr = mid_nr; page_nr < end_nr; page_nr++) + copy_page_retry(uffd, page_nr * page_size); + + return NULL; +} + +static int stress(struct uffd_args *args) +{ + unsigned long cpu; + pthread_t locking_threads[nr_cpus]; + pthread_t uffd_threads[nr_cpus]; + pthread_t background_threads[nr_cpus]; + + finished = 0; + for (cpu = 0; cpu < nr_cpus; cpu++) { + if (pthread_create(&locking_threads[cpu], &attr, + locking_thread, (void *)cpu)) + return 1; + if (bounces & BOUNCE_POLL) { + if (pthread_create(&uffd_threads[cpu], &attr, + uffd_poll_thread, + (void *)&args[cpu])) + return 1; + } else { + if (pthread_create(&uffd_threads[cpu], &attr, + uffd_read_thread, + (void *)&args[cpu])) + return 1; + pthread_mutex_lock(&uffd_read_mutex); + } + if (pthread_create(&background_threads[cpu], &attr, + background_thread, (void *)cpu)) + return 1; + } + for (cpu = 0; cpu < nr_cpus; cpu++) + if (pthread_join(background_threads[cpu], NULL)) + return 1; + + /* + * Be strict and immediately zap area_src, the whole area has + * been transferred already by the background treads. The + * area_src could then be faulted in a racy way by still + * running uffdio_threads reading zeropages after we zapped + * area_src (but they're guaranteed to get -EEXIST from + * UFFDIO_COPY without writing zero pages into area_dst + * because the background threads already completed). + */ + uffd_test_ops->release_pages(area_src); + + finished = 1; + for (cpu = 0; cpu < nr_cpus; cpu++) + if (pthread_join(locking_threads[cpu], NULL)) + return 1; + + for (cpu = 0; cpu < nr_cpus; cpu++) { + char c; + if (bounces & BOUNCE_POLL) { + if (write(pipefd[cpu*2+1], &c, 1) != 1) + err("pipefd write error"); + if (pthread_join(uffd_threads[cpu], + (void *)&args[cpu])) + return 1; + } else { + if (pthread_cancel(uffd_threads[cpu])) + return 1; + if (pthread_join(uffd_threads[cpu], NULL)) + return 1; + } + } + + return 0; +} + +static int userfaultfd_stress(void) +{ + void *area; + unsigned long nr; + struct uffd_args args[nr_cpus]; + uint64_t mem_size = nr_pages * page_size; + + if (uffd_test_ctx_init(UFFD_FEATURE_WP_UNPOPULATED, NULL)) + err("context init failed"); + + if (posix_memalign(&area, page_size, page_size)) + err("out of memory"); + zeropage = area; + bzero(zeropage, page_size); + + pthread_mutex_lock(&uffd_read_mutex); + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 16*1024*1024); + + while (bounces--) { + printf("bounces: %d, mode:", bounces); + if (bounces & BOUNCE_RANDOM) + printf(" rnd"); + if (bounces & BOUNCE_RACINGFAULTS) + printf(" racing"); + if (bounces & BOUNCE_VERIFY) + printf(" ver"); + if (bounces & BOUNCE_POLL) + printf(" poll"); + else + printf(" read"); + printf(", "); + fflush(stdout); + + if (bounces & BOUNCE_POLL) + fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK); + else + fcntl(uffd, F_SETFL, uffd_flags & ~O_NONBLOCK); + + /* register */ + if (uffd_register(uffd, area_dst, mem_size, + true, test_uffdio_wp, false)) + err("register failure"); + + if (area_dst_alias) { + if (uffd_register(uffd, area_dst_alias, mem_size, + true, test_uffdio_wp, false)) + err("register failure alias"); + } + + /* + * The madvise done previously isn't enough: some + * uffd_thread could have read userfaults (one of + * those already resolved by the background thread) + * and it may be in the process of calling + * UFFDIO_COPY. UFFDIO_COPY will read the zapped + * area_src and it would map a zero page in it (of + * course such a UFFDIO_COPY is perfectly safe as it'd + * return -EEXIST). The problem comes at the next + * bounce though: that racing UFFDIO_COPY would + * generate zeropages in the area_src, so invalidating + * the previous MADV_DONTNEED. Without this additional + * MADV_DONTNEED those zeropages leftovers in the + * area_src would lead to -EEXIST failure during the + * next bounce, effectively leaving a zeropage in the + * area_dst. + * + * Try to comment this out madvise to see the memory + * corruption being caught pretty quick. + * + * khugepaged is also inhibited to collapse THP after + * MADV_DONTNEED only after the UFFDIO_REGISTER, so it's + * required to MADV_DONTNEED here. + */ + uffd_test_ops->release_pages(area_dst); + + uffd_stats_reset(args, nr_cpus); + + /* bounce pass */ + if (stress(args)) + return 1; + + /* Clear all the write protections if there is any */ + if (test_uffdio_wp) + wp_range(uffd, (unsigned long)area_dst, + nr_pages * page_size, false); + + /* unregister */ + if (uffd_unregister(uffd, area_dst, mem_size)) + err("unregister failure"); + if (area_dst_alias) { + if (uffd_unregister(uffd, area_dst_alias, mem_size)) + err("unregister failure alias"); + } + + /* verification */ + if (bounces & BOUNCE_VERIFY) + for (nr = 0; nr < nr_pages; nr++) + if (*area_count(area_dst, nr) != count_verify[nr]) + err("error area_count %llu %llu %lu\n", + *area_count(area_src, nr), + count_verify[nr], nr); + + /* prepare next bounce */ + swap(area_src, area_dst); + + swap(area_src_alias, area_dst_alias); + + uffd_stats_report(args, nr_cpus); + } + + return 0; +} + +static void set_test_type(const char *type) +{ + if (!strcmp(type, "anon")) { + test_type = TEST_ANON; + uffd_test_ops = &anon_uffd_test_ops; + } else if (!strcmp(type, "hugetlb")) { + test_type = TEST_HUGETLB; + uffd_test_ops = &hugetlb_uffd_test_ops; + map_shared = true; + } else if (!strcmp(type, "hugetlb-private")) { + test_type = TEST_HUGETLB; + uffd_test_ops = &hugetlb_uffd_test_ops; + } else if (!strcmp(type, "shmem")) { + map_shared = true; + test_type = TEST_SHMEM; + uffd_test_ops = &shmem_uffd_test_ops; + } else if (!strcmp(type, "shmem-private")) { + test_type = TEST_SHMEM; + uffd_test_ops = &shmem_uffd_test_ops; + } +} + +static void parse_test_type_arg(const char *raw_type) +{ + uint64_t features = UFFD_API_FEATURES; + + set_test_type(raw_type); + + if (!test_type) + err("failed to parse test type argument: '%s'", raw_type); + + if (test_type == TEST_HUGETLB) + page_size = default_huge_page_size(); + else + page_size = sysconf(_SC_PAGE_SIZE); + + if (!page_size) + err("Unable to determine page size"); + if ((unsigned long) area_count(NULL, 0) + sizeof(unsigned long long) * 2 + > page_size) + err("Impossible to run this test"); + + /* + * Whether we can test certain features depends not just on test type, + * but also on whether or not this particular kernel supports the + * feature. + */ + + if (userfaultfd_open(&features)) + err("Userfaultfd open failed"); + + test_uffdio_wp = test_uffdio_wp && + (features & UFFD_FEATURE_PAGEFAULT_FLAG_WP); + + close(uffd); + uffd = -1; +} + +static void sigalrm(int sig) +{ + if (sig != SIGALRM) + abort(); + test_uffdio_copy_eexist = true; + alarm(ALARM_INTERVAL_SECS); +} + +int main(int argc, char **argv) +{ + size_t bytes; + + if (argc < 4) + usage(); + + if (signal(SIGALRM, sigalrm) == SIG_ERR) + err("failed to arm SIGALRM"); + alarm(ALARM_INTERVAL_SECS); + + parse_test_type_arg(argv[1]); + bytes = atol(argv[2]) * 1024 * 1024; + + nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); + + nr_pages_per_cpu = bytes / page_size / nr_cpus; + if (!nr_pages_per_cpu) { + _err("invalid MiB"); + usage(); + } + + bounces = atoi(argv[3]); + if (bounces <= 0) { + _err("invalid bounces"); + usage(); + } + nr_pages = nr_pages_per_cpu * nr_cpus; + + printf("nr_pages: %lu, nr_pages_per_cpu: %lu\n", + nr_pages, nr_pages_per_cpu); + return userfaultfd_stress(); +} + +#else /* __NR_userfaultfd */ + +#warning "missing __NR_userfaultfd definition" + +int main(void) +{ + printf("skip: Skipping userfaultfd test (missing __NR_userfaultfd)\n"); + return KSFT_SKIP; +} + +#endif /* __NR_userfaultfd */ diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c new file mode 100644 index 000000000000..269c86768a02 --- /dev/null +++ b/tools/testing/selftests/mm/uffd-unit-tests.c @@ -0,0 +1,1228 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Userfaultfd unit tests. + * + * Copyright (C) 2015-2023 Red Hat, Inc. + */ + +#include "uffd-common.h" + +#include "../../../../mm/gup_test.h" + +#ifdef __NR_userfaultfd + +/* The unit test doesn't need a large or random size, make it 32MB for now */ +#define UFFD_TEST_MEM_SIZE (32UL << 20) + +#define MEM_ANON BIT_ULL(0) +#define MEM_SHMEM BIT_ULL(1) +#define MEM_SHMEM_PRIVATE BIT_ULL(2) +#define MEM_HUGETLB BIT_ULL(3) +#define MEM_HUGETLB_PRIVATE BIT_ULL(4) + +#define MEM_ALL (MEM_ANON | MEM_SHMEM | MEM_SHMEM_PRIVATE | \ + MEM_HUGETLB | MEM_HUGETLB_PRIVATE) + +struct mem_type { + const char *name; + unsigned int mem_flag; + uffd_test_ops_t *mem_ops; + bool shared; +}; +typedef struct mem_type mem_type_t; + +mem_type_t mem_types[] = { + { + .name = "anon", + .mem_flag = MEM_ANON, + .mem_ops = &anon_uffd_test_ops, + .shared = false, + }, + { + .name = "shmem", + .mem_flag = MEM_SHMEM, + .mem_ops = &shmem_uffd_test_ops, + .shared = true, + }, + { + .name = "shmem-private", + .mem_flag = MEM_SHMEM_PRIVATE, + .mem_ops = &shmem_uffd_test_ops, + .shared = false, + }, + { + .name = "hugetlb", + .mem_flag = MEM_HUGETLB, + .mem_ops = &hugetlb_uffd_test_ops, + .shared = true, + }, + { + .name = "hugetlb-private", + .mem_flag = MEM_HUGETLB_PRIVATE, + .mem_ops = &hugetlb_uffd_test_ops, + .shared = false, + }, +}; + +/* Arguments to be passed over to each uffd unit test */ +struct uffd_test_args { + mem_type_t *mem_type; +}; +typedef struct uffd_test_args uffd_test_args_t; + +/* Returns: UFFD_TEST_* */ +typedef void (*uffd_test_fn)(uffd_test_args_t *); + +typedef struct { + const char *name; + uffd_test_fn uffd_fn; + unsigned int mem_targets; + uint64_t uffd_feature_required; +} uffd_test_case_t; + +static void uffd_test_report(void) +{ + printf("Userfaults unit tests: pass=%u, skip=%u, fail=%u (total=%u)\n", + ksft_get_pass_cnt(), + ksft_get_xskip_cnt(), + ksft_get_fail_cnt(), + ksft_test_num()); +} + +static void uffd_test_pass(void) +{ + printf("done\n"); + ksft_inc_pass_cnt(); +} + +#define uffd_test_start(...) do { \ + printf("Testing "); \ + printf(__VA_ARGS__); \ + printf("... "); \ + fflush(stdout); \ + } while (0) + +#define uffd_test_fail(...) do { \ + printf("failed [reason: "); \ + printf(__VA_ARGS__); \ + printf("]\n"); \ + ksft_inc_fail_cnt(); \ + } while (0) + +#define uffd_test_skip(...) do { \ + printf("skipped [reason: "); \ + printf(__VA_ARGS__); \ + printf("]\n"); \ + ksft_inc_xskip_cnt(); \ + } while (0) + +/* + * Returns 1 if specific userfaultfd supported, 0 otherwise. Note, we'll + * return 1 even if some test failed as long as uffd supported, because in + * that case we still want to proceed with the rest uffd unit tests. + */ +static int test_uffd_api(bool use_dev) +{ + struct uffdio_api uffdio_api; + int uffd; + + uffd_test_start("UFFDIO_API (with %s)", + use_dev ? "/dev/userfaultfd" : "syscall"); + + if (use_dev) + uffd = uffd_open_dev(UFFD_FLAGS); + else + uffd = uffd_open_sys(UFFD_FLAGS); + if (uffd < 0) { + uffd_test_skip("cannot open userfaultfd handle"); + return 0; + } + + /* Test wrong UFFD_API */ + uffdio_api.api = 0xab; + uffdio_api.features = 0; + if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) { + uffd_test_fail("UFFDIO_API should fail with wrong api but didn't"); + goto out; + } + + /* Test wrong feature bit */ + uffdio_api.api = UFFD_API; + uffdio_api.features = BIT_ULL(63); + if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) { + uffd_test_fail("UFFDIO_API should fail with wrong feature but didn't"); + goto out; + } + + /* Test normal UFFDIO_API */ + uffdio_api.api = UFFD_API; + uffdio_api.features = 0; + if (ioctl(uffd, UFFDIO_API, &uffdio_api)) { + uffd_test_fail("UFFDIO_API should succeed but failed"); + goto out; + } + + /* Test double requests of UFFDIO_API with a random feature set */ + uffdio_api.features = BIT_ULL(0); + if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) { + uffd_test_fail("UFFDIO_API should reject initialized uffd"); + goto out; + } + + uffd_test_pass(); +out: + close(uffd); + /* We have a valid uffd handle */ + return 1; +} + +/* + * This function initializes the global variables. TODO: remove global + * vars and then remove this. + */ +static int +uffd_setup_environment(uffd_test_args_t *args, uffd_test_case_t *test, + mem_type_t *mem_type, const char **errmsg) +{ + map_shared = mem_type->shared; + uffd_test_ops = mem_type->mem_ops; + + if (mem_type->mem_flag & (MEM_HUGETLB_PRIVATE | MEM_HUGETLB)) + page_size = default_huge_page_size(); + else + page_size = psize(); + + nr_pages = UFFD_TEST_MEM_SIZE / page_size; + /* TODO: remove this global var.. it's so ugly */ + nr_cpus = 1; + + /* Initialize test arguments */ + args->mem_type = mem_type; + + return uffd_test_ctx_init(test->uffd_feature_required, errmsg); +} + +static bool uffd_feature_supported(uffd_test_case_t *test) +{ + uint64_t features; + + if (uffd_get_features(&features)) + return false; + + return (features & test->uffd_feature_required) == + test->uffd_feature_required; +} + +static int pagemap_open(void) +{ + int fd = open("/proc/self/pagemap", O_RDONLY); + + if (fd < 0) + err("open pagemap"); + + return fd; +} + +/* This macro let __LINE__ works in err() */ +#define pagemap_check_wp(value, wp) do { \ + if (!!(value & PM_UFFD_WP) != wp) \ + err("pagemap uffd-wp bit error: 0x%"PRIx64, value); \ + } while (0) + +typedef struct { + int parent_uffd, child_uffd; +} fork_event_args; + +static void *fork_event_consumer(void *data) +{ + fork_event_args *args = data; + struct uffd_msg msg = { 0 }; + + /* Read until a full msg received */ + while (uffd_read_msg(args->parent_uffd, &msg)); + + if (msg.event != UFFD_EVENT_FORK) + err("wrong message: %u\n", msg.event); + + /* Just to be properly freed later */ + args->child_uffd = msg.arg.fork.ufd; + return NULL; +} + +typedef struct { + int gup_fd; + bool pinned; +} pin_args; + +/* + * Returns 0 if succeed, <0 for errors. pin_pages() needs to be paired + * with unpin_pages(). Currently it needs to be RO longterm pin to satisfy + * all needs of the test cases (e.g., trigger unshare, trigger fork() early + * CoW, etc.). + */ +static int pin_pages(pin_args *args, void *buffer, size_t size) +{ + struct pin_longterm_test test = { + .addr = (uintptr_t)buffer, + .size = size, + /* Read-only pins */ + .flags = 0, + }; + + if (args->pinned) + err("already pinned"); + + args->gup_fd = open("/sys/kernel/debug/gup_test", O_RDWR); + if (args->gup_fd < 0) + return -errno; + + if (ioctl(args->gup_fd, PIN_LONGTERM_TEST_START, &test)) { + /* Even if gup_test existed, can be an old gup_test / kernel */ + close(args->gup_fd); + return -errno; + } + args->pinned = true; + return 0; +} + +static void unpin_pages(pin_args *args) +{ + if (!args->pinned) + err("unpin without pin first"); + if (ioctl(args->gup_fd, PIN_LONGTERM_TEST_STOP)) + err("PIN_LONGTERM_TEST_STOP"); + close(args->gup_fd); + args->pinned = false; +} + +static int pagemap_test_fork(int uffd, bool with_event, bool test_pin) +{ + fork_event_args args = { .parent_uffd = uffd, .child_uffd = -1 }; + pthread_t thread; + pid_t child; + uint64_t value; + int fd, result; + + /* Prepare a thread to resolve EVENT_FORK */ + if (with_event) { + if (pthread_create(&thread, NULL, fork_event_consumer, &args)) + err("pthread_create()"); + } + + child = fork(); + if (!child) { + /* Open the pagemap fd of the child itself */ + pin_args args = {}; + + fd = pagemap_open(); + + if (test_pin && pin_pages(&args, area_dst, page_size)) + /* + * Normally when reach here we have pinned in + * previous tests, so shouldn't fail anymore + */ + err("pin page failed in child"); + + value = pagemap_get_entry(fd, area_dst); + /* + * After fork(), we should handle uffd-wp bit differently: + * + * (1) when with EVENT_FORK, it should persist + * (2) when without EVENT_FORK, it should be dropped + */ + pagemap_check_wp(value, with_event); + if (test_pin) + unpin_pages(&args); + /* Succeed */ + exit(0); + } + waitpid(child, &result, 0); + + if (with_event) { + if (pthread_join(thread, NULL)) + err("pthread_join()"); + if (args.child_uffd < 0) + err("Didn't receive child uffd"); + close(args.child_uffd); + } + + return result; +} + +static void uffd_wp_unpopulated_test(uffd_test_args_t *args) +{ + uint64_t value; + int pagemap_fd; + + if (uffd_register(uffd, area_dst, nr_pages * page_size, + false, true, false)) + err("register failed"); + + pagemap_fd = pagemap_open(); + + /* Test applying pte marker to anon unpopulated */ + wp_range(uffd, (uint64_t)area_dst, page_size, true); + value = pagemap_get_entry(pagemap_fd, area_dst); + pagemap_check_wp(value, true); + + /* Test unprotect on anon pte marker */ + wp_range(uffd, (uint64_t)area_dst, page_size, false); + value = pagemap_get_entry(pagemap_fd, area_dst); + pagemap_check_wp(value, false); + + /* Test zap on anon marker */ + wp_range(uffd, (uint64_t)area_dst, page_size, true); + if (madvise(area_dst, page_size, MADV_DONTNEED)) + err("madvise(MADV_DONTNEED) failed"); + value = pagemap_get_entry(pagemap_fd, area_dst); + pagemap_check_wp(value, false); + + /* Test fault in after marker removed */ + *area_dst = 1; + value = pagemap_get_entry(pagemap_fd, area_dst); + pagemap_check_wp(value, false); + /* Drop it to make pte none again */ + if (madvise(area_dst, page_size, MADV_DONTNEED)) + err("madvise(MADV_DONTNEED) failed"); + + /* Test read-zero-page upon pte marker */ + wp_range(uffd, (uint64_t)area_dst, page_size, true); + *(volatile char *)area_dst; + /* Drop it to make pte none again */ + if (madvise(area_dst, page_size, MADV_DONTNEED)) + err("madvise(MADV_DONTNEED) failed"); + + uffd_test_pass(); +} + +static void uffd_wp_fork_test_common(uffd_test_args_t *args, + bool with_event) +{ + int pagemap_fd; + uint64_t value; + + if (uffd_register(uffd, area_dst, nr_pages * page_size, + false, true, false)) + err("register failed"); + + pagemap_fd = pagemap_open(); + + /* Touch the page */ + *area_dst = 1; + wp_range(uffd, (uint64_t)area_dst, page_size, true); + value = pagemap_get_entry(pagemap_fd, area_dst); + pagemap_check_wp(value, true); + if (pagemap_test_fork(uffd, with_event, false)) { + uffd_test_fail("Detected %s uffd-wp bit in child in present pte", + with_event ? "missing" : "stall"); + goto out; + } + + /* + * This is an attempt for zapping the pgtable so as to test the + * markers. + * + * For private mappings, PAGEOUT will only work on exclusive ptes + * (PM_MMAP_EXCLUSIVE) which we should satisfy. + * + * For shared, PAGEOUT may not work. Use DONTNEED instead which + * plays a similar role of zapping (rather than freeing the page) + * to expose pte markers. + */ + if (args->mem_type->shared) { + if (madvise(area_dst, page_size, MADV_DONTNEED)) + err("MADV_DONTNEED"); + } else { + /* + * NOTE: ignore retval because private-hugetlb doesn't yet + * support swapping, so it could fail. + */ + madvise(area_dst, page_size, MADV_PAGEOUT); + } + + /* Uffd-wp should persist even swapped out */ + value = pagemap_get_entry(pagemap_fd, area_dst); + pagemap_check_wp(value, true); + if (pagemap_test_fork(uffd, with_event, false)) { + uffd_test_fail("Detected %s uffd-wp bit in child in zapped pte", + with_event ? "missing" : "stall"); + goto out; + } + + /* Unprotect; this tests swap pte modifications */ + wp_range(uffd, (uint64_t)area_dst, page_size, false); + value = pagemap_get_entry(pagemap_fd, area_dst); + pagemap_check_wp(value, false); + + /* Fault in the page from disk */ + *area_dst = 2; + value = pagemap_get_entry(pagemap_fd, area_dst); + pagemap_check_wp(value, false); + uffd_test_pass(); +out: + if (uffd_unregister(uffd, area_dst, nr_pages * page_size)) + err("unregister failed"); + close(pagemap_fd); +} + +static void uffd_wp_fork_test(uffd_test_args_t *args) +{ + uffd_wp_fork_test_common(args, false); +} + +static void uffd_wp_fork_with_event_test(uffd_test_args_t *args) +{ + uffd_wp_fork_test_common(args, true); +} + +static void uffd_wp_fork_pin_test_common(uffd_test_args_t *args, + bool with_event) +{ + int pagemap_fd; + pin_args pin_args = {}; + + if (uffd_register(uffd, area_dst, page_size, false, true, false)) + err("register failed"); + + pagemap_fd = pagemap_open(); + + /* Touch the page */ + *area_dst = 1; + wp_range(uffd, (uint64_t)area_dst, page_size, true); + + /* + * 1. First pin, then fork(). This tests fork() special path when + * doing early CoW if the page is private. + */ + if (pin_pages(&pin_args, area_dst, page_size)) { + uffd_test_skip("Possibly CONFIG_GUP_TEST missing " + "or unprivileged"); + close(pagemap_fd); + uffd_unregister(uffd, area_dst, page_size); + return; + } + + if (pagemap_test_fork(uffd, with_event, false)) { + uffd_test_fail("Detected %s uffd-wp bit in early CoW of fork()", + with_event ? "missing" : "stall"); + unpin_pages(&pin_args); + goto out; + } + + unpin_pages(&pin_args); + + /* + * 2. First fork(), then pin (in the child, where test_pin==true). + * This tests COR, aka, page unsharing on private memories. + */ + if (pagemap_test_fork(uffd, with_event, true)) { + uffd_test_fail("Detected %s uffd-wp bit when RO pin", + with_event ? "missing" : "stall"); + goto out; + } + uffd_test_pass(); +out: + if (uffd_unregister(uffd, area_dst, page_size)) + err("register failed"); + close(pagemap_fd); +} + +static void uffd_wp_fork_pin_test(uffd_test_args_t *args) +{ + uffd_wp_fork_pin_test_common(args, false); +} + +static void uffd_wp_fork_pin_with_event_test(uffd_test_args_t *args) +{ + uffd_wp_fork_pin_test_common(args, true); +} + +static void check_memory_contents(char *p) +{ + unsigned long i, j; + uint8_t expected_byte; + + for (i = 0; i < nr_pages; ++i) { + expected_byte = ~((uint8_t)(i % ((uint8_t)-1))); + for (j = 0; j < page_size; j++) { + uint8_t v = *(uint8_t *)(p + (i * page_size) + j); + if (v != expected_byte) + err("unexpected page contents"); + } + } +} + +static void uffd_minor_test_common(bool test_collapse, bool test_wp) +{ + unsigned long p; + pthread_t uffd_mon; + char c; + struct uffd_args args = { 0 }; + + /* + * NOTE: MADV_COLLAPSE is not yet compatible with WP, so testing + * both do not make much sense. + */ + assert(!(test_collapse && test_wp)); + + if (uffd_register(uffd, area_dst_alias, nr_pages * page_size, + /* NOTE! MADV_COLLAPSE may not work with uffd-wp */ + false, test_wp, true)) + err("register failure"); + + /* + * After registering with UFFD, populate the non-UFFD-registered side of + * the shared mapping. This should *not* trigger any UFFD minor faults. + */ + for (p = 0; p < nr_pages; ++p) + memset(area_dst + (p * page_size), p % ((uint8_t)-1), + page_size); + + args.apply_wp = test_wp; + if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args)) + err("uffd_poll_thread create"); + + /* + * Read each of the pages back using the UFFD-registered mapping. We + * expect that the first time we touch a page, it will result in a minor + * fault. uffd_poll_thread will resolve the fault by bit-flipping the + * page's contents, and then issuing a CONTINUE ioctl. + */ + check_memory_contents(area_dst_alias); + + if (write(pipefd[1], &c, sizeof(c)) != sizeof(c)) + err("pipe write"); + if (pthread_join(uffd_mon, NULL)) + err("join() failed"); + + if (test_collapse) { + if (madvise(area_dst_alias, nr_pages * page_size, + MADV_COLLAPSE)) { + /* It's fine to fail for this one... */ + uffd_test_skip("MADV_COLLAPSE failed"); + return; + } + + uffd_test_ops->check_pmd_mapping(area_dst, + nr_pages * page_size / + read_pmd_pagesize()); + /* + * This won't cause uffd-fault - it purely just makes sure there + * was no corruption. + */ + check_memory_contents(area_dst_alias); + } + + if (args.missing_faults != 0 || args.minor_faults != nr_pages) + uffd_test_fail("stats check error"); + else + uffd_test_pass(); +} + +void uffd_minor_test(uffd_test_args_t *args) +{ + uffd_minor_test_common(false, false); +} + +void uffd_minor_wp_test(uffd_test_args_t *args) +{ + uffd_minor_test_common(false, true); +} + +void uffd_minor_collapse_test(uffd_test_args_t *args) +{ + uffd_minor_test_common(true, false); +} + +static sigjmp_buf jbuf, *sigbuf; + +static void sighndl(int sig, siginfo_t *siginfo, void *ptr) +{ + if (sig == SIGBUS) { + if (sigbuf) + siglongjmp(*sigbuf, 1); + abort(); + } +} + +/* + * For non-cooperative userfaultfd test we fork() a process that will + * generate pagefaults, will mremap the area monitored by the + * userfaultfd and at last this process will release the monitored + * area. + * For the anonymous and shared memory the area is divided into two + * parts, the first part is accessed before mremap, and the second + * part is accessed after mremap. Since hugetlbfs does not support + * mremap, the entire monitored area is accessed in a single pass for + * HUGETLB_TEST. + * The release of the pages currently generates event for shmem and + * anonymous memory (UFFD_EVENT_REMOVE), hence it is not checked + * for hugetlb. + * For signal test(UFFD_FEATURE_SIGBUS), signal_test = 1, we register + * monitored area, generate pagefaults and test that signal is delivered. + * Use UFFDIO_COPY to allocate missing page and retry. For signal_test = 2 + * test robustness use case - we release monitored area, fork a process + * that will generate pagefaults and verify signal is generated. + * This also tests UFFD_FEATURE_EVENT_FORK event along with the signal + * feature. Using monitor thread, verify no userfault events are generated. + */ +static int faulting_process(int signal_test, bool wp) +{ + unsigned long nr, i; + unsigned long long count; + unsigned long split_nr_pages; + unsigned long lastnr; + struct sigaction act; + volatile unsigned long signalled = 0; + + split_nr_pages = (nr_pages + 1) / 2; + + if (signal_test) { + sigbuf = &jbuf; + memset(&act, 0, sizeof(act)); + act.sa_sigaction = sighndl; + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGBUS, &act, 0)) + err("sigaction"); + lastnr = (unsigned long)-1; + } + + for (nr = 0; nr < split_nr_pages; nr++) { + volatile int steps = 1; + unsigned long offset = nr * page_size; + + if (signal_test) { + if (sigsetjmp(*sigbuf, 1) != 0) { + if (steps == 1 && nr == lastnr) + err("Signal repeated"); + + lastnr = nr; + if (signal_test == 1) { + if (steps == 1) { + /* This is a MISSING request */ + steps++; + if (copy_page(uffd, offset, wp)) + signalled++; + } else { + /* This is a WP request */ + assert(steps == 2); + wp_range(uffd, + (__u64)area_dst + + offset, + page_size, false); + } + } else { + signalled++; + continue; + } + } + } + + count = *area_count(area_dst, nr); + if (count != count_verify[nr]) + err("nr %lu memory corruption %llu %llu\n", + nr, count, count_verify[nr]); + /* + * Trigger write protection if there is by writing + * the same value back. + */ + *area_count(area_dst, nr) = count; + } + + if (signal_test) + return signalled != split_nr_pages; + + area_dst = mremap(area_dst, nr_pages * page_size, nr_pages * page_size, + MREMAP_MAYMOVE | MREMAP_FIXED, area_src); + if (area_dst == MAP_FAILED) + err("mremap"); + /* Reset area_src since we just clobbered it */ + area_src = NULL; + + for (; nr < nr_pages; nr++) { + count = *area_count(area_dst, nr); + if (count != count_verify[nr]) { + err("nr %lu memory corruption %llu %llu\n", + nr, count, count_verify[nr]); + } + /* + * Trigger write protection if there is by writing + * the same value back. + */ + *area_count(area_dst, nr) = count; + } + + uffd_test_ops->release_pages(area_dst); + + for (nr = 0; nr < nr_pages; nr++) + for (i = 0; i < page_size; i++) + if (*(area_dst + nr * page_size + i) != 0) + err("page %lu offset %lu is not zero", nr, i); + + return 0; +} + +static void uffd_sigbus_test_common(bool wp) +{ + unsigned long userfaults; + pthread_t uffd_mon; + pid_t pid; + int err; + char c; + struct uffd_args args = { 0 }; + + fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK); + + if (uffd_register(uffd, area_dst, nr_pages * page_size, + true, wp, false)) + err("register failure"); + + if (faulting_process(1, wp)) + err("faulting process failed"); + + uffd_test_ops->release_pages(area_dst); + + args.apply_wp = wp; + if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args)) + err("uffd_poll_thread create"); + + pid = fork(); + if (pid < 0) + err("fork"); + + if (!pid) + exit(faulting_process(2, wp)); + + waitpid(pid, &err, 0); + if (err) + err("faulting process failed"); + if (write(pipefd[1], &c, sizeof(c)) != sizeof(c)) + err("pipe write"); + if (pthread_join(uffd_mon, (void **)&userfaults)) + err("pthread_join()"); + + if (userfaults) + uffd_test_fail("Signal test failed, userfaults: %ld", userfaults); + else + uffd_test_pass(); +} + +static void uffd_sigbus_test(uffd_test_args_t *args) +{ + uffd_sigbus_test_common(false); +} + +static void uffd_sigbus_wp_test(uffd_test_args_t *args) +{ + uffd_sigbus_test_common(true); +} + +static void uffd_events_test_common(bool wp) +{ + pthread_t uffd_mon; + pid_t pid; + int err; + char c; + struct uffd_args args = { 0 }; + + fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK); + if (uffd_register(uffd, area_dst, nr_pages * page_size, + true, wp, false)) + err("register failure"); + + args.apply_wp = wp; + if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args)) + err("uffd_poll_thread create"); + + pid = fork(); + if (pid < 0) + err("fork"); + + if (!pid) + exit(faulting_process(0, wp)); + + waitpid(pid, &err, 0); + if (err) + err("faulting process failed"); + if (write(pipefd[1], &c, sizeof(c)) != sizeof(c)) + err("pipe write"); + if (pthread_join(uffd_mon, NULL)) + err("pthread_join()"); + + if (args.missing_faults != nr_pages) + uffd_test_fail("Fault counts wrong"); + else + uffd_test_pass(); +} + +static void uffd_events_test(uffd_test_args_t *args) +{ + uffd_events_test_common(false); +} + +static void uffd_events_wp_test(uffd_test_args_t *args) +{ + uffd_events_test_common(true); +} + +static void retry_uffdio_zeropage(int ufd, + struct uffdio_zeropage *uffdio_zeropage) +{ + uffd_test_ops->alias_mapping(&uffdio_zeropage->range.start, + uffdio_zeropage->range.len, + 0); + if (ioctl(ufd, UFFDIO_ZEROPAGE, uffdio_zeropage)) { + if (uffdio_zeropage->zeropage != -EEXIST) + err("UFFDIO_ZEROPAGE error: %"PRId64, + (int64_t)uffdio_zeropage->zeropage); + } else { + err("UFFDIO_ZEROPAGE error: %"PRId64, + (int64_t)uffdio_zeropage->zeropage); + } +} + +static bool do_uffdio_zeropage(int ufd, bool has_zeropage) +{ + struct uffdio_zeropage uffdio_zeropage = { 0 }; + int ret; + __s64 res; + + uffdio_zeropage.range.start = (unsigned long) area_dst; + uffdio_zeropage.range.len = page_size; + uffdio_zeropage.mode = 0; + ret = ioctl(ufd, UFFDIO_ZEROPAGE, &uffdio_zeropage); + res = uffdio_zeropage.zeropage; + if (ret) { + /* real retval in ufdio_zeropage.zeropage */ + if (has_zeropage) + err("UFFDIO_ZEROPAGE error: %"PRId64, (int64_t)res); + else if (res != -EINVAL) + err("UFFDIO_ZEROPAGE not -EINVAL"); + } else if (has_zeropage) { + if (res != page_size) + err("UFFDIO_ZEROPAGE unexpected size"); + else + retry_uffdio_zeropage(ufd, &uffdio_zeropage); + return true; + } else + err("UFFDIO_ZEROPAGE succeeded"); + + return false; +} + +/* + * Registers a range with MISSING mode only for zeropage test. Return true + * if UFFDIO_ZEROPAGE supported, false otherwise. Can't use uffd_register() + * because we want to detect .ioctls along the way. + */ +static bool +uffd_register_detect_zeropage(int uffd, void *addr, uint64_t len) +{ + uint64_t ioctls = 0; + + if (uffd_register_with_ioctls(uffd, addr, len, true, + false, false, &ioctls)) + err("zeropage register fail"); + + return ioctls & (1 << _UFFDIO_ZEROPAGE); +} + +/* exercise UFFDIO_ZEROPAGE */ +static void uffd_zeropage_test(uffd_test_args_t *args) +{ + bool has_zeropage; + int i; + + has_zeropage = uffd_register_detect_zeropage(uffd, area_dst, page_size); + if (area_dst_alias) + /* Ignore the retval; we already have it */ + uffd_register_detect_zeropage(uffd, area_dst_alias, page_size); + + if (do_uffdio_zeropage(uffd, has_zeropage)) + for (i = 0; i < page_size; i++) + if (area_dst[i] != 0) + err("data non-zero at offset %d\n", i); + + if (uffd_unregister(uffd, area_dst, page_size)) + err("unregister"); + + if (area_dst_alias && uffd_unregister(uffd, area_dst_alias, page_size)) + err("unregister"); + + uffd_test_pass(); +} + +/* + * Test the returned uffdio_register.ioctls with different register modes. + * Note that _UFFDIO_ZEROPAGE is tested separately in the zeropage test. + */ +static void +do_register_ioctls_test(uffd_test_args_t *args, bool miss, bool wp, bool minor) +{ + uint64_t ioctls = 0, expected = BIT_ULL(_UFFDIO_WAKE); + mem_type_t *mem_type = args->mem_type; + int ret; + + ret = uffd_register_with_ioctls(uffd, area_dst, page_size, + miss, wp, minor, &ioctls); + + /* + * Handle special cases of UFFDIO_REGISTER here where it should + * just fail with -EINVAL first.. + * + * Case 1: register MINOR on anon + * Case 2: register with no mode selected + */ + if ((minor && (mem_type->mem_flag == MEM_ANON)) || + (!miss && !wp && !minor)) { + if (ret != -EINVAL) + err("register (miss=%d, wp=%d, minor=%d) failed " + "with wrong errno=%d", miss, wp, minor, ret); + return; + } + + /* UFFDIO_REGISTER should succeed, then check ioctls returned */ + if (miss) + expected |= BIT_ULL(_UFFDIO_COPY); + if (wp) + expected |= BIT_ULL(_UFFDIO_WRITEPROTECT); + if (minor) + expected |= BIT_ULL(_UFFDIO_CONTINUE); + + if ((ioctls & expected) != expected) + err("unexpected uffdio_register.ioctls " + "(miss=%d, wp=%d, minor=%d): expected=0x%"PRIx64", " + "returned=0x%"PRIx64, miss, wp, minor, expected, ioctls); + + if (uffd_unregister(uffd, area_dst, page_size)) + err("unregister"); +} + +static void uffd_register_ioctls_test(uffd_test_args_t *args) +{ + int miss, wp, minor; + + for (miss = 0; miss <= 1; miss++) + for (wp = 0; wp <= 1; wp++) + for (minor = 0; minor <= 1; minor++) + do_register_ioctls_test(args, miss, wp, minor); + + uffd_test_pass(); +} + +uffd_test_case_t uffd_tests[] = { + { + /* Test returned uffdio_register.ioctls. */ + .name = "register-ioctls", + .uffd_fn = uffd_register_ioctls_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_MISSING_HUGETLBFS | + UFFD_FEATURE_MISSING_SHMEM | + UFFD_FEATURE_PAGEFAULT_FLAG_WP | + UFFD_FEATURE_WP_HUGETLBFS_SHMEM | + UFFD_FEATURE_MINOR_HUGETLBFS | + UFFD_FEATURE_MINOR_SHMEM, + }, + { + .name = "zeropage", + .uffd_fn = uffd_zeropage_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = 0, + }, + { + .name = "wp-fork", + .uffd_fn = uffd_wp_fork_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP | + UFFD_FEATURE_WP_HUGETLBFS_SHMEM, + }, + { + .name = "wp-fork-with-event", + .uffd_fn = uffd_wp_fork_with_event_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP | + UFFD_FEATURE_WP_HUGETLBFS_SHMEM | + /* when set, child process should inherit uffd-wp bits */ + UFFD_FEATURE_EVENT_FORK, + }, + { + .name = "wp-fork-pin", + .uffd_fn = uffd_wp_fork_pin_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP | + UFFD_FEATURE_WP_HUGETLBFS_SHMEM, + }, + { + .name = "wp-fork-pin-with-event", + .uffd_fn = uffd_wp_fork_pin_with_event_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP | + UFFD_FEATURE_WP_HUGETLBFS_SHMEM | + /* when set, child process should inherit uffd-wp bits */ + UFFD_FEATURE_EVENT_FORK, + }, + { + .name = "wp-unpopulated", + .uffd_fn = uffd_wp_unpopulated_test, + .mem_targets = MEM_ANON, + .uffd_feature_required = + UFFD_FEATURE_PAGEFAULT_FLAG_WP | UFFD_FEATURE_WP_UNPOPULATED, + }, + { + .name = "minor", + .uffd_fn = uffd_minor_test, + .mem_targets = MEM_SHMEM | MEM_HUGETLB, + .uffd_feature_required = + UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM, + }, + { + .name = "minor-wp", + .uffd_fn = uffd_minor_wp_test, + .mem_targets = MEM_SHMEM | MEM_HUGETLB, + .uffd_feature_required = + UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM | + UFFD_FEATURE_PAGEFAULT_FLAG_WP | + /* + * HACK: here we leveraged WP_UNPOPULATED to detect whether + * minor mode supports wr-protect. There's no feature flag + * for it so this is the best we can test against. + */ + UFFD_FEATURE_WP_UNPOPULATED, + }, + { + .name = "minor-collapse", + .uffd_fn = uffd_minor_collapse_test, + /* MADV_COLLAPSE only works with shmem */ + .mem_targets = MEM_SHMEM, + /* We can't test MADV_COLLAPSE, so try our luck */ + .uffd_feature_required = UFFD_FEATURE_MINOR_SHMEM, + }, + { + .name = "sigbus", + .uffd_fn = uffd_sigbus_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_SIGBUS | + UFFD_FEATURE_EVENT_FORK, + }, + { + .name = "sigbus-wp", + .uffd_fn = uffd_sigbus_wp_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_SIGBUS | + UFFD_FEATURE_EVENT_FORK | UFFD_FEATURE_PAGEFAULT_FLAG_WP, + }, + { + .name = "events", + .uffd_fn = uffd_events_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_EVENT_FORK | + UFFD_FEATURE_EVENT_REMAP | UFFD_FEATURE_EVENT_REMOVE, + }, + { + .name = "events-wp", + .uffd_fn = uffd_events_wp_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_EVENT_FORK | + UFFD_FEATURE_EVENT_REMAP | UFFD_FEATURE_EVENT_REMOVE | + UFFD_FEATURE_PAGEFAULT_FLAG_WP | + UFFD_FEATURE_WP_HUGETLBFS_SHMEM, + }, +}; + +static void usage(const char *prog) +{ + printf("usage: %s [-f TESTNAME]\n", prog); + puts(""); + puts(" -f: test name to filter (e.g., event)"); + puts(" -h: show the help msg"); + puts(" -l: list tests only"); + puts(""); + exit(KSFT_FAIL); +} + +int main(int argc, char *argv[]) +{ + int n_tests = sizeof(uffd_tests) / sizeof(uffd_test_case_t); + int n_mems = sizeof(mem_types) / sizeof(mem_type_t); + const char *test_filter = NULL; + bool list_only = false; + uffd_test_case_t *test; + mem_type_t *mem_type; + uffd_test_args_t args; + char test_name[128]; + const char *errmsg; + int has_uffd, opt; + int i, j; + + while ((opt = getopt(argc, argv, "f:hl")) != -1) { + switch (opt) { + case 'f': + test_filter = optarg; + break; + case 'l': + list_only = true; + break; + case 'h': + default: + /* Unknown */ + usage(argv[0]); + break; + } + } + + if (!test_filter && !list_only) { + has_uffd = test_uffd_api(false); + has_uffd |= test_uffd_api(true); + + if (!has_uffd) { + printf("Userfaultfd not supported or unprivileged, skip all tests\n"); + exit(KSFT_SKIP); + } + } + + for (i = 0; i < n_tests; i++) { + test = &uffd_tests[i]; + if (test_filter && !strstr(test->name, test_filter)) + continue; + if (list_only) { + printf("%s\n", test->name); + continue; + } + for (j = 0; j < n_mems; j++) { + mem_type = &mem_types[j]; + if (!(test->mem_targets & mem_type->mem_flag)) + continue; + snprintf(test_name, sizeof(test_name), + "%s on %s", test->name, mem_type->name); + + uffd_test_start(test_name); + if (!uffd_feature_supported(test)) { + uffd_test_skip("feature missing"); + continue; + } + if (uffd_setup_environment(&args, test, mem_type, + &errmsg)) { + uffd_test_skip(errmsg); + continue; + } + test->uffd_fn(&args); + } + } + + if (!list_only) + uffd_test_report(); + + return ksft_get_fail_cnt() ? KSFT_FAIL : KSFT_PASS; +} + +#else /* __NR_userfaultfd */ + +#warning "missing __NR_userfaultfd definition" + +int main(void) +{ + printf("Skipping %s (missing __NR_userfaultfd)\n", __file__); + return KSFT_SKIP; +} + +#endif /* __NR_userfaultfd */ diff --git a/tools/testing/selftests/mm/userfaultfd.c b/tools/testing/selftests/mm/userfaultfd.c deleted file mode 100644 index 7f22844ed704..000000000000 --- a/tools/testing/selftests/mm/userfaultfd.c +++ /dev/null @@ -1,1858 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Stress userfaultfd syscall. - * - * Copyright (C) 2015 Red Hat, Inc. - * - * This test allocates two virtual areas and bounces the physical - * memory across the two virtual areas (from area_src to area_dst) - * using userfaultfd. - * - * There are three threads running per CPU: - * - * 1) one per-CPU thread takes a per-page pthread_mutex in a random - * page of the area_dst (while the physical page may still be in - * area_src), and increments a per-page counter in the same page, - * and checks its value against a verification region. - * - * 2) another per-CPU thread handles the userfaults generated by - * thread 1 above. userfaultfd blocking reads or poll() modes are - * exercised interleaved. - * - * 3) one last per-CPU thread transfers the memory in the background - * at maximum bandwidth (if not already transferred by thread - * 2). Each cpu thread takes cares of transferring a portion of the - * area. - * - * When all threads of type 3 completed the transfer, one bounce is - * complete. area_src and area_dst are then swapped. All threads are - * respawned and so the bounce is immediately restarted in the - * opposite direction. - * - * per-CPU threads 1 by triggering userfaults inside - * pthread_mutex_lock will also verify the atomicity of the memory - * transfer (UFFDIO_COPY). - */ - -#define _GNU_SOURCE -#include <stdio.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <time.h> -#include <signal.h> -#include <poll.h> -#include <string.h> -#include <linux/mman.h> -#include <sys/mman.h> -#include <sys/syscall.h> -#include <sys/ioctl.h> -#include <sys/wait.h> -#include <pthread.h> -#include <linux/userfaultfd.h> -#include <setjmp.h> -#include <stdbool.h> -#include <assert.h> -#include <inttypes.h> -#include <stdint.h> -#include <sys/random.h> - -#include "../kselftest.h" -#include "vm_util.h" - -#ifdef __NR_userfaultfd - -static unsigned long nr_cpus, nr_pages, nr_pages_per_cpu, page_size, hpage_size; - -#define BOUNCE_RANDOM (1<<0) -#define BOUNCE_RACINGFAULTS (1<<1) -#define BOUNCE_VERIFY (1<<2) -#define BOUNCE_POLL (1<<3) -static int bounces; - -#define TEST_ANON 1 -#define TEST_HUGETLB 2 -#define TEST_SHMEM 3 -static int test_type; - -#define UFFD_FLAGS (O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY) - -#define BASE_PMD_ADDR ((void *)(1UL << 30)) - -/* test using /dev/userfaultfd, instead of userfaultfd(2) */ -static bool test_dev_userfaultfd; - -/* exercise the test_uffdio_*_eexist every ALARM_INTERVAL_SECS */ -#define ALARM_INTERVAL_SECS 10 -static volatile bool test_uffdio_copy_eexist = true; -static volatile bool test_uffdio_zeropage_eexist = true; -/* Whether to test uffd write-protection */ -static bool test_uffdio_wp = true; -/* Whether to test uffd minor faults */ -static bool test_uffdio_minor = false; -static bool map_shared; -static int mem_fd; -static unsigned long long *count_verify; -static int uffd = -1; -static int uffd_flags, finished, *pipefd; -static char *area_src, *area_src_alias, *area_dst, *area_dst_alias, *area_remap; -static char *zeropage; -pthread_attr_t attr; -static bool test_collapse; - -/* Userfaultfd test statistics */ -struct uffd_stats { - int cpu; - unsigned long missing_faults; - unsigned long wp_faults; - unsigned long minor_faults; -}; - -/* pthread_mutex_t starts at page offset 0 */ -#define area_mutex(___area, ___nr) \ - ((pthread_mutex_t *) ((___area) + (___nr)*page_size)) -/* - * count is placed in the page after pthread_mutex_t naturally aligned - * to avoid non alignment faults on non-x86 archs. - */ -#define area_count(___area, ___nr) \ - ((volatile unsigned long long *) ((unsigned long) \ - ((___area) + (___nr)*page_size + \ - sizeof(pthread_mutex_t) + \ - sizeof(unsigned long long) - 1) & \ - ~(unsigned long)(sizeof(unsigned long long) \ - - 1))) - -#define swap(a, b) \ - do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0) - -#define factor_of_2(x) ((x) ^ ((x) & ((x) - 1))) - -const char *examples = - "# Run anonymous memory test on 100MiB region with 99999 bounces:\n" - "./userfaultfd anon 100 99999\n\n" - "# Run the same anonymous memory test, but using /dev/userfaultfd:\n" - "./userfaultfd anon:dev 100 99999\n\n" - "# Run share memory test on 1GiB region with 99 bounces:\n" - "./userfaultfd shmem 1000 99\n\n" - "# Run hugetlb memory test on 256MiB region with 50 bounces:\n" - "./userfaultfd hugetlb 256 50\n\n" - "# Run the same hugetlb test but using shared file:\n" - "./userfaultfd hugetlb_shared 256 50\n\n" - "# 10MiB-~6GiB 999 bounces anonymous test, " - "continue forever unless an error triggers\n" - "while ./userfaultfd anon $[RANDOM % 6000 + 10] 999; do true; done\n\n"; - -static void usage(void) -{ - fprintf(stderr, "\nUsage: ./userfaultfd <test type> <MiB> <bounces> " - "[hugetlbfs_file]\n\n"); - fprintf(stderr, "Supported <test type>: anon, hugetlb, " - "hugetlb_shared, shmem\n\n"); - fprintf(stderr, "'Test mods' can be joined to the test type string with a ':'. " - "Supported mods:\n"); - fprintf(stderr, "\tsyscall - Use userfaultfd(2) (default)\n"); - fprintf(stderr, "\tdev - Use /dev/userfaultfd instead of userfaultfd(2)\n"); - fprintf(stderr, "\tcollapse - Test MADV_COLLAPSE of UFFDIO_REGISTER_MODE_MINOR\n" - "memory\n"); - fprintf(stderr, "\nExample test mod usage:\n"); - fprintf(stderr, "# Run anonymous memory test with /dev/userfaultfd:\n"); - fprintf(stderr, "./userfaultfd anon:dev 100 99999\n\n"); - - fprintf(stderr, "Examples:\n\n"); - fprintf(stderr, "%s", examples); - exit(1); -} - -#define _err(fmt, ...) \ - do { \ - int ret = errno; \ - fprintf(stderr, "ERROR: " fmt, ##__VA_ARGS__); \ - fprintf(stderr, " (errno=%d, line=%d)\n", \ - ret, __LINE__); \ - } while (0) - -#define errexit(exitcode, fmt, ...) \ - do { \ - _err(fmt, ##__VA_ARGS__); \ - exit(exitcode); \ - } while (0) - -#define err(fmt, ...) errexit(1, fmt, ##__VA_ARGS__) - -static void uffd_stats_reset(struct uffd_stats *uffd_stats, - unsigned long n_cpus) -{ - int i; - - for (i = 0; i < n_cpus; i++) { - uffd_stats[i].cpu = i; - uffd_stats[i].missing_faults = 0; - uffd_stats[i].wp_faults = 0; - uffd_stats[i].minor_faults = 0; - } -} - -static void uffd_stats_report(struct uffd_stats *stats, int n_cpus) -{ - int i; - unsigned long long miss_total = 0, wp_total = 0, minor_total = 0; - - for (i = 0; i < n_cpus; i++) { - miss_total += stats[i].missing_faults; - wp_total += stats[i].wp_faults; - minor_total += stats[i].minor_faults; - } - - printf("userfaults: "); - if (miss_total) { - printf("%llu missing (", miss_total); - for (i = 0; i < n_cpus; i++) - printf("%lu+", stats[i].missing_faults); - printf("\b) "); - } - if (wp_total) { - printf("%llu wp (", wp_total); - for (i = 0; i < n_cpus; i++) - printf("%lu+", stats[i].wp_faults); - printf("\b) "); - } - if (minor_total) { - printf("%llu minor (", minor_total); - for (i = 0; i < n_cpus; i++) - printf("%lu+", stats[i].minor_faults); - printf("\b)"); - } - printf("\n"); -} - -static void anon_release_pages(char *rel_area) -{ - if (madvise(rel_area, nr_pages * page_size, MADV_DONTNEED)) - err("madvise(MADV_DONTNEED) failed"); -} - -static void anon_allocate_area(void **alloc_area, bool is_src) -{ - *alloc_area = mmap(NULL, nr_pages * page_size, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); -} - -static void noop_alias_mapping(__u64 *start, size_t len, unsigned long offset) -{ -} - -static void hugetlb_release_pages(char *rel_area) -{ - if (!map_shared) { - if (madvise(rel_area, nr_pages * page_size, MADV_DONTNEED)) - err("madvise(MADV_DONTNEED) failed"); - } else { - if (madvise(rel_area, nr_pages * page_size, MADV_REMOVE)) - err("madvise(MADV_REMOVE) failed"); - } -} - -static void hugetlb_allocate_area(void **alloc_area, bool is_src) -{ - off_t size = nr_pages * page_size; - off_t offset = is_src ? 0 : size; - void *area_alias = NULL; - char **alloc_area_alias; - - *alloc_area = mmap(NULL, size, PROT_READ | PROT_WRITE, - (map_shared ? MAP_SHARED : MAP_PRIVATE) | - (is_src ? 0 : MAP_NORESERVE), - mem_fd, offset); - if (*alloc_area == MAP_FAILED) - err("mmap of hugetlbfs file failed"); - - if (map_shared) { - area_alias = mmap(NULL, size, PROT_READ | PROT_WRITE, - MAP_SHARED, mem_fd, offset); - if (area_alias == MAP_FAILED) - err("mmap of hugetlb file alias failed"); - } - - if (is_src) { - alloc_area_alias = &area_src_alias; - } else { - alloc_area_alias = &area_dst_alias; - } - if (area_alias) - *alloc_area_alias = area_alias; -} - -static void hugetlb_alias_mapping(__u64 *start, size_t len, unsigned long offset) -{ - if (!map_shared) - return; - - *start = (unsigned long) area_dst_alias + offset; -} - -static void shmem_release_pages(char *rel_area) -{ - if (madvise(rel_area, nr_pages * page_size, MADV_REMOVE)) - err("madvise(MADV_REMOVE) failed"); -} - -static void shmem_allocate_area(void **alloc_area, bool is_src) -{ - void *area_alias = NULL; - size_t bytes = nr_pages * page_size; - unsigned long offset = is_src ? 0 : bytes; - char *p = NULL, *p_alias = NULL; - - if (test_collapse) { - p = BASE_PMD_ADDR; - if (!is_src) - /* src map + alias + interleaved hpages */ - p += 2 * (bytes + hpage_size); - p_alias = p; - p_alias += bytes; - p_alias += hpage_size; /* Prevent src/dst VMA merge */ - } - - *alloc_area = mmap(p, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, - mem_fd, offset); - if (*alloc_area == MAP_FAILED) - err("mmap of memfd failed"); - if (test_collapse && *alloc_area != p) - err("mmap of memfd failed at %p", p); - - area_alias = mmap(p_alias, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, - mem_fd, offset); - if (area_alias == MAP_FAILED) - err("mmap of memfd alias failed"); - if (test_collapse && area_alias != p_alias) - err("mmap of anonymous memory failed at %p", p_alias); - - if (is_src) - area_src_alias = area_alias; - else - area_dst_alias = area_alias; -} - -static void shmem_alias_mapping(__u64 *start, size_t len, unsigned long offset) -{ - *start = (unsigned long)area_dst_alias + offset; -} - -static void shmem_check_pmd_mapping(void *p, int expect_nr_hpages) -{ - if (!check_huge_shmem(area_dst_alias, expect_nr_hpages, hpage_size)) - err("Did not find expected %d number of hugepages", - expect_nr_hpages); -} - -struct uffd_test_ops { - void (*allocate_area)(void **alloc_area, bool is_src); - void (*release_pages)(char *rel_area); - void (*alias_mapping)(__u64 *start, size_t len, unsigned long offset); - void (*check_pmd_mapping)(void *p, int expect_nr_hpages); -}; - -static struct uffd_test_ops anon_uffd_test_ops = { - .allocate_area = anon_allocate_area, - .release_pages = anon_release_pages, - .alias_mapping = noop_alias_mapping, - .check_pmd_mapping = NULL, -}; - -static struct uffd_test_ops shmem_uffd_test_ops = { - .allocate_area = shmem_allocate_area, - .release_pages = shmem_release_pages, - .alias_mapping = shmem_alias_mapping, - .check_pmd_mapping = shmem_check_pmd_mapping, -}; - -static struct uffd_test_ops hugetlb_uffd_test_ops = { - .allocate_area = hugetlb_allocate_area, - .release_pages = hugetlb_release_pages, - .alias_mapping = hugetlb_alias_mapping, - .check_pmd_mapping = NULL, -}; - -static struct uffd_test_ops *uffd_test_ops; - -static inline uint64_t uffd_minor_feature(void) -{ - if (test_type == TEST_HUGETLB && map_shared) - return UFFD_FEATURE_MINOR_HUGETLBFS; - else if (test_type == TEST_SHMEM) - return UFFD_FEATURE_MINOR_SHMEM; - else - return 0; -} - -static uint64_t get_expected_ioctls(uint64_t mode) -{ - uint64_t ioctls = UFFD_API_RANGE_IOCTLS; - - if (test_type == TEST_HUGETLB) - ioctls &= ~(1 << _UFFDIO_ZEROPAGE); - - if (!((mode & UFFDIO_REGISTER_MODE_WP) && test_uffdio_wp)) - ioctls &= ~(1 << _UFFDIO_WRITEPROTECT); - - if (!((mode & UFFDIO_REGISTER_MODE_MINOR) && test_uffdio_minor)) - ioctls &= ~(1 << _UFFDIO_CONTINUE); - - return ioctls; -} - -static void assert_expected_ioctls_present(uint64_t mode, uint64_t ioctls) -{ - uint64_t expected = get_expected_ioctls(mode); - uint64_t actual = ioctls & expected; - - if (actual != expected) { - err("missing ioctl(s): expected %"PRIx64" actual: %"PRIx64, - expected, actual); - } -} - -static int __userfaultfd_open_dev(void) -{ - int fd, _uffd; - - fd = open("/dev/userfaultfd", O_RDWR | O_CLOEXEC); - if (fd < 0) - errexit(KSFT_SKIP, "opening /dev/userfaultfd failed"); - - _uffd = ioctl(fd, USERFAULTFD_IOC_NEW, UFFD_FLAGS); - if (_uffd < 0) - errexit(errno == ENOTTY ? KSFT_SKIP : 1, - "creating userfaultfd failed"); - close(fd); - return _uffd; -} - -static void userfaultfd_open(uint64_t *features) -{ - struct uffdio_api uffdio_api; - - if (test_dev_userfaultfd) - uffd = __userfaultfd_open_dev(); - else { - uffd = syscall(__NR_userfaultfd, UFFD_FLAGS); - if (uffd < 0) - errexit(errno == ENOSYS ? KSFT_SKIP : 1, - "creating userfaultfd failed"); - } - uffd_flags = fcntl(uffd, F_GETFD, NULL); - - uffdio_api.api = UFFD_API; - uffdio_api.features = *features; - if (ioctl(uffd, UFFDIO_API, &uffdio_api)) - err("UFFDIO_API failed.\nPlease make sure to " - "run with either root or ptrace capability."); - if (uffdio_api.api != UFFD_API) - err("UFFDIO_API error: %" PRIu64, (uint64_t)uffdio_api.api); - - *features = uffdio_api.features; -} - -static inline void munmap_area(void **area) -{ - if (*area) - if (munmap(*area, nr_pages * page_size)) - err("munmap"); - - *area = NULL; -} - -static void uffd_test_ctx_clear(void) -{ - size_t i; - - if (pipefd) { - for (i = 0; i < nr_cpus * 2; ++i) { - if (close(pipefd[i])) - err("close pipefd"); - } - free(pipefd); - pipefd = NULL; - } - - if (count_verify) { - free(count_verify); - count_verify = NULL; - } - - if (uffd != -1) { - if (close(uffd)) - err("close uffd"); - uffd = -1; - } - - munmap_area((void **)&area_src); - munmap_area((void **)&area_src_alias); - munmap_area((void **)&area_dst); - munmap_area((void **)&area_dst_alias); - munmap_area((void **)&area_remap); -} - -static void uffd_test_ctx_init(uint64_t features) -{ - unsigned long nr, cpu; - - uffd_test_ctx_clear(); - - uffd_test_ops->allocate_area((void **)&area_src, true); - uffd_test_ops->allocate_area((void **)&area_dst, false); - - userfaultfd_open(&features); - - count_verify = malloc(nr_pages * sizeof(unsigned long long)); - if (!count_verify) - err("count_verify"); - - for (nr = 0; nr < nr_pages; nr++) { - *area_mutex(area_src, nr) = - (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; - count_verify[nr] = *area_count(area_src, nr) = 1; - /* - * In the transition between 255 to 256, powerpc will - * read out of order in my_bcmp and see both bytes as - * zero, so leave a placeholder below always non-zero - * after the count, to avoid my_bcmp to trigger false - * positives. - */ - *(area_count(area_src, nr) + 1) = 1; - } - - /* - * After initialization of area_src, we must explicitly release pages - * for area_dst to make sure it's fully empty. Otherwise we could have - * some area_dst pages be errornously initialized with zero pages, - * hence we could hit memory corruption later in the test. - * - * One example is when THP is globally enabled, above allocate_area() - * calls could have the two areas merged into a single VMA (as they - * will have the same VMA flags so they're mergeable). When we - * initialize the area_src above, it's possible that some part of - * area_dst could have been faulted in via one huge THP that will be - * shared between area_src and area_dst. It could cause some of the - * area_dst won't be trapped by missing userfaults. - * - * This release_pages() will guarantee even if that happened, we'll - * proactively split the thp and drop any accidentally initialized - * pages within area_dst. - */ - uffd_test_ops->release_pages(area_dst); - - pipefd = malloc(sizeof(int) * nr_cpus * 2); - if (!pipefd) - err("pipefd"); - for (cpu = 0; cpu < nr_cpus; cpu++) - if (pipe2(&pipefd[cpu * 2], O_CLOEXEC | O_NONBLOCK)) - err("pipe"); -} - -static int my_bcmp(char *str1, char *str2, size_t n) -{ - unsigned long i; - for (i = 0; i < n; i++) - if (str1[i] != str2[i]) - return 1; - return 0; -} - -static void wp_range(int ufd, __u64 start, __u64 len, bool wp) -{ - struct uffdio_writeprotect prms; - - /* Write protection page faults */ - prms.range.start = start; - prms.range.len = len; - /* Undo write-protect, do wakeup after that */ - prms.mode = wp ? UFFDIO_WRITEPROTECT_MODE_WP : 0; - - if (ioctl(ufd, UFFDIO_WRITEPROTECT, &prms)) - err("clear WP failed: address=0x%"PRIx64, (uint64_t)start); -} - -static void continue_range(int ufd, __u64 start, __u64 len) -{ - struct uffdio_continue req; - int ret; - - req.range.start = start; - req.range.len = len; - req.mode = 0; - - if (ioctl(ufd, UFFDIO_CONTINUE, &req)) - err("UFFDIO_CONTINUE failed for address 0x%" PRIx64, - (uint64_t)start); - - /* - * Error handling within the kernel for continue is subtly different - * from copy or zeropage, so it may be a source of bugs. Trigger an - * error (-EEXIST) on purpose, to verify doing so doesn't cause a BUG. - */ - req.mapped = 0; - ret = ioctl(ufd, UFFDIO_CONTINUE, &req); - if (ret >= 0 || req.mapped != -EEXIST) - err("failed to exercise UFFDIO_CONTINUE error handling, ret=%d, mapped=%" PRId64, - ret, (int64_t) req.mapped); -} - -static void *locking_thread(void *arg) -{ - unsigned long cpu = (unsigned long) arg; - unsigned long page_nr; - unsigned long long count; - - if (!(bounces & BOUNCE_RANDOM)) { - page_nr = -bounces; - if (!(bounces & BOUNCE_RACINGFAULTS)) - page_nr += cpu * nr_pages_per_cpu; - } - - while (!finished) { - if (bounces & BOUNCE_RANDOM) { - if (getrandom(&page_nr, sizeof(page_nr), 0) != sizeof(page_nr)) - err("getrandom failed"); - } else - page_nr += 1; - page_nr %= nr_pages; - pthread_mutex_lock(area_mutex(area_dst, page_nr)); - count = *area_count(area_dst, page_nr); - if (count != count_verify[page_nr]) - err("page_nr %lu memory corruption %llu %llu", - page_nr, count, count_verify[page_nr]); - count++; - *area_count(area_dst, page_nr) = count_verify[page_nr] = count; - pthread_mutex_unlock(area_mutex(area_dst, page_nr)); - } - - return NULL; -} - -static void retry_copy_page(int ufd, struct uffdio_copy *uffdio_copy, - unsigned long offset) -{ - uffd_test_ops->alias_mapping(&uffdio_copy->dst, - uffdio_copy->len, - offset); - if (ioctl(ufd, UFFDIO_COPY, uffdio_copy)) { - /* real retval in ufdio_copy.copy */ - if (uffdio_copy->copy != -EEXIST) - err("UFFDIO_COPY retry error: %"PRId64, - (int64_t)uffdio_copy->copy); - } else { - err("UFFDIO_COPY retry unexpected: %"PRId64, - (int64_t)uffdio_copy->copy); - } -} - -static void wake_range(int ufd, unsigned long addr, unsigned long len) -{ - struct uffdio_range uffdio_wake; - - uffdio_wake.start = addr; - uffdio_wake.len = len; - - if (ioctl(ufd, UFFDIO_WAKE, &uffdio_wake)) - fprintf(stderr, "error waking %lu\n", - addr), exit(1); -} - -static int __copy_page(int ufd, unsigned long offset, bool retry) -{ - struct uffdio_copy uffdio_copy; - - if (offset >= nr_pages * page_size) - err("unexpected offset %lu\n", offset); - uffdio_copy.dst = (unsigned long) area_dst + offset; - uffdio_copy.src = (unsigned long) area_src + offset; - uffdio_copy.len = page_size; - if (test_uffdio_wp) - uffdio_copy.mode = UFFDIO_COPY_MODE_WP; - else - uffdio_copy.mode = 0; - uffdio_copy.copy = 0; - if (ioctl(ufd, UFFDIO_COPY, &uffdio_copy)) { - /* real retval in ufdio_copy.copy */ - if (uffdio_copy.copy != -EEXIST) - err("UFFDIO_COPY error: %"PRId64, - (int64_t)uffdio_copy.copy); - wake_range(ufd, uffdio_copy.dst, page_size); - } else if (uffdio_copy.copy != page_size) { - err("UFFDIO_COPY error: %"PRId64, (int64_t)uffdio_copy.copy); - } else { - if (test_uffdio_copy_eexist && retry) { - test_uffdio_copy_eexist = false; - retry_copy_page(ufd, &uffdio_copy, offset); - } - return 1; - } - return 0; -} - -static int copy_page_retry(int ufd, unsigned long offset) -{ - return __copy_page(ufd, offset, true); -} - -static int copy_page(int ufd, unsigned long offset) -{ - return __copy_page(ufd, offset, false); -} - -static int uffd_read_msg(int ufd, struct uffd_msg *msg) -{ - int ret = read(uffd, msg, sizeof(*msg)); - - if (ret != sizeof(*msg)) { - if (ret < 0) { - if (errno == EAGAIN || errno == EINTR) - return 1; - err("blocking read error"); - } else { - err("short read"); - } - } - - return 0; -} - -static void uffd_handle_page_fault(struct uffd_msg *msg, - struct uffd_stats *stats) -{ - unsigned long offset; - - if (msg->event != UFFD_EVENT_PAGEFAULT) - err("unexpected msg event %u", msg->event); - - if (msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) { - /* Write protect page faults */ - wp_range(uffd, msg->arg.pagefault.address, page_size, false); - stats->wp_faults++; - } else if (msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_MINOR) { - uint8_t *area; - int b; - - /* - * Minor page faults - * - * To prove we can modify the original range for testing - * purposes, we're going to bit flip this range before - * continuing. - * - * Note that this requires all minor page fault tests operate on - * area_dst (non-UFFD-registered) and area_dst_alias - * (UFFD-registered). - */ - - area = (uint8_t *)(area_dst + - ((char *)msg->arg.pagefault.address - - area_dst_alias)); - for (b = 0; b < page_size; ++b) - area[b] = ~area[b]; - continue_range(uffd, msg->arg.pagefault.address, page_size); - stats->minor_faults++; - } else { - /* - * Missing page faults. - * - * Here we force a write check for each of the missing mode - * faults. It's guaranteed because the only threads that - * will trigger uffd faults are the locking threads, and - * their first instruction to touch the missing page will - * always be pthread_mutex_lock(). - * - * Note that here we relied on an NPTL glibc impl detail to - * always read the lock type at the entry of the lock op - * (pthread_mutex_t.__data.__type, offset 0x10) before - * doing any locking operations to guarantee that. It's - * actually not good to rely on this impl detail because - * logically a pthread-compatible lib can implement the - * locks without types and we can fail when linking with - * them. However since we used to find bugs with this - * strict check we still keep it around. Hopefully this - * could be a good hint when it fails again. If one day - * it'll break on some other impl of glibc we'll revisit. - */ - if (msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE) - err("unexpected write fault"); - - offset = (char *)(unsigned long)msg->arg.pagefault.address - area_dst; - offset &= ~(page_size-1); - - if (copy_page(uffd, offset)) - stats->missing_faults++; - } -} - -static void *uffd_poll_thread(void *arg) -{ - struct uffd_stats *stats = (struct uffd_stats *)arg; - unsigned long cpu = stats->cpu; - struct pollfd pollfd[2]; - struct uffd_msg msg; - struct uffdio_register uffd_reg; - int ret; - char tmp_chr; - - pollfd[0].fd = uffd; - pollfd[0].events = POLLIN; - pollfd[1].fd = pipefd[cpu*2]; - pollfd[1].events = POLLIN; - - for (;;) { - ret = poll(pollfd, 2, -1); - if (ret <= 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - err("poll error: %d", ret); - } - if (pollfd[1].revents & POLLIN) { - if (read(pollfd[1].fd, &tmp_chr, 1) != 1) - err("read pipefd error"); - break; - } - if (!(pollfd[0].revents & POLLIN)) - err("pollfd[0].revents %d", pollfd[0].revents); - if (uffd_read_msg(uffd, &msg)) - continue; - switch (msg.event) { - default: - err("unexpected msg event %u\n", msg.event); - break; - case UFFD_EVENT_PAGEFAULT: - uffd_handle_page_fault(&msg, stats); - break; - case UFFD_EVENT_FORK: - close(uffd); - uffd = msg.arg.fork.ufd; - pollfd[0].fd = uffd; - break; - case UFFD_EVENT_REMOVE: - uffd_reg.range.start = msg.arg.remove.start; - uffd_reg.range.len = msg.arg.remove.end - - msg.arg.remove.start; - if (ioctl(uffd, UFFDIO_UNREGISTER, &uffd_reg.range)) - err("remove failure"); - break; - case UFFD_EVENT_REMAP: - area_remap = area_dst; /* save for later unmap */ - area_dst = (char *)(unsigned long)msg.arg.remap.to; - break; - } - } - - return NULL; -} - -pthread_mutex_t uffd_read_mutex = PTHREAD_MUTEX_INITIALIZER; - -static void *uffd_read_thread(void *arg) -{ - struct uffd_stats *stats = (struct uffd_stats *)arg; - struct uffd_msg msg; - - pthread_mutex_unlock(&uffd_read_mutex); - /* from here cancellation is ok */ - - for (;;) { - if (uffd_read_msg(uffd, &msg)) - continue; - uffd_handle_page_fault(&msg, stats); - } - - return NULL; -} - -static void *background_thread(void *arg) -{ - unsigned long cpu = (unsigned long) arg; - unsigned long page_nr, start_nr, mid_nr, end_nr; - - start_nr = cpu * nr_pages_per_cpu; - end_nr = (cpu+1) * nr_pages_per_cpu; - mid_nr = (start_nr + end_nr) / 2; - - /* Copy the first half of the pages */ - for (page_nr = start_nr; page_nr < mid_nr; page_nr++) - copy_page_retry(uffd, page_nr * page_size); - - /* - * If we need to test uffd-wp, set it up now. Then we'll have - * at least the first half of the pages mapped already which - * can be write-protected for testing - */ - if (test_uffdio_wp) - wp_range(uffd, (unsigned long)area_dst + start_nr * page_size, - nr_pages_per_cpu * page_size, true); - - /* - * Continue the 2nd half of the page copying, handling write - * protection faults if any - */ - for (page_nr = mid_nr; page_nr < end_nr; page_nr++) - copy_page_retry(uffd, page_nr * page_size); - - return NULL; -} - -static int stress(struct uffd_stats *uffd_stats) -{ - unsigned long cpu; - pthread_t locking_threads[nr_cpus]; - pthread_t uffd_threads[nr_cpus]; - pthread_t background_threads[nr_cpus]; - - finished = 0; - for (cpu = 0; cpu < nr_cpus; cpu++) { - if (pthread_create(&locking_threads[cpu], &attr, - locking_thread, (void *)cpu)) - return 1; - if (bounces & BOUNCE_POLL) { - if (pthread_create(&uffd_threads[cpu], &attr, - uffd_poll_thread, - (void *)&uffd_stats[cpu])) - return 1; - } else { - if (pthread_create(&uffd_threads[cpu], &attr, - uffd_read_thread, - (void *)&uffd_stats[cpu])) - return 1; - pthread_mutex_lock(&uffd_read_mutex); - } - if (pthread_create(&background_threads[cpu], &attr, - background_thread, (void *)cpu)) - return 1; - } - for (cpu = 0; cpu < nr_cpus; cpu++) - if (pthread_join(background_threads[cpu], NULL)) - return 1; - - /* - * Be strict and immediately zap area_src, the whole area has - * been transferred already by the background treads. The - * area_src could then be faulted in a racy way by still - * running uffdio_threads reading zeropages after we zapped - * area_src (but they're guaranteed to get -EEXIST from - * UFFDIO_COPY without writing zero pages into area_dst - * because the background threads already completed). - */ - uffd_test_ops->release_pages(area_src); - - finished = 1; - for (cpu = 0; cpu < nr_cpus; cpu++) - if (pthread_join(locking_threads[cpu], NULL)) - return 1; - - for (cpu = 0; cpu < nr_cpus; cpu++) { - char c; - if (bounces & BOUNCE_POLL) { - if (write(pipefd[cpu*2+1], &c, 1) != 1) - err("pipefd write error"); - if (pthread_join(uffd_threads[cpu], - (void *)&uffd_stats[cpu])) - return 1; - } else { - if (pthread_cancel(uffd_threads[cpu])) - return 1; - if (pthread_join(uffd_threads[cpu], NULL)) - return 1; - } - } - - return 0; -} - -sigjmp_buf jbuf, *sigbuf; - -static void sighndl(int sig, siginfo_t *siginfo, void *ptr) -{ - if (sig == SIGBUS) { - if (sigbuf) - siglongjmp(*sigbuf, 1); - abort(); - } -} - -/* - * For non-cooperative userfaultfd test we fork() a process that will - * generate pagefaults, will mremap the area monitored by the - * userfaultfd and at last this process will release the monitored - * area. - * For the anonymous and shared memory the area is divided into two - * parts, the first part is accessed before mremap, and the second - * part is accessed after mremap. Since hugetlbfs does not support - * mremap, the entire monitored area is accessed in a single pass for - * HUGETLB_TEST. - * The release of the pages currently generates event for shmem and - * anonymous memory (UFFD_EVENT_REMOVE), hence it is not checked - * for hugetlb. - * For signal test(UFFD_FEATURE_SIGBUS), signal_test = 1, we register - * monitored area, generate pagefaults and test that signal is delivered. - * Use UFFDIO_COPY to allocate missing page and retry. For signal_test = 2 - * test robustness use case - we release monitored area, fork a process - * that will generate pagefaults and verify signal is generated. - * This also tests UFFD_FEATURE_EVENT_FORK event along with the signal - * feature. Using monitor thread, verify no userfault events are generated. - */ -static int faulting_process(int signal_test) -{ - unsigned long nr; - unsigned long long count; - unsigned long split_nr_pages; - unsigned long lastnr; - struct sigaction act; - volatile unsigned long signalled = 0; - - split_nr_pages = (nr_pages + 1) / 2; - - if (signal_test) { - sigbuf = &jbuf; - memset(&act, 0, sizeof(act)); - act.sa_sigaction = sighndl; - act.sa_flags = SA_SIGINFO; - if (sigaction(SIGBUS, &act, 0)) - err("sigaction"); - lastnr = (unsigned long)-1; - } - - for (nr = 0; nr < split_nr_pages; nr++) { - volatile int steps = 1; - unsigned long offset = nr * page_size; - - if (signal_test) { - if (sigsetjmp(*sigbuf, 1) != 0) { - if (steps == 1 && nr == lastnr) - err("Signal repeated"); - - lastnr = nr; - if (signal_test == 1) { - if (steps == 1) { - /* This is a MISSING request */ - steps++; - if (copy_page(uffd, offset)) - signalled++; - } else { - /* This is a WP request */ - assert(steps == 2); - wp_range(uffd, - (__u64)area_dst + - offset, - page_size, false); - } - } else { - signalled++; - continue; - } - } - } - - count = *area_count(area_dst, nr); - if (count != count_verify[nr]) - err("nr %lu memory corruption %llu %llu\n", - nr, count, count_verify[nr]); - /* - * Trigger write protection if there is by writing - * the same value back. - */ - *area_count(area_dst, nr) = count; - } - - if (signal_test) - return signalled != split_nr_pages; - - area_dst = mremap(area_dst, nr_pages * page_size, nr_pages * page_size, - MREMAP_MAYMOVE | MREMAP_FIXED, area_src); - if (area_dst == MAP_FAILED) - err("mremap"); - /* Reset area_src since we just clobbered it */ - area_src = NULL; - - for (; nr < nr_pages; nr++) { - count = *area_count(area_dst, nr); - if (count != count_verify[nr]) { - err("nr %lu memory corruption %llu %llu\n", - nr, count, count_verify[nr]); - } - /* - * Trigger write protection if there is by writing - * the same value back. - */ - *area_count(area_dst, nr) = count; - } - - uffd_test_ops->release_pages(area_dst); - - for (nr = 0; nr < nr_pages; nr++) - if (my_bcmp(area_dst + nr * page_size, zeropage, page_size)) - err("nr %lu is not zero", nr); - - return 0; -} - -static void retry_uffdio_zeropage(int ufd, - struct uffdio_zeropage *uffdio_zeropage, - unsigned long offset) -{ - uffd_test_ops->alias_mapping(&uffdio_zeropage->range.start, - uffdio_zeropage->range.len, - offset); - if (ioctl(ufd, UFFDIO_ZEROPAGE, uffdio_zeropage)) { - if (uffdio_zeropage->zeropage != -EEXIST) - err("UFFDIO_ZEROPAGE error: %"PRId64, - (int64_t)uffdio_zeropage->zeropage); - } else { - err("UFFDIO_ZEROPAGE error: %"PRId64, - (int64_t)uffdio_zeropage->zeropage); - } -} - -static int __uffdio_zeropage(int ufd, unsigned long offset, bool retry) -{ - struct uffdio_zeropage uffdio_zeropage; - int ret; - bool has_zeropage = get_expected_ioctls(0) & (1 << _UFFDIO_ZEROPAGE); - __s64 res; - - if (offset >= nr_pages * page_size) - err("unexpected offset %lu", offset); - uffdio_zeropage.range.start = (unsigned long) area_dst + offset; - uffdio_zeropage.range.len = page_size; - uffdio_zeropage.mode = 0; - ret = ioctl(ufd, UFFDIO_ZEROPAGE, &uffdio_zeropage); - res = uffdio_zeropage.zeropage; - if (ret) { - /* real retval in ufdio_zeropage.zeropage */ - if (has_zeropage) - err("UFFDIO_ZEROPAGE error: %"PRId64, (int64_t)res); - else if (res != -EINVAL) - err("UFFDIO_ZEROPAGE not -EINVAL"); - } else if (has_zeropage) { - if (res != page_size) { - err("UFFDIO_ZEROPAGE unexpected size"); - } else { - if (test_uffdio_zeropage_eexist && retry) { - test_uffdio_zeropage_eexist = false; - retry_uffdio_zeropage(ufd, &uffdio_zeropage, - offset); - } - return 1; - } - } else - err("UFFDIO_ZEROPAGE succeeded"); - - return 0; -} - -static int uffdio_zeropage(int ufd, unsigned long offset) -{ - return __uffdio_zeropage(ufd, offset, false); -} - -/* exercise UFFDIO_ZEROPAGE */ -static int userfaultfd_zeropage_test(void) -{ - struct uffdio_register uffdio_register; - - printf("testing UFFDIO_ZEROPAGE: "); - fflush(stdout); - - uffd_test_ctx_init(0); - - uffdio_register.range.start = (unsigned long) area_dst; - uffdio_register.range.len = nr_pages * page_size; - uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; - if (test_uffdio_wp) - uffdio_register.mode |= UFFDIO_REGISTER_MODE_WP; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) - err("register failure"); - - assert_expected_ioctls_present( - uffdio_register.mode, uffdio_register.ioctls); - - if (uffdio_zeropage(uffd, 0)) - if (my_bcmp(area_dst, zeropage, page_size)) - err("zeropage is not zero"); - - printf("done.\n"); - return 0; -} - -static int userfaultfd_events_test(void) -{ - struct uffdio_register uffdio_register; - pthread_t uffd_mon; - int err, features; - pid_t pid; - char c; - struct uffd_stats stats = { 0 }; - - printf("testing events (fork, remap, remove): "); - fflush(stdout); - - features = UFFD_FEATURE_EVENT_FORK | UFFD_FEATURE_EVENT_REMAP | - UFFD_FEATURE_EVENT_REMOVE; - uffd_test_ctx_init(features); - - fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK); - - uffdio_register.range.start = (unsigned long) area_dst; - uffdio_register.range.len = nr_pages * page_size; - uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; - if (test_uffdio_wp) - uffdio_register.mode |= UFFDIO_REGISTER_MODE_WP; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) - err("register failure"); - - assert_expected_ioctls_present( - uffdio_register.mode, uffdio_register.ioctls); - - if (pthread_create(&uffd_mon, &attr, uffd_poll_thread, &stats)) - err("uffd_poll_thread create"); - - pid = fork(); - if (pid < 0) - err("fork"); - - if (!pid) - exit(faulting_process(0)); - - waitpid(pid, &err, 0); - if (err) - err("faulting process failed"); - if (write(pipefd[1], &c, sizeof(c)) != sizeof(c)) - err("pipe write"); - if (pthread_join(uffd_mon, NULL)) - return 1; - - uffd_stats_report(&stats, 1); - - return stats.missing_faults != nr_pages; -} - -static int userfaultfd_sig_test(void) -{ - struct uffdio_register uffdio_register; - unsigned long userfaults; - pthread_t uffd_mon; - int err, features; - pid_t pid; - char c; - struct uffd_stats stats = { 0 }; - - printf("testing signal delivery: "); - fflush(stdout); - - features = UFFD_FEATURE_EVENT_FORK|UFFD_FEATURE_SIGBUS; - uffd_test_ctx_init(features); - - fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK); - - uffdio_register.range.start = (unsigned long) area_dst; - uffdio_register.range.len = nr_pages * page_size; - uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; - if (test_uffdio_wp) - uffdio_register.mode |= UFFDIO_REGISTER_MODE_WP; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) - err("register failure"); - - assert_expected_ioctls_present( - uffdio_register.mode, uffdio_register.ioctls); - - if (faulting_process(1)) - err("faulting process failed"); - - uffd_test_ops->release_pages(area_dst); - - if (pthread_create(&uffd_mon, &attr, uffd_poll_thread, &stats)) - err("uffd_poll_thread create"); - - pid = fork(); - if (pid < 0) - err("fork"); - - if (!pid) - exit(faulting_process(2)); - - waitpid(pid, &err, 0); - if (err) - err("faulting process failed"); - if (write(pipefd[1], &c, sizeof(c)) != sizeof(c)) - err("pipe write"); - if (pthread_join(uffd_mon, (void **)&userfaults)) - return 1; - - printf("done.\n"); - if (userfaults) - err("Signal test failed, userfaults: %ld", userfaults); - - return userfaults != 0; -} - -void check_memory_contents(char *p) -{ - unsigned long i; - uint8_t expected_byte; - void *expected_page; - - if (posix_memalign(&expected_page, page_size, page_size)) - err("out of memory"); - - for (i = 0; i < nr_pages; ++i) { - expected_byte = ~((uint8_t)(i % ((uint8_t)-1))); - memset(expected_page, expected_byte, page_size); - if (my_bcmp(expected_page, p + (i * page_size), page_size)) - err("unexpected page contents after minor fault"); - } - - free(expected_page); -} - -static int userfaultfd_minor_test(void) -{ - unsigned long p; - struct uffdio_register uffdio_register; - pthread_t uffd_mon; - char c; - struct uffd_stats stats = { 0 }; - - if (!test_uffdio_minor) - return 0; - - printf("testing minor faults: "); - fflush(stdout); - - uffd_test_ctx_init(uffd_minor_feature()); - - uffdio_register.range.start = (unsigned long)area_dst_alias; - uffdio_register.range.len = nr_pages * page_size; - uffdio_register.mode = UFFDIO_REGISTER_MODE_MINOR; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) - err("register failure"); - - assert_expected_ioctls_present( - uffdio_register.mode, uffdio_register.ioctls); - - /* - * After registering with UFFD, populate the non-UFFD-registered side of - * the shared mapping. This should *not* trigger any UFFD minor faults. - */ - for (p = 0; p < nr_pages; ++p) { - memset(area_dst + (p * page_size), p % ((uint8_t)-1), - page_size); - } - - if (pthread_create(&uffd_mon, &attr, uffd_poll_thread, &stats)) - err("uffd_poll_thread create"); - - /* - * Read each of the pages back using the UFFD-registered mapping. We - * expect that the first time we touch a page, it will result in a minor - * fault. uffd_poll_thread will resolve the fault by bit-flipping the - * page's contents, and then issuing a CONTINUE ioctl. - */ - check_memory_contents(area_dst_alias); - - if (write(pipefd[1], &c, sizeof(c)) != sizeof(c)) - err("pipe write"); - if (pthread_join(uffd_mon, NULL)) - return 1; - - uffd_stats_report(&stats, 1); - - if (test_collapse) { - printf("testing collapse of uffd memory into PMD-mapped THPs:"); - if (madvise(area_dst_alias, nr_pages * page_size, - MADV_COLLAPSE)) - err("madvise(MADV_COLLAPSE)"); - - uffd_test_ops->check_pmd_mapping(area_dst, - nr_pages * page_size / - hpage_size); - /* - * This won't cause uffd-fault - it purely just makes sure there - * was no corruption. - */ - check_memory_contents(area_dst_alias); - printf(" done.\n"); - } - - return stats.missing_faults != 0 || stats.minor_faults != nr_pages; -} - -#define BIT_ULL(nr) (1ULL << (nr)) -#define PM_SOFT_DIRTY BIT_ULL(55) -#define PM_MMAP_EXCLUSIVE BIT_ULL(56) -#define PM_UFFD_WP BIT_ULL(57) -#define PM_FILE BIT_ULL(61) -#define PM_SWAP BIT_ULL(62) -#define PM_PRESENT BIT_ULL(63) - -static int pagemap_open(void) -{ - int fd = open("/proc/self/pagemap", O_RDONLY); - - if (fd < 0) - err("open pagemap"); - - return fd; -} - -static uint64_t pagemap_read_vaddr(int fd, void *vaddr) -{ - uint64_t value; - int ret; - - ret = pread(fd, &value, sizeof(uint64_t), - ((uint64_t)vaddr >> 12) * sizeof(uint64_t)); - if (ret != sizeof(uint64_t)) - err("pread() on pagemap failed"); - - return value; -} - -/* This macro let __LINE__ works in err() */ -#define pagemap_check_wp(value, wp) do { \ - if (!!(value & PM_UFFD_WP) != wp) \ - err("pagemap uffd-wp bit error: 0x%"PRIx64, value); \ - } while (0) - -static int pagemap_test_fork(bool present) -{ - pid_t child = fork(); - uint64_t value; - int fd, result; - - if (!child) { - /* Open the pagemap fd of the child itself */ - fd = pagemap_open(); - value = pagemap_read_vaddr(fd, area_dst); - /* - * After fork() uffd-wp bit should be gone as long as we're - * without UFFD_FEATURE_EVENT_FORK - */ - pagemap_check_wp(value, false); - /* Succeed */ - exit(0); - } - waitpid(child, &result, 0); - return result; -} - -static void userfaultfd_pagemap_test(unsigned int test_pgsize) -{ - struct uffdio_register uffdio_register; - int pagemap_fd; - uint64_t value; - - /* Pagemap tests uffd-wp only */ - if (!test_uffdio_wp) - return; - - /* Not enough memory to test this page size */ - if (test_pgsize > nr_pages * page_size) - return; - - printf("testing uffd-wp with pagemap (pgsize=%u): ", test_pgsize); - /* Flush so it doesn't flush twice in parent/child later */ - fflush(stdout); - - uffd_test_ctx_init(0); - - if (test_pgsize > page_size) { - /* This is a thp test */ - if (madvise(area_dst, nr_pages * page_size, MADV_HUGEPAGE)) - err("madvise(MADV_HUGEPAGE) failed"); - } else if (test_pgsize == page_size) { - /* This is normal page test; force no thp */ - if (madvise(area_dst, nr_pages * page_size, MADV_NOHUGEPAGE)) - err("madvise(MADV_NOHUGEPAGE) failed"); - } - - uffdio_register.range.start = (unsigned long) area_dst; - uffdio_register.range.len = nr_pages * page_size; - uffdio_register.mode = UFFDIO_REGISTER_MODE_WP; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) - err("register failed"); - - pagemap_fd = pagemap_open(); - - /* Touch the page */ - *area_dst = 1; - wp_range(uffd, (uint64_t)area_dst, test_pgsize, true); - value = pagemap_read_vaddr(pagemap_fd, area_dst); - pagemap_check_wp(value, true); - /* Make sure uffd-wp bit dropped when fork */ - if (pagemap_test_fork(true)) - err("Detected stall uffd-wp bit in child"); - - /* Exclusive required or PAGEOUT won't work */ - if (!(value & PM_MMAP_EXCLUSIVE)) - err("multiple mapping detected: 0x%"PRIx64, value); - - if (madvise(area_dst, test_pgsize, MADV_PAGEOUT)) - err("madvise(MADV_PAGEOUT) failed"); - - /* Uffd-wp should persist even swapped out */ - value = pagemap_read_vaddr(pagemap_fd, area_dst); - pagemap_check_wp(value, true); - /* Make sure uffd-wp bit dropped when fork */ - if (pagemap_test_fork(false)) - err("Detected stall uffd-wp bit in child"); - - /* Unprotect; this tests swap pte modifications */ - wp_range(uffd, (uint64_t)area_dst, page_size, false); - value = pagemap_read_vaddr(pagemap_fd, area_dst); - pagemap_check_wp(value, false); - - /* Fault in the page from disk */ - *area_dst = 2; - value = pagemap_read_vaddr(pagemap_fd, area_dst); - pagemap_check_wp(value, false); - - close(pagemap_fd); - printf("done\n"); -} - -static int userfaultfd_stress(void) -{ - void *area; - unsigned long nr; - struct uffdio_register uffdio_register; - struct uffd_stats uffd_stats[nr_cpus]; - - uffd_test_ctx_init(0); - - if (posix_memalign(&area, page_size, page_size)) - err("out of memory"); - zeropage = area; - bzero(zeropage, page_size); - - pthread_mutex_lock(&uffd_read_mutex); - - pthread_attr_init(&attr); - pthread_attr_setstacksize(&attr, 16*1024*1024); - - while (bounces--) { - printf("bounces: %d, mode:", bounces); - if (bounces & BOUNCE_RANDOM) - printf(" rnd"); - if (bounces & BOUNCE_RACINGFAULTS) - printf(" racing"); - if (bounces & BOUNCE_VERIFY) - printf(" ver"); - if (bounces & BOUNCE_POLL) - printf(" poll"); - else - printf(" read"); - printf(", "); - fflush(stdout); - - if (bounces & BOUNCE_POLL) - fcntl(uffd, F_SETFL, uffd_flags | O_NONBLOCK); - else - fcntl(uffd, F_SETFL, uffd_flags & ~O_NONBLOCK); - - /* register */ - uffdio_register.range.start = (unsigned long) area_dst; - uffdio_register.range.len = nr_pages * page_size; - uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; - if (test_uffdio_wp) - uffdio_register.mode |= UFFDIO_REGISTER_MODE_WP; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) - err("register failure"); - assert_expected_ioctls_present( - uffdio_register.mode, uffdio_register.ioctls); - - if (area_dst_alias) { - uffdio_register.range.start = (unsigned long) - area_dst_alias; - if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) - err("register failure alias"); - } - - /* - * The madvise done previously isn't enough: some - * uffd_thread could have read userfaults (one of - * those already resolved by the background thread) - * and it may be in the process of calling - * UFFDIO_COPY. UFFDIO_COPY will read the zapped - * area_src and it would map a zero page in it (of - * course such a UFFDIO_COPY is perfectly safe as it'd - * return -EEXIST). The problem comes at the next - * bounce though: that racing UFFDIO_COPY would - * generate zeropages in the area_src, so invalidating - * the previous MADV_DONTNEED. Without this additional - * MADV_DONTNEED those zeropages leftovers in the - * area_src would lead to -EEXIST failure during the - * next bounce, effectively leaving a zeropage in the - * area_dst. - * - * Try to comment this out madvise to see the memory - * corruption being caught pretty quick. - * - * khugepaged is also inhibited to collapse THP after - * MADV_DONTNEED only after the UFFDIO_REGISTER, so it's - * required to MADV_DONTNEED here. - */ - uffd_test_ops->release_pages(area_dst); - - uffd_stats_reset(uffd_stats, nr_cpus); - - /* bounce pass */ - if (stress(uffd_stats)) - return 1; - - /* Clear all the write protections if there is any */ - if (test_uffdio_wp) - wp_range(uffd, (unsigned long)area_dst, - nr_pages * page_size, false); - - /* unregister */ - if (ioctl(uffd, UFFDIO_UNREGISTER, &uffdio_register.range)) - err("unregister failure"); - if (area_dst_alias) { - uffdio_register.range.start = (unsigned long) area_dst; - if (ioctl(uffd, UFFDIO_UNREGISTER, - &uffdio_register.range)) - err("unregister failure alias"); - } - - /* verification */ - if (bounces & BOUNCE_VERIFY) - for (nr = 0; nr < nr_pages; nr++) - if (*area_count(area_dst, nr) != count_verify[nr]) - err("error area_count %llu %llu %lu\n", - *area_count(area_src, nr), - count_verify[nr], nr); - - /* prepare next bounce */ - swap(area_src, area_dst); - - swap(area_src_alias, area_dst_alias); - - uffd_stats_report(uffd_stats, nr_cpus); - } - - if (test_type == TEST_ANON) { - /* - * shmem/hugetlb won't be able to run since they have different - * behavior on fork() (file-backed memory normally drops ptes - * directly when fork), meanwhile the pagemap test will verify - * pgtable entry of fork()ed child. - */ - userfaultfd_pagemap_test(page_size); - /* - * Hard-code for x86_64 for now for 2M THP, as x86_64 is - * currently the only one that supports uffd-wp - */ - userfaultfd_pagemap_test(page_size * 512); - } - - return userfaultfd_zeropage_test() || userfaultfd_sig_test() - || userfaultfd_events_test() || userfaultfd_minor_test(); -} - -/* - * Copied from mlock2-tests.c - */ -unsigned long default_huge_page_size(void) -{ - unsigned long hps = 0; - char *line = NULL; - size_t linelen = 0; - FILE *f = fopen("/proc/meminfo", "r"); - - if (!f) - return 0; - while (getline(&line, &linelen, f) > 0) { - if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) { - hps <<= 10; - break; - } - } - - free(line); - fclose(f); - return hps; -} - -static void set_test_type(const char *type) -{ - if (!strcmp(type, "anon")) { - test_type = TEST_ANON; - uffd_test_ops = &anon_uffd_test_ops; - } else if (!strcmp(type, "hugetlb")) { - test_type = TEST_HUGETLB; - uffd_test_ops = &hugetlb_uffd_test_ops; - } else if (!strcmp(type, "hugetlb_shared")) { - map_shared = true; - test_type = TEST_HUGETLB; - uffd_test_ops = &hugetlb_uffd_test_ops; - /* Minor faults require shared hugetlb; only enable here. */ - test_uffdio_minor = true; - } else if (!strcmp(type, "shmem")) { - map_shared = true; - test_type = TEST_SHMEM; - uffd_test_ops = &shmem_uffd_test_ops; - test_uffdio_minor = true; - } -} - -static void parse_test_type_arg(const char *raw_type) -{ - char *buf = strdup(raw_type); - uint64_t features = UFFD_API_FEATURES; - - while (buf) { - const char *token = strsep(&buf, ":"); - - if (!test_type) - set_test_type(token); - else if (!strcmp(token, "dev")) - test_dev_userfaultfd = true; - else if (!strcmp(token, "syscall")) - test_dev_userfaultfd = false; - else if (!strcmp(token, "collapse")) - test_collapse = true; - else - err("unrecognized test mod '%s'", token); - } - - if (!test_type) - err("failed to parse test type argument: '%s'", raw_type); - - if (test_collapse && test_type != TEST_SHMEM) - err("Unsupported test: %s", raw_type); - - if (test_type == TEST_HUGETLB) - page_size = hpage_size; - else - page_size = sysconf(_SC_PAGE_SIZE); - - if (!page_size) - err("Unable to determine page size"); - if ((unsigned long) area_count(NULL, 0) + sizeof(unsigned long long) * 2 - > page_size) - err("Impossible to run this test"); - - /* - * Whether we can test certain features depends not just on test type, - * but also on whether or not this particular kernel supports the - * feature. - */ - - userfaultfd_open(&features); - - test_uffdio_wp = test_uffdio_wp && - (features & UFFD_FEATURE_PAGEFAULT_FLAG_WP); - test_uffdio_minor = test_uffdio_minor && - (features & uffd_minor_feature()); - - close(uffd); - uffd = -1; -} - -static void sigalrm(int sig) -{ - if (sig != SIGALRM) - abort(); - test_uffdio_copy_eexist = true; - test_uffdio_zeropage_eexist = true; - alarm(ALARM_INTERVAL_SECS); -} - -int main(int argc, char **argv) -{ - size_t bytes; - - if (argc < 4) - usage(); - - if (signal(SIGALRM, sigalrm) == SIG_ERR) - err("failed to arm SIGALRM"); - alarm(ALARM_INTERVAL_SECS); - - hpage_size = default_huge_page_size(); - parse_test_type_arg(argv[1]); - bytes = atol(argv[2]) * 1024 * 1024; - - if (test_collapse && bytes & (hpage_size - 1)) - err("MiB must be multiple of %lu if :collapse mod set", - hpage_size >> 20); - - nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); - - if (test_collapse) { - /* nr_cpus must divide (bytes / page_size), otherwise, - * area allocations of (nr_pages * paze_size) won't be a - * multiple of hpage_size, even if bytes is a multiple of - * hpage_size. - * - * This means that nr_cpus must divide (N * (2 << (H-P)) - * where: - * bytes = hpage_size * N - * hpage_size = 2 << H - * page_size = 2 << P - * - * And we want to chose nr_cpus to be the largest value - * satisfying this constraint, not larger than the number - * of online CPUs. Unfortunately, prime factorization of - * N and nr_cpus may be arbitrary, so have to search for it. - * Instead, just use the highest power of 2 dividing both - * nr_cpus and (bytes / page_size). - */ - int x = factor_of_2(nr_cpus); - int y = factor_of_2(bytes / page_size); - - nr_cpus = x < y ? x : y; - } - nr_pages_per_cpu = bytes / page_size / nr_cpus; - if (!nr_pages_per_cpu) { - _err("invalid MiB"); - usage(); - } - - bounces = atoi(argv[3]); - if (bounces <= 0) { - _err("invalid bounces"); - usage(); - } - nr_pages = nr_pages_per_cpu * nr_cpus; - - if (test_type == TEST_SHMEM || test_type == TEST_HUGETLB) { - unsigned int memfd_flags = 0; - - if (test_type == TEST_HUGETLB) - memfd_flags = MFD_HUGETLB; - mem_fd = memfd_create(argv[0], memfd_flags); - if (mem_fd < 0) - err("memfd_create"); - if (ftruncate(mem_fd, nr_pages * page_size * 2)) - err("ftruncate"); - if (fallocate(mem_fd, - FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, - nr_pages * page_size * 2)) - err("fallocate"); - } - printf("nr_pages: %lu, nr_pages_per_cpu: %lu\n", - nr_pages, nr_pages_per_cpu); - return userfaultfd_stress(); -} - -#else /* __NR_userfaultfd */ - -#warning "missing __NR_userfaultfd definition" - -int main(void) -{ - printf("skip: Skipping userfaultfd test (missing __NR_userfaultfd)\n"); - return KSFT_SKIP; -} - -#endif /* __NR_userfaultfd */ diff --git a/tools/testing/selftests/mm/util.h b/tools/testing/selftests/mm/util.h deleted file mode 100644 index b27d26199334..000000000000 --- a/tools/testing/selftests/mm/util.h +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef __KSELFTEST_VM_UTIL_H -#define __KSELFTEST_VM_UTIL_H - -#include <stdint.h> -#include <sys/mman.h> -#include <err.h> -#include <string.h> /* ffsl() */ -#include <unistd.h> /* _SC_PAGESIZE */ - -static unsigned int __page_size; -static unsigned int __page_shift; - -static inline unsigned int page_size(void) -{ - if (!__page_size) - __page_size = sysconf(_SC_PAGESIZE); - return __page_size; -} - -static inline unsigned int page_shift(void) -{ - if (!__page_shift) - __page_shift = (ffsl(page_size()) - 1); - return __page_shift; -} - -#define PAGE_SHIFT (page_shift()) -#define PAGE_SIZE (page_size()) -/* - * On ppc64 this will only work with radix 2M hugepage size - */ -#define HPAGE_SHIFT 21 -#define HPAGE_SIZE (1 << HPAGE_SHIFT) - -#define PAGEMAP_PRESENT(ent) (((ent) & (1ull << 63)) != 0) -#define PAGEMAP_PFN(ent) ((ent) & ((1ull << 55) - 1)) - - -static inline int64_t allocate_transhuge(void *ptr, int pagemap_fd) -{ - uint64_t ent[2]; - - /* drop pmd */ - if (mmap(ptr, HPAGE_SIZE, PROT_READ | PROT_WRITE, - MAP_FIXED | MAP_ANONYMOUS | - MAP_NORESERVE | MAP_PRIVATE, -1, 0) != ptr) - errx(2, "mmap transhuge"); - - if (madvise(ptr, HPAGE_SIZE, MADV_HUGEPAGE)) - err(2, "MADV_HUGEPAGE"); - - /* allocate transparent huge page */ - *(volatile void **)ptr = ptr; - - if (pread(pagemap_fd, ent, sizeof(ent), - (uintptr_t)ptr >> (PAGE_SHIFT - 3)) != sizeof(ent)) - err(2, "read pagemap"); - - if (PAGEMAP_PRESENT(ent[0]) && PAGEMAP_PRESENT(ent[1]) && - PAGEMAP_PFN(ent[0]) + 1 == PAGEMAP_PFN(ent[1]) && - !(PAGEMAP_PFN(ent[0]) & ((1 << (HPAGE_SHIFT - PAGE_SHIFT)) - 1))) - return PAGEMAP_PFN(ent[0]); - - return -1; -} - -#endif diff --git a/tools/testing/selftests/mm/va_128TBswitch.c b/tools/testing/selftests/mm/va_high_addr_switch.c index 1d2068989883..7cfaf4a74c57 100644 --- a/tools/testing/selftests/mm/va_128TBswitch.c +++ b/tools/testing/selftests/mm/va_high_addr_switch.c @@ -17,18 +17,38 @@ * This will work with 16M and 2M hugepage size */ #define HUGETLB_SIZE (16 << 20) +#elif __aarch64__ +/* + * The default hugepage size for 64k base pagesize + * is 512MB. + */ +#define PAGE_SIZE (64 << 10) +#define HUGETLB_SIZE (512 << 20) #else #define PAGE_SIZE (4 << 10) #define HUGETLB_SIZE (2 << 20) #endif /* - * >= 128TB is the hint addr value we used to select - * large address space. + * The hint addr value is used to allocate addresses + * beyond the high address switch boundary. */ -#define ADDR_SWITCH_HINT (1UL << 47) + +#define ADDR_MARK_128TB (1UL << 47) +#define ADDR_MARK_256TB (1UL << 48) + +#define HIGH_ADDR_128TB ((void *) (1UL << 48)) +#define HIGH_ADDR_256TB ((void *) (1UL << 49)) + #define LOW_ADDR ((void *) (1UL << 30)) -#define HIGH_ADDR ((void *) (1UL << 48)) + +#ifdef __aarch64__ +#define ADDR_SWITCH_HINT ADDR_MARK_256TB +#define HIGH_ADDR HIGH_ADDR_256TB +#else +#define ADDR_SWITCH_HINT ADDR_MARK_128TB +#define HIGH_ADDR HIGH_ADDR_128TB +#endif struct testcase { void *addr; @@ -53,9 +73,10 @@ static struct testcase testcases[] = { }, { /* - * We should never allocate at the requested address or above it - * The len cross the 128TB boundary. Without MAP_FIXED - * we will always search in the lower address space. + * Unless MAP_FIXED is specified, allocation based on hint + * addr is never at requested address or above it, which is + * beyond high address switch boundary in this case. Instead, + * a suitable allocation is found in lower address space. */ .addr = ((void *)(ADDR_SWITCH_HINT - PAGE_SIZE)), .size = 2 * PAGE_SIZE, @@ -65,8 +86,8 @@ static struct testcase testcases[] = { }, { /* - * Exact mapping at 128TB, the area is free we should get that - * even without MAP_FIXED. + * Exact mapping at high address switch boundary, should + * be obtained even without MAP_FIXED as area is free. */ .addr = ((void *)(ADDR_SWITCH_HINT)), .size = PAGE_SIZE, @@ -270,6 +291,8 @@ static int supported_arch(void) return 1; #elif defined(__x86_64__) return 1; +#elif defined(__aarch64__) + return 1; #else return 0; #endif diff --git a/tools/testing/selftests/mm/va_128TBswitch.sh b/tools/testing/selftests/mm/va_high_addr_switch.sh index 41580751dc51..45cae7cab27e 100644 --- a/tools/testing/selftests/mm/va_128TBswitch.sh +++ b/tools/testing/selftests/mm/va_high_addr_switch.sh @@ -51,4 +51,8 @@ check_test_requirements() } check_test_requirements -./va_128TBswitch +./va_high_addr_switch + +# In order to run hugetlb testcases, "--run-hugetlb" must be appended +# to the binary. +./va_high_addr_switch --run-hugetlb diff --git a/tools/testing/selftests/mm/virtual_address_range.c b/tools/testing/selftests/mm/virtual_address_range.c index c0592646ed93..bae0ceaf95b1 100644 --- a/tools/testing/selftests/mm/virtual_address_range.c +++ b/tools/testing/selftests/mm/virtual_address_range.c @@ -15,11 +15,15 @@ /* * Maximum address range mapped with a single mmap() - * call is little bit more than 16GB. Hence 16GB is + * call is little bit more than 1GB. Hence 1GB is * chosen as the single chunk size for address space * mapping. */ -#define MAP_CHUNK_SIZE 17179869184UL /* 16GB */ + +#define SZ_1GB (1024 * 1024 * 1024UL) +#define SZ_1TB (1024 * 1024 * 1024 * 1024UL) + +#define MAP_CHUNK_SIZE SZ_1GB /* * Address space till 128TB is mapped without any hint @@ -32,13 +36,15 @@ * till it reaches 512TB. One with size 128TB and the * other being 384TB. * - * On Arm64 the address space is 256TB and no high mappings - * are supported so far. + * On Arm64 the address space is 256TB and support for + * high mappings up to 4PB virtual address space has + * been added. */ -#define NR_CHUNKS_128TB 8192UL /* Number of 16GB chunks for 128TB */ +#define NR_CHUNKS_128TB ((128 * SZ_1TB) / MAP_CHUNK_SIZE) /* Number of chunks for 128TB */ #define NR_CHUNKS_256TB (NR_CHUNKS_128TB * 2UL) #define NR_CHUNKS_384TB (NR_CHUNKS_128TB * 3UL) +#define NR_CHUNKS_3840TB (NR_CHUNKS_128TB * 30UL) #define ADDR_MARK_128TB (1UL << 47) /* First address beyond 128TB */ #define ADDR_MARK_256TB (1UL << 48) /* First address beyond 256TB */ @@ -47,7 +53,7 @@ #define HIGH_ADDR_MARK ADDR_MARK_256TB #define HIGH_ADDR_SHIFT 49 #define NR_CHUNKS_LOW NR_CHUNKS_256TB -#define NR_CHUNKS_HIGH 0 +#define NR_CHUNKS_HIGH NR_CHUNKS_3840TB #else #define HIGH_ADDR_MARK ADDR_MARK_128TB #define HIGH_ADDR_SHIFT 48 @@ -97,7 +103,7 @@ static int validate_lower_address_hint(void) int main(int argc, char *argv[]) { char *ptr[NR_CHUNKS_LOW]; - char *hptr[NR_CHUNKS_HIGH]; + char **hptr; char *hint; unsigned long i, lchunks, hchunks; @@ -115,6 +121,9 @@ int main(int argc, char *argv[]) return 1; } lchunks = i; + hptr = (char **) calloc(NR_CHUNKS_HIGH, sizeof(char *)); + if (hptr == NULL) + return 1; for (i = 0; i < NR_CHUNKS_HIGH; i++) { hint = hind_addr(); @@ -135,5 +144,6 @@ int main(int argc, char *argv[]) for (i = 0; i < hchunks; i++) munmap(hptr[i], MAP_CHUNK_SIZE); + free(hptr); return 0; } diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c index 40e795624ff3..9b06a5034808 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 #include <string.h> #include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/userfaultfd.h> +#include <sys/syscall.h> +#include <unistd.h> #include "../kselftest.h" #include "vm_util.h" @@ -8,6 +12,9 @@ #define SMAP_FILE_PATH "/proc/self/smaps" #define MAX_LINE_LENGTH 500 +unsigned int __page_size; +unsigned int __page_shift; + uint64_t pagemap_get_entry(int fd, char *start) { const unsigned long pfn = (unsigned long)start / getpagesize(); @@ -22,25 +29,17 @@ uint64_t pagemap_get_entry(int fd, char *start) bool pagemap_is_softdirty(int fd, char *start) { - uint64_t entry = pagemap_get_entry(fd, start); - - // Check if dirty bit (55th bit) is set - return entry & 0x0080000000000000ull; + return pagemap_get_entry(fd, start) & PM_SOFT_DIRTY; } bool pagemap_is_swapped(int fd, char *start) { - uint64_t entry = pagemap_get_entry(fd, start); - - return entry & 0x4000000000000000ull; + return pagemap_get_entry(fd, start) & PM_SWAP; } bool pagemap_is_populated(int fd, char *start) { - uint64_t entry = pagemap_get_entry(fd, start); - - /* Present or swapped. */ - return entry & 0xc000000000000000ull; + return pagemap_get_entry(fd, start) & (PM_PRESENT | PM_SWAP); } unsigned long pagemap_get_pfn(int fd, char *start) @@ -48,7 +47,7 @@ unsigned long pagemap_get_pfn(int fd, char *start) uint64_t entry = pagemap_get_entry(fd, start); /* If present (63th bit), PFN is at bit 0 -- 54. */ - if (entry & 0x8000000000000000ull) + if (entry & PM_PRESENT) return entry & 0x007fffffffffffffull; return -1ul; } @@ -84,12 +83,12 @@ uint64_t read_pmd_pagesize(void) fd = open(PMD_SIZE_FILE_PATH, O_RDONLY); if (fd == -1) - ksft_exit_fail_msg("Open hpage_pmd_size failed\n"); + return 0; num_read = read(fd, buf, 19); if (num_read < 1) { close(fd); - ksft_exit_fail_msg("Read hpage_pmd_size failed\n"); + return 0; } buf[num_read] = '\0'; close(fd); @@ -149,3 +148,156 @@ bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size) { return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); } + +int64_t allocate_transhuge(void *ptr, int pagemap_fd) +{ + uint64_t ent[2]; + + /* drop pmd */ + if (mmap(ptr, HPAGE_SIZE, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_ANONYMOUS | + MAP_NORESERVE | MAP_PRIVATE, -1, 0) != ptr) + errx(2, "mmap transhuge"); + + if (madvise(ptr, HPAGE_SIZE, MADV_HUGEPAGE)) + err(2, "MADV_HUGEPAGE"); + + /* allocate transparent huge page */ + *(volatile void **)ptr = ptr; + + if (pread(pagemap_fd, ent, sizeof(ent), + (uintptr_t)ptr >> (pshift() - 3)) != sizeof(ent)) + err(2, "read pagemap"); + + if (PAGEMAP_PRESENT(ent[0]) && PAGEMAP_PRESENT(ent[1]) && + PAGEMAP_PFN(ent[0]) + 1 == PAGEMAP_PFN(ent[1]) && + !(PAGEMAP_PFN(ent[0]) & ((1 << (HPAGE_SHIFT - pshift())) - 1))) + return PAGEMAP_PFN(ent[0]); + + return -1; +} + +unsigned long default_huge_page_size(void) +{ + unsigned long hps = 0; + char *line = NULL; + size_t linelen = 0; + FILE *f = fopen("/proc/meminfo", "r"); + + if (!f) + return 0; + while (getline(&line, &linelen, f) > 0) { + if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) { + hps <<= 10; + break; + } + } + + free(line); + fclose(f); + return hps; +} + +/* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */ +int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len, + bool miss, bool wp, bool minor, uint64_t *ioctls) +{ + struct uffdio_register uffdio_register = { 0 }; + uint64_t mode = 0; + int ret = 0; + + if (miss) + mode |= UFFDIO_REGISTER_MODE_MISSING; + if (wp) + mode |= UFFDIO_REGISTER_MODE_WP; + if (minor) + mode |= UFFDIO_REGISTER_MODE_MINOR; + + uffdio_register.range.start = (unsigned long)addr; + uffdio_register.range.len = len; + uffdio_register.mode = mode; + + if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) + ret = -errno; + else if (ioctls) + *ioctls = uffdio_register.ioctls; + + return ret; +} + +int uffd_register(int uffd, void *addr, uint64_t len, + bool miss, bool wp, bool minor) +{ + return uffd_register_with_ioctls(uffd, addr, len, + miss, wp, minor, NULL); +} + +int uffd_unregister(int uffd, void *addr, uint64_t len) +{ + struct uffdio_range range = { .start = (uintptr_t)addr, .len = len }; + int ret = 0; + + if (ioctl(uffd, UFFDIO_UNREGISTER, &range) == -1) + ret = -errno; + + return ret; +} + +int uffd_open_dev(unsigned int flags) +{ + int fd, uffd; + + fd = open("/dev/userfaultfd", O_RDWR | O_CLOEXEC); + if (fd < 0) + return fd; + uffd = ioctl(fd, USERFAULTFD_IOC_NEW, flags); + close(fd); + + return uffd; +} + +int uffd_open_sys(unsigned int flags) +{ +#ifdef __NR_userfaultfd + return syscall(__NR_userfaultfd, flags); +#else + return -1; +#endif +} + +int uffd_open(unsigned int flags) +{ + int uffd = uffd_open_sys(flags); + + if (uffd < 0) + uffd = uffd_open_dev(flags); + + return uffd; +} + +int uffd_get_features(uint64_t *features) +{ + struct uffdio_api uffdio_api = { .api = UFFD_API, .features = 0 }; + /* + * This should by default work in most kernels; the feature list + * will be the same no matter what we pass in here. + */ + int fd = uffd_open(UFFD_USER_MODE_ONLY); + + if (fd < 0) + /* Maybe the kernel is older than user-only mode? */ + fd = uffd_open(0); + + if (fd < 0) + return fd; + + if (ioctl(fd, UFFDIO_API, &uffdio_api)) { + close(fd); + return -errno; + } + + *features = uffdio_api.features; + close(fd); + + return 0; +} diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h index 1995ee911ef2..b950bd16083a 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -1,6 +1,35 @@ /* SPDX-License-Identifier: GPL-2.0 */ #include <stdint.h> #include <stdbool.h> +#include <sys/mman.h> +#include <err.h> +#include <string.h> /* ffsl() */ +#include <unistd.h> /* _SC_PAGESIZE */ + +#define BIT_ULL(nr) (1ULL << (nr)) +#define PM_SOFT_DIRTY BIT_ULL(55) +#define PM_MMAP_EXCLUSIVE BIT_ULL(56) +#define PM_UFFD_WP BIT_ULL(57) +#define PM_FILE BIT_ULL(61) +#define PM_SWAP BIT_ULL(62) +#define PM_PRESENT BIT_ULL(63) + +extern unsigned int __page_size; +extern unsigned int __page_shift; + +static inline unsigned int psize(void) +{ + if (!__page_size) + __page_size = sysconf(_SC_PAGESIZE); + return __page_size; +} + +static inline unsigned int pshift(void) +{ + if (!__page_shift) + __page_shift = (ffsl(psize()) - 1); + return __page_shift; +} uint64_t pagemap_get_entry(int fd, char *start); bool pagemap_is_softdirty(int fd, char *start); @@ -13,3 +42,24 @@ uint64_t read_pmd_pagesize(void); bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size); bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size); bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size); +int64_t allocate_transhuge(void *ptr, int pagemap_fd); +unsigned long default_huge_page_size(void); + +int uffd_register(int uffd, void *addr, uint64_t len, + bool miss, bool wp, bool minor); +int uffd_unregister(int uffd, void *addr, uint64_t len); +int uffd_open_dev(unsigned int flags); +int uffd_open_sys(unsigned int flags); +int uffd_open(unsigned int flags); +int uffd_get_features(uint64_t *features); +int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len, + bool miss, bool wp, bool minor, uint64_t *ioctls); + +/* + * On ppc64 this will only work with radix 2M hugepage size + */ +#define HPAGE_SHIFT 21 +#define HPAGE_SIZE (1 << HPAGE_SHIFT) + +#define PAGEMAP_PRESENT(ent) (((ent) & (1ull << 63)) != 0) +#define PAGEMAP_PFN(ent) ((ent) & ((1ull << 55) - 1)) diff --git a/tools/testing/selftests/nolibc/Makefile b/tools/testing/selftests/nolibc/Makefile index 8fe61d3e3cce..bbce57420465 100644 --- a/tools/testing/selftests/nolibc/Makefile +++ b/tools/testing/selftests/nolibc/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for nolibc tests include ../../../scripts/Makefile.include +# We need this for the "cc-option" macro. +include ../../../build/Build.include # we're in ".../tools/testing/selftests/nolibc" ifeq ($(srctree),) @@ -13,52 +15,56 @@ ARCH = $(SUBARCH) endif # kernel image names by architecture -IMAGE_i386 = arch/x86/boot/bzImage -IMAGE_x86_64 = arch/x86/boot/bzImage -IMAGE_x86 = arch/x86/boot/bzImage -IMAGE_arm64 = arch/arm64/boot/Image -IMAGE_arm = arch/arm/boot/zImage -IMAGE_mips = vmlinuz -IMAGE_riscv = arch/riscv/boot/Image -IMAGE_s390 = arch/s390/boot/bzImage -IMAGE = $(IMAGE_$(ARCH)) -IMAGE_NAME = $(notdir $(IMAGE)) +IMAGE_i386 = arch/x86/boot/bzImage +IMAGE_x86_64 = arch/x86/boot/bzImage +IMAGE_x86 = arch/x86/boot/bzImage +IMAGE_arm64 = arch/arm64/boot/Image +IMAGE_arm = arch/arm/boot/zImage +IMAGE_mips = vmlinuz +IMAGE_riscv = arch/riscv/boot/Image +IMAGE_s390 = arch/s390/boot/bzImage +IMAGE_loongarch = arch/loongarch/boot/vmlinuz.efi +IMAGE = $(IMAGE_$(ARCH)) +IMAGE_NAME = $(notdir $(IMAGE)) # default kernel configurations that appear to be usable -DEFCONFIG_i386 = defconfig -DEFCONFIG_x86_64 = defconfig -DEFCONFIG_x86 = defconfig -DEFCONFIG_arm64 = defconfig -DEFCONFIG_arm = multi_v7_defconfig -DEFCONFIG_mips = malta_defconfig -DEFCONFIG_riscv = defconfig -DEFCONFIG_s390 = defconfig -DEFCONFIG = $(DEFCONFIG_$(ARCH)) +DEFCONFIG_i386 = defconfig +DEFCONFIG_x86_64 = defconfig +DEFCONFIG_x86 = defconfig +DEFCONFIG_arm64 = defconfig +DEFCONFIG_arm = multi_v7_defconfig +DEFCONFIG_mips = malta_defconfig +DEFCONFIG_riscv = defconfig +DEFCONFIG_s390 = defconfig +DEFCONFIG_loongarch = defconfig +DEFCONFIG = $(DEFCONFIG_$(ARCH)) # optional tests to run (default = all) TEST = # QEMU_ARCH: arch names used by qemu -QEMU_ARCH_i386 = i386 -QEMU_ARCH_x86_64 = x86_64 -QEMU_ARCH_x86 = x86_64 -QEMU_ARCH_arm64 = aarch64 -QEMU_ARCH_arm = arm -QEMU_ARCH_mips = mipsel # works with malta_defconfig -QEMU_ARCH_riscv = riscv64 -QEMU_ARCH_s390 = s390x -QEMU_ARCH = $(QEMU_ARCH_$(ARCH)) +QEMU_ARCH_i386 = i386 +QEMU_ARCH_x86_64 = x86_64 +QEMU_ARCH_x86 = x86_64 +QEMU_ARCH_arm64 = aarch64 +QEMU_ARCH_arm = arm +QEMU_ARCH_mips = mipsel # works with malta_defconfig +QEMU_ARCH_riscv = riscv64 +QEMU_ARCH_s390 = s390x +QEMU_ARCH_loongarch = loongarch64 +QEMU_ARCH = $(QEMU_ARCH_$(ARCH)) # QEMU_ARGS : some arch-specific args to pass to qemu -QEMU_ARGS_i386 = -M pc -append "console=ttyS0,9600 i8042.noaux panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS_x86_64 = -M pc -append "console=ttyS0,9600 i8042.noaux panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS_x86 = -M pc -append "console=ttyS0,9600 i8042.noaux panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS_arm64 = -M virt -cpu cortex-a53 -append "panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS_arm = -M virt -append "panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS_mips = -M malta -append "panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS_riscv = -M virt -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS_s390 = -M s390-ccw-virtio -m 1G -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" -QEMU_ARGS = $(QEMU_ARGS_$(ARCH)) +QEMU_ARGS_i386 = -M pc -append "console=ttyS0,9600 i8042.noaux panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_x86_64 = -M pc -append "console=ttyS0,9600 i8042.noaux panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_x86 = -M pc -append "console=ttyS0,9600 i8042.noaux panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_arm64 = -M virt -cpu cortex-a53 -append "panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_arm = -M virt -append "panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_mips = -M malta -append "panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_riscv = -M virt -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_s390 = -M s390-ccw-virtio -m 1G -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_loongarch = -M virt -append "console=ttyS0,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS = $(QEMU_ARGS_$(ARCH)) # OUTPUT is only set when run from the main makefile, otherwise # it defaults to this nolibc directory. @@ -70,8 +76,16 @@ else Q=@ endif +CFLAGS_STACKPROTECTOR = -DNOLIBC_STACKPROTECTOR \ + $(call cc-option,-mstack-protector-guard=global) \ + $(call cc-option,-fstack-protector-all) +CFLAGS_STKP_i386 = $(CFLAGS_STACKPROTECTOR) +CFLAGS_STKP_x86_64 = $(CFLAGS_STACKPROTECTOR) +CFLAGS_STKP_x86 = $(CFLAGS_STACKPROTECTOR) CFLAGS_s390 = -m64 -CFLAGS ?= -Os -fno-ident -fno-asynchronous-unwind-tables $(CFLAGS_$(ARCH)) +CFLAGS ?= -Os -fno-ident -fno-asynchronous-unwind-tables \ + $(call cc-option,-fno-stack-protector) \ + $(CFLAGS_STKP_$(ARCH)) $(CFLAGS_$(ARCH)) LDFLAGS := -s help: diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index c4a0c915139c..21bacc928bf7 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -130,111 +130,111 @@ static int pad_spc(int llen, int cnt, const char *fmt, ...) */ #define EXPECT_ZR(cond, expr) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_zr(expr, llen); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_zr(expr, llen); } while (0) static int expect_zr(int expr, int llen) { int ret = !(expr == 0); llen += printf(" = %d ", expr); - pad_spc(llen, 40, ret ? "[FAIL]\n" : " [OK]\n"); + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } #define EXPECT_NZ(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_nz(expr, llen; } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_nz(expr, llen; } while (0) static int expect_nz(int expr, int llen) { int ret = !(expr != 0); llen += printf(" = %d ", expr); - pad_spc(llen, 40, ret ? "[FAIL]\n" : " [OK]\n"); + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } #define EXPECT_EQ(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_eq(expr, llen, val); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_eq(expr, llen, val); } while (0) -static int expect_eq(int expr, int llen, int val) +static int expect_eq(uint64_t expr, int llen, uint64_t val) { int ret = !(expr == val); - llen += printf(" = %d ", expr); - pad_spc(llen, 40, ret ? "[FAIL]\n" : " [OK]\n"); + llen += printf(" = %lld ", expr); + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } #define EXPECT_NE(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_ne(expr, llen, val); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_ne(expr, llen, val); } while (0) static int expect_ne(int expr, int llen, int val) { int ret = !(expr != val); llen += printf(" = %d ", expr); - pad_spc(llen, 40, ret ? "[FAIL]\n" : " [OK]\n"); + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } #define EXPECT_GE(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_ge(expr, llen, val); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_ge(expr, llen, val); } while (0) static int expect_ge(int expr, int llen, int val) { int ret = !(expr >= val); llen += printf(" = %d ", expr); - pad_spc(llen, 40, ret ? "[FAIL]\n" : " [OK]\n"); + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } #define EXPECT_GT(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_gt(expr, llen, val); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_gt(expr, llen, val); } while (0) static int expect_gt(int expr, int llen, int val) { int ret = !(expr > val); llen += printf(" = %d ", expr); - pad_spc(llen, 40, ret ? "[FAIL]\n" : " [OK]\n"); + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } #define EXPECT_LE(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_le(expr, llen, val); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_le(expr, llen, val); } while (0) static int expect_le(int expr, int llen, int val) { int ret = !(expr <= val); llen += printf(" = %d ", expr); - pad_spc(llen, 40, ret ? "[FAIL]\n" : " [OK]\n"); + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } #define EXPECT_LT(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_lt(expr, llen, val); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_lt(expr, llen, val); } while (0) static int expect_lt(int expr, int llen, int val) { int ret = !(expr < val); llen += printf(" = %d ", expr); - pad_spc(llen, 40, ret ? "[FAIL]\n" : " [OK]\n"); + pad_spc(llen, 64, ret ? "[FAIL]\n" : " [OK]\n"); return ret; } #define EXPECT_SYSZR(cond, expr) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_syszr(expr, llen); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_syszr(expr, llen); } while (0) static int expect_syszr(int expr, int llen) { @@ -243,17 +243,17 @@ static int expect_syszr(int expr, int llen) if (expr) { ret = 1; llen += printf(" = %d %s ", expr, errorname(errno)); - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { llen += printf(" = %d ", expr); - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_SYSEQ(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_syseq(expr, llen, val); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_syseq(expr, llen, val); } while (0) static int expect_syseq(int expr, int llen, int val) { @@ -262,17 +262,17 @@ static int expect_syseq(int expr, int llen, int val) if (expr != val) { ret = 1; llen += printf(" = %d %s ", expr, errorname(errno)); - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { llen += printf(" = %d ", expr); - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_SYSNE(cond, expr, val) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_sysne(expr, llen, val); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_sysne(expr, llen, val); } while (0) static int expect_sysne(int expr, int llen, int val) { @@ -281,17 +281,17 @@ static int expect_sysne(int expr, int llen, int val) if (expr == val) { ret = 1; llen += printf(" = %d %s ", expr, errorname(errno)); - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { llen += printf(" = %d ", expr); - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_SYSER(cond, expr, expret, experr) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_syserr(expr, expret, experr, llen); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_syserr(expr, expret, experr, llen); } while (0) static int expect_syserr(int expr, int expret, int experr, int llen) { @@ -302,16 +302,16 @@ static int expect_syserr(int expr, int expret, int experr, int llen) if (expr != expret || _errno != experr) { ret = 1; llen += printf(" != (%d %s) ", expret, errorname(experr)); - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_PTRZR(cond, expr) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_ptrzr(expr, llen); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_ptrzr(expr, llen); } while (0) static int expect_ptrzr(const void *expr, int llen) { @@ -320,16 +320,16 @@ static int expect_ptrzr(const void *expr, int llen) llen += printf(" = <%p> ", expr); if (expr) { ret = 1; - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_PTRNZ(cond, expr) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_ptrnz(expr, llen); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_ptrnz(expr, llen); } while (0) static int expect_ptrnz(const void *expr, int llen) { @@ -338,16 +338,16 @@ static int expect_ptrnz(const void *expr, int llen) llen += printf(" = <%p> ", expr); if (!expr) { ret = 1; - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_STRZR(cond, expr) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_strzr(expr, llen); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_strzr(expr, llen); } while (0) static int expect_strzr(const char *expr, int llen) { @@ -356,16 +356,16 @@ static int expect_strzr(const char *expr, int llen) llen += printf(" = <%s> ", expr); if (expr) { ret = 1; - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_STRNZ(cond, expr) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_strnz(expr, llen); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_strnz(expr, llen); } while (0) static int expect_strnz(const char *expr, int llen) { @@ -374,16 +374,16 @@ static int expect_strnz(const char *expr, int llen) llen += printf(" = <%s> ", expr); if (!expr) { ret = 1; - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_STREQ(cond, expr, cmp) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_streq(expr, llen, cmp); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_streq(expr, llen, cmp); } while (0) static int expect_streq(const char *expr, int llen, const char *cmp) { @@ -392,16 +392,16 @@ static int expect_streq(const char *expr, int llen, const char *cmp) llen += printf(" = <%s> ", expr); if (strcmp(expr, cmp) != 0) { ret = 1; - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } #define EXPECT_STRNE(cond, expr, cmp) \ - do { if (!cond) pad_spc(llen, 40, "[SKIPPED]\n"); else ret += expect_strne(expr, llen, cmp); } while (0) + do { if (!cond) pad_spc(llen, 64, "[SKIPPED]\n"); else ret += expect_strne(expr, llen, cmp); } while (0) static int expect_strne(const char *expr, int llen, const char *cmp) { @@ -410,9 +410,9 @@ static int expect_strne(const char *expr, int llen, const char *cmp) llen += printf(" = <%s> ", expr); if (strcmp(expr, cmp) == 0) { ret = 1; - llen += pad_spc(llen, 40, "[FAIL]\n"); + llen += pad_spc(llen, 64, "[FAIL]\n"); } else { - llen += pad_spc(llen, 40, " [OK]\n"); + llen += pad_spc(llen, 64, " [OK]\n"); } return ret; } @@ -477,6 +477,7 @@ static int test_getpagesize(void) int run_syscall(int min, int max) { struct stat stat_buf; + int euid0; int proc; int test; int tmp; @@ -486,6 +487,9 @@ int run_syscall(int min, int max) /* <proc> indicates whether or not /proc is mounted */ proc = stat("/proc", &stat_buf) == 0; + /* this will be used to skip certain tests that can't be run unprivileged */ + euid0 = geteuid() == 0; + for (test = min; test >= 0 && test <= max; test++) { int llen = 0; // line length @@ -511,7 +515,7 @@ int run_syscall(int min, int max) CASE_TEST(chmod_net); EXPECT_SYSZR(proc, chmod("/proc/self/net", 0555)); break; CASE_TEST(chmod_self); EXPECT_SYSER(proc, chmod("/proc/self", 0555), -1, EPERM); break; CASE_TEST(chown_self); EXPECT_SYSER(proc, chown("/proc/self", 0, 0), -1, EPERM); break; - CASE_TEST(chroot_root); EXPECT_SYSZR(1, chroot("/")); break; + CASE_TEST(chroot_root); EXPECT_SYSZR(euid0, chroot("/")); break; CASE_TEST(chroot_blah); EXPECT_SYSER(1, chroot("/proc/self/blah"), -1, ENOENT); break; CASE_TEST(chroot_exe); EXPECT_SYSER(proc, chroot("/proc/self/exe"), -1, ENOTDIR); break; CASE_TEST(close_m1); EXPECT_SYSER(1, close(-1), -1, EBADF); break; @@ -536,7 +540,7 @@ int run_syscall(int min, int max) CASE_TEST(ioctl_tiocinq); EXPECT_SYSZR(1, ioctl(0, TIOCINQ, &tmp)); break; CASE_TEST(link_root1); EXPECT_SYSER(1, link("/", "/"), -1, EEXIST); break; CASE_TEST(link_blah); EXPECT_SYSER(1, link("/proc/self/blah", "/blah"), -1, ENOENT); break; - CASE_TEST(link_dir); EXPECT_SYSER(1, link("/", "/blah"), -1, EPERM); break; + CASE_TEST(link_dir); EXPECT_SYSER(euid0, link("/", "/blah"), -1, EPERM); break; CASE_TEST(link_cross); EXPECT_SYSER(proc, link("/proc/self/net", "/blah"), -1, EXDEV); break; CASE_TEST(lseek_m1); EXPECT_SYSER(1, lseek(-1, 0, SEEK_SET), -1, EBADF); break; CASE_TEST(lseek_0); EXPECT_SYSER(1, lseek(0, 0, SEEK_SET), -1, ESPIPE); break; @@ -602,6 +606,59 @@ int run_stdlib(int min, int max) CASE_TEST(memcmp_e0_20); EXPECT_GT(1, memcmp("aaa\xe0", "aaa\x20", 4), 0); break; CASE_TEST(memcmp_80_e0); EXPECT_LT(1, memcmp("aaa\x80", "aaa\xe0", 4), 0); break; CASE_TEST(memcmp_e0_80); EXPECT_GT(1, memcmp("aaa\xe0", "aaa\x80", 4), 0); break; + CASE_TEST(limit_int8_max); EXPECT_EQ(1, INT8_MAX, (int8_t) 0x7f); break; + CASE_TEST(limit_int8_min); EXPECT_EQ(1, INT8_MIN, (int8_t) 0x80); break; + CASE_TEST(limit_uint8_max); EXPECT_EQ(1, UINT8_MAX, (uint8_t) 0xff); break; + CASE_TEST(limit_int16_max); EXPECT_EQ(1, INT16_MAX, (int16_t) 0x7fff); break; + CASE_TEST(limit_int16_min); EXPECT_EQ(1, INT16_MIN, (int16_t) 0x8000); break; + CASE_TEST(limit_uint16_max); EXPECT_EQ(1, UINT16_MAX, (uint16_t) 0xffff); break; + CASE_TEST(limit_int32_max); EXPECT_EQ(1, INT32_MAX, (int32_t) 0x7fffffff); break; + CASE_TEST(limit_int32_min); EXPECT_EQ(1, INT32_MIN, (int32_t) 0x80000000); break; + CASE_TEST(limit_uint32_max); EXPECT_EQ(1, UINT32_MAX, (uint32_t) 0xffffffff); break; + CASE_TEST(limit_int64_max); EXPECT_EQ(1, INT64_MAX, (int64_t) 0x7fffffffffffffff); break; + CASE_TEST(limit_int64_min); EXPECT_EQ(1, INT64_MIN, (int64_t) 0x8000000000000000); break; + CASE_TEST(limit_uint64_max); EXPECT_EQ(1, UINT64_MAX, (uint64_t) 0xffffffffffffffff); break; + CASE_TEST(limit_int_least8_max); EXPECT_EQ(1, INT_LEAST8_MAX, (int_least8_t) 0x7f); break; + CASE_TEST(limit_int_least8_min); EXPECT_EQ(1, INT_LEAST8_MIN, (int_least8_t) 0x80); break; + CASE_TEST(limit_uint_least8_max); EXPECT_EQ(1, UINT_LEAST8_MAX, (uint_least8_t) 0xff); break; + CASE_TEST(limit_int_least16_max); EXPECT_EQ(1, INT_LEAST16_MAX, (int_least16_t) 0x7fff); break; + CASE_TEST(limit_int_least16_min); EXPECT_EQ(1, INT_LEAST16_MIN, (int_least16_t) 0x8000); break; + CASE_TEST(limit_uint_least16_max); EXPECT_EQ(1, UINT_LEAST16_MAX, (uint_least16_t) 0xffff); break; + CASE_TEST(limit_int_least32_max); EXPECT_EQ(1, INT_LEAST32_MAX, (int_least32_t) 0x7fffffff); break; + CASE_TEST(limit_int_least32_min); EXPECT_EQ(1, INT_LEAST32_MIN, (int_least32_t) 0x80000000); break; + CASE_TEST(limit_uint_least32_max); EXPECT_EQ(1, UINT_LEAST32_MAX, (uint_least32_t) 0xffffffffU); break; + CASE_TEST(limit_int_least64_min); EXPECT_EQ(1, INT_LEAST64_MIN, (int_least64_t) 0x8000000000000000LL); break; + CASE_TEST(limit_int_least64_max); EXPECT_EQ(1, INT_LEAST64_MAX, (int_least64_t) 0x7fffffffffffffffLL); break; + CASE_TEST(limit_uint_least64_max); EXPECT_EQ(1, UINT_LEAST64_MAX, (uint_least64_t) 0xffffffffffffffffULL); break; + CASE_TEST(limit_int_fast8_max); EXPECT_EQ(1, INT_FAST8_MAX, (int_fast8_t) 0x7f); break; + CASE_TEST(limit_int_fast8_min); EXPECT_EQ(1, INT_FAST8_MIN, (int_fast8_t) 0x80); break; + CASE_TEST(limit_uint_fast8_max); EXPECT_EQ(1, UINT_FAST8_MAX, (uint_fast8_t) 0xff); break; + CASE_TEST(limit_int_fast16_min); EXPECT_EQ(1, INT_FAST16_MIN, (int_fast16_t) INTPTR_MIN); break; + CASE_TEST(limit_int_fast16_max); EXPECT_EQ(1, INT_FAST16_MAX, (int_fast16_t) INTPTR_MAX); break; + CASE_TEST(limit_uint_fast16_max); EXPECT_EQ(1, UINT_FAST16_MAX, (uint_fast16_t) UINTPTR_MAX); break; + CASE_TEST(limit_int_fast32_min); EXPECT_EQ(1, INT_FAST32_MIN, (int_fast32_t) INTPTR_MIN); break; + CASE_TEST(limit_int_fast32_max); EXPECT_EQ(1, INT_FAST32_MAX, (int_fast32_t) INTPTR_MAX); break; + CASE_TEST(limit_uint_fast32_max); EXPECT_EQ(1, UINT_FAST32_MAX, (uint_fast32_t) UINTPTR_MAX); break; + CASE_TEST(limit_int_fast64_min); EXPECT_EQ(1, INT_FAST64_MIN, (int_fast64_t) INTPTR_MIN); break; + CASE_TEST(limit_int_fast64_max); EXPECT_EQ(1, INT_FAST64_MAX, (int_fast64_t) INTPTR_MAX); break; + CASE_TEST(limit_uint_fast64_max); EXPECT_EQ(1, UINT_FAST64_MAX, (uint_fast64_t) UINTPTR_MAX); break; +#if __SIZEOF_LONG__ == 8 + CASE_TEST(limit_intptr_min); EXPECT_EQ(1, INTPTR_MIN, (intptr_t) 0x8000000000000000LL); break; + CASE_TEST(limit_intptr_max); EXPECT_EQ(1, INTPTR_MAX, (intptr_t) 0x7fffffffffffffffLL); break; + CASE_TEST(limit_uintptr_max); EXPECT_EQ(1, UINTPTR_MAX, (uintptr_t) 0xffffffffffffffffULL); break; + CASE_TEST(limit_ptrdiff_min); EXPECT_EQ(1, PTRDIFF_MIN, (ptrdiff_t) 0x8000000000000000LL); break; + CASE_TEST(limit_ptrdiff_max); EXPECT_EQ(1, PTRDIFF_MAX, (ptrdiff_t) 0x7fffffffffffffffLL); break; + CASE_TEST(limit_size_max); EXPECT_EQ(1, SIZE_MAX, (size_t) 0xffffffffffffffffULL); break; +#elif __SIZEOF_LONG__ == 4 + CASE_TEST(limit_intptr_min); EXPECT_EQ(1, INTPTR_MIN, (intptr_t) 0x80000000); break; + CASE_TEST(limit_intptr_max); EXPECT_EQ(1, INTPTR_MAX, (intptr_t) 0x7fffffff); break; + CASE_TEST(limit_uintptr_max); EXPECT_EQ(1, UINTPTR_MAX, (uintptr_t) 0xffffffffU); break; + CASE_TEST(limit_ptrdiff_min); EXPECT_EQ(1, PTRDIFF_MIN, (ptrdiff_t) 0x80000000); break; + CASE_TEST(limit_ptrdiff_max); EXPECT_EQ(1, PTRDIFF_MAX, (ptrdiff_t) 0x7fffffff); break; + CASE_TEST(limit_size_max); EXPECT_EQ(1, SIZE_MAX, (size_t) 0xffffffffU); break; +#else +# warning "__SIZEOF_LONG__ is undefined" +#endif /* __SIZEOF_LONG__ */ case __LINE__: return ret; /* must be last */ /* note: do not set any defaults so as to permit holes above */ @@ -610,6 +667,63 @@ int run_stdlib(int min, int max) return ret; } +#if defined(__clang__) +__attribute__((optnone)) +#elif defined(__GNUC__) +__attribute__((optimize("O0"))) +#endif +static int smash_stack(void) +{ + char buf[100]; + + for (size_t i = 0; i < 200; i++) + buf[i] = 'P'; + + return 1; +} + +static int run_protection(int min, int max) +{ + pid_t pid; + int llen = 0, status; + + llen += printf("0 -fstackprotector "); + +#if !defined(NOLIBC_STACKPROTECTOR) + llen += printf("not supported"); + pad_spc(llen, 64, "[SKIPPED]\n"); + return 0; +#endif + + pid = -1; + pid = fork(); + + switch (pid) { + case -1: + llen += printf("fork()"); + pad_spc(llen, 64, "[FAIL]\n"); + return 1; + + case 0: + close(STDOUT_FILENO); + close(STDERR_FILENO); + + smash_stack(); + return 1; + + default: + pid = waitpid(pid, &status, 0); + + if (pid == -1 || !WIFSIGNALED(status) || WTERMSIG(status) != SIGABRT) { + llen += printf("waitpid()"); + pad_spc(llen, 64, "[FAIL]\n"); + return 1; + } + pad_spc(llen, 64, " [OK]\n"); + return 0; + } +} + /* prepare what needs to be prepared for pid 1 (stdio, /dev, /proc, etc) */ int prepare(void) { @@ -660,10 +774,11 @@ int prepare(void) } /* This is the definition of known test names, with their functions */ -static struct test test_names[] = { +static const struct test test_names[] = { /* add new tests here */ - { .name = "syscall", .func = run_syscall }, - { .name = "stdlib", .func = run_stdlib }, + { .name = "syscall", .func = run_syscall }, + { .name = "stdlib", .func = run_stdlib }, + { .name = "protection", .func = run_protection }, { 0 } }; diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile index 6ba95cd19e42..ae2bfc0d822f 100644 --- a/tools/testing/selftests/powerpc/Makefile +++ b/tools/testing/selftests/powerpc/Makefile @@ -45,28 +45,28 @@ $(SUB_DIRS): include ../lib.mk override define RUN_TESTS - @for TARGET in $(SUB_DIRS); do \ + +@for TARGET in $(SUB_DIRS); do \ BUILD_TARGET=$(OUTPUT)/$$TARGET; \ $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests;\ done; endef override define INSTALL_RULE - @for TARGET in $(SUB_DIRS); do \ + +@for TARGET in $(SUB_DIRS); do \ BUILD_TARGET=$(OUTPUT)/$$TARGET; \ $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install;\ done; endef override define EMIT_TESTS - @for TARGET in $(SUB_DIRS); do \ + +@for TARGET in $(SUB_DIRS); do \ BUILD_TARGET=$(OUTPUT)/$$TARGET; \ $(MAKE) OUTPUT=$$BUILD_TARGET -s -C $$TARGET emit_tests;\ done; endef override define CLEAN - @for TARGET in $(SUB_DIRS); do \ + +@for TARGET in $(SUB_DIRS); do \ BUILD_TARGET=$(OUTPUT)/$$TARGET; \ $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET clean; \ done; diff --git a/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h b/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h index 003e1b3d9300..a89f1fbf86ec 100644 --- a/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h +++ b/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h @@ -27,6 +27,7 @@ #define _GLOBAL_TOC(A) _GLOBAL(A) #define _GLOBAL_TOC_KASAN(A) _GLOBAL(A) #define _GLOBAL_KASAN(A) _GLOBAL(A) +#define CFUNC(name) name #define PPC_MTOCRF(A, B) mtocrf A, B diff --git a/tools/testing/selftests/powerpc/dscr/Makefile b/tools/testing/selftests/powerpc/dscr/Makefile index 845db6273a1b..9289d5febe1e 100644 --- a/tools/testing/selftests/powerpc/dscr/Makefile +++ b/tools/testing/selftests/powerpc/dscr/Makefile @@ -3,11 +3,10 @@ TEST_GEN_PROGS := dscr_default_test dscr_explicit_test dscr_user_test \ dscr_inherit_test dscr_inherit_exec_test dscr_sysfs_test \ dscr_sysfs_thread_test -TEST_FILES := settings - top_srcdir = ../../../../.. include ../../lib.mk $(OUTPUT)/dscr_default_test: LDLIBS += -lpthread +$(OUTPUT)/dscr_explicit_test: LDLIBS += -lpthread $(TEST_GEN_PROGS): ../harness.c ../utils.c diff --git a/tools/testing/selftests/powerpc/dscr/dscr.h b/tools/testing/selftests/powerpc/dscr/dscr.h index 2c54998d4715..b281659071e8 100644 --- a/tools/testing/selftests/powerpc/dscr/dscr.h +++ b/tools/testing/selftests/powerpc/dscr/dscr.h @@ -86,8 +86,4 @@ void set_default_dscr(unsigned long val) } } -double uniform_deviate(int seed) -{ - return seed * (1.0 / (RAND_MAX + 1.0)); -} #endif /* _SELFTESTS_POWERPC_DSCR_DSCR_H */ diff --git a/tools/testing/selftests/powerpc/dscr/dscr_default_test.c b/tools/testing/selftests/powerpc/dscr/dscr_default_test.c index e76611e608af..60ab02525b79 100644 --- a/tools/testing/selftests/powerpc/dscr/dscr_default_test.c +++ b/tools/testing/selftests/powerpc/dscr/dscr_default_test.c @@ -9,118 +9,161 @@ * Copyright 2012, Anton Blanchard, IBM Corporation. * Copyright 2015, Anshuman Khandual, IBM Corporation. */ -#include "dscr.h" - -static unsigned long dscr; /* System DSCR default */ -static unsigned long sequence; -static unsigned long result[THREADS]; - -static void *do_test(void *in) -{ - unsigned long thread = (unsigned long)in; - unsigned long i; - for (i = 0; i < COUNT; i++) { - unsigned long d, cur_dscr, cur_dscr_usr; - unsigned long s1, s2; +#define _GNU_SOURCE - s1 = READ_ONCE(sequence); - if (s1 & 1) - continue; - rmb(); +#include "dscr.h" - d = dscr; - cur_dscr = get_dscr(); - cur_dscr_usr = get_dscr_usr(); +#include <pthread.h> +#include <semaphore.h> +#include <unistd.h> - rmb(); - s2 = sequence; +static void *dscr_default_lockstep_writer(void *arg) +{ + sem_t *reader_sem = (sem_t *)arg; + sem_t *writer_sem = (sem_t *)arg + 1; + unsigned long expected_dscr = 0; - if (s1 != s2) - continue; + for (int i = 0; i < COUNT; i++) { + FAIL_IF_EXIT(sem_wait(writer_sem)); - if (cur_dscr != d) { - fprintf(stderr, "thread %ld kernel DSCR should be %ld " - "but is %ld\n", thread, d, cur_dscr); - result[thread] = 1; - pthread_exit(&result[thread]); - } + set_default_dscr(expected_dscr); + expected_dscr = (expected_dscr + 1) % DSCR_MAX; - if (cur_dscr_usr != d) { - fprintf(stderr, "thread %ld user DSCR should be %ld " - "but is %ld\n", thread, d, cur_dscr_usr); - result[thread] = 1; - pthread_exit(&result[thread]); - } + FAIL_IF_EXIT(sem_post(reader_sem)); } - result[thread] = 0; - pthread_exit(&result[thread]); + + return NULL; } -int dscr_default(void) +int dscr_default_lockstep_test(void) { - pthread_t threads[THREADS]; - unsigned long i, *status[THREADS]; - unsigned long orig_dscr_default; + pthread_t writer; + sem_t rw_semaphores[2]; + sem_t *reader_sem = &rw_semaphores[0]; + sem_t *writer_sem = &rw_semaphores[1]; + unsigned long expected_dscr = 0; SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR)); - orig_dscr_default = get_default_dscr(); + FAIL_IF(sem_init(reader_sem, 0, 0)); + FAIL_IF(sem_init(writer_sem, 0, 1)); /* writer starts first */ + FAIL_IF(bind_to_cpu(BIND_CPU_ANY) < 0); + FAIL_IF(pthread_create(&writer, NULL, dscr_default_lockstep_writer, (void *)rw_semaphores)); - /* Initial DSCR default */ - dscr = 1; - set_default_dscr(dscr); + for (int i = 0; i < COUNT ; i++) { + FAIL_IF(sem_wait(reader_sem)); - /* Spawn all testing threads */ - for (i = 0; i < THREADS; i++) { - if (pthread_create(&threads[i], NULL, do_test, (void *)i)) { - perror("pthread_create() failed"); - goto fail; - } - } + FAIL_IF(get_dscr() != expected_dscr); + FAIL_IF(get_dscr_usr() != expected_dscr); - srand(getpid()); + expected_dscr = (expected_dscr + 1) % DSCR_MAX; - /* Keep changing the DSCR default */ - for (i = 0; i < COUNT; i++) { - double ret = uniform_deviate(rand()); + FAIL_IF(sem_post(writer_sem)); + } - if (ret < 0.0001) { - sequence++; - wmb(); + FAIL_IF(pthread_join(writer, NULL)); + FAIL_IF(sem_destroy(reader_sem)); + FAIL_IF(sem_destroy(writer_sem)); - dscr++; - if (dscr > DSCR_MAX) - dscr = 0; + return 0; +} - set_default_dscr(dscr); +struct random_thread_args { + pthread_t thread_id; + unsigned long *expected_system_dscr; + pthread_rwlock_t *rw_lock; + pthread_barrier_t *barrier; +}; - wmb(); - sequence++; +static void *dscr_default_random_thread(void *in) +{ + struct random_thread_args *args = (struct random_thread_args *)in; + unsigned long *expected_dscr_p = args->expected_system_dscr; + pthread_rwlock_t *rw_lock = args->rw_lock; + int err; + + srand(gettid()); + + err = pthread_barrier_wait(args->barrier); + FAIL_IF_EXIT(err != 0 && err != PTHREAD_BARRIER_SERIAL_THREAD); + + for (int i = 0; i < COUNT; i++) { + unsigned long expected_dscr; + unsigned long current_dscr; + unsigned long current_dscr_usr; + + FAIL_IF_EXIT(pthread_rwlock_rdlock(rw_lock)); + expected_dscr = *expected_dscr_p; + current_dscr = get_dscr(); + current_dscr_usr = get_dscr_usr(); + FAIL_IF_EXIT(pthread_rwlock_unlock(rw_lock)); + + FAIL_IF_EXIT(current_dscr != expected_dscr); + FAIL_IF_EXIT(current_dscr_usr != expected_dscr); + + if (rand() % 10 == 0) { + unsigned long next_dscr; + + FAIL_IF_EXIT(pthread_rwlock_wrlock(rw_lock)); + next_dscr = (*expected_dscr_p + 1) % DSCR_MAX; + set_default_dscr(next_dscr); + *expected_dscr_p = next_dscr; + FAIL_IF_EXIT(pthread_rwlock_unlock(rw_lock)); } } - /* Individual testing thread exit status */ - for (i = 0; i < THREADS; i++) { - if (pthread_join(threads[i], (void **)&(status[i]))) { - perror("pthread_join() failed"); - goto fail; - } + pthread_exit((void *)0); +} - if (*status[i]) { - printf("%ldth thread failed to join with %ld status\n", - i, *status[i]); - goto fail; - } +int dscr_default_random_test(void) +{ + struct random_thread_args threads[THREADS]; + unsigned long expected_system_dscr = 0; + pthread_rwlockattr_t rwlock_attr; + pthread_rwlock_t rw_lock; + pthread_barrier_t barrier; + + SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR)); + + FAIL_IF(pthread_rwlockattr_setkind_np(&rwlock_attr, + PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP)); + FAIL_IF(pthread_rwlock_init(&rw_lock, &rwlock_attr)); + FAIL_IF(pthread_barrier_init(&barrier, NULL, THREADS)); + + set_default_dscr(expected_system_dscr); + + for (int i = 0; i < THREADS; i++) { + threads[i].expected_system_dscr = &expected_system_dscr; + threads[i].rw_lock = &rw_lock; + threads[i].barrier = &barrier; + + FAIL_IF(pthread_create(&threads[i].thread_id, NULL, + dscr_default_random_thread, (void *)&threads[i])); } - set_default_dscr(orig_dscr_default); + + for (int i = 0; i < THREADS; i++) + FAIL_IF(pthread_join(threads[i].thread_id, NULL)); + + FAIL_IF(pthread_barrier_destroy(&barrier)); + FAIL_IF(pthread_rwlock_destroy(&rw_lock)); + return 0; -fail: - set_default_dscr(orig_dscr_default); - return 1; } int main(int argc, char *argv[]) { - return test_harness(dscr_default, "dscr_default_test"); + unsigned long orig_dscr_default = 0; + int err = 0; + + if (have_hwcap2(PPC_FEATURE2_DSCR)) + orig_dscr_default = get_default_dscr(); + + err |= test_harness(dscr_default_lockstep_test, "dscr_default_lockstep_test"); + err |= test_harness(dscr_default_random_test, "dscr_default_random_test"); + + if (have_hwcap2(PPC_FEATURE2_DSCR)) + set_default_dscr(orig_dscr_default); + + return err; } diff --git a/tools/testing/selftests/powerpc/dscr/dscr_explicit_test.c b/tools/testing/selftests/powerpc/dscr/dscr_explicit_test.c index 32fcf2b324b1..e2268e9183a8 100644 --- a/tools/testing/selftests/powerpc/dscr/dscr_explicit_test.c +++ b/tools/testing/selftests/powerpc/dscr/dscr_explicit_test.c @@ -7,64 +7,167 @@ * privilege state SPR and the problem state SPR for this purpose. * * When using the privilege state SPR, the instructions such as - * mfspr or mtspr are priviledged and the kernel emulates them - * for us. Instructions using problem state SPR can be exuecuted + * mfspr or mtspr are privileged and the kernel emulates them + * for us. Instructions using problem state SPR can be executed * directly without any emulation if the HW supports them. Else * they also get emulated by the kernel. * * Copyright 2012, Anton Blanchard, IBM Corporation. * Copyright 2015, Anshuman Khandual, IBM Corporation. */ + +#define _GNU_SOURCE + #include "dscr.h" +#include "utils.h" + +#include <pthread.h> +#include <sched.h> +#include <semaphore.h> -int dscr_explicit(void) +void *dscr_explicit_lockstep_thread(void *args) { - unsigned long i, dscr = 0; + sem_t *prev = (sem_t *)args; + sem_t *next = (sem_t *)args + 1; + unsigned long expected_dscr = 0; + + set_dscr(expected_dscr); + srand(gettid()); + + for (int i = 0; i < COUNT; i++) { + FAIL_IF_EXIT(sem_wait(prev)); + + FAIL_IF_EXIT(expected_dscr != get_dscr()); + FAIL_IF_EXIT(expected_dscr != get_dscr_usr()); + + expected_dscr = (expected_dscr + 1) % DSCR_MAX; + set_dscr(expected_dscr); + + FAIL_IF_EXIT(sem_post(next)); + } + + return NULL; +} + +int dscr_explicit_lockstep_test(void) +{ + pthread_t thread; + sem_t semaphores[2]; + sem_t *prev = &semaphores[1]; /* reversed prev/next than for the other thread */ + sem_t *next = &semaphores[0]; + unsigned long expected_dscr = 0; SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR)); - srand(getpid()); - set_dscr(dscr); + srand(gettid()); + set_dscr(expected_dscr); - for (i = 0; i < COUNT; i++) { - unsigned long cur_dscr, cur_dscr_usr; - double ret = uniform_deviate(rand()); + FAIL_IF(sem_init(prev, 0, 0)); + FAIL_IF(sem_init(next, 0, 1)); /* other thread starts first */ + FAIL_IF(bind_to_cpu(BIND_CPU_ANY) < 0); + FAIL_IF(pthread_create(&thread, NULL, dscr_explicit_lockstep_thread, (void *)semaphores)); - if (ret < 0.001) { - dscr++; - if (dscr > DSCR_MAX) - dscr = 0; + for (int i = 0; i < COUNT; i++) { + FAIL_IF(sem_wait(prev)); - set_dscr(dscr); - } + FAIL_IF(expected_dscr != get_dscr()); + FAIL_IF(expected_dscr != get_dscr_usr()); - cur_dscr = get_dscr(); - if (cur_dscr != dscr) { - fprintf(stderr, "Kernel DSCR should be %ld but " - "is %ld\n", dscr, cur_dscr); - return 1; - } + expected_dscr = (expected_dscr - 1) % DSCR_MAX; + set_dscr(expected_dscr); + + FAIL_IF(sem_post(next)); + } + + FAIL_IF(pthread_join(thread, NULL)); + FAIL_IF(sem_destroy(prev)); + FAIL_IF(sem_destroy(next)); + + return 0; +} + +struct random_thread_args { + pthread_t thread_id; + bool do_yields; + pthread_barrier_t *barrier; +}; + +void *dscr_explicit_random_thread(void *in) +{ + struct random_thread_args *args = (struct random_thread_args *)in; + unsigned long expected_dscr = 0; + int err; + + srand(gettid()); + + err = pthread_barrier_wait(args->barrier); + FAIL_IF_EXIT(err != 0 && err != PTHREAD_BARRIER_SERIAL_THREAD); - ret = uniform_deviate(rand()); - if (ret < 0.001) { - dscr++; - if (dscr > DSCR_MAX) - dscr = 0; + for (int i = 0; i < COUNT; i++) { + expected_dscr = rand() % DSCR_MAX; + set_dscr(expected_dscr); - set_dscr_usr(dscr); + for (int j = rand() % 5; j > 0; --j) { + FAIL_IF_EXIT(get_dscr() != expected_dscr); + FAIL_IF_EXIT(get_dscr_usr() != expected_dscr); + + if (args->do_yields && rand() % 2) + sched_yield(); } - cur_dscr_usr = get_dscr_usr(); - if (cur_dscr_usr != dscr) { - fprintf(stderr, "User DSCR should be %ld but " - "is %ld\n", dscr, cur_dscr_usr); - return 1; + expected_dscr = rand() % DSCR_MAX; + set_dscr_usr(expected_dscr); + + for (int j = rand() % 5; j > 0; --j) { + FAIL_IF_EXIT(get_dscr() != expected_dscr); + FAIL_IF_EXIT(get_dscr_usr() != expected_dscr); + + if (args->do_yields && rand() % 2) + sched_yield(); } } + + return NULL; +} + +int dscr_explicit_random_test(void) +{ + struct random_thread_args threads[THREADS]; + pthread_barrier_t barrier; + + SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR)); + + FAIL_IF(pthread_barrier_init(&barrier, NULL, THREADS)); + + for (int i = 0; i < THREADS; i++) { + threads[i].do_yields = i % 2 == 0; + threads[i].barrier = &barrier; + + FAIL_IF(pthread_create(&threads[i].thread_id, NULL, + dscr_explicit_random_thread, (void *)&threads[i])); + } + + for (int i = 0; i < THREADS; i++) + FAIL_IF(pthread_join(threads[i].thread_id, NULL)); + + FAIL_IF(pthread_barrier_destroy(&barrier)); + return 0; } int main(int argc, char *argv[]) { - return test_harness(dscr_explicit, "dscr_explicit_test"); + unsigned long orig_dscr_default = 0; + int err = 0; + + if (have_hwcap2(PPC_FEATURE2_DSCR)) + orig_dscr_default = get_default_dscr(); + + err |= test_harness(dscr_explicit_lockstep_test, "dscr_explicit_lockstep_test"); + err |= test_harness(dscr_explicit_random_test, "dscr_explicit_random_test"); + + if (have_hwcap2(PPC_FEATURE2_DSCR)) + set_default_dscr(orig_dscr_default); + + return err; } diff --git a/tools/testing/selftests/powerpc/dscr/dscr_inherit_test.c b/tools/testing/selftests/powerpc/dscr/dscr_inherit_test.c index f9dfd3d3c2d5..68ce328e813e 100644 --- a/tools/testing/selftests/powerpc/dscr/dscr_inherit_test.c +++ b/tools/testing/selftests/powerpc/dscr/dscr_inherit_test.c @@ -7,8 +7,8 @@ * value using mfspr. * * When using the privilege state SPR, the instructions such as - * mfspr or mtspr are priviledged and the kernel emulates them - * for us. Instructions using problem state SPR can be exuecuted + * mfspr or mtspr are privileged and the kernel emulates them + * for us. Instructions using problem state SPR can be executed * directly without any emulation if the HW supports them. Else * they also get emulated by the kernel. * diff --git a/tools/testing/selftests/powerpc/dscr/dscr_sysfs_test.c b/tools/testing/selftests/powerpc/dscr/dscr_sysfs_test.c index 4f1fef6198fc..e7cd0d6b1fad 100644 --- a/tools/testing/selftests/powerpc/dscr/dscr_sysfs_test.c +++ b/tools/testing/selftests/powerpc/dscr/dscr_sysfs_test.c @@ -67,17 +67,14 @@ static int check_all_cpu_dscr_defaults(unsigned long val) int dscr_sysfs(void) { unsigned long orig_dscr_default; - int i, j; SKIP_IF(!have_hwcap2(PPC_FEATURE2_DSCR)); orig_dscr_default = get_default_dscr(); - for (i = 0; i < COUNT; i++) { - for (j = 0; j < DSCR_MAX; j++) { - set_default_dscr(j); - if (check_all_cpu_dscr_defaults(j)) - goto fail; - } + for (int i = 0; i < DSCR_MAX; i++) { + set_default_dscr(i); + if (check_all_cpu_dscr_defaults(i)) + goto fail; } set_default_dscr(orig_dscr_default); return 0; diff --git a/tools/testing/selftests/powerpc/dscr/dscr_user_test.c b/tools/testing/selftests/powerpc/dscr/dscr_user_test.c index e09072446dd3..67bb872a246a 100644 --- a/tools/testing/selftests/powerpc/dscr/dscr_user_test.c +++ b/tools/testing/selftests/powerpc/dscr/dscr_user_test.c @@ -8,8 +8,8 @@ * numbers. * * When using the privilege state SPR, the instructions such as - * mfspr or mtspr are priviledged and the kernel emulates them - * for us. Instructions using problem state SPR can be exuecuted + * mfspr or mtspr are privileged and the kernel emulates them + * for us. Instructions using problem state SPR can be executed * directly without any emulation if the HW supports them. Else * they also get emulated by the kernel. * diff --git a/tools/testing/selftests/powerpc/dscr/settings b/tools/testing/selftests/powerpc/dscr/settings deleted file mode 100644 index e7b9417537fb..000000000000 --- a/tools/testing/selftests/powerpc/dscr/settings +++ /dev/null @@ -1 +0,0 @@ -timeout=0 diff --git a/tools/testing/selftests/powerpc/include/utils.h b/tools/testing/selftests/powerpc/include/utils.h index eed7dd7582b2..44bfd48b93d6 100644 --- a/tools/testing/selftests/powerpc/include/utils.h +++ b/tools/testing/selftests/powerpc/include/utils.h @@ -31,7 +31,10 @@ int read_auxv(char *buf, ssize_t buf_size); void *find_auxv_entry(int type, char *auxv); void *get_auxv_entry(int type); +#define BIND_CPU_ANY (-1) + int pick_online_cpu(void); +int bind_to_cpu(int cpu); int parse_intmax(const char *buffer, size_t count, intmax_t *result, int base); int parse_uintmax(const char *buffer, size_t count, uintmax_t *result, int base); diff --git a/tools/testing/selftests/powerpc/math/vmx_signal.c b/tools/testing/selftests/powerpc/math/vmx_signal.c index b340a5c4e79d..c307dff19c12 100644 --- a/tools/testing/selftests/powerpc/math/vmx_signal.c +++ b/tools/testing/selftests/powerpc/math/vmx_signal.c @@ -151,5 +151,6 @@ int test_signal_vmx(void) int main(int argc, char *argv[]) { + test_harness_set_timeout(360); return test_harness(test_signal_vmx, "vmx_signal"); } diff --git a/tools/testing/selftests/powerpc/mm/Makefile b/tools/testing/selftests/powerpc/mm/Makefile index 19dd0b2ea397..4a6608beef0e 100644 --- a/tools/testing/selftests/powerpc/mm/Makefile +++ b/tools/testing/selftests/powerpc/mm/Makefile @@ -32,7 +32,7 @@ $(OUTPUT)/stack_expansion_ldst: CFLAGS += -fno-stack-protector $(OUTPUT)/stack_expansion_ldst: ../utils.c $(OUTPUT)/tempfile: - dd if=/dev/zero of=$@ bs=64k count=1 + dd if=/dev/zero of=$@ bs=64k count=1 status=none $(OUTPUT)/tlbie_test: LDLIBS += -lpthread $(OUTPUT)/pkey_siginfo: LDLIBS += -lpthread diff --git a/tools/testing/selftests/powerpc/pmu/Makefile b/tools/testing/selftests/powerpc/pmu/Makefile index 30803353bd7c..2b95e44d20ff 100644 --- a/tools/testing/selftests/powerpc/pmu/Makefile +++ b/tools/testing/selftests/powerpc/pmu/Makefile @@ -25,32 +25,35 @@ $(OUTPUT)/per_event_excludes: ../utils.c DEFAULT_RUN_TESTS := $(RUN_TESTS) override define RUN_TESTS $(DEFAULT_RUN_TESTS) - TARGET=ebb; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests - TARGET=sampling_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests - TARGET=event_code_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests + +TARGET=ebb; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests + +TARGET=sampling_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests + +TARGET=event_code_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests endef DEFAULT_EMIT_TESTS := $(EMIT_TESTS) override define EMIT_TESTS $(DEFAULT_EMIT_TESTS) - TARGET=ebb; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -s -C $$TARGET emit_tests - TARGET=sampling_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -s -C $$TARGET emit_tests - TARGET=event_code_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -s -C $$TARGET emit_tests + +TARGET=ebb; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -s -C $$TARGET emit_tests + +TARGET=sampling_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -s -C $$TARGET emit_tests + +TARGET=event_code_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -s -C $$TARGET emit_tests endef DEFAULT_INSTALL_RULE := $(INSTALL_RULE) override define INSTALL_RULE $(DEFAULT_INSTALL_RULE) - TARGET=ebb; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install - TARGET=sampling_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install - TARGET=event_code_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install + +TARGET=ebb; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install + +TARGET=sampling_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install + +TARGET=event_code_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install endef -clean: +DEFAULT_CLEAN := $(CLEAN) +override define CLEAN + $(DEFAULT_CLEAN) $(RM) $(TEST_GEN_PROGS) $(OUTPUT)/loop.o - TARGET=ebb; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET clean - TARGET=sampling_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET clean - TARGET=event_code_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET clean + +TARGET=ebb; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET clean + +TARGET=sampling_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET clean + +TARGET=event_code_tests; BUILD_TARGET=$$OUTPUT/$$TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET clean +endef ebb: TARGET=$@; BUILD_TARGET=$$OUTPUT/$$TARGET; mkdir -p $$BUILD_TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -k -C $$TARGET all @@ -61,4 +64,4 @@ sampling_tests: event_code_tests: TARGET=$@; BUILD_TARGET=$$OUTPUT/$$TARGET; mkdir -p $$BUILD_TARGET; $(MAKE) OUTPUT=$$BUILD_TARGET -k -C $$TARGET all -.PHONY: all run_tests clean ebb sampling_tests event_code_tests +.PHONY: all run_tests ebb sampling_tests event_code_tests diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c index 3cd33eb51e5e..fab7f34d7ce1 100644 --- a/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c +++ b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c @@ -45,9 +45,8 @@ int cpu_event_pinned_vs_ebb(void) SKIP_IF(!ebb_is_supported()); - cpu = pick_online_cpu(); + cpu = bind_to_cpu(BIND_CPU_ANY); FAIL_IF(cpu < 0); - FAIL_IF(bind_to_cpu(cpu)); FAIL_IF(pipe(read_pipe.fds) == -1); FAIL_IF(pipe(write_pipe.fds) == -1); diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c index 8466ef9d7de8..7c54c262036e 100644 --- a/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c +++ b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c @@ -43,9 +43,8 @@ int cpu_event_vs_ebb(void) SKIP_IF(!ebb_is_supported()); - cpu = pick_online_cpu(); + cpu = bind_to_cpu(BIND_CPU_ANY); FAIL_IF(cpu < 0); - FAIL_IF(bind_to_cpu(cpu)); FAIL_IF(pipe(read_pipe.fds) == -1); FAIL_IF(pipe(write_pipe.fds) == -1); diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c index 4d822cb3589c..d7064b54c64f 100644 --- a/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c +++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c @@ -43,9 +43,8 @@ int ebb_vs_cpu_event(void) SKIP_IF(!ebb_is_supported()); - cpu = pick_online_cpu(); + cpu = bind_to_cpu(BIND_CPU_ANY); FAIL_IF(cpu < 0); - FAIL_IF(bind_to_cpu(cpu)); FAIL_IF(pipe(read_pipe.fds) == -1); FAIL_IF(pipe(write_pipe.fds) == -1); diff --git a/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c b/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c index 9b0f70d59702..4ac22b2e774f 100644 --- a/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c +++ b/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c @@ -75,13 +75,11 @@ static int cycles_child(void) int multi_ebb_procs(void) { pid_t pids[NR_CHILDREN]; - int cpu, rc, i; + int rc, i; SKIP_IF(!ebb_is_supported()); - cpu = pick_online_cpu(); - FAIL_IF(cpu < 0); - FAIL_IF(bind_to_cpu(cpu)); + FAIL_IF(bind_to_cpu(BIND_CPU_ANY) < 0); for (i = 0; i < NR_CHILDREN; i++) { pids[i] = fork(); diff --git a/tools/testing/selftests/powerpc/pmu/lib.c b/tools/testing/selftests/powerpc/pmu/lib.c index 719f94f10d41..321357987408 100644 --- a/tools/testing/selftests/powerpc/pmu/lib.c +++ b/tools/testing/selftests/powerpc/pmu/lib.c @@ -14,19 +14,6 @@ #include "utils.h" #include "lib.h" - -int bind_to_cpu(int cpu) -{ - cpu_set_t mask; - - printf("Binding to cpu %d\n", cpu); - - CPU_ZERO(&mask); - CPU_SET(cpu, &mask); - - return sched_setaffinity(0, sizeof(mask), &mask); -} - #define PARENT_TOKEN 0xAA #define CHILD_TOKEN 0x55 @@ -116,12 +103,10 @@ static int eat_cpu_child(union pipe read_pipe, union pipe write_pipe) pid_t eat_cpu(int (test_function)(void)) { union pipe read_pipe, write_pipe; - int cpu, rc; + int rc; pid_t pid; - cpu = pick_online_cpu(); - FAIL_IF(cpu < 0); - FAIL_IF(bind_to_cpu(cpu)); + FAIL_IF(bind_to_cpu(BIND_CPU_ANY) < 0); if (pipe(read_pipe.fds) == -1) return -1; diff --git a/tools/testing/selftests/powerpc/pmu/lib.h b/tools/testing/selftests/powerpc/pmu/lib.h index bf1bec013bbb..1d62403ae6ea 100644 --- a/tools/testing/selftests/powerpc/pmu/lib.h +++ b/tools/testing/selftests/powerpc/pmu/lib.h @@ -20,7 +20,6 @@ union pipe { int fds[2]; }; -extern int bind_to_cpu(int cpu); extern int kill_child_and_wait(pid_t child_pid); extern int wait_for_child(pid_t child_pid); extern int sync_with_child(union pipe read_pipe, union pipe write_pipe); diff --git a/tools/testing/selftests/powerpc/pmu/sampling_tests/mmcra_thresh_marked_sample_test.c b/tools/testing/selftests/powerpc/pmu/sampling_tests/mmcra_thresh_marked_sample_test.c index 022cc1655eb5..75527876ad3c 100644 --- a/tools/testing/selftests/powerpc/pmu/sampling_tests/mmcra_thresh_marked_sample_test.c +++ b/tools/testing/selftests/powerpc/pmu/sampling_tests/mmcra_thresh_marked_sample_test.c @@ -63,9 +63,9 @@ static int mmcra_thresh_marked_sample(void) get_mmcra_thd_stop(get_reg_value(intr_regs, "MMCRA"), 4)); FAIL_IF(EV_CODE_EXTRACT(event.attr.config, marked) != get_mmcra_marked(get_reg_value(intr_regs, "MMCRA"), 4)); - FAIL_IF(EV_CODE_EXTRACT(event.attr.config, sample >> 2) != + FAIL_IF((EV_CODE_EXTRACT(event.attr.config, sample) >> 2) != get_mmcra_rand_samp_elig(get_reg_value(intr_regs, "MMCRA"), 4)); - FAIL_IF(EV_CODE_EXTRACT(event.attr.config, sample & 0x3) != + FAIL_IF((EV_CODE_EXTRACT(event.attr.config, sample) & 0x3) != get_mmcra_sample_mode(get_reg_value(intr_regs, "MMCRA"), 4)); FAIL_IF(EV_CODE_EXTRACT(event.attr.config, sm) != get_mmcra_sm(get_reg_value(intr_regs, "MMCRA"), 4)); diff --git a/tools/testing/selftests/powerpc/stringloops/asm/ppc_asm.h b/tools/testing/selftests/powerpc/stringloops/asm/ppc_asm.h index 2b488b78c4f2..e713b69d694a 100644 --- a/tools/testing/selftests/powerpc/stringloops/asm/ppc_asm.h +++ b/tools/testing/selftests/powerpc/stringloops/asm/ppc_asm.h @@ -9,6 +9,7 @@ #define _GLOBAL(A) FUNC_START(test_ ## A) #define _GLOBAL_TOC(A) FUNC_START(test_ ## A) +#define CFUNC(name) name #define CONFIG_ALTIVEC diff --git a/tools/testing/selftests/powerpc/utils.c b/tools/testing/selftests/powerpc/utils.c index 7c8cfedb012a..252fb4a95e90 100644 --- a/tools/testing/selftests/powerpc/utils.c +++ b/tools/testing/selftests/powerpc/utils.c @@ -452,6 +452,29 @@ done: return cpu; } +int bind_to_cpu(int cpu) +{ + cpu_set_t mask; + int err; + + if (cpu == BIND_CPU_ANY) { + cpu = pick_online_cpu(); + if (cpu < 0) + return cpu; + } + + printf("Binding to cpu %d\n", cpu); + + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + + err = sched_setaffinity(0, sizeof(mask), &mask); + if (err) + return err; + + return cpu; +} + bool is_ppc64le(void) { struct utsname uts; diff --git a/tools/testing/selftests/prctl/.gitignore b/tools/testing/selftests/prctl/.gitignore index 91af2b631bc9..7a657b25f686 100644 --- a/tools/testing/selftests/prctl/.gitignore +++ b/tools/testing/selftests/prctl/.gitignore @@ -2,3 +2,4 @@ disable-tsc-ctxt-sw-stress-test disable-tsc-on-off-stress-test disable-tsc-test +set-anon-vma-name-test diff --git a/tools/testing/selftests/prctl/Makefile b/tools/testing/selftests/prctl/Makefile index c7923b205222..c058b81eeb41 100644 --- a/tools/testing/selftests/prctl/Makefile +++ b/tools/testing/selftests/prctl/Makefile @@ -5,7 +5,7 @@ ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/) ifeq ($(ARCH),x86) TEST_PROGS := disable-tsc-ctxt-sw-stress-test disable-tsc-on-off-stress-test \ - disable-tsc-test + disable-tsc-test set-anon-vma-name-test all: $(TEST_PROGS) include ../lib.mk diff --git a/tools/testing/selftests/prctl/config b/tools/testing/selftests/prctl/config new file mode 100644 index 000000000000..c6ed03c544e5 --- /dev/null +++ b/tools/testing/selftests/prctl/config @@ -0,0 +1 @@ +CONFIG_ANON_VMA_NAME=y diff --git a/tools/testing/selftests/prctl/set-anon-vma-name-test.c b/tools/testing/selftests/prctl/set-anon-vma-name-test.c new file mode 100644 index 000000000000..26d853c5a0c1 --- /dev/null +++ b/tools/testing/selftests/prctl/set-anon-vma-name-test.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This test covers the anonymous VMA naming functionality through prctl calls + */ + +#include <errno.h> +#include <sys/prctl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <string.h> + +#include "../kselftest_harness.h" + +#define AREA_SIZE 1024 + +#define GOOD_NAME "goodname" +#define BAD_NAME "badname\1" + +#ifndef PR_SET_VMA +#define PR_SET_VMA 0x53564d41 +#define PR_SET_VMA_ANON_NAME 0 +#endif + + +int rename_vma(unsigned long addr, unsigned long size, char *name) +{ + int res; + + res = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, addr, size, name); + if (res < 0) + return -errno; + return res; +} + +int was_renaming_successful(char *target_name, unsigned long ptr) +{ + FILE *maps_file; + + char line_buf[512], name[128], mode[8]; + unsigned long start_addr, end_addr, offset; + unsigned int major_id, minor_id, node_id; + + char target_buf[128]; + int res = 0, sscanf_res; + + // The entry name in maps will be in format [anon:<target_name>] + sprintf(target_buf, "[anon:%s]", target_name); + maps_file = fopen("/proc/self/maps", "r"); + if (!maps_file) { + printf("## /proc/self/maps file opening error\n"); + return 0; + } + + // Parse the maps file to find the entry we renamed + while (fgets(line_buf, sizeof(line_buf), maps_file)) { + sscanf_res = sscanf(line_buf, "%lx-%lx %7s %lx %u:%u %u %s", &start_addr, + &end_addr, mode, &offset, &major_id, + &minor_id, &node_id, name); + if (sscanf_res == EOF) { + res = 0; + printf("## EOF while parsing the maps file\n"); + break; + } + if (!strcmp(name, target_buf) && start_addr == ptr) { + res = 1; + break; + } + } + fclose(maps_file); + return res; +} + +FIXTURE(vma) { + void *ptr_anon, *ptr_not_anon; +}; + +FIXTURE_SETUP(vma) { + self->ptr_anon = mmap(NULL, AREA_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + ASSERT_NE(self->ptr_anon, NULL); + self->ptr_not_anon = mmap(NULL, AREA_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE, 0, 0); + ASSERT_NE(self->ptr_not_anon, NULL); +} + +FIXTURE_TEARDOWN(vma) { + munmap(self->ptr_anon, AREA_SIZE); + munmap(self->ptr_not_anon, AREA_SIZE); +} + +TEST_F(vma, renaming) { + TH_LOG("Try to rename the VMA with correct parameters"); + EXPECT_GE(rename_vma((unsigned long)self->ptr_anon, AREA_SIZE, GOOD_NAME), 0); + EXPECT_TRUE(was_renaming_successful(GOOD_NAME, (unsigned long)self->ptr_anon)); + + TH_LOG("Try to pass invalid name (with non-printable character \\1) to rename the VMA"); + EXPECT_EQ(rename_vma((unsigned long)self->ptr_anon, AREA_SIZE, BAD_NAME), -EINVAL); + + TH_LOG("Try to rename non-anonynous VMA"); + EXPECT_EQ(rename_vma((unsigned long) self->ptr_not_anon, AREA_SIZE, GOOD_NAME), -EINVAL); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/proc/proc-uptime-001.c b/tools/testing/selftests/proc/proc-uptime-001.c index 781f7a50fc3f..f335eec5067e 100644 --- a/tools/testing/selftests/proc/proc-uptime-001.c +++ b/tools/testing/selftests/proc/proc-uptime-001.c @@ -13,7 +13,9 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -// Test that values in /proc/uptime increment monotonically. +// Test that boottime value in /proc/uptime and CLOCK_BOOTTIME increment +// monotonically. We don't test idle time monotonicity due to broken iowait +// task counting, cf: comment above get_cpu_idle_time_us() #undef NDEBUG #include <assert.h> #include <stdint.h> @@ -25,20 +27,31 @@ int main(void) { - uint64_t start, u0, u1, i0, i1; + uint64_t start, u0, u1, c0, c1; int fd; fd = open("/proc/uptime", O_RDONLY); assert(fd >= 0); - proc_uptime(fd, &u0, &i0); + u0 = proc_uptime(fd); start = u0; + c0 = clock_boottime(); + do { - proc_uptime(fd, &u1, &i1); + u1 = proc_uptime(fd); + c1 = clock_boottime(); + + /* Is /proc/uptime monotonic ? */ assert(u1 >= u0); - assert(i1 >= i0); + + /* Is CLOCK_BOOTTIME monotonic ? */ + assert(c1 >= c0); + + /* Is CLOCK_BOOTTIME VS /proc/uptime monotonic ? */ + assert(c0 >= u0); + u0 = u1; - i0 = i1; + c0 = c1; } while (u1 - start < 100); return 0; diff --git a/tools/testing/selftests/proc/proc-uptime-002.c b/tools/testing/selftests/proc/proc-uptime-002.c index 7d0aa22bdc12..ae453daa96c1 100644 --- a/tools/testing/selftests/proc/proc-uptime-002.c +++ b/tools/testing/selftests/proc/proc-uptime-002.c @@ -13,8 +13,10 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -// Test that values in /proc/uptime increment monotonically -// while shifting across CPUs. +// Test that boottime value in /proc/uptime and CLOCK_BOOTTIME increment +// monotonically while shifting across CPUs. We don't test idle time +// monotonicity due to broken iowait task counting, cf: comment above +// get_cpu_idle_time_us() #undef NDEBUG #include <assert.h> #include <errno.h> @@ -42,10 +44,10 @@ static inline int sys_sched_setaffinity(pid_t pid, unsigned int len, unsigned lo int main(void) { + uint64_t u0, u1, c0, c1; unsigned int len; unsigned long *m; unsigned int cpu; - uint64_t u0, u1, i0, i1; int fd; /* find out "nr_cpu_ids" */ @@ -60,7 +62,9 @@ int main(void) fd = open("/proc/uptime", O_RDONLY); assert(fd >= 0); - proc_uptime(fd, &u0, &i0); + u0 = proc_uptime(fd); + c0 = clock_boottime(); + for (cpu = 0; cpu < len * 8; cpu++) { memset(m, 0, len); m[cpu / (8 * sizeof(unsigned long))] |= 1UL << (cpu % (8 * sizeof(unsigned long))); @@ -68,11 +72,20 @@ int main(void) /* CPU might not exist, ignore error */ sys_sched_setaffinity(0, len, m); - proc_uptime(fd, &u1, &i1); + u1 = proc_uptime(fd); + c1 = clock_boottime(); + + /* Is /proc/uptime monotonic ? */ assert(u1 >= u0); - assert(i1 >= i0); + + /* Is CLOCK_BOOTTIME monotonic ? */ + assert(c1 >= c0); + + /* Is CLOCK_BOOTTIME VS /proc/uptime monotonic ? */ + assert(c0 >= u0); + u0 = u1; - i0 = i1; + c0 = c1; } return 0; diff --git a/tools/testing/selftests/proc/proc-uptime.h b/tools/testing/selftests/proc/proc-uptime.h index dc6a42b1d6b0..730cce4a3d73 100644 --- a/tools/testing/selftests/proc/proc-uptime.h +++ b/tools/testing/selftests/proc/proc-uptime.h @@ -19,10 +19,22 @@ #include <string.h> #include <stdlib.h> #include <unistd.h> +#include <time.h> #include "proc.h" -static void proc_uptime(int fd, uint64_t *uptime, uint64_t *idle) +static uint64_t clock_boottime(void) +{ + struct timespec ts; + int err; + + err = clock_gettime(CLOCK_BOOTTIME, &ts); + assert(err >= 0); + + return (ts.tv_sec * 100) + (ts.tv_nsec / 10000000); +} + +static uint64_t proc_uptime(int fd) { uint64_t val1, val2; char buf[64], *p; @@ -43,18 +55,6 @@ static void proc_uptime(int fd, uint64_t *uptime, uint64_t *idle) assert(p[3] == ' '); val2 = (p[1] - '0') * 10 + p[2] - '0'; - *uptime = val1 * 100 + val2; - - p += 4; - - val1 = xstrtoull(p, &p); - assert(p[0] == '.'); - assert('0' <= p[1] && p[1] <= '9'); - assert('0' <= p[2] && p[2] <= '9'); - assert(p[3] == '\n'); - - val2 = (p[1] - '0') * 10 + p[2] - '0'; - *idle = val1 * 100 + val2; - assert(p + 4 == buf + rv); + return val1 * 100 + val2; } diff --git a/tools/testing/selftests/ptrace/.gitignore b/tools/testing/selftests/ptrace/.gitignore index 792318aaa30c..b7dde152e75a 100644 --- a/tools/testing/selftests/ptrace/.gitignore +++ b/tools/testing/selftests/ptrace/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only get_syscall_info +get_set_sud peeksiginfo vmaccess diff --git a/tools/testing/selftests/ptrace/Makefile b/tools/testing/selftests/ptrace/Makefile index 96ffa94afb91..1c631740a730 100644 --- a/tools/testing/selftests/ptrace/Makefile +++ b/tools/testing/selftests/ptrace/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only CFLAGS += -std=c99 -pthread -Wall $(KHDR_INCLUDES) -TEST_GEN_PROGS := get_syscall_info peeksiginfo vmaccess +TEST_GEN_PROGS := get_syscall_info peeksiginfo vmaccess get_set_sud include ../lib.mk diff --git a/tools/testing/selftests/ptrace/get_set_sud.c b/tools/testing/selftests/ptrace/get_set_sud.c new file mode 100644 index 000000000000..5297b10d25c3 --- /dev/null +++ b/tools/testing/selftests/ptrace/get_set_sud.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include "../kselftest_harness.h" +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <sys/wait.h> +#include <sys/syscall.h> +#include <sys/prctl.h> + +#include "linux/ptrace.h" + +static int sys_ptrace(int request, pid_t pid, void *addr, void *data) +{ + return syscall(SYS_ptrace, request, pid, addr, data); +} + +TEST(get_set_sud) +{ + struct ptrace_sud_config config; + pid_t child; + int ret = 0; + int status; + + child = fork(); + ASSERT_GE(child, 0); + if (child == 0) { + ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) { + TH_LOG("PTRACE_TRACEME: %m"); + } + kill(getpid(), SIGSTOP); + _exit(1); + } + + waitpid(child, &status, 0); + + memset(&config, 0xff, sizeof(config)); + config.mode = PR_SYS_DISPATCH_ON; + + ret = sys_ptrace(PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG, child, + (void *)sizeof(config), &config); + + ASSERT_EQ(ret, 0); + ASSERT_EQ(config.mode, PR_SYS_DISPATCH_OFF); + ASSERT_EQ(config.selector, 0); + ASSERT_EQ(config.offset, 0); + ASSERT_EQ(config.len, 0); + + config.mode = PR_SYS_DISPATCH_ON; + config.selector = 0; + config.offset = 0x400000; + config.len = 0x1000; + + ret = sys_ptrace(PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG, child, + (void *)sizeof(config), &config); + + ASSERT_EQ(ret, 0); + + memset(&config, 1, sizeof(config)); + ret = sys_ptrace(PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG, child, + (void *)sizeof(config), &config); + + ASSERT_EQ(ret, 0); + ASSERT_EQ(config.mode, PR_SYS_DISPATCH_ON); + ASSERT_EQ(config.selector, 0); + ASSERT_EQ(config.offset, 0x400000); + ASSERT_EQ(config.len, 0x1000); + + kill(child, SIGKILL); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/ptrace/peeksiginfo.c b/tools/testing/selftests/ptrace/peeksiginfo.c index 54900657eb44..a6884f66dc01 100644 --- a/tools/testing/selftests/ptrace/peeksiginfo.c +++ b/tools/testing/selftests/ptrace/peeksiginfo.c @@ -151,7 +151,7 @@ out: int main(int argc, char *argv[]) { - siginfo_t siginfo[SIGNR]; + siginfo_t siginfo; int i, exit_code = 1; sigset_t blockmask; pid_t child; @@ -176,13 +176,13 @@ int main(int argc, char *argv[]) /* Send signals in process-wide and per-thread queues */ for (i = 0; i < SIGNR; i++) { - siginfo->si_code = TEST_SICODE_SHARE; - siginfo->si_int = i; - sys_rt_sigqueueinfo(child, SIGRTMIN, siginfo); + siginfo.si_code = TEST_SICODE_SHARE; + siginfo.si_int = i; + sys_rt_sigqueueinfo(child, SIGRTMIN, &siginfo); - siginfo->si_code = TEST_SICODE_PRIV; - siginfo->si_int = i; - sys_rt_tgsigqueueinfo(child, child, SIGRTMIN, siginfo); + siginfo.si_code = TEST_SICODE_PRIV; + siginfo.si_int = i; + sys_rt_tgsigqueueinfo(child, child, SIGRTMIN, &siginfo); } if (sys_ptrace(PTRACE_ATTACH, child, NULL, NULL) == -1) diff --git a/tools/testing/selftests/rcutorture/bin/kvm-again.sh b/tools/testing/selftests/rcutorture/bin/kvm-again.sh index 8a968fbda02c..88ca4e368489 100755 --- a/tools/testing/selftests/rcutorture/bin/kvm-again.sh +++ b/tools/testing/selftests/rcutorture/bin/kvm-again.sh @@ -193,7 +193,7 @@ do qemu_cmd_dir="`dirname "$i"`" kernel_dir="`echo $qemu_cmd_dir | sed -e 's/\.[0-9]\+$//'`" jitter_dir="`dirname "$kernel_dir"`" - kvm-transform.sh "$kernel_dir/bzImage" "$qemu_cmd_dir/console.log" "$jitter_dir" $dur "$bootargs" < $T/qemu-cmd > $i + kvm-transform.sh "$kernel_dir/bzImage" "$qemu_cmd_dir/console.log" "$jitter_dir" "$dur" "$bootargs" < $T/qemu-cmd > $i if test -n "$arg_remote" then echo "# TORTURE_KCONFIG_GDB_ARG=''" >> $i diff --git a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh new file mode 100755 index 000000000000..2e63ef009d59 --- /dev/null +++ b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0+ +# +# Run SRCU-lockdep tests and report any that fail to meet expectations. +# +# Copyright (C) 2021 Meta Platforms, Inc. +# +# Authors: Paul E. McKenney <paulmck@kernel.org> + +usage () { + echo "Usage: $scriptname optional arguments:" + echo " --datestamp string" + exit 1 +} + +ds=`date +%Y.%m.%d-%H.%M.%S`-srcu_lockdep +scriptname="$0" + +T="`mktemp -d ${TMPDIR-/tmp}/srcu_lockdep.sh.XXXXXX`" +trap 'rm -rf $T' 0 + +RCUTORTURE="`pwd`/tools/testing/selftests/rcutorture"; export RCUTORTURE +PATH=${RCUTORTURE}/bin:$PATH; export PATH +. functions.sh + +while test $# -gt 0 +do + case "$1" in + --datestamp) + checkarg --datestamp "(relative pathname)" "$#" "$2" '^[a-zA-Z0-9._/-]*$' '^--' + ds=$2 + shift + ;; + *) + echo Unknown argument $1 + usage + ;; + esac + shift +done + +err= +nerrs=0 +for d in 0 1 +do + for t in 0 1 2 + do + for c in 1 2 3 + do + err= + val=$((d*1000+t*10+c)) + tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 5s --configs "SRCU-P" --bootargs "rcutorture.test_srcu_lockdep=$val" --trust-make --datestamp "$ds/$val" > "$T/kvm.sh.out" 2>&1 + ret=$? + mv "$T/kvm.sh.out" "$RCUTORTURE/res/$ds/$val" + if test "$d" -ne 0 && test "$ret" -eq 0 + then + err=1 + echo -n Unexpected success for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + fi + if test "$d" -eq 0 && test "$ret" -ne 0 + then + err=1 + echo -n Unexpected failure for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + fi + if test -n "$err" + then + grep "rcu_torture_init_srcu_lockdep: test_srcu_lockdep = " "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log" | sed -e 's/^.*rcu_torture_init_srcu_lockdep://' >> "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + cat "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + nerrs=$((nerrs+1)) + fi + done + done +done +if test "$nerrs" -ne 0 +then + exit 1 +fi +exit 0 diff --git a/tools/testing/selftests/rcutorture/bin/torture.sh b/tools/testing/selftests/rcutorture/bin/torture.sh index 130d0de4c3bb..5a2ae2264403 100755 --- a/tools/testing/selftests/rcutorture/bin/torture.sh +++ b/tools/testing/selftests/rcutorture/bin/torture.sh @@ -497,16 +497,16 @@ fi if test "$do_clocksourcewd" = "yes" then - torture_bootargs="rcupdate.rcu_cpu_stall_suppress_at_boot=1 torture.disable_onoff_at_boot rcupdate.rcu_task_stall_timeout=30000" + torture_bootargs="rcupdate.rcu_cpu_stall_suppress_at_boot=1 torture.disable_onoff_at_boot rcupdate.rcu_task_stall_timeout=30000 tsc=watchdog" torture_set "clocksourcewd-1" tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 45s --configs TREE03 --kconfig "CONFIG_TEST_CLOCKSOURCE_WATCHDOG=y" --trust-make - torture_bootargs="rcupdate.rcu_cpu_stall_suppress_at_boot=1 torture.disable_onoff_at_boot rcupdate.rcu_task_stall_timeout=30000 clocksource.max_cswd_read_retries=1" + torture_bootargs="rcupdate.rcu_cpu_stall_suppress_at_boot=1 torture.disable_onoff_at_boot rcupdate.rcu_task_stall_timeout=30000 clocksource.max_cswd_read_retries=1 tsc=watchdog" torture_set "clocksourcewd-2" tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 45s --configs TREE03 --kconfig "CONFIG_TEST_CLOCKSOURCE_WATCHDOG=y" --trust-make # In case our work is already done... if test "$do_rcutorture" != "yes" then - torture_bootargs="rcupdate.rcu_cpu_stall_suppress_at_boot=1 torture.disable_onoff_at_boot rcupdate.rcu_task_stall_timeout=30000" + torture_bootargs="rcupdate.rcu_cpu_stall_suppress_at_boot=1 torture.disable_onoff_at_boot rcupdate.rcu_task_stall_timeout=30000 tsc=watchdog" torture_set "clocksourcewd-3" tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 45s --configs TREE03 --trust-make fi fi diff --git a/tools/testing/selftests/rcutorture/configs/lock/CFLIST b/tools/testing/selftests/rcutorture/configs/lock/CFLIST index 41bae5824339..28e23d05d5a5 100644 --- a/tools/testing/selftests/rcutorture/configs/lock/CFLIST +++ b/tools/testing/selftests/rcutorture/configs/lock/CFLIST @@ -5,3 +5,5 @@ LOCK04 LOCK05 LOCK06 LOCK07 +LOCK08 +LOCK09 diff --git a/tools/testing/selftests/rcutorture/configs/lock/LOCK08 b/tools/testing/selftests/rcutorture/configs/lock/LOCK08 new file mode 100644 index 000000000000..1d1da1477fc3 --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/lock/LOCK08 @@ -0,0 +1,6 @@ +CONFIG_SMP=y +CONFIG_NR_CPUS=4 +CONFIG_HOTPLUG_CPU=y +CONFIG_PREEMPT_NONE=n +CONFIG_PREEMPT_VOLUNTARY=n +CONFIG_PREEMPT=y diff --git a/tools/testing/selftests/rcutorture/configs/lock/LOCK08.boot b/tools/testing/selftests/rcutorture/configs/lock/LOCK08.boot new file mode 100644 index 000000000000..b8b6caebb89e --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/lock/LOCK08.boot @@ -0,0 +1 @@ +locktorture.torture_type=mutex_lock locktorture.nested_locks=8 diff --git a/tools/testing/selftests/rcutorture/configs/lock/LOCK09 b/tools/testing/selftests/rcutorture/configs/lock/LOCK09 new file mode 100644 index 000000000000..1d1da1477fc3 --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/lock/LOCK09 @@ -0,0 +1,6 @@ +CONFIG_SMP=y +CONFIG_NR_CPUS=4 +CONFIG_HOTPLUG_CPU=y +CONFIG_PREEMPT_NONE=n +CONFIG_PREEMPT_VOLUNTARY=n +CONFIG_PREEMPT=y diff --git a/tools/testing/selftests/rcutorture/configs/lock/LOCK09.boot b/tools/testing/selftests/rcutorture/configs/lock/LOCK09.boot new file mode 100644 index 000000000000..fd5eff148a93 --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/lock/LOCK09.boot @@ -0,0 +1 @@ +locktorture.torture_type=rtmutex_lock locktorture.nested_locks=8 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE01 b/tools/testing/selftests/rcutorture/configs/rcu/TREE01 index 8ae41d5f81a3..04831ef1f9b5 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE01 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE01 @@ -15,3 +15,4 @@ CONFIG_DEBUG_LOCK_ALLOC=n CONFIG_RCU_BOOST=n CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y +CONFIG_BOOTPARAM_HOTPLUG_CPU0=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE04 b/tools/testing/selftests/rcutorture/configs/rcu/TREE04 index ae395981b5e5..dc4985064b3a 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE04 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE04 @@ -15,3 +15,4 @@ CONFIG_DEBUG_LOCK_ALLOC=n CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y CONFIG_RCU_EQS_DEBUG=y +CONFIG_RCU_LAZY=y diff --git a/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt b/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt index 42acb1a64ce1..3f5fb66f16df 100644 --- a/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt +++ b/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt @@ -71,9 +71,5 @@ CONFIG_TASKS_RCU These are controlled by CONFIG_PREEMPT and/or CONFIG_SMP. -CONFIG_SRCU - - Selected by CONFIG_RCU_TORTURE_TEST, so cannot disable. - boot parameters ignored: TBD diff --git a/tools/testing/selftests/resctrl/cache.c b/tools/testing/selftests/resctrl/cache.c index 68ff856d36f0..8a4fe8693be6 100644 --- a/tools/testing/selftests/resctrl/cache.c +++ b/tools/testing/selftests/resctrl/cache.c @@ -48,7 +48,7 @@ static int perf_event_open_llc_miss(pid_t pid, int cpu_no) return 0; } -static int initialize_llc_perf(void) +static void initialize_llc_perf(void) { memset(&pea_llc_miss, 0, sizeof(struct perf_event_attr)); memset(&rf_cqm, 0, sizeof(struct read_format)); @@ -59,8 +59,6 @@ static int initialize_llc_perf(void) pea_llc_miss.config = PERF_COUNT_HW_CACHE_MISSES; rf_cqm.nr = 1; - - return 0; } static int reset_enable_llc_perf(pid_t pid, int cpu_no) @@ -79,7 +77,7 @@ static int reset_enable_llc_perf(pid_t pid, int cpu_no) /* * get_llc_perf: llc cache miss through perf events - * @cpu_no: CPU number that the benchmark PID is binded to + * @llc_perf_miss: LLC miss counter that is filled on success * * Perf events like HW_CACHE_MISSES could be used to validate number of * cache lines allocated. @@ -234,20 +232,19 @@ int cat_val(struct resctrl_val_param *param) if (ret) return ret; - if (!strncmp(resctrl_val, CAT_STR, sizeof(CAT_STR))) { - ret = initialize_llc_perf(); - if (ret) - return ret; - } + if (!strncmp(resctrl_val, CAT_STR, sizeof(CAT_STR))) + initialize_llc_perf(); /* Test runs until the callback setup() tells the test to stop. */ while (1) { if (!strncmp(resctrl_val, CAT_STR, sizeof(CAT_STR))) { ret = param->setup(1, param); - if (ret) { + if (ret == END_OF_TESTS) { ret = 0; break; } + if (ret < 0) + break; ret = reset_enable_llc_perf(bm_pid, param->cpu_no); if (ret) break; diff --git a/tools/testing/selftests/resctrl/cat_test.c b/tools/testing/selftests/resctrl/cat_test.c index 1c5e90c63254..fb1443f888c4 100644 --- a/tools/testing/selftests/resctrl/cat_test.c +++ b/tools/testing/selftests/resctrl/cat_test.c @@ -40,7 +40,7 @@ static int cat_setup(int num, ...) /* Run NUM_OF_RUNS times */ if (p->num_of_runs >= NUM_OF_RUNS) - return -1; + return END_OF_TESTS; if (p->num_of_runs == 0) { sprintf(schemata, "%lx", p->mask); @@ -103,7 +103,6 @@ int cat_perf_miss_val(int cpu_no, int n, char *cache_type) unsigned long l_mask, l_mask_1; int ret, pipefd[2], sibling_cpu_no; char pipe_message; - pid_t bm_pid; cache_size = 0; @@ -145,7 +144,7 @@ int cat_perf_miss_val(int cpu_no, int n, char *cache_type) struct resctrl_val_param param = { .resctrl_val = CAT_STR, .cpu_no = cpu_no, - .mum_resctrlfs = 0, + .mum_resctrlfs = false, .setup = cat_setup, }; @@ -167,6 +166,7 @@ int cat_perf_miss_val(int cpu_no, int n, char *cache_type) return errno; } + fflush(stdout); bm_pid = fork(); /* Set param values for child thread which will be allocated bitmask @@ -180,28 +180,31 @@ int cat_perf_miss_val(int cpu_no, int n, char *cache_type) strcpy(param.filename, RESULT_FILE_NAME1); param.num_of_runs = 0; param.cpu_no = sibling_cpu_no; + } else { + ret = signal_handler_register(); + if (ret) { + kill(bm_pid, SIGKILL); + goto out; + } } remove(param.filename); ret = cat_val(¶m); - if (ret) - return ret; - - ret = check_results(¶m); - if (ret) - return ret; + if (ret == 0) + ret = check_results(¶m); if (bm_pid == 0) { /* Tell parent that child is ready */ close(pipefd[0]); pipe_message = 1; if (write(pipefd[1], &pipe_message, sizeof(pipe_message)) < - sizeof(pipe_message)) { - close(pipefd[1]); + sizeof(pipe_message)) + /* + * Just print the error message. + * Let while(1) run and wait for itself to be killed. + */ perror("# failed signaling parent process"); - return errno; - } close(pipefd[1]); while (1) @@ -219,11 +222,13 @@ int cat_perf_miss_val(int cpu_no, int n, char *cache_type) } close(pipefd[0]); kill(bm_pid, SIGKILL); + signal_handler_unregister(); } +out: cat_test_cleanup(); if (bm_pid) umount_resctrlfs(); - return 0; + return ret; } diff --git a/tools/testing/selftests/resctrl/cmt_test.c b/tools/testing/selftests/resctrl/cmt_test.c index 8968e36db99d..af71b2141271 100644 --- a/tools/testing/selftests/resctrl/cmt_test.c +++ b/tools/testing/selftests/resctrl/cmt_test.c @@ -32,7 +32,7 @@ static int cmt_setup(int num, ...) /* Run NUM_OF_RUNS times */ if (p->num_of_runs >= NUM_OF_RUNS) - return -1; + return END_OF_TESTS; p->num_of_runs++; @@ -82,12 +82,11 @@ void cmt_test_cleanup(void) int cmt_resctrl_val(int cpu_no, int n, char **benchmark_cmd) { - int ret, mum_resctrlfs; + int ret; cache_size = 0; - mum_resctrlfs = 1; - ret = remount_resctrlfs(mum_resctrlfs); + ret = remount_resctrlfs(true); if (ret) return ret; @@ -118,7 +117,7 @@ int cmt_resctrl_val(int cpu_no, int n, char **benchmark_cmd) .ctrlgrp = "c1", .mongrp = "m1", .cpu_no = cpu_no, - .mum_resctrlfs = 0, + .mum_resctrlfs = false, .filename = RESULT_FILE_NAME, .mask = ~(long_mask << n) & long_mask, .span = cache_size * n / count_of_bits, @@ -133,13 +132,12 @@ int cmt_resctrl_val(int cpu_no, int n, char **benchmark_cmd) ret = resctrl_val(benchmark_cmd, ¶m); if (ret) - return ret; + goto out; ret = check_results(¶m, n); - if (ret) - return ret; +out: cmt_test_cleanup(); - return 0; + return ret; } diff --git a/tools/testing/selftests/resctrl/fill_buf.c b/tools/testing/selftests/resctrl/fill_buf.c index 56ccbeae0638..341cc93ca84c 100644 --- a/tools/testing/selftests/resctrl/fill_buf.c +++ b/tools/testing/selftests/resctrl/fill_buf.c @@ -14,7 +14,6 @@ #include <sys/types.h> #include <sys/wait.h> #include <inttypes.h> -#include <malloc.h> #include <string.h> #include "resctrl.h" @@ -33,14 +32,6 @@ static void sb(void) #endif } -static void ctrl_handler(int signo) -{ - free(startptr); - printf("\nEnding\n"); - sb(); - exit(EXIT_SUCCESS); -} - static void cl_flush(void *p) { #if defined(__i386) || defined(__x86_64) @@ -64,10 +55,14 @@ static void mem_flush(void *p, size_t s) static void *malloc_and_init_memory(size_t s) { + void *p = NULL; uint64_t *p64; size_t s64; + int ret; - void *p = memalign(PAGE_SIZE, s); + ret = posix_memalign(&p, PAGE_SIZE, s); + if (ret < 0) + return NULL; p64 = (uint64_t *)p; s64 = s / sizeof(uint64_t); @@ -198,12 +193,6 @@ int run_fill_buf(unsigned long span, int malloc_and_init_memory, unsigned long long cache_size = span; int ret; - /* set up ctrl-c handler */ - if (signal(SIGINT, ctrl_handler) == SIG_ERR) - printf("Failed to catch SIGINT!\n"); - if (signal(SIGHUP, ctrl_handler) == SIG_ERR) - printf("Failed to catch SIGHUP!\n"); - ret = fill_cache(cache_size, malloc_and_init_memory, memflush, op, resctrl_val); if (ret) { diff --git a/tools/testing/selftests/resctrl/mba_test.c b/tools/testing/selftests/resctrl/mba_test.c index 1a1bdb6180cf..cde3781a9ab0 100644 --- a/tools/testing/selftests/resctrl/mba_test.c +++ b/tools/testing/selftests/resctrl/mba_test.c @@ -28,6 +28,7 @@ static int mba_setup(int num, ...) struct resctrl_val_param *p; char allocation_str[64]; va_list param; + int ret; va_start(param, num); p = va_arg(param, struct resctrl_val_param *); @@ -41,20 +42,24 @@ static int mba_setup(int num, ...) return 0; if (allocation < ALLOCATION_MIN || allocation > ALLOCATION_MAX) - return -1; + return END_OF_TESTS; sprintf(allocation_str, "%d", allocation); - write_schemata(p->ctrlgrp, allocation_str, p->cpu_no, p->resctrl_val); + ret = write_schemata(p->ctrlgrp, allocation_str, p->cpu_no, + p->resctrl_val); + if (ret < 0) + return ret; + allocation -= ALLOCATION_STEP; return 0; } -static void show_mba_info(unsigned long *bw_imc, unsigned long *bw_resc) +static bool show_mba_info(unsigned long *bw_imc, unsigned long *bw_resc) { int allocation, runs; - bool failed = false; + bool ret = false; ksft_print_msg("Results are displayed in (MB)\n"); /* Memory bandwidth from 100% down to 10% */ @@ -90,13 +95,15 @@ static void show_mba_info(unsigned long *bw_imc, unsigned long *bw_resc) ksft_print_msg("avg_bw_imc: %lu\n", avg_bw_imc); ksft_print_msg("avg_bw_resc: %lu\n", avg_bw_resc); if (avg_diff_per > MAX_DIFF_PERCENT) - failed = true; + ret = true; } ksft_print_msg("%s Check schemata change using MBA\n", - failed ? "Fail:" : "Pass:"); - if (failed) + ret ? "Fail:" : "Pass:"); + if (ret) ksft_print_msg("At least one test failed\n"); + + return ret; } static int check_results(void) @@ -132,9 +139,7 @@ static int check_results(void) fclose(fp); - show_mba_info(bw_imc, bw_resc); - - return 0; + return show_mba_info(bw_imc, bw_resc); } void mba_test_cleanup(void) @@ -149,7 +154,7 @@ int mba_schemata_change(int cpu_no, char *bw_report, char **benchmark_cmd) .ctrlgrp = "c1", .mongrp = "m1", .cpu_no = cpu_no, - .mum_resctrlfs = 1, + .mum_resctrlfs = true, .filename = RESULT_FILE_NAME, .bw_report = bw_report, .setup = mba_setup @@ -160,13 +165,12 @@ int mba_schemata_change(int cpu_no, char *bw_report, char **benchmark_cmd) ret = resctrl_val(benchmark_cmd, ¶m); if (ret) - return ret; + goto out; ret = check_results(); - if (ret) - return ret; +out: mba_test_cleanup(); - return 0; + return ret; } diff --git a/tools/testing/selftests/resctrl/mbm_test.c b/tools/testing/selftests/resctrl/mbm_test.c index 8392e5c55ed0..538d35a6485a 100644 --- a/tools/testing/selftests/resctrl/mbm_test.c +++ b/tools/testing/selftests/resctrl/mbm_test.c @@ -89,23 +89,24 @@ static int check_results(int span) static int mbm_setup(int num, ...) { struct resctrl_val_param *p; - static int num_of_runs; va_list param; int ret = 0; - /* Run NUM_OF_RUNS times */ - if (num_of_runs++ >= NUM_OF_RUNS) - return -1; - va_start(param, num); p = va_arg(param, struct resctrl_val_param *); va_end(param); + /* Run NUM_OF_RUNS times */ + if (p->num_of_runs >= NUM_OF_RUNS) + return END_OF_TESTS; + /* Set up shemata with 100% allocation on the first run. */ - if (num_of_runs == 0) + if (p->num_of_runs == 0) ret = write_schemata(p->ctrlgrp, "100", p->cpu_no, p->resctrl_val); + p->num_of_runs++; + return ret; } @@ -122,7 +123,7 @@ int mbm_bw_change(int span, int cpu_no, char *bw_report, char **benchmark_cmd) .mongrp = "m1", .span = span, .cpu_no = cpu_no, - .mum_resctrlfs = 1, + .mum_resctrlfs = true, .filename = RESULT_FILE_NAME, .bw_report = bw_report, .setup = mbm_setup @@ -133,13 +134,12 @@ int mbm_bw_change(int span, int cpu_no, char *bw_report, char **benchmark_cmd) ret = resctrl_val(benchmark_cmd, ¶m); if (ret) - return ret; + goto out; ret = check_results(span); - if (ret) - return ret; +out: mbm_test_cleanup(); - return 0; + return ret; } diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h index f0ded31fb3c7..87e39456dee0 100644 --- a/tools/testing/selftests/resctrl/resctrl.h +++ b/tools/testing/selftests/resctrl/resctrl.h @@ -28,7 +28,7 @@ #define MB (1024 * 1024) #define RESCTRL_PATH "/sys/fs/resctrl" #define PHYS_ID_PATH "/sys/devices/system/cpu/cpu" -#define CBM_MASK_PATH "/sys/fs/resctrl/info" +#define INFO_PATH "/sys/fs/resctrl/info" #define L3_PATH "/sys/fs/resctrl/info/L3" #define MB_PATH "/sys/fs/resctrl/info/MB" #define L3_MON_PATH "/sys/fs/resctrl/info/L3_MON" @@ -37,6 +37,8 @@ #define ARCH_INTEL 1 #define ARCH_AMD 2 +#define END_OF_TESTS 1 + #define PARENT_EXIT(err_msg) \ do { \ perror(err_msg); \ @@ -62,7 +64,7 @@ struct resctrl_val_param { char mongrp[64]; int cpu_no; unsigned long span; - int mum_resctrlfs; + bool mum_resctrlfs; char filename[64]; char *bw_report; unsigned long mask; @@ -107,6 +109,8 @@ void mba_test_cleanup(void); int get_cbm_mask(char *cache_type, char *cbm_mask); int get_cache_size(int cpu_no, char *cache_type, unsigned long *cache_size); void ctrlc_handler(int signum, siginfo_t *info, void *ptr); +int signal_handler_register(void); +void signal_handler_unregister(void); int cat_val(struct resctrl_val_param *param); void cat_test_cleanup(void); int cat_perf_miss_val(int cpu_no, int no_of_bits, char *cache_type); diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c index df0d8d8526fc..9b9751206e1c 100644 --- a/tools/testing/selftests/resctrl/resctrl_tests.c +++ b/tools/testing/selftests/resctrl/resctrl_tests.c @@ -77,7 +77,7 @@ static void run_mbm_test(bool has_ben, char **benchmark_cmd, int span, ksft_print_msg("Starting MBM BW change ...\n"); - if (!validate_resctrl_feature_request(MBM_STR)) { + if (!validate_resctrl_feature_request(MBM_STR) || (get_vendor() != ARCH_INTEL)) { ksft_test_result_skip("Hardware does not support MBM or MBM is disabled\n"); return; } @@ -88,7 +88,6 @@ static void run_mbm_test(bool has_ben, char **benchmark_cmd, int span, ksft_test_result(!res, "MBM: bw change\n"); if ((get_vendor() == ARCH_INTEL) && res) ksft_print_msg("Intel MBM may be inaccurate when Sub-NUMA Clustering is enabled. Check BIOS configuration.\n"); - mbm_test_cleanup(); } static void run_mba_test(bool has_ben, char **benchmark_cmd, int span, @@ -98,7 +97,7 @@ static void run_mba_test(bool has_ben, char **benchmark_cmd, int span, ksft_print_msg("Starting MBA Schemata change ...\n"); - if (!validate_resctrl_feature_request(MBA_STR)) { + if (!validate_resctrl_feature_request(MBA_STR) || (get_vendor() != ARCH_INTEL)) { ksft_test_result_skip("Hardware does not support MBA or MBA is disabled\n"); return; } @@ -107,7 +106,6 @@ static void run_mba_test(bool has_ben, char **benchmark_cmd, int span, sprintf(benchmark_cmd[1], "%d", span); res = mba_schemata_change(cpu_no, bw_report, benchmark_cmd); ksft_test_result(!res, "MBA: schemata change\n"); - mba_test_cleanup(); } static void run_cmt_test(bool has_ben, char **benchmark_cmd, int cpu_no) @@ -126,7 +124,6 @@ static void run_cmt_test(bool has_ben, char **benchmark_cmd, int cpu_no) ksft_test_result(!res, "CMT: test\n"); if ((get_vendor() == ARCH_INTEL) && res) ksft_print_msg("Intel CMT may be inaccurate when Sub-NUMA Clustering is enabled. Check BIOS configuration.\n"); - cmt_test_cleanup(); } static void run_cat_test(int cpu_no, int no_of_bits) @@ -142,7 +139,6 @@ static void run_cat_test(int cpu_no, int no_of_bits) res = cat_perf_miss_val(cpu_no, no_of_bits, "L3"); ksft_test_result(!res, "CAT: test\n"); - cat_test_cleanup(); } int main(int argc, char **argv) @@ -258,10 +254,10 @@ int main(int argc, char **argv) ksft_set_plan(tests ? : 4); - if ((get_vendor() == ARCH_INTEL) && mbm_test) + if (mbm_test) run_mbm_test(has_ben, benchmark_cmd, span, cpu_no, bw_report); - if ((get_vendor() == ARCH_INTEL) && mba_test) + if (mba_test) run_mba_test(has_ben, benchmark_cmd, span, cpu_no, bw_report); if (cmt_test) @@ -272,5 +268,5 @@ int main(int argc, char **argv) umount_resctrlfs(); - return ksft_exit_pass(); + ksft_finished(); } diff --git a/tools/testing/selftests/resctrl/resctrl_val.c b/tools/testing/selftests/resctrl/resctrl_val.c index b32b96356ec7..ab1eab1e7ff6 100644 --- a/tools/testing/selftests/resctrl/resctrl_val.c +++ b/tools/testing/selftests/resctrl/resctrl_val.c @@ -477,6 +477,45 @@ void ctrlc_handler(int signum, siginfo_t *info, void *ptr) } /* + * Register CTRL-C handler for parent, as it has to kill + * child process before exiting. + */ +int signal_handler_register(void) +{ + struct sigaction sigact; + int ret = 0; + + sigact.sa_sigaction = ctrlc_handler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = SA_SIGINFO; + if (sigaction(SIGINT, &sigact, NULL) || + sigaction(SIGTERM, &sigact, NULL) || + sigaction(SIGHUP, &sigact, NULL)) { + perror("# sigaction"); + ret = -1; + } + return ret; +} + +/* + * Reset signal handler to SIG_DFL. + * Non-Value return because the caller should keep + * the error code of other path even if sigaction fails. + */ +void signal_handler_unregister(void) +{ + struct sigaction sigact; + + sigact.sa_handler = SIG_DFL; + sigemptyset(&sigact.sa_mask); + if (sigaction(SIGINT, &sigact, NULL) || + sigaction(SIGTERM, &sigact, NULL) || + sigaction(SIGHUP, &sigact, NULL)) { + perror("# sigaction"); + } +} + +/* * print_results_bw: the memory bandwidth results are stored in a file * @filename: file that stores the results * @bm_pid: child pid that runs benchmark @@ -629,6 +668,7 @@ int resctrl_val(char **benchmark_cmd, struct resctrl_val_param *param) * Fork to start benchmark, save child's pid so that it can be killed * when needed */ + fflush(stdout); bm_pid = fork(); if (bm_pid == -1) { perror("# Unable to fork"); @@ -670,39 +710,28 @@ int resctrl_val(char **benchmark_cmd, struct resctrl_val_param *param) ksft_print_msg("Benchmark PID: %d\n", bm_pid); - /* - * Register CTRL-C handler for parent, as it has to kill benchmark - * before exiting - */ - sigact.sa_sigaction = ctrlc_handler; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = SA_SIGINFO; - if (sigaction(SIGINT, &sigact, NULL) || - sigaction(SIGTERM, &sigact, NULL) || - sigaction(SIGHUP, &sigact, NULL)) { - perror("# sigaction"); - ret = errno; + ret = signal_handler_register(); + if (ret) goto out; - } value.sival_ptr = benchmark_cmd; /* Taskset benchmark to specified cpu */ ret = taskset_benchmark(bm_pid, param->cpu_no); if (ret) - goto out; + goto unregister; /* Write benchmark to specified control&monitoring grp in resctrl FS */ ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp, resctrl_val); if (ret) - goto out; + goto unregister; if (!strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR)) || !strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR))) { ret = initialize_mem_bw_imc(); if (ret) - goto out; + goto unregister; initialize_mem_bw_resctrl(param->ctrlgrp, param->mongrp, param->cpu_no, resctrl_val); @@ -717,7 +746,7 @@ int resctrl_val(char **benchmark_cmd, struct resctrl_val_param *param) sizeof(pipe_message)) { perror("# failed reading message from child process"); close(pipefd[0]); - goto out; + goto unregister; } } close(pipefd[0]); @@ -726,7 +755,7 @@ int resctrl_val(char **benchmark_cmd, struct resctrl_val_param *param) if (sigqueue(bm_pid, SIGUSR1, value) == -1) { perror("# sigqueue SIGUSR1 to child"); ret = errno; - goto out; + goto unregister; } /* Give benchmark enough time to fully run */ @@ -734,32 +763,29 @@ int resctrl_val(char **benchmark_cmd, struct resctrl_val_param *param) /* Test runs until the callback setup() tells the test to stop. */ while (1) { + ret = param->setup(1, param); + if (ret == END_OF_TESTS) { + ret = 0; + break; + } + if (ret < 0) + break; + if (!strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR)) || !strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR))) { - ret = param->setup(1, param); - if (ret) { - ret = 0; - break; - } - ret = measure_vals(param, &bw_resc_start); if (ret) break; } else if (!strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR))) { - ret = param->setup(1, param); - if (ret) { - ret = 0; - break; - } sleep(1); ret = measure_cache_vals(param, bm_pid); if (ret) break; - } else { - break; } } +unregister: + signal_handler_unregister(); out: kill(bm_pid, SIGKILL); umount_resctrlfs(); diff --git a/tools/testing/selftests/resctrl/resctrlfs.c b/tools/testing/selftests/resctrl/resctrlfs.c index 6f543e470ad4..fb00245dee92 100644 --- a/tools/testing/selftests/resctrl/resctrlfs.c +++ b/tools/testing/selftests/resctrl/resctrlfs.c @@ -210,7 +210,7 @@ int get_cbm_mask(char *cache_type, char *cbm_mask) if (!cbm_mask) return -1; - sprintf(cbm_mask_path, "%s/%s/cbm_mask", CBM_MASK_PATH, cache_type); + sprintf(cbm_mask_path, "%s/%s/cbm_mask", INFO_PATH, cache_type); fp = fopen(cbm_mask_path, "r"); if (!fp) { @@ -498,6 +498,7 @@ int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, char *resctrl_val) FILE *fp; if (strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR)) && + strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR)) && strncmp(resctrl_val, CAT_STR, sizeof(CAT_STR)) && strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR))) return -ENOENT; @@ -523,7 +524,8 @@ int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, char *resctrl_val) if (!strncmp(resctrl_val, CAT_STR, sizeof(CAT_STR)) || !strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR))) sprintf(schema, "%s%d%c%s", "L3:", resource_id, '=', schemata); - if (!strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR))) + if (!strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR)) || + !strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR))) sprintf(schema, "%s%d%c%s", "MB:", resource_id, '=', schemata); fp = fopen(controlgroup, "w"); @@ -676,6 +678,7 @@ int filter_dmesg(void) perror("pipe"); return ret; } + fflush(stdout); pid = fork(); if (pid == 0) { close(pipefds[0]); diff --git a/tools/testing/selftests/riscv/Makefile b/tools/testing/selftests/riscv/Makefile new file mode 100644 index 000000000000..32a72902d045 --- /dev/null +++ b/tools/testing/selftests/riscv/Makefile @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0 +# Originally tools/testing/arm64/Makefile + +# When ARCH not overridden for crosscompiling, lookup machine +ARCH ?= $(shell uname -m 2>/dev/null || echo not) + +ifneq (,$(filter $(ARCH),riscv)) +RISCV_SUBTARGETS ?= hwprobe +else +RISCV_SUBTARGETS := +endif + +CFLAGS := -Wall -O2 -g + +# A proper top_srcdir is needed by KSFT(lib.mk) +top_srcdir = $(realpath ../../../../) + +# Additional include paths needed by kselftest.h and local headers +CFLAGS += -I$(top_srcdir)/tools/testing/selftests/ + +CFLAGS += $(KHDR_INCLUDES) + +export CFLAGS +export top_srcdir + +all: + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + mkdir -p $$BUILD_TARGET; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +install: all + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +run_tests: all + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +# Avoid any output on non riscv on emit_tests +emit_tests: all + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +clean: + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +.PHONY: all clean install run_tests emit_tests diff --git a/tools/testing/selftests/riscv/hwprobe/Makefile b/tools/testing/selftests/riscv/hwprobe/Makefile new file mode 100644 index 000000000000..ebdbb3c22e54 --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2021 ARM Limited +# Originally tools/testing/arm64/abi/Makefile + +TEST_GEN_PROGS := hwprobe + +include ../../lib.mk + +$(OUTPUT)/hwprobe: hwprobe.c sys_hwprobe.S + $(CC) -o$@ $(CFLAGS) $(LDFLAGS) $^ diff --git a/tools/testing/selftests/riscv/hwprobe/hwprobe.c b/tools/testing/selftests/riscv/hwprobe/hwprobe.c new file mode 100644 index 000000000000..09f290a67420 --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/hwprobe.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <stddef.h> +#include <asm/hwprobe.h> + +/* + * Rather than relying on having a new enough libc to define this, just do it + * ourselves. This way we don't need to be coupled to a new-enough libc to + * contain the call. + */ +long riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, + size_t cpu_count, unsigned long *cpus, unsigned int flags); + +int main(int argc, char **argv) +{ + struct riscv_hwprobe pairs[8]; + unsigned long cpus; + long out; + + /* Fake the CPU_SET ops. */ + cpus = -1; + + /* + * Just run a basic test: pass enough pairs to get up to the base + * behavior, and then check to make sure it's sane. + */ + for (long i = 0; i < 8; i++) + pairs[i].key = i; + out = riscv_hwprobe(pairs, 8, 1, &cpus, 0); + if (out != 0) + return -1; + for (long i = 0; i < 4; ++i) { + /* Fail if the kernel claims not to recognize a base key. */ + if ((i < 4) && (pairs[i].key != i)) + return -2; + + if (pairs[i].key != RISCV_HWPROBE_KEY_BASE_BEHAVIOR) + continue; + + if (pairs[i].value & RISCV_HWPROBE_BASE_BEHAVIOR_IMA) + continue; + + return -3; + } + + /* + * This should also work with a NULL CPU set, but should not work + * with an improperly supplied CPU set. + */ + out = riscv_hwprobe(pairs, 8, 0, 0, 0); + if (out != 0) + return -4; + + out = riscv_hwprobe(pairs, 8, 0, &cpus, 0); + if (out == 0) + return -5; + + out = riscv_hwprobe(pairs, 8, 1, 0, 0); + if (out == 0) + return -6; + + /* + * Check that keys work by providing one that we know exists, and + * checking to make sure the resultig pair is what we asked for. + */ + pairs[0].key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR; + out = riscv_hwprobe(pairs, 1, 1, &cpus, 0); + if (out != 0) + return -7; + if (pairs[0].key != RISCV_HWPROBE_KEY_BASE_BEHAVIOR) + return -8; + + /* + * Check that an unknown key gets overwritten with -1, + * but doesn't block elements after it. + */ + pairs[0].key = 0x5555; + pairs[1].key = 1; + pairs[1].value = 0xAAAA; + out = riscv_hwprobe(pairs, 2, 0, 0, 0); + if (out != 0) + return -9; + + if (pairs[0].key != -1) + return -10; + + if ((pairs[1].key != 1) || (pairs[1].value == 0xAAAA)) + return -11; + + return 0; +} diff --git a/tools/testing/selftests/riscv/hwprobe/sys_hwprobe.S b/tools/testing/selftests/riscv/hwprobe/sys_hwprobe.S new file mode 100644 index 000000000000..a4773c88d267 --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/sys_hwprobe.S @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023 Rivos, Inc */ + +.text +.global riscv_hwprobe +riscv_hwprobe: + # Put __NR_riscv_hwprobe in the syscall number register, then just shim + # back the kernel's return. This doesn't do any sort of errno + # handling, the caller can deal with it. + li a7, 258 + ecall + ret diff --git a/tools/testing/selftests/sched/cs_prctl_test.c b/tools/testing/selftests/sched/cs_prctl_test.c index 25e0d95d3713..3e1619b6bf2d 100644 --- a/tools/testing/selftests/sched/cs_prctl_test.c +++ b/tools/testing/selftests/sched/cs_prctl_test.c @@ -334,6 +334,12 @@ int main(int argc, char *argv[]) validate(get_cs_cookie(pid) != 0); validate(get_cs_cookie(pid) == get_cs_cookie(procs[pidx].thr_tids[0])); + validate(_prctl(PR_SCHED_CORE, PR_SCHED_CORE_MAX, 0, PIDTYPE_PGID, 0) < 0 + && errno == EINVAL); + + validate(_prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, 0, PIDTYPE_PGID, 1) < 0 + && errno == EINVAL); + if (errors) { printf("TESTS FAILED. errors: %d\n", errors); res = 10; diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c index 0ba500056e63..8a17c0e8d82b 100644 --- a/tools/testing/selftests/timers/posix_timers.c +++ b/tools/testing/selftests/timers/posix_timers.c @@ -188,6 +188,80 @@ static int check_timer_create(int which) return 0; } +int remain; +__thread int got_signal; + +static void *distribution_thread(void *arg) +{ + while (__atomic_load_n(&remain, __ATOMIC_RELAXED)); + return NULL; +} + +static void distribution_handler(int nr) +{ + if (!__atomic_exchange_n(&got_signal, 1, __ATOMIC_RELAXED)) + __atomic_fetch_sub(&remain, 1, __ATOMIC_RELAXED); +} + +/* + * Test that all running threads _eventually_ receive CLOCK_PROCESS_CPUTIME_ID + * timer signals. This primarily tests that the kernel does not favour any one. + */ +static int check_timer_distribution(void) +{ + int err, i; + timer_t id; + const int nthreads = 10; + pthread_t threads[nthreads]; + struct itimerspec val = { + .it_value.tv_sec = 0, + .it_value.tv_nsec = 1000 * 1000, + .it_interval.tv_sec = 0, + .it_interval.tv_nsec = 1000 * 1000, + }; + + printf("Check timer_create() per process signal distribution... "); + fflush(stdout); + + remain = nthreads + 1; /* worker threads + this thread */ + signal(SIGALRM, distribution_handler); + err = timer_create(CLOCK_PROCESS_CPUTIME_ID, NULL, &id); + if (err < 0) { + perror("Can't create timer\n"); + return -1; + } + err = timer_settime(id, 0, &val, NULL); + if (err < 0) { + perror("Can't set timer\n"); + return -1; + } + + for (i = 0; i < nthreads; i++) { + if (pthread_create(&threads[i], NULL, distribution_thread, NULL)) { + perror("Can't create thread\n"); + return -1; + } + } + + /* Wait for all threads to receive the signal. */ + while (__atomic_load_n(&remain, __ATOMIC_RELAXED)); + + for (i = 0; i < nthreads; i++) { + if (pthread_join(threads[i], NULL)) { + perror("Can't join thread\n"); + return -1; + } + } + + if (timer_delete(id)) { + perror("Can't delete timer\n"); + return -1; + } + + printf("[OK]\n"); + return 0; +} + int main(int argc, char **argv) { printf("Testing posix timers. False negative may happen on CPU execution \n"); @@ -217,5 +291,8 @@ int main(int argc, char **argv) if (check_timer_create(CLOCK_PROCESS_CPUTIME_ID) < 0) return ksft_exit_fail(); + if (check_timer_distribution() < 0) + return ksft_exit_fail(); + return ksft_exit_pass(); } diff --git a/tools/testing/selftests/user_events/Makefile b/tools/testing/selftests/user_events/Makefile index 6b512b86aec3..9e95bd41b0b4 100644 --- a/tools/testing/selftests/user_events/Makefile +++ b/tools/testing/selftests/user_events/Makefile @@ -10,7 +10,7 @@ LDLIBS += -lrt -lpthread -lm # This test will not compile until user_events.h is added # back to uapi. -TEST_GEN_PROGS = ftrace_test dyn_test perf_test +TEST_GEN_PROGS = ftrace_test dyn_test perf_test abi_test TEST_FILES := settings diff --git a/tools/testing/selftests/user_events/abi_test.c b/tools/testing/selftests/user_events/abi_test.c new file mode 100644 index 000000000000..5125c42efe65 --- /dev/null +++ b/tools/testing/selftests/user_events/abi_test.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * User Events ABI Test Program + * + * Copyright (c) 2022 Beau Belgrave <beaub@linux.microsoft.com> + */ + +#define _GNU_SOURCE +#include <sched.h> + +#include <errno.h> +#include <linux/user_events.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <unistd.h> +#include <asm/unistd.h> + +#include "../kselftest_harness.h" + +const char *data_file = "/sys/kernel/tracing/user_events_data"; +const char *enable_file = "/sys/kernel/tracing/events/user_events/__abi_event/enable"; + +static int change_event(bool enable) +{ + int fd = open(enable_file, O_RDWR); + int ret; + + if (fd < 0) + return -1; + + if (enable) + ret = write(fd, "1", 1); + else + ret = write(fd, "0", 1); + + close(fd); + + if (ret == 1) + ret = 0; + else + ret = -1; + + return ret; +} + +static int reg_enable(long *enable, int size, int bit) +{ + struct user_reg reg = {0}; + int fd = open(data_file, O_RDWR); + int ret; + + if (fd < 0) + return -1; + + reg.size = sizeof(reg); + reg.name_args = (__u64)"__abi_event"; + reg.enable_bit = bit; + reg.enable_addr = (__u64)enable; + reg.enable_size = size; + + ret = ioctl(fd, DIAG_IOCSREG, ®); + + close(fd); + + return ret; +} + +static int reg_disable(long *enable, int bit) +{ + struct user_unreg reg = {0}; + int fd = open(data_file, O_RDWR); + int ret; + + if (fd < 0) + return -1; + + reg.size = sizeof(reg); + reg.disable_bit = bit; + reg.disable_addr = (__u64)enable; + + ret = ioctl(fd, DIAG_IOCSUNREG, ®); + + close(fd); + + return ret; +} + +FIXTURE(user) { + long check; +}; + +FIXTURE_SETUP(user) { + change_event(false); + self->check = 0; +} + +FIXTURE_TEARDOWN(user) { +} + +TEST_F(user, enablement) { + /* Changes should be reflected immediately */ + ASSERT_EQ(0, self->check); + ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); + ASSERT_EQ(0, change_event(true)); + ASSERT_EQ(1, self->check); + ASSERT_EQ(0, change_event(false)); + ASSERT_EQ(0, self->check); + + /* Ensure kernel clears bit after disable */ + ASSERT_EQ(0, change_event(true)); + ASSERT_EQ(1, self->check); + ASSERT_EQ(0, reg_disable(&self->check, 0)); + ASSERT_EQ(0, self->check); + + /* Ensure doesn't change after unreg */ + ASSERT_EQ(0, change_event(true)); + ASSERT_EQ(0, self->check); + ASSERT_EQ(0, change_event(false)); +} + +TEST_F(user, bit_sizes) { + /* Allow 0-31 bits for 32-bit */ + ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); + ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 31)); + ASSERT_NE(0, reg_enable(&self->check, sizeof(int), 32)); + ASSERT_EQ(0, reg_disable(&self->check, 0)); + ASSERT_EQ(0, reg_disable(&self->check, 31)); + +#if BITS_PER_LONG == 8 + /* Allow 0-64 bits for 64-bit */ + ASSERT_EQ(0, reg_enable(&self->check, sizeof(long), 63)); + ASSERT_NE(0, reg_enable(&self->check, sizeof(long), 64)); + ASSERT_EQ(0, reg_disable(&self->check, 63)); +#endif + + /* Disallowed sizes (everything beside 4 and 8) */ + ASSERT_NE(0, reg_enable(&self->check, 1, 0)); + ASSERT_NE(0, reg_enable(&self->check, 2, 0)); + ASSERT_NE(0, reg_enable(&self->check, 3, 0)); + ASSERT_NE(0, reg_enable(&self->check, 5, 0)); + ASSERT_NE(0, reg_enable(&self->check, 6, 0)); + ASSERT_NE(0, reg_enable(&self->check, 7, 0)); + ASSERT_NE(0, reg_enable(&self->check, 9, 0)); + ASSERT_NE(0, reg_enable(&self->check, 128, 0)); +} + +TEST_F(user, forks) { + int i; + + /* Ensure COW pages get updated after fork */ + ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); + ASSERT_EQ(0, self->check); + + if (fork() == 0) { + /* Force COW */ + self->check = 0; + + /* Up to 1 sec for enablement */ + for (i = 0; i < 10; ++i) { + usleep(100000); + + if (self->check) + exit(0); + } + + exit(1); + } + + /* Allow generous time for COW, then enable */ + usleep(100000); + ASSERT_EQ(0, change_event(true)); + + ASSERT_NE(-1, wait(&i)); + ASSERT_EQ(0, WEXITSTATUS(i)); + + /* Ensure child doesn't disable parent */ + if (fork() == 0) + exit(reg_disable(&self->check, 0)); + + ASSERT_NE(-1, wait(&i)); + ASSERT_EQ(0, WEXITSTATUS(i)); + ASSERT_EQ(1, self->check); + ASSERT_EQ(0, change_event(false)); + ASSERT_EQ(0, self->check); +} + +/* Waits up to 1 sec for enablement */ +static int clone_check(void *check) +{ + int i; + + for (i = 0; i < 10; ++i) { + usleep(100000); + + if (*(long *)check) + return 0; + } + + return 1; +} + +TEST_F(user, clones) { + int i, stack_size = 4096; + void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, + -1, 0); + + ASSERT_NE(MAP_FAILED, stack); + ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0)); + ASSERT_EQ(0, self->check); + + /* Shared VM should see enablements */ + ASSERT_NE(-1, clone(&clone_check, stack + stack_size, + CLONE_VM | SIGCHLD, &self->check)); + + ASSERT_EQ(0, change_event(true)); + ASSERT_NE(-1, wait(&i)); + ASSERT_EQ(0, WEXITSTATUS(i)); + munmap(stack, stack_size); + ASSERT_EQ(0, change_event(false)); +} + +int main(int argc, char **argv) +{ + return test_harness_run(argc, argv); +} diff --git a/tools/testing/selftests/user_events/dyn_test.c b/tools/testing/selftests/user_events/dyn_test.c index d6265d14cd51..8879a7b04c6a 100644 --- a/tools/testing/selftests/user_events/dyn_test.c +++ b/tools/testing/selftests/user_events/dyn_test.c @@ -16,7 +16,7 @@ #include "../kselftest_harness.h" -const char *dyn_file = "/sys/kernel/debug/tracing/dynamic_events"; +const char *dyn_file = "/sys/kernel/tracing/dynamic_events"; const char *clear = "!u:__test_event"; static int Append(const char *value) diff --git a/tools/testing/selftests/user_events/ftrace_test.c b/tools/testing/selftests/user_events/ftrace_test.c index 404a2713dcae..7c99cef94a65 100644 --- a/tools/testing/selftests/user_events/ftrace_test.c +++ b/tools/testing/selftests/user_events/ftrace_test.c @@ -12,20 +12,16 @@ #include <fcntl.h> #include <sys/ioctl.h> #include <sys/stat.h> +#include <sys/uio.h> #include <unistd.h> #include "../kselftest_harness.h" -const char *data_file = "/sys/kernel/debug/tracing/user_events_data"; -const char *status_file = "/sys/kernel/debug/tracing/user_events_status"; -const char *enable_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/enable"; -const char *trace_file = "/sys/kernel/debug/tracing/trace"; -const char *fmt_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/format"; - -static inline int status_check(char *status_page, int status_bit) -{ - return status_page[status_bit >> 3] & (1 << (status_bit & 7)); -} +const char *data_file = "/sys/kernel/tracing/user_events_data"; +const char *status_file = "/sys/kernel/tracing/user_events_status"; +const char *enable_file = "/sys/kernel/tracing/events/user_events/__test_event/enable"; +const char *trace_file = "/sys/kernel/tracing/trace"; +const char *fmt_file = "/sys/kernel/tracing/events/user_events/__test_event/format"; static int trace_bytes(void) { @@ -106,13 +102,23 @@ err: return -1; } -static int clear(void) +static int clear(int *check) { + struct user_unreg unreg = {0}; + + unreg.size = sizeof(unreg); + unreg.disable_bit = 31; + unreg.disable_addr = (__u64)check; + int fd = open(data_file, O_RDWR); if (fd == -1) return -1; + if (ioctl(fd, DIAG_IOCSUNREG, &unreg) == -1) + if (errno != ENOENT) + return -1; + if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1) if (errno != ENOENT) return -1; @@ -122,7 +128,7 @@ static int clear(void) return 0; } -static int check_print_fmt(const char *event, const char *expected) +static int check_print_fmt(const char *event, const char *expected, int *check) { struct user_reg reg = {0}; char print_fmt[256]; @@ -130,7 +136,7 @@ static int check_print_fmt(const char *event, const char *expected) int fd; /* Ensure cleared */ - ret = clear(); + ret = clear(check); if (ret != 0) return ret; @@ -142,14 +148,19 @@ static int check_print_fmt(const char *event, const char *expected) reg.size = sizeof(reg); reg.name_args = (__u64)event; + reg.enable_bit = 31; + reg.enable_addr = (__u64)check; + reg.enable_size = sizeof(*check); /* Register should work */ ret = ioctl(fd, DIAG_IOCSREG, ®); close(fd); - if (ret != 0) + if (ret != 0) { + printf("Reg failed in fmt\n"); return ret; + } /* Ensure correct print_fmt */ ret = get_print_fmt(print_fmt, sizeof(print_fmt)); @@ -164,6 +175,7 @@ FIXTURE(user) { int status_fd; int data_fd; int enable_fd; + int check; }; FIXTURE_SETUP(user) { @@ -185,59 +197,63 @@ FIXTURE_TEARDOWN(user) { close(self->enable_fd); } - ASSERT_EQ(0, clear()); + if (clear(&self->check) != 0) + printf("WARNING: Clear didn't work!\n"); } TEST_F(user, register_events) { struct user_reg reg = {0}; - int page_size = sysconf(_SC_PAGESIZE); - char *status_page; + struct user_unreg unreg = {0}; reg.size = sizeof(reg); reg.name_args = (__u64)"__test_event u32 field1; u32 field2"; + reg.enable_bit = 31; + reg.enable_addr = (__u64)&self->check; + reg.enable_size = sizeof(self->check); - status_page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, - self->status_fd, 0); + unreg.size = sizeof(unreg); + unreg.disable_bit = 31; + unreg.disable_addr = (__u64)&self->check; /* Register should work */ ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); ASSERT_EQ(0, reg.write_index); - ASSERT_NE(0, reg.status_bit); - /* Multiple registers should result in same index */ + /* Multiple registers to the same addr + bit should fail */ + ASSERT_EQ(-1, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(EADDRINUSE, errno); + + /* Multiple registers to same name should result in same index */ + reg.enable_bit = 30; ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); ASSERT_EQ(0, reg.write_index); - ASSERT_NE(0, reg.status_bit); /* Ensure disabled */ self->enable_fd = open(enable_file, O_RDWR); ASSERT_NE(-1, self->enable_fd); ASSERT_NE(-1, write(self->enable_fd, "0", sizeof("0"))) - /* MMAP should work and be zero'd */ - ASSERT_NE(MAP_FAILED, status_page); - ASSERT_NE(NULL, status_page); - ASSERT_EQ(0, status_check(status_page, reg.status_bit)); - /* Enable event and ensure bits updated in status */ ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) - ASSERT_NE(0, status_check(status_page, reg.status_bit)); + ASSERT_EQ(1 << reg.enable_bit, self->check); /* Disable event and ensure bits updated in status */ ASSERT_NE(-1, write(self->enable_fd, "0", sizeof("0"))) - ASSERT_EQ(0, status_check(status_page, reg.status_bit)); + ASSERT_EQ(0, self->check); /* File still open should return -EBUSY for delete */ ASSERT_EQ(-1, ioctl(self->data_fd, DIAG_IOCSDEL, "__test_event")); ASSERT_EQ(EBUSY, errno); - /* Delete should work only after close */ + /* Unregister */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSUNREG, &unreg)); + unreg.disable_bit = 30; + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSUNREG, &unreg)); + + /* Delete should work only after close and unregister */ close(self->data_fd); self->data_fd = open(data_file, O_RDWR); ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSDEL, "__test_event")); - - /* Unmap should work */ - ASSERT_EQ(0, munmap(status_page, page_size)); } TEST_F(user, write_events) { @@ -245,11 +261,12 @@ TEST_F(user, write_events) { struct iovec io[3]; __u32 field1, field2; int before = 0, after = 0; - int page_size = sysconf(_SC_PAGESIZE); - char *status_page; reg.size = sizeof(reg); reg.name_args = (__u64)"__test_event u32 field1; u32 field2"; + reg.enable_bit = 31; + reg.enable_addr = (__u64)&self->check; + reg.enable_size = sizeof(self->check); field1 = 1; field2 = 2; @@ -261,18 +278,10 @@ TEST_F(user, write_events) { io[2].iov_base = &field2; io[2].iov_len = sizeof(field2); - status_page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, - self->status_fd, 0); - /* Register should work */ ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); ASSERT_EQ(0, reg.write_index); - ASSERT_NE(0, reg.status_bit); - - /* MMAP should work and be zero'd */ - ASSERT_NE(MAP_FAILED, status_page); - ASSERT_NE(NULL, status_page); - ASSERT_EQ(0, status_check(status_page, reg.status_bit)); + ASSERT_EQ(0, self->check); /* Write should fail on invalid slot with ENOENT */ io[0].iov_base = &field2; @@ -287,13 +296,18 @@ TEST_F(user, write_events) { ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) /* Event should now be enabled */ - ASSERT_NE(0, status_check(status_page, reg.status_bit)); + ASSERT_NE(1 << reg.enable_bit, self->check); /* Write should make it out to ftrace buffers */ before = trace_bytes(); ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 3)); after = trace_bytes(); ASSERT_GT(after, before); + + /* Negative index should fail with EINVAL */ + reg.write_index = -1; + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + ASSERT_EQ(EINVAL, errno); } TEST_F(user, write_fault) { @@ -304,6 +318,9 @@ TEST_F(user, write_fault) { reg.size = sizeof(reg); reg.name_args = (__u64)"__test_event u64 anon"; + reg.enable_bit = 31; + reg.enable_addr = (__u64)&self->check; + reg.enable_size = sizeof(self->check); anon = mmap(NULL, l, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); ASSERT_NE(MAP_FAILED, anon); @@ -316,7 +333,6 @@ TEST_F(user, write_fault) { /* Register should work */ ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); ASSERT_EQ(0, reg.write_index); - ASSERT_NE(0, reg.status_bit); /* Write should work normally */ ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 2)); @@ -333,24 +349,17 @@ TEST_F(user, write_validator) { int loc, bytes; char data[8]; int before = 0, after = 0; - int page_size = sysconf(_SC_PAGESIZE); - char *status_page; - - status_page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, - self->status_fd, 0); reg.size = sizeof(reg); reg.name_args = (__u64)"__test_event __rel_loc char[] data"; + reg.enable_bit = 31; + reg.enable_addr = (__u64)&self->check; + reg.enable_size = sizeof(self->check); /* Register should work */ ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); ASSERT_EQ(0, reg.write_index); - ASSERT_NE(0, reg.status_bit); - - /* MMAP should work and be zero'd */ - ASSERT_NE(MAP_FAILED, status_page); - ASSERT_NE(NULL, status_page); - ASSERT_EQ(0, status_check(status_page, reg.status_bit)); + ASSERT_EQ(0, self->check); io[0].iov_base = ®.write_index; io[0].iov_len = sizeof(reg.write_index); @@ -369,7 +378,7 @@ TEST_F(user, write_validator) { ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) /* Event should now be enabled */ - ASSERT_NE(0, status_check(status_page, reg.status_bit)); + ASSERT_EQ(1 << reg.enable_bit, self->check); /* Full in-bounds write should work */ before = trace_bytes(); @@ -409,71 +418,88 @@ TEST_F(user, print_fmt) { int ret; ret = check_print_fmt("__test_event __rel_loc char[] data", - "print fmt: \"data=%s\", __get_rel_str(data)"); + "print fmt: \"data=%s\", __get_rel_str(data)", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event __data_loc char[] data", - "print fmt: \"data=%s\", __get_str(data)"); + "print fmt: \"data=%s\", __get_str(data)", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event s64 data", - "print fmt: \"data=%lld\", REC->data"); + "print fmt: \"data=%lld\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event u64 data", - "print fmt: \"data=%llu\", REC->data"); + "print fmt: \"data=%llu\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event s32 data", - "print fmt: \"data=%d\", REC->data"); + "print fmt: \"data=%d\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event u32 data", - "print fmt: \"data=%u\", REC->data"); + "print fmt: \"data=%u\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event int data", - "print fmt: \"data=%d\", REC->data"); + "print fmt: \"data=%d\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event unsigned int data", - "print fmt: \"data=%u\", REC->data"); + "print fmt: \"data=%u\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event s16 data", - "print fmt: \"data=%d\", REC->data"); + "print fmt: \"data=%d\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event u16 data", - "print fmt: \"data=%u\", REC->data"); + "print fmt: \"data=%u\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event short data", - "print fmt: \"data=%d\", REC->data"); + "print fmt: \"data=%d\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event unsigned short data", - "print fmt: \"data=%u\", REC->data"); + "print fmt: \"data=%u\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event s8 data", - "print fmt: \"data=%d\", REC->data"); + "print fmt: \"data=%d\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event u8 data", - "print fmt: \"data=%u\", REC->data"); + "print fmt: \"data=%u\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event char data", - "print fmt: \"data=%d\", REC->data"); + "print fmt: \"data=%d\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event unsigned char data", - "print fmt: \"data=%u\", REC->data"); + "print fmt: \"data=%u\", REC->data", + &self->check); ASSERT_EQ(0, ret); ret = check_print_fmt("__test_event char[4] data", - "print fmt: \"data=%s\", REC->data"); + "print fmt: \"data=%s\", REC->data", + &self->check); ASSERT_EQ(0, ret); } diff --git a/tools/testing/selftests/user_events/perf_test.c b/tools/testing/selftests/user_events/perf_test.c index 8b4c7879d5a7..a070258d4449 100644 --- a/tools/testing/selftests/user_events/perf_test.c +++ b/tools/testing/selftests/user_events/perf_test.c @@ -18,10 +18,9 @@ #include "../kselftest_harness.h" -const char *data_file = "/sys/kernel/debug/tracing/user_events_data"; -const char *status_file = "/sys/kernel/debug/tracing/user_events_status"; -const char *id_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/id"; -const char *fmt_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/format"; +const char *data_file = "/sys/kernel/tracing/user_events_data"; +const char *id_file = "/sys/kernel/tracing/events/user_events/__test_event/id"; +const char *fmt_file = "/sys/kernel/tracing/events/user_events/__test_event/format"; struct event { __u32 index; @@ -35,11 +34,6 @@ static long perf_event_open(struct perf_event_attr *pe, pid_t pid, return syscall(__NR_perf_event_open, pe, pid, cpu, group_fd, flags); } -static inline int status_check(char *status_page, int status_bit) -{ - return status_page[status_bit >> 3] & (1 << (status_bit & 7)); -} - static int get_id(void) { FILE *fp = fopen(id_file, "r"); @@ -88,45 +82,38 @@ static int get_offset(void) } FIXTURE(user) { - int status_fd; int data_fd; + int check; }; FIXTURE_SETUP(user) { - self->status_fd = open(status_file, O_RDONLY); - ASSERT_NE(-1, self->status_fd); - self->data_fd = open(data_file, O_RDWR); ASSERT_NE(-1, self->data_fd); } FIXTURE_TEARDOWN(user) { - close(self->status_fd); close(self->data_fd); } TEST_F(user, perf_write) { struct perf_event_attr pe = {0}; struct user_reg reg = {0}; - int page_size = sysconf(_SC_PAGESIZE); - char *status_page; struct event event; struct perf_event_mmap_page *perf_page; + int page_size = sysconf(_SC_PAGESIZE); int id, fd, offset; __u32 *val; reg.size = sizeof(reg); reg.name_args = (__u64)"__test_event u32 field1; u32 field2"; - - status_page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, - self->status_fd, 0); - ASSERT_NE(MAP_FAILED, status_page); + reg.enable_bit = 31; + reg.enable_addr = (__u64)&self->check; + reg.enable_size = sizeof(self->check); /* Register should work */ ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); ASSERT_EQ(0, reg.write_index); - ASSERT_NE(0, reg.status_bit); - ASSERT_EQ(0, status_check(status_page, reg.status_bit)); + ASSERT_EQ(0, self->check); /* Id should be there */ id = get_id(); @@ -149,7 +136,7 @@ TEST_F(user, perf_write) { ASSERT_NE(MAP_FAILED, perf_page); /* Status should be updated */ - ASSERT_NE(0, status_check(status_page, reg.status_bit)); + ASSERT_EQ(1 << reg.enable_bit, self->check); event.index = reg.write_index; event.field1 = 0xc001; @@ -165,6 +152,12 @@ TEST_F(user, perf_write) { /* Ensure correct */ ASSERT_EQ(event.field1, *val++); ASSERT_EQ(event.field2, *val++); + + munmap(perf_page, page_size * 2); + close(fd); + + /* Status should be updated */ + ASSERT_EQ(0, self->check); } int main(int argc, char **argv) diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index ca9374b56ead..598135d3162b 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -18,7 +18,7 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ vdso_restorer TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \ - corrupt_xstate_header amx + corrupt_xstate_header amx lam # Some selftests require 32bit support enabled also on 64bit systems TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall diff --git a/tools/testing/selftests/x86/lam.c b/tools/testing/selftests/x86/lam.c new file mode 100644 index 000000000000..eb0e46905bf9 --- /dev/null +++ b/tools/testing/selftests/x86/lam.c @@ -0,0 +1,1241 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/syscall.h> +#include <time.h> +#include <signal.h> +#include <setjmp.h> +#include <sys/mman.h> +#include <sys/utsname.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <inttypes.h> +#include <sched.h> + +#include <sys/uio.h> +#include <linux/io_uring.h> +#include "../kselftest.h" + +#ifndef __x86_64__ +# error This test is 64-bit only +#endif + +/* LAM modes, these definitions were copied from kernel code */ +#define LAM_NONE 0 +#define LAM_U57_BITS 6 + +#define LAM_U57_MASK (0x3fULL << 57) +/* arch prctl for LAM */ +#define ARCH_GET_UNTAG_MASK 0x4001 +#define ARCH_ENABLE_TAGGED_ADDR 0x4002 +#define ARCH_GET_MAX_TAG_BITS 0x4003 +#define ARCH_FORCE_TAGGED_SVA 0x4004 + +/* Specified test function bits */ +#define FUNC_MALLOC 0x1 +#define FUNC_BITS 0x2 +#define FUNC_MMAP 0x4 +#define FUNC_SYSCALL 0x8 +#define FUNC_URING 0x10 +#define FUNC_INHERITE 0x20 +#define FUNC_PASID 0x40 + +#define TEST_MASK 0x7f + +#define LOW_ADDR (0x1UL << 30) +#define HIGH_ADDR (0x3UL << 48) + +#define MALLOC_LEN 32 + +#define PAGE_SIZE (4 << 10) + +#define STACK_SIZE 65536 + +#define barrier() ({ \ + __asm__ __volatile__("" : : : "memory"); \ +}) + +#define URING_QUEUE_SZ 1 +#define URING_BLOCK_SZ 2048 + +/* Pasid test define */ +#define LAM_CMD_BIT 0x1 +#define PAS_CMD_BIT 0x2 +#define SVA_CMD_BIT 0x4 + +#define PAS_CMD(cmd1, cmd2, cmd3) (((cmd3) << 8) | ((cmd2) << 4) | ((cmd1) << 0)) + +struct testcases { + unsigned int later; + int expected; /* 2: SIGSEGV Error; 1: other errors */ + unsigned long lam; + uint64_t addr; + uint64_t cmd; + int (*test_func)(struct testcases *test); + const char *msg; +}; + +/* Used by CQ of uring, source file handler and file's size */ +struct file_io { + int file_fd; + off_t file_sz; + struct iovec iovecs[]; +}; + +struct io_uring_queue { + unsigned int *head; + unsigned int *tail; + unsigned int *ring_mask; + unsigned int *ring_entries; + unsigned int *flags; + unsigned int *array; + union { + struct io_uring_cqe *cqes; + struct io_uring_sqe *sqes; + } queue; + size_t ring_sz; +}; + +struct io_ring { + int ring_fd; + struct io_uring_queue sq_ring; + struct io_uring_queue cq_ring; +}; + +int tests_cnt; +jmp_buf segv_env; + +static void segv_handler(int sig) +{ + ksft_print_msg("Get segmentation fault(%d).", sig); + + siglongjmp(segv_env, 1); +} + +static inline int cpu_has_lam(void) +{ + unsigned int cpuinfo[4]; + + __cpuid_count(0x7, 1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); + + return (cpuinfo[0] & (1 << 26)); +} + +/* Check 5-level page table feature in CPUID.(EAX=07H, ECX=00H):ECX.[bit 16] */ +static inline int cpu_has_la57(void) +{ + unsigned int cpuinfo[4]; + + __cpuid_count(0x7, 0, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); + + return (cpuinfo[2] & (1 << 16)); +} + +/* + * Set tagged address and read back untag mask. + * check if the untagged mask is expected. + * + * @return: + * 0: Set LAM mode successfully + * others: failed to set LAM + */ +static int set_lam(unsigned long lam) +{ + int ret = 0; + uint64_t ptr = 0; + + if (lam != LAM_U57_BITS && lam != LAM_NONE) + return -1; + + /* Skip check return */ + syscall(SYS_arch_prctl, ARCH_ENABLE_TAGGED_ADDR, lam); + + /* Get untagged mask */ + syscall(SYS_arch_prctl, ARCH_GET_UNTAG_MASK, &ptr); + + /* Check mask returned is expected */ + if (lam == LAM_U57_BITS) + ret = (ptr != ~(LAM_U57_MASK)); + else if (lam == LAM_NONE) + ret = (ptr != -1ULL); + + return ret; +} + +static unsigned long get_default_tag_bits(void) +{ + pid_t pid; + int lam = LAM_NONE; + int ret = 0; + + pid = fork(); + if (pid < 0) { + perror("Fork failed."); + } else if (pid == 0) { + /* Set LAM mode in child process */ + if (set_lam(LAM_U57_BITS) == 0) + lam = LAM_U57_BITS; + else + lam = LAM_NONE; + exit(lam); + } else { + wait(&ret); + lam = WEXITSTATUS(ret); + } + + return lam; +} + +/* + * Set tagged address and read back untag mask. + * check if the untag mask is expected. + */ +static int get_lam(void) +{ + uint64_t ptr = 0; + int ret = -1; + /* Get untagged mask */ + if (syscall(SYS_arch_prctl, ARCH_GET_UNTAG_MASK, &ptr) == -1) + return -1; + + /* Check mask returned is expected */ + if (ptr == ~(LAM_U57_MASK)) + ret = LAM_U57_BITS; + else if (ptr == -1ULL) + ret = LAM_NONE; + + + return ret; +} + +/* According to LAM mode, set metadata in high bits */ +static uint64_t set_metadata(uint64_t src, unsigned long lam) +{ + uint64_t metadata; + + srand(time(NULL)); + + switch (lam) { + case LAM_U57_BITS: /* Set metadata in bits 62:57 */ + /* Get a random non-zero value as metadata */ + metadata = (rand() % ((1UL << LAM_U57_BITS) - 1) + 1) << 57; + metadata |= (src & ~(LAM_U57_MASK)); + break; + default: + metadata = src; + break; + } + + return metadata; +} + +/* + * Set metadata in user pointer, compare new pointer with original pointer. + * both pointers should point to the same address. + * + * @return: + * 0: value on the pointer with metadate and value on original are same + * 1: not same. + */ +static int handle_lam_test(void *src, unsigned int lam) +{ + char *ptr; + + strcpy((char *)src, "USER POINTER"); + + ptr = (char *)set_metadata((uint64_t)src, lam); + if (src == ptr) + return 0; + + /* Copy a string into the pointer with metadata */ + strcpy((char *)ptr, "METADATA POINTER"); + + return (!!strcmp((char *)src, (char *)ptr)); +} + + +int handle_max_bits(struct testcases *test) +{ + unsigned long exp_bits = get_default_tag_bits(); + unsigned long bits = 0; + + if (exp_bits != LAM_NONE) + exp_bits = LAM_U57_BITS; + + /* Get LAM max tag bits */ + if (syscall(SYS_arch_prctl, ARCH_GET_MAX_TAG_BITS, &bits) == -1) + return 1; + + return (exp_bits != bits); +} + +/* + * Test lam feature through dereference pointer get from malloc. + * @return 0: Pass test. 1: Get failure during test 2: Get SIGSEGV + */ +static int handle_malloc(struct testcases *test) +{ + char *ptr = NULL; + int ret = 0; + + if (test->later == 0 && test->lam != 0) + if (set_lam(test->lam) == -1) + return 1; + + ptr = (char *)malloc(MALLOC_LEN); + if (ptr == NULL) { + perror("malloc() failure\n"); + return 1; + } + + /* Set signal handler */ + if (sigsetjmp(segv_env, 1) == 0) { + signal(SIGSEGV, segv_handler); + ret = handle_lam_test(ptr, test->lam); + } else { + ret = 2; + } + + if (test->later != 0 && test->lam != 0) + if (set_lam(test->lam) == -1 && ret == 0) + ret = 1; + + free(ptr); + + return ret; +} + +static int handle_mmap(struct testcases *test) +{ + void *ptr; + unsigned int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED; + int ret = 0; + + if (test->later == 0 && test->lam != 0) + if (set_lam(test->lam) != 0) + return 1; + + ptr = mmap((void *)test->addr, PAGE_SIZE, PROT_READ | PROT_WRITE, + flags, -1, 0); + if (ptr == MAP_FAILED) { + if (test->addr == HIGH_ADDR) + if (!cpu_has_la57()) + return 3; /* unsupport LA57 */ + return 1; + } + + if (test->later != 0 && test->lam != 0) + if (set_lam(test->lam) != 0) + ret = 1; + + if (ret == 0) { + if (sigsetjmp(segv_env, 1) == 0) { + signal(SIGSEGV, segv_handler); + ret = handle_lam_test(ptr, test->lam); + } else { + ret = 2; + } + } + + munmap(ptr, PAGE_SIZE); + return ret; +} + +static int handle_syscall(struct testcases *test) +{ + struct utsname unme, *pu; + int ret = 0; + + if (test->later == 0 && test->lam != 0) + if (set_lam(test->lam) != 0) + return 1; + + if (sigsetjmp(segv_env, 1) == 0) { + signal(SIGSEGV, segv_handler); + pu = (struct utsname *)set_metadata((uint64_t)&unme, test->lam); + ret = uname(pu); + if (ret < 0) + ret = 1; + } else { + ret = 2; + } + + if (test->later != 0 && test->lam != 0) + if (set_lam(test->lam) != -1 && ret == 0) + ret = 1; + + return ret; +} + +int sys_uring_setup(unsigned int entries, struct io_uring_params *p) +{ + return (int)syscall(__NR_io_uring_setup, entries, p); +} + +int sys_uring_enter(int fd, unsigned int to, unsigned int min, unsigned int flags) +{ + return (int)syscall(__NR_io_uring_enter, fd, to, min, flags, NULL, 0); +} + +/* Init submission queue and completion queue */ +int mmap_io_uring(struct io_uring_params p, struct io_ring *s) +{ + struct io_uring_queue *sring = &s->sq_ring; + struct io_uring_queue *cring = &s->cq_ring; + + sring->ring_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned int); + cring->ring_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe); + + if (p.features & IORING_FEAT_SINGLE_MMAP) { + if (cring->ring_sz > sring->ring_sz) + sring->ring_sz = cring->ring_sz; + + cring->ring_sz = sring->ring_sz; + } + + void *sq_ptr = mmap(0, sring->ring_sz, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, s->ring_fd, + IORING_OFF_SQ_RING); + + if (sq_ptr == MAP_FAILED) { + perror("sub-queue!"); + return 1; + } + + void *cq_ptr = sq_ptr; + + if (!(p.features & IORING_FEAT_SINGLE_MMAP)) { + cq_ptr = mmap(0, cring->ring_sz, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, s->ring_fd, + IORING_OFF_CQ_RING); + if (cq_ptr == MAP_FAILED) { + perror("cpl-queue!"); + munmap(sq_ptr, sring->ring_sz); + return 1; + } + } + + sring->head = sq_ptr + p.sq_off.head; + sring->tail = sq_ptr + p.sq_off.tail; + sring->ring_mask = sq_ptr + p.sq_off.ring_mask; + sring->ring_entries = sq_ptr + p.sq_off.ring_entries; + sring->flags = sq_ptr + p.sq_off.flags; + sring->array = sq_ptr + p.sq_off.array; + + /* Map a queue as mem map */ + s->sq_ring.queue.sqes = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, + s->ring_fd, IORING_OFF_SQES); + if (s->sq_ring.queue.sqes == MAP_FAILED) { + munmap(sq_ptr, sring->ring_sz); + if (sq_ptr != cq_ptr) { + ksft_print_msg("failed to mmap uring queue!"); + munmap(cq_ptr, cring->ring_sz); + return 1; + } + } + + cring->head = cq_ptr + p.cq_off.head; + cring->tail = cq_ptr + p.cq_off.tail; + cring->ring_mask = cq_ptr + p.cq_off.ring_mask; + cring->ring_entries = cq_ptr + p.cq_off.ring_entries; + cring->queue.cqes = cq_ptr + p.cq_off.cqes; + + return 0; +} + +/* Init io_uring queues */ +int setup_io_uring(struct io_ring *s) +{ + struct io_uring_params para; + + memset(¶, 0, sizeof(para)); + s->ring_fd = sys_uring_setup(URING_QUEUE_SZ, ¶); + if (s->ring_fd < 0) + return 1; + + return mmap_io_uring(para, s); +} + +/* + * Get data from completion queue. the data buffer saved the file data + * return 0: success; others: error; + */ +int handle_uring_cq(struct io_ring *s) +{ + struct file_io *fi = NULL; + struct io_uring_queue *cring = &s->cq_ring; + struct io_uring_cqe *cqe; + unsigned int head; + off_t len = 0; + + head = *cring->head; + + do { + barrier(); + if (head == *cring->tail) + break; + /* Get the entry */ + cqe = &cring->queue.cqes[head & *s->cq_ring.ring_mask]; + fi = (struct file_io *)cqe->user_data; + if (cqe->res < 0) + break; + + int blocks = (int)(fi->file_sz + URING_BLOCK_SZ - 1) / URING_BLOCK_SZ; + + for (int i = 0; i < blocks; i++) + len += fi->iovecs[i].iov_len; + + head++; + } while (1); + + *cring->head = head; + barrier(); + + return (len != fi->file_sz); +} + +/* + * Submit squeue. specify via IORING_OP_READV. + * the buffer need to be set metadata according to LAM mode + */ +int handle_uring_sq(struct io_ring *ring, struct file_io *fi, unsigned long lam) +{ + int file_fd = fi->file_fd; + struct io_uring_queue *sring = &ring->sq_ring; + unsigned int index = 0, cur_block = 0, tail = 0, next_tail = 0; + struct io_uring_sqe *sqe; + + off_t remain = fi->file_sz; + int blocks = (int)(remain + URING_BLOCK_SZ - 1) / URING_BLOCK_SZ; + + while (remain) { + off_t bytes = remain; + void *buf; + + if (bytes > URING_BLOCK_SZ) + bytes = URING_BLOCK_SZ; + + fi->iovecs[cur_block].iov_len = bytes; + + if (posix_memalign(&buf, URING_BLOCK_SZ, URING_BLOCK_SZ)) + return 1; + + fi->iovecs[cur_block].iov_base = (void *)set_metadata((uint64_t)buf, lam); + remain -= bytes; + cur_block++; + } + + next_tail = *sring->tail; + tail = next_tail; + next_tail++; + + barrier(); + + index = tail & *ring->sq_ring.ring_mask; + + sqe = &ring->sq_ring.queue.sqes[index]; + sqe->fd = file_fd; + sqe->flags = 0; + sqe->opcode = IORING_OP_READV; + sqe->addr = (unsigned long)fi->iovecs; + sqe->len = blocks; + sqe->off = 0; + sqe->user_data = (uint64_t)fi; + + sring->array[index] = index; + tail = next_tail; + + if (*sring->tail != tail) { + *sring->tail = tail; + barrier(); + } + + if (sys_uring_enter(ring->ring_fd, 1, 1, IORING_ENTER_GETEVENTS) < 0) + return 1; + + return 0; +} + +/* + * Test LAM in async I/O and io_uring, read current binery through io_uring + * Set metadata in pointers to iovecs buffer. + */ +int do_uring(unsigned long lam) +{ + struct io_ring *ring; + struct file_io *fi; + struct stat st; + int ret = 1; + char path[PATH_MAX] = {0}; + + /* get current process path */ + if (readlink("/proc/self/exe", path, PATH_MAX) <= 0) + return 1; + + int file_fd = open(path, O_RDONLY); + + if (file_fd < 0) + return 1; + + if (fstat(file_fd, &st) < 0) + return 1; + + off_t file_sz = st.st_size; + + int blocks = (int)(file_sz + URING_BLOCK_SZ - 1) / URING_BLOCK_SZ; + + fi = malloc(sizeof(*fi) + sizeof(struct iovec) * blocks); + if (!fi) + return 1; + + fi->file_sz = file_sz; + fi->file_fd = file_fd; + + ring = malloc(sizeof(*ring)); + if (!ring) + return 1; + + memset(ring, 0, sizeof(struct io_ring)); + + if (setup_io_uring(ring)) + goto out; + + if (handle_uring_sq(ring, fi, lam)) + goto out; + + ret = handle_uring_cq(ring); + +out: + free(ring); + + for (int i = 0; i < blocks; i++) { + if (fi->iovecs[i].iov_base) { + uint64_t addr = ((uint64_t)fi->iovecs[i].iov_base); + + switch (lam) { + case LAM_U57_BITS: /* Clear bits 62:57 */ + addr = (addr & ~(LAM_U57_MASK)); + break; + } + free((void *)addr); + fi->iovecs[i].iov_base = NULL; + } + } + + free(fi); + + return ret; +} + +int handle_uring(struct testcases *test) +{ + int ret = 0; + + if (test->later == 0 && test->lam != 0) + if (set_lam(test->lam) != 0) + return 1; + + if (sigsetjmp(segv_env, 1) == 0) { + signal(SIGSEGV, segv_handler); + ret = do_uring(test->lam); + } else { + ret = 2; + } + + return ret; +} + +static int fork_test(struct testcases *test) +{ + int ret, child_ret; + pid_t pid; + + pid = fork(); + if (pid < 0) { + perror("Fork failed."); + ret = 1; + } else if (pid == 0) { + ret = test->test_func(test); + exit(ret); + } else { + wait(&child_ret); + ret = WEXITSTATUS(child_ret); + } + + return ret; +} + +static int handle_execve(struct testcases *test) +{ + int ret, child_ret; + int lam = test->lam; + pid_t pid; + + pid = fork(); + if (pid < 0) { + perror("Fork failed."); + ret = 1; + } else if (pid == 0) { + char path[PATH_MAX]; + + /* Set LAM mode in parent process */ + if (set_lam(lam) != 0) + return 1; + + /* Get current binary's path and the binary was run by execve */ + if (readlink("/proc/self/exe", path, PATH_MAX) <= 0) + exit(-1); + + /* run binary to get LAM mode and return to parent process */ + if (execlp(path, path, "-t 0x0", NULL) < 0) { + perror("error on exec"); + exit(-1); + } + } else { + wait(&child_ret); + ret = WEXITSTATUS(child_ret); + if (ret != LAM_NONE) + return 1; + } + + return 0; +} + +static int handle_inheritance(struct testcases *test) +{ + int ret, child_ret; + int lam = test->lam; + pid_t pid; + + /* Set LAM mode in parent process */ + if (set_lam(lam) != 0) + return 1; + + pid = fork(); + if (pid < 0) { + perror("Fork failed."); + return 1; + } else if (pid == 0) { + /* Set LAM mode in parent process */ + int child_lam = get_lam(); + + exit(child_lam); + } else { + wait(&child_ret); + ret = WEXITSTATUS(child_ret); + + if (lam != ret) + return 1; + } + + return 0; +} + +static int thread_fn_get_lam(void *arg) +{ + return get_lam(); +} + +static int thread_fn_set_lam(void *arg) +{ + struct testcases *test = arg; + + return set_lam(test->lam); +} + +static int handle_thread(struct testcases *test) +{ + char stack[STACK_SIZE]; + int ret, child_ret; + int lam = 0; + pid_t pid; + + /* Set LAM mode in parent process */ + if (!test->later) { + lam = test->lam; + if (set_lam(lam) != 0) + return 1; + } + + pid = clone(thread_fn_get_lam, stack + STACK_SIZE, + SIGCHLD | CLONE_FILES | CLONE_FS | CLONE_VM, NULL); + if (pid < 0) { + perror("Clone failed."); + return 1; + } + + waitpid(pid, &child_ret, 0); + ret = WEXITSTATUS(child_ret); + + if (lam != ret) + return 1; + + if (test->later) { + if (set_lam(test->lam) != 0) + return 1; + } + + return 0; +} + +static int handle_thread_enable(struct testcases *test) +{ + char stack[STACK_SIZE]; + int ret, child_ret; + int lam = test->lam; + pid_t pid; + + pid = clone(thread_fn_set_lam, stack + STACK_SIZE, + SIGCHLD | CLONE_FILES | CLONE_FS | CLONE_VM, test); + if (pid < 0) { + perror("Clone failed."); + return 1; + } + + waitpid(pid, &child_ret, 0); + ret = WEXITSTATUS(child_ret); + + if (lam != ret) + return 1; + + return 0; +} +static void run_test(struct testcases *test, int count) +{ + int i, ret = 0; + + for (i = 0; i < count; i++) { + struct testcases *t = test + i; + + /* fork a process to run test case */ + tests_cnt++; + ret = fork_test(t); + + /* return 3 is not support LA57, the case should be skipped */ + if (ret == 3) { + ksft_test_result_skip(t->msg); + continue; + } + + if (ret != 0) + ret = (t->expected == ret); + else + ret = !(t->expected); + + ksft_test_result(ret, t->msg); + } +} + +static struct testcases uring_cases[] = { + { + .later = 0, + .lam = LAM_U57_BITS, + .test_func = handle_uring, + .msg = "URING: LAM_U57. Dereferencing pointer with metadata\n", + }, + { + .later = 1, + .expected = 1, + .lam = LAM_U57_BITS, + .test_func = handle_uring, + .msg = "URING:[Negative] Disable LAM. Dereferencing pointer with metadata.\n", + }, +}; + +static struct testcases malloc_cases[] = { + { + .later = 0, + .lam = LAM_U57_BITS, + .test_func = handle_malloc, + .msg = "MALLOC: LAM_U57. Dereferencing pointer with metadata\n", + }, + { + .later = 1, + .expected = 2, + .lam = LAM_U57_BITS, + .test_func = handle_malloc, + .msg = "MALLOC:[Negative] Disable LAM. Dereferencing pointer with metadata.\n", + }, +}; + +static struct testcases bits_cases[] = { + { + .test_func = handle_max_bits, + .msg = "BITS: Check default tag bits\n", + }, +}; + +static struct testcases syscall_cases[] = { + { + .later = 0, + .lam = LAM_U57_BITS, + .test_func = handle_syscall, + .msg = "SYSCALL: LAM_U57. syscall with metadata\n", + }, + { + .later = 1, + .expected = 1, + .lam = LAM_U57_BITS, + .test_func = handle_syscall, + .msg = "SYSCALL:[Negative] Disable LAM. Dereferencing pointer with metadata.\n", + }, +}; + +static struct testcases mmap_cases[] = { + { + .later = 1, + .expected = 0, + .lam = LAM_U57_BITS, + .addr = HIGH_ADDR, + .test_func = handle_mmap, + .msg = "MMAP: First mmap high address, then set LAM_U57.\n", + }, + { + .later = 0, + .expected = 0, + .lam = LAM_U57_BITS, + .addr = HIGH_ADDR, + .test_func = handle_mmap, + .msg = "MMAP: First LAM_U57, then High address.\n", + }, + { + .later = 0, + .expected = 0, + .lam = LAM_U57_BITS, + .addr = LOW_ADDR, + .test_func = handle_mmap, + .msg = "MMAP: First LAM_U57, then Low address.\n", + }, +}; + +static struct testcases inheritance_cases[] = { + { + .expected = 0, + .lam = LAM_U57_BITS, + .test_func = handle_inheritance, + .msg = "FORK: LAM_U57, child process should get LAM mode same as parent\n", + }, + { + .expected = 0, + .lam = LAM_U57_BITS, + .test_func = handle_thread, + .msg = "THREAD: LAM_U57, child thread should get LAM mode same as parent\n", + }, + { + .expected = 1, + .lam = LAM_U57_BITS, + .test_func = handle_thread_enable, + .msg = "THREAD: [NEGATIVE] Enable LAM in child.\n", + }, + { + .expected = 1, + .later = 1, + .lam = LAM_U57_BITS, + .test_func = handle_thread, + .msg = "THREAD: [NEGATIVE] Enable LAM in parent after thread created.\n", + }, + { + .expected = 0, + .lam = LAM_U57_BITS, + .test_func = handle_execve, + .msg = "EXECVE: LAM_U57, child process should get disabled LAM mode\n", + }, +}; + +static void cmd_help(void) +{ + printf("usage: lam [-h] [-t test list]\n"); + printf("\t-t test list: run tests specified in the test list, default:0x%x\n", TEST_MASK); + printf("\t\t0x1:malloc; 0x2:max_bits; 0x4:mmap; 0x8:syscall; 0x10:io_uring; 0x20:inherit;\n"); + printf("\t-h: help\n"); +} + +/* Check for file existence */ +uint8_t file_Exists(const char *fileName) +{ + struct stat buffer; + + uint8_t ret = (stat(fileName, &buffer) == 0); + + return ret; +} + +/* Sysfs idxd files */ +const char *dsa_configs[] = { + "echo 1 > /sys/bus/dsa/devices/dsa0/wq0.1/group_id", + "echo shared > /sys/bus/dsa/devices/dsa0/wq0.1/mode", + "echo 10 > /sys/bus/dsa/devices/dsa0/wq0.1/priority", + "echo 16 > /sys/bus/dsa/devices/dsa0/wq0.1/size", + "echo 15 > /sys/bus/dsa/devices/dsa0/wq0.1/threshold", + "echo user > /sys/bus/dsa/devices/dsa0/wq0.1/type", + "echo MyApp1 > /sys/bus/dsa/devices/dsa0/wq0.1/name", + "echo 1 > /sys/bus/dsa/devices/dsa0/engine0.1/group_id", + "echo dsa0 > /sys/bus/dsa/drivers/idxd/bind", + /* bind files and devices, generated a device file in /dev */ + "echo wq0.1 > /sys/bus/dsa/drivers/user/bind", +}; + +/* DSA device file */ +const char *dsaDeviceFile = "/dev/dsa/wq0.1"; +/* file for io*/ +const char *dsaPasidEnable = "/sys/bus/dsa/devices/dsa0/pasid_enabled"; + +/* + * DSA depends on kernel cmdline "intel_iommu=on,sm_on" + * return pasid_enabled (0: disable 1:enable) + */ +int Check_DSA_Kernel_Setting(void) +{ + char command[256] = ""; + char buf[256] = ""; + char *ptr; + int rv = -1; + + snprintf(command, sizeof(command) - 1, "cat %s", dsaPasidEnable); + + FILE *cmd = popen(command, "r"); + + if (cmd) { + while (fgets(buf, sizeof(buf) - 1, cmd) != NULL); + + pclose(cmd); + rv = strtol(buf, &ptr, 16); + } + + return rv; +} + +/* + * Config DSA's sysfs files as shared DSA's WQ. + * Generated a device file /dev/dsa/wq0.1 + * Return: 0 OK; 1 Failed; 3 Skip(SVA disabled). + */ +int Dsa_Init_Sysfs(void) +{ + uint len = ARRAY_SIZE(dsa_configs); + const char **p = dsa_configs; + + if (file_Exists(dsaDeviceFile) == 1) + return 0; + + /* check the idxd driver */ + if (file_Exists(dsaPasidEnable) != 1) { + printf("Please make sure idxd driver was loaded\n"); + return 3; + } + + /* Check SVA feature */ + if (Check_DSA_Kernel_Setting() != 1) { + printf("Please enable SVA.(Add intel_iommu=on,sm_on in kernel cmdline)\n"); + return 3; + } + + /* Check the idxd device file on /dev/dsa/ */ + for (int i = 0; i < len; i++) { + if (system(p[i])) + return 1; + } + + /* After config, /dev/dsa/wq0.1 should be generated */ + return (file_Exists(dsaDeviceFile) != 1); +} + +/* + * Open DSA device file, triger API: iommu_sva_alloc_pasid + */ +void *allocate_dsa_pasid(void) +{ + int fd; + void *wq; + + fd = open(dsaDeviceFile, O_RDWR); + if (fd < 0) { + perror("open"); + return MAP_FAILED; + } + + wq = mmap(NULL, 0x1000, PROT_WRITE, + MAP_SHARED | MAP_POPULATE, fd, 0); + if (wq == MAP_FAILED) + perror("mmap"); + + return wq; +} + +int set_force_svm(void) +{ + int ret = 0; + + ret = syscall(SYS_arch_prctl, ARCH_FORCE_TAGGED_SVA); + + return ret; +} + +int handle_pasid(struct testcases *test) +{ + uint tmp = test->cmd; + uint runed = 0x0; + int ret = 0; + void *wq = NULL; + + ret = Dsa_Init_Sysfs(); + if (ret != 0) + return ret; + + for (int i = 0; i < 3; i++) { + int err = 0; + + if (tmp & 0x1) { + /* run set lam mode*/ + if ((runed & 0x1) == 0) { + err = set_lam(LAM_U57_BITS); + runed = runed | 0x1; + } else + err = 1; + } else if (tmp & 0x4) { + /* run force svm */ + if ((runed & 0x4) == 0) { + err = set_force_svm(); + runed = runed | 0x4; + } else + err = 1; + } else if (tmp & 0x2) { + /* run allocate pasid */ + if ((runed & 0x2) == 0) { + runed = runed | 0x2; + wq = allocate_dsa_pasid(); + if (wq == MAP_FAILED) + err = 1; + } else + err = 1; + } + + ret = ret + err; + if (ret > 0) + break; + + tmp = tmp >> 4; + } + + if (wq != MAP_FAILED && wq != NULL) + if (munmap(wq, 0x1000)) + printf("munmap failed %d\n", errno); + + if (runed != 0x7) + ret = 1; + + return (ret != 0); +} + +/* + * Pasid test depends on idxd and SVA, kernel should enable iommu and sm. + * command line(intel_iommu=on,sm_on) + */ +static struct testcases pasid_cases[] = { + { + .expected = 1, + .cmd = PAS_CMD(LAM_CMD_BIT, PAS_CMD_BIT, SVA_CMD_BIT), + .test_func = handle_pasid, + .msg = "PASID: [Negative] Execute LAM, PASID, SVA in sequence\n", + }, + { + .expected = 0, + .cmd = PAS_CMD(LAM_CMD_BIT, SVA_CMD_BIT, PAS_CMD_BIT), + .test_func = handle_pasid, + .msg = "PASID: Execute LAM, SVA, PASID in sequence\n", + }, + { + .expected = 1, + .cmd = PAS_CMD(PAS_CMD_BIT, LAM_CMD_BIT, SVA_CMD_BIT), + .test_func = handle_pasid, + .msg = "PASID: [Negative] Execute PASID, LAM, SVA in sequence\n", + }, + { + .expected = 0, + .cmd = PAS_CMD(PAS_CMD_BIT, SVA_CMD_BIT, LAM_CMD_BIT), + .test_func = handle_pasid, + .msg = "PASID: Execute PASID, SVA, LAM in sequence\n", + }, + { + .expected = 0, + .cmd = PAS_CMD(SVA_CMD_BIT, LAM_CMD_BIT, PAS_CMD_BIT), + .test_func = handle_pasid, + .msg = "PASID: Execute SVA, LAM, PASID in sequence\n", + }, + { + .expected = 0, + .cmd = PAS_CMD(SVA_CMD_BIT, PAS_CMD_BIT, LAM_CMD_BIT), + .test_func = handle_pasid, + .msg = "PASID: Execute SVA, PASID, LAM in sequence\n", + }, +}; + +int main(int argc, char **argv) +{ + int c = 0; + unsigned int tests = TEST_MASK; + + tests_cnt = 0; + + if (!cpu_has_lam()) { + ksft_print_msg("Unsupported LAM feature!\n"); + return -1; + } + + while ((c = getopt(argc, argv, "ht:")) != -1) { + switch (c) { + case 't': + tests = strtoul(optarg, NULL, 16); + if (tests && !(tests & TEST_MASK)) { + ksft_print_msg("Invalid argument!\n"); + return -1; + } + break; + case 'h': + cmd_help(); + return 0; + default: + ksft_print_msg("Invalid argument\n"); + return -1; + } + } + + /* + * When tests is 0, it is not a real test case; + * the option used by test case(execve) to check the lam mode in + * process generated by execve, the process read back lam mode and + * check with lam mode in parent process. + */ + if (!tests) + return (get_lam()); + + /* Run test cases */ + if (tests & FUNC_MALLOC) + run_test(malloc_cases, ARRAY_SIZE(malloc_cases)); + + if (tests & FUNC_BITS) + run_test(bits_cases, ARRAY_SIZE(bits_cases)); + + if (tests & FUNC_MMAP) + run_test(mmap_cases, ARRAY_SIZE(mmap_cases)); + + if (tests & FUNC_SYSCALL) + run_test(syscall_cases, ARRAY_SIZE(syscall_cases)); + + if (tests & FUNC_URING) + run_test(uring_cases, ARRAY_SIZE(uring_cases)); + + if (tests & FUNC_INHERITE) + run_test(inheritance_cases, ARRAY_SIZE(inheritance_cases)); + + if (tests & FUNC_PASID) + run_test(pasid_cases, ARRAY_SIZE(pasid_cases)); + + ksft_set_plan(tests_cnt); + + return ksft_exit_pass(); +} diff --git a/tools/tracing/rtla/.gitignore b/tools/tracing/rtla/.gitignore new file mode 100644 index 000000000000..e9df32419b2b --- /dev/null +++ b/tools/tracing/rtla/.gitignore @@ -0,0 +1 @@ +/rtla diff --git a/tools/tracing/rtla/src/timerlat_aa.c b/tools/tracing/rtla/src/timerlat_aa.c index ec4e0f4b0e6c..1843fff66da5 100644 --- a/tools/tracing/rtla/src/timerlat_aa.c +++ b/tools/tracing/rtla/src/timerlat_aa.c @@ -548,7 +548,7 @@ static void timerlat_thread_analysis(struct timerlat_aa_data *taa_data, int cpu, exp_irq_ts = taa_data->timer_irq_start_time - taa_data->timer_irq_start_delay; if (exp_irq_ts < taa_data->prev_irq_timstamp + taa_data->prev_irq_duration) - printf(" Previous IRQ interference: \t up to %9.2f us", + printf(" Previous IRQ interference: \t\t up to %9.2f us\n", ns_to_usf(taa_data->prev_irq_duration)); /* diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index eea5b3357e27..92c658c64f28 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -33,6 +33,7 @@ struct timerlat_top_params { int set_sched; int dma_latency; int no_aa; + int aa_only; int dump_tasks; struct sched_attr sched_param; struct trace_events *events; @@ -142,10 +143,12 @@ timerlat_top_handler(struct trace_seq *s, struct tep_record *record, top = container_of(trace, struct osnoise_tool, trace); params = top->params; - tep_get_field_val(s, event, "context", record, &thread, 1); - tep_get_field_val(s, event, "timer_latency", record, &latency, 1); + if (!params->aa_only) { + tep_get_field_val(s, event, "context", record, &thread, 1); + tep_get_field_val(s, event, "timer_latency", record, &latency, 1); - timerlat_top_update(top, cpu, thread, latency); + timerlat_top_update(top, cpu, thread, latency); + } if (!params->no_aa) timerlat_aa_handler(s, record, event, context); @@ -250,6 +253,9 @@ timerlat_print_stats(struct timerlat_top_params *params, struct osnoise_tool *to static int nr_cpus = -1; int i; + if (params->aa_only) + return; + if (nr_cpus == -1) nr_cpus = sysconf(_SC_NPROCESSORS_CONF); @@ -279,10 +285,11 @@ static void timerlat_top_usage(char *usage) "", " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\", " [[-t[=file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] \\", - " [-P priority] [--dma-latency us]", + " [-P priority] [--dma-latency us] [--aa-only us]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", + " --aa-only us: stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)", " -p/--period us: timerlat period in us", " -i/--irq us: stop trace if the irq latency is higher than the argument in us", " -T/--thread us: stop trace if the thread latency is higher than the argument in us", @@ -362,13 +369,14 @@ static struct timerlat_top_params {"dma-latency", required_argument, 0, '2'}, {"no-aa", no_argument, 0, '3'}, {"dump-tasks", no_argument, 0, '4'}, + {"aa-only", required_argument, 0, '5'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:0:1:2:34", + c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:0:1:2:345:", long_options, &option_index); /* detect the end of the options. */ @@ -389,6 +397,20 @@ static struct timerlat_top_params /* set trace */ params->trace_output = "timerlat_trace.txt"; break; + case '5': + /* it is here because it is similar to -a */ + auto_thresh = get_llong_from_str(optarg); + + /* set thread stop to auto_thresh */ + params->stop_total_us = auto_thresh; + params->stop_us = auto_thresh; + + /* get stack trace */ + params->print_stack = auto_thresh; + + /* set aa_only to avoid parsing the trace */ + params->aa_only = 1; + break; case 'c': retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); if (retval) @@ -503,6 +525,9 @@ static struct timerlat_top_params if (!params->stop_us && !params->stop_total_us) params->no_aa = 1; + if (params->no_aa && params->aa_only) + timerlat_top_usage("--no-aa and --aa-only are mutually exclusive!"); + return params; } @@ -634,6 +659,7 @@ int timerlat_top_main(int argc, char *argv[]) struct trace_instance *trace; int dma_latency_fd = -1; int return_value = 1; + char *max_lat; int retval; params = timerlat_top_parse_args(argc, argv); @@ -700,6 +726,9 @@ int timerlat_top_main(int argc, char *argv[]) while (!stop_tracing) { sleep(params->sleep_time); + if (params->aa_only && !trace_is_off(&top->trace, &record->trace)) + continue; + retval = tracefs_iterate_raw_events(trace->tep, trace->inst, NULL, @@ -733,6 +762,16 @@ int timerlat_top_main(int argc, char *argv[]) printf(" Saving trace to %s\n", params->trace_output); save_trace_to_file(record->trace.inst, params->trace_output); } + } else if (params->aa_only) { + /* + * If the trace did not stop with --aa-only, at least print the + * max known latency. + */ + max_lat = tracefs_instance_file_read(trace->inst, "tracing_max_latency", NULL); + if (max_lat) { + printf(" Max latency was %s\n", max_lat); + free(max_lat); + } } out_top: diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c index e601cd9c411e..1ddb85532816 100644 --- a/tools/verification/rv/src/rv.c +++ b/tools/verification/rv/src/rv.c @@ -74,7 +74,7 @@ static void rv_list(int argc, char **argv) static void rv_mon(int argc, char **argv) { char *monitor_name; - int i, run; + int i, run = 0; static const char *const usage[] = { "", diff --git a/tools/virtio/linux/compiler.h b/tools/virtio/linux/compiler.h index 2c51bccb97bb..1f3a15b954b9 100644 --- a/tools/virtio/linux/compiler.h +++ b/tools/virtio/linux/compiler.h @@ -2,6 +2,8 @@ #ifndef LINUX_COMPILER_H #define LINUX_COMPILER_H +#include "../../../include/linux/compiler_types.h" + #define WRITE_ONCE(var, val) \ (*((volatile typeof(val) *)(&(var))) = (val)) diff --git a/tools/virtio/linux/kernel.h b/tools/virtio/linux/kernel.h index 8b877167933d..6702008f7f5c 100644 --- a/tools/virtio/linux/kernel.h +++ b/tools/virtio/linux/kernel.h @@ -10,6 +10,7 @@ #include <stdarg.h> #include <linux/compiler.h> +#include "../../../include/linux/container_of.h" #include <linux/log2.h> #include <linux/types.h> #include <linux/overflow.h> @@ -107,10 +108,6 @@ static inline void free_page(unsigned long addr) free((void *)addr); } -#define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) - # ifndef likely # define likely(x) (__builtin_expect(!!(x), 1)) # endif diff --git a/tools/virtio/linux/uaccess.h b/tools/virtio/linux/uaccess.h index 991dfb263998..f13828e0c409 100644 --- a/tools/virtio/linux/uaccess.h +++ b/tools/virtio/linux/uaccess.h @@ -6,15 +6,10 @@ extern void *__user_addr_min, *__user_addr_max; -static inline void __chk_user_ptr(const volatile void *p, size_t size) -{ - assert(p >= __user_addr_min && p + size <= __user_addr_max); -} - #define put_user(x, ptr) \ ({ \ typeof(ptr) __pu_ptr = (ptr); \ - __chk_user_ptr(__pu_ptr, sizeof(*__pu_ptr)); \ + __chk_user_ptr(__pu_ptr); \ WRITE_ONCE(*(__pu_ptr), x); \ 0; \ }) @@ -22,7 +17,7 @@ static inline void __chk_user_ptr(const volatile void *p, size_t size) #define get_user(x, ptr) \ ({ \ typeof(ptr) __pu_ptr = (ptr); \ - __chk_user_ptr(__pu_ptr, sizeof(*__pu_ptr)); \ + __chk_user_ptr(__pu_ptr); \ x = READ_ONCE(*(__pu_ptr)); \ 0; \ }) @@ -37,7 +32,6 @@ static void volatile_memcpy(volatile char *to, const volatile char *from, static inline int copy_from_user(void *to, const void __user volatile *from, unsigned long n) { - __chk_user_ptr(from, n); volatile_memcpy(to, from, n); return 0; } @@ -45,7 +39,6 @@ static inline int copy_from_user(void *to, const void __user volatile *from, static inline int copy_to_user(void __user volatile *to, const void *from, unsigned long n) { - __chk_user_ptr(to, n); volatile_memcpy(to, from, n); return 0; } diff --git a/tools/virtio/virtio_test.c b/tools/virtio/virtio_test.c index 120062f94590..028f54e6854a 100644 --- a/tools/virtio/virtio_test.c +++ b/tools/virtio/virtio_test.c @@ -134,7 +134,7 @@ static void vdev_info_init(struct vdev_info* dev, unsigned long long features) dev->buf_size = 1024; dev->buf = malloc(dev->buf_size); assert(dev->buf); - dev->control = open("/dev/vhost-test", O_RDWR); + dev->control = open("/dev/vhost-test", O_RDWR); assert(dev->control >= 0); r = ioctl(dev->control, VHOST_SET_OWNER, NULL); assert(r >= 0); @@ -327,7 +327,7 @@ const struct option longopts[] = { } }; -static void help(void) +static void help(int status) { fprintf(stderr, "Usage: virtio_test [--help]" " [--no-indirect]" @@ -337,6 +337,8 @@ static void help(void) " [--batch=random/N]" " [--reset=N]" "\n"); + + exit(status); } int main(int argc, char **argv) @@ -354,14 +356,12 @@ int main(int argc, char **argv) case -1: goto done; case '?': - help(); - exit(2); + help(2); case 'e': features &= ~(1ULL << VIRTIO_RING_F_EVENT_IDX); break; case 'h': - help(); - goto done; + help(0); case 'i': features &= ~(1ULL << VIRTIO_RING_F_INDIRECT_DESC); break; |