|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * Copyright (C) 2016 Google, Inc. | 
|  | * | 
|  | * Original Code by Pavel Labath <labath@google.com> | 
|  | * | 
|  | * Code modified by Pratyush Anand <panand@redhat.com> | 
|  | * for testing different byte select for each access size. | 
|  | */ | 
|  |  | 
|  | #define _GNU_SOURCE | 
|  |  | 
|  | #include <asm/ptrace.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/wait.h> | 
|  | #include <sys/ptrace.h> | 
|  | #include <sys/param.h> | 
|  | #include <sys/uio.h> | 
|  | #include <stdint.h> | 
|  | #include <stdbool.h> | 
|  | #include <stddef.h> | 
|  | #include <string.h> | 
|  | #include <stdio.h> | 
|  | #include <unistd.h> | 
|  | #include <elf.h> | 
|  | #include <errno.h> | 
|  | #include <signal.h> | 
|  |  | 
|  | #include "../kselftest.h" | 
|  |  | 
|  | static volatile uint8_t var[96] __attribute__((__aligned__(32))); | 
|  |  | 
|  | static void child(int size, int wr) | 
|  | { | 
|  | volatile uint8_t *addr = &var[32 + wr]; | 
|  |  | 
|  | if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { | 
|  | ksft_print_msg( | 
|  | "ptrace(PTRACE_TRACEME) failed: %s\n", | 
|  | strerror(errno)); | 
|  | _exit(1); | 
|  | } | 
|  |  | 
|  | if (raise(SIGSTOP) != 0) { | 
|  | ksft_print_msg( | 
|  | "raise(SIGSTOP) failed: %s\n", strerror(errno)); | 
|  | _exit(1); | 
|  | } | 
|  |  | 
|  | if ((uintptr_t) addr % size) { | 
|  | ksft_print_msg( | 
|  | "Wrong address write for the given size: %s\n", | 
|  | strerror(errno)); | 
|  | _exit(1); | 
|  | } | 
|  |  | 
|  | switch (size) { | 
|  | case 1: | 
|  | *addr = 47; | 
|  | break; | 
|  | case 2: | 
|  | *(uint16_t *)addr = 47; | 
|  | break; | 
|  | case 4: | 
|  | *(uint32_t *)addr = 47; | 
|  | break; | 
|  | case 8: | 
|  | *(uint64_t *)addr = 47; | 
|  | break; | 
|  | case 16: | 
|  | __asm__ volatile ("stp x29, x30, %0" : "=m" (addr[0])); | 
|  | break; | 
|  | case 32: | 
|  | __asm__ volatile ("stp q29, q30, %0" : "=m" (addr[0])); | 
|  | break; | 
|  | } | 
|  |  | 
|  | _exit(0); | 
|  | } | 
|  |  | 
|  | static bool set_watchpoint(pid_t pid, int size, int wp) | 
|  | { | 
|  | const volatile uint8_t *addr = &var[32 + wp]; | 
|  | const int offset = (uintptr_t)addr % 8; | 
|  | const unsigned int byte_mask = ((1 << size) - 1) << offset; | 
|  | const unsigned int type = 2; /* Write */ | 
|  | const unsigned int enable = 1; | 
|  | const unsigned int control = byte_mask << 5 | type << 3 | enable; | 
|  | struct user_hwdebug_state dreg_state; | 
|  | struct iovec iov; | 
|  |  | 
|  | memset(&dreg_state, 0, sizeof(dreg_state)); | 
|  | dreg_state.dbg_regs[0].addr = (uintptr_t)(addr - offset); | 
|  | dreg_state.dbg_regs[0].ctrl = control; | 
|  | iov.iov_base = &dreg_state; | 
|  | iov.iov_len = offsetof(struct user_hwdebug_state, dbg_regs) + | 
|  | sizeof(dreg_state.dbg_regs[0]); | 
|  | if (ptrace(PTRACE_SETREGSET, pid, NT_ARM_HW_WATCH, &iov) == 0) | 
|  | return true; | 
|  |  | 
|  | if (errno == EIO) | 
|  | ksft_print_msg( | 
|  | "ptrace(PTRACE_SETREGSET, NT_ARM_HW_WATCH) not supported on this hardware: %s\n", | 
|  | strerror(errno)); | 
|  |  | 
|  | ksft_print_msg( | 
|  | "ptrace(PTRACE_SETREGSET, NT_ARM_HW_WATCH) failed: %s\n", | 
|  | strerror(errno)); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool run_test(int wr_size, int wp_size, int wr, int wp) | 
|  | { | 
|  | int status; | 
|  | siginfo_t siginfo; | 
|  | pid_t pid = fork(); | 
|  | pid_t wpid; | 
|  |  | 
|  | if (pid < 0) { | 
|  | ksft_test_result_fail( | 
|  | "fork() failed: %s\n", strerror(errno)); | 
|  | return false; | 
|  | } | 
|  | if (pid == 0) | 
|  | child(wr_size, wr); | 
|  |  | 
|  | wpid = waitpid(pid, &status, __WALL); | 
|  | if (wpid != pid) { | 
|  | ksft_print_msg( | 
|  | "waitpid() failed: %s\n", strerror(errno)); | 
|  | return false; | 
|  | } | 
|  | if (!WIFSTOPPED(status)) { | 
|  | ksft_print_msg( | 
|  | "child did not stop: %s\n", strerror(errno)); | 
|  | return false; | 
|  | } | 
|  | if (WSTOPSIG(status) != SIGSTOP) { | 
|  | ksft_print_msg("child did not stop with SIGSTOP\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!set_watchpoint(pid, wp_size, wp)) | 
|  | return false; | 
|  |  | 
|  | if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) { | 
|  | ksft_print_msg( | 
|  | "ptrace(PTRACE_CONT) failed: %s\n", | 
|  | strerror(errno)); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | alarm(3); | 
|  | wpid = waitpid(pid, &status, __WALL); | 
|  | if (wpid != pid) { | 
|  | ksft_print_msg( | 
|  | "waitpid() failed: %s\n", strerror(errno)); | 
|  | return false; | 
|  | } | 
|  | alarm(0); | 
|  | if (WIFEXITED(status)) { | 
|  | ksft_print_msg("child exited prematurely\n"); | 
|  | return false; | 
|  | } | 
|  | if (!WIFSTOPPED(status)) { | 
|  | ksft_print_msg("child did not stop\n"); | 
|  | return false; | 
|  | } | 
|  | if (WSTOPSIG(status) != SIGTRAP) { | 
|  | ksft_print_msg("child did not stop with SIGTRAP\n"); | 
|  | return false; | 
|  | } | 
|  | if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &siginfo) != 0) { | 
|  | ksft_print_msg( | 
|  | "ptrace(PTRACE_GETSIGINFO): %s\n", | 
|  | strerror(errno)); | 
|  | return false; | 
|  | } | 
|  | if (siginfo.si_code != TRAP_HWBKPT) { | 
|  | ksft_print_msg( | 
|  | "Unexpected si_code %d\n", siginfo.si_code); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | kill(pid, SIGKILL); | 
|  | wpid = waitpid(pid, &status, 0); | 
|  | if (wpid != pid) { | 
|  | ksft_print_msg( | 
|  | "waitpid() failed: %s\n", strerror(errno)); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static void sigalrm(int sig) | 
|  | { | 
|  | } | 
|  |  | 
|  | int main(int argc, char **argv) | 
|  | { | 
|  | int opt; | 
|  | bool succeeded = true; | 
|  | struct sigaction act; | 
|  | int wr, wp, size; | 
|  | bool result; | 
|  |  | 
|  | ksft_print_header(); | 
|  | ksft_set_plan(213); | 
|  |  | 
|  | act.sa_handler = sigalrm; | 
|  | sigemptyset(&act.sa_mask); | 
|  | act.sa_flags = 0; | 
|  | sigaction(SIGALRM, &act, NULL); | 
|  | for (size = 1; size <= 32; size = size*2) { | 
|  | for (wr = 0; wr <= 32; wr = wr + size) { | 
|  | for (wp = wr - size; wp <= wr + size; wp = wp + size) { | 
|  | result = run_test(size, MIN(size, 8), wr, wp); | 
|  | if ((result && wr == wp) || | 
|  | (!result && wr != wp)) | 
|  | ksft_test_result_pass( | 
|  | "Test size = %d write offset = %d watchpoint offset = %d\n", | 
|  | size, wr, wp); | 
|  | else { | 
|  | ksft_test_result_fail( | 
|  | "Test size = %d write offset = %d watchpoint offset = %d\n", | 
|  | size, wr, wp); | 
|  | succeeded = false; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | for (size = 1; size <= 32; size = size*2) { | 
|  | if (run_test(size, 8, -size, -8)) | 
|  | ksft_test_result_pass( | 
|  | "Test size = %d write offset = %d watchpoint offset = -8\n", | 
|  | size, -size); | 
|  | else { | 
|  | ksft_test_result_fail( | 
|  | "Test size = %d write offset = %d watchpoint offset = -8\n", | 
|  | size, -size); | 
|  | succeeded = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (succeeded) | 
|  | ksft_exit_pass(); | 
|  | else | 
|  | ksft_exit_fail(); | 
|  | } |