@@ -4,7 +4,7 @@ SIGNAL_CONTEXT_CHK_TESTS := tm-signal-context-chk-gpr tm-signal-context-chk-fpu
TEST_GEN_PROGS := tm-resched-dscr tm-syscall tm-signal-msr-resv tm-signal-stack \
tm-vmxcopy tm-fork tm-tar tm-tmspr tm-vmx-unavail tm-unavailable tm-trap \
- $(SIGNAL_CONTEXT_CHK_TESTS) tm-sigreturn
+ $(SIGNAL_CONTEXT_CHK_TESTS) tm-sigreturn tm-core
include ../../lib.mk
@@ -19,6 +19,7 @@ $(OUTPUT)/tm-vmx-unavail: CFLAGS += -pthread -m64
$(OUTPUT)/tm-resched-dscr: ../pmu/lib.c
$(OUTPUT)/tm-unavailable: CFLAGS += -O0 -pthread -m64 -Wno-error=uninitialized -mvsx
$(OUTPUT)/tm-trap: CFLAGS += -O0 -pthread -m64
+$(OUTPUT)/tm-core: CFLAGS += -pthread
SIGNAL_CONTEXT_CHK_TESTS := $(patsubst %,$(OUTPUT)/%,$(SIGNAL_CONTEXT_CHK_TESTS))
$(SIGNAL_CONTEXT_CHK_TESTS): tm-signal.S
new file mode 100644
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2018, Breno Leitao, Gustavo Romero, IBM Corp.
+ * Licensed under GPLv2.
+ *
+ * Test case that sets TM SPR and sleeps, waiting kernel load_tm to be
+ * zero. Then causes a segfault to generate a core dump file that will be
+ * analyzed. The coredump needs to have the same HTM SPR values set
+ * initially for a successful test.
+ */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <error.h>
+#include <elf.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <linux/kernel.h>
+
+#include "utils.h"
+#include "tm.h"
+
+/* Default time that causes load_tm = 0 on P8/pseries */
+#define DEFAULT_SLEEP_TIME 0x00d0000000
+
+/* MAX string length for string concatenation */
+#define LEN_MAX 1024
+
+/* Maximum coredump file size */
+#define CORE_FILE_LIMIT (5 * 1024 * 1024)
+
+/* Name of the coredump file to be generated */
+#define COREDUMPFILE "core-tm-spr"
+
+/* Function that returns concatenated strings */
+#define COREDUMP(suffix) (COREDUMPFILE#suffix)
+
+/* File to change the coredump file name pattern */
+#define CORE_PATTERN_FILE "/proc/sys/kernel/core_pattern"
+
+/* Logging macros */
+#define err_at_line(status, errnum, format, ...) \
+ error_at_line(status, errnum, __FILE__, __LINE__, format ##__VA_ARGS__)
+#define pr_err(code, format, ...) err_at_line(1, code, format, ##__VA_ARGS__)
+
+/* Child PID */
+static pid_t child;
+
+/* pthread attribute for both ping and pong threads */
+static pthread_attr_t attr;
+
+/* SPR values to be written to TM SPRs and verified later */
+const unsigned long texasr = 0x31;
+const unsigned long tfiar = 0xdeadbeef0;
+const unsigned long tfhar = 0xbaadf00d0;
+
+struct tm_sprs {
+ unsigned long texasr;
+ unsigned long tfhar;
+ unsigned long tfiar;
+};
+
+struct coremem {
+ void *p;
+ off_t len;
+};
+
+/* Set the process limits to be able to create a coredump file */
+static int increase_core_file_limit(void)
+{
+ struct rlimit rlim;
+ int ret;
+
+ ret = getrlimit(RLIMIT_CORE, &rlim);
+ if (ret != 0) {
+ pr_err(ret, "getrlimit CORE failed\n");
+ return -1;
+ }
+
+ if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < CORE_FILE_LIMIT) {
+ rlim.rlim_cur = CORE_FILE_LIMIT;
+
+ if (rlim.rlim_max != RLIM_INFINITY &&
+ rlim.rlim_max < CORE_FILE_LIMIT)
+ rlim.rlim_max = CORE_FILE_LIMIT;
+
+ ret = setrlimit(RLIMIT_CORE, &rlim);
+ if (ret != 0) {
+ pr_err(ret, "setrlimit CORE failed\n");
+ return -1;
+ }
+ }
+
+ ret = getrlimit(RLIMIT_FSIZE, &rlim);
+ if (ret != 0) {
+ pr_err(ret, "getrlimit FSIZE failed\n");
+ return -1;
+ }
+
+ if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < CORE_FILE_LIMIT) {
+ rlim.rlim_cur = CORE_FILE_LIMIT;
+
+ if (rlim.rlim_max != RLIM_INFINITY &&
+ rlim.rlim_max < CORE_FILE_LIMIT)
+ rlim.rlim_max = CORE_FILE_LIMIT;
+
+ ret = setrlimit(RLIMIT_FSIZE, &rlim);
+ if (ret != 0) {
+ pr_err(ret, "setrlimit FSIZE failed\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Set pattern for coredump file name. It returns current pattern being
+ * used as 'old' if old != NULL
+ */
+static int write_core_pattern(const char *core_pattern, char *old)
+{
+ FILE *f;
+ size_t len = strlen(core_pattern), ret;
+
+ f = fopen(CORE_PATTERN_FILE, "r+");
+ if (!f) {
+ perror("Error writing to core_pattern file");
+ return -1;
+ }
+
+ /* Skip saving old value */
+ if (old != NULL) {
+ ret = fread(old, 1, LEN_MAX, f);
+ if (!ret) {
+ perror("Error reading core_pattern file");
+ fclose(f);
+ return -1;
+ }
+ rewind(f);
+ }
+
+ ret = fwrite(core_pattern, 1, len, f);
+
+ fclose(f);
+
+ if (ret != len) {
+ perror("Error writing to core_pattern file");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* Thread to force context switch. Will die when the test is done */
+static void __attribute__((noreturn)) *tm_core_pong(void *not_used)
+{
+ while (1)
+ sched_yield();
+}
+
+/* Sleep in user space waiting for load_tm to reach zero */
+static void wait_lazy(unsigned long counter)
+{
+ asm volatile (
+ "mtctr %[counter] ;"
+ "1: bdnz 1b ;"
+ :
+ : [counter] "r" (counter)
+ :
+ );
+}
+
+/*
+ * This function will fork, and the child will set the SPRs and sleep
+ * expecting load_tm to be zero. After a while, it will segfault and
+ * generate a core dump. The parent process just wait the child to die
+ * before continuing.
+ */
+static void *sleep_and_dump(void *time)
+{
+ int status;
+ unsigned long t = *(unsigned long *)time;
+
+ /* Fork, and the child process will sleep and die */
+ child = fork();
+ if (child < 0) {
+ pr_err(child, "fork failure");
+ } else if (child == 0) {
+ /* Set TM SPRS to be checked later */
+ mtspr(SPRN_TFIAR, tfiar);
+ mtspr(SPRN_TFHAR, tfhar);
+ mtspr(SPRN_TEXASR, texasr);
+
+ /*
+ * Sleep in userspace. Not sleeping with
+ * sleep()/nanosleep() because we want to continue to do
+ * context switch, incrementing load_tm until it overflows
+ */
+ wait_lazy(t);
+
+ /*
+ * Cause a segfault and coredump. Can not call any syscalls,
+ * which will reload load_tm due to 'tabort.' being executed
+ * before each syscall by some glibc versions.
+ */
+ asm(".long 0x0");
+ }
+
+ /* Only parent will continue here */
+ waitpid(child, &status, 0);
+ if (!WCOREDUMP(status)) {
+ pr_err(status, "Core dump not generated.");
+ return (void *) -1;
+ }
+
+ return NULL;
+}
+
+
+/* Speed up load_tm overflow with this thread */
+static int start_pong_thread(void)
+{
+ int ret;
+ pthread_t t1;
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(0, &cpuset);
+
+ /* Init pthread attribute. */
+ ret = pthread_attr_init(&attr);
+ if (ret) {
+ pr_err(ret, "pthread_attr_init()");
+ return ret;
+ }
+
+ ret = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
+ if (ret) {
+ pr_err(ret, "pthread_attr_setaffinity_np()");
+ return ret;
+ }
+
+ ret = pthread_create(&t1, &attr /* CPU 0 */, tm_core_pong, NULL);
+ if (ret) {
+ pr_err(ret, "pthread_create()");
+ return ret;
+ }
+
+ return 0;
+}
+
+
+/* Main test thread */
+static int start_main_thread(unsigned long t)
+{
+ pthread_t t0;
+ void *ret_value;
+ int ret, rc = 0;
+ char old_core_pattern[LEN_MAX];
+
+ ret = increase_core_file_limit();
+ if (ret)
+ return ret;
+
+ /* Change the name of the core dump file */
+ ret = write_core_pattern(COREDUMP(.%p), old_core_pattern);
+ if (ret) {
+ pr_err(ret, "Not able to change core pattern. Are you root!?");
+ return -1;
+ }
+
+ ret = pthread_create(&t0, &attr, sleep_and_dump, &t);
+ if (ret) {
+ pr_err(ret, "pthread_create()");
+ /* Not returning because core_pattern should be restored */
+ rc = ret;
+ }
+
+ ret = pthread_join(t0, &ret_value);
+ if (ret || ret_value != NULL) {
+ pr_err(ret, "sleep_and_dump didn't finished successfully");
+ rc += ret;
+ }
+
+ /* Restore old core pattern to the original value */
+ ret = write_core_pattern(old_core_pattern, NULL);
+ if (ret != 0) {
+ pr_err(ret, "/proc/sys/kernel/core_pattern not restored properly");
+ rc += ret;
+ }
+
+ return rc;
+}
+
+/* Open coredump file and return it mapped into memory */
+static void open_coredump(struct coremem *c)
+{
+ struct stat buf;
+ int fd; int ret;
+ void *core;
+ off_t core_size;
+
+ char coredump[LEN_MAX];
+
+ /* default return value */
+ c->p = NULL;
+
+ sprintf(coredump, COREDUMP(.%d), child);
+
+ fd = open(coredump, O_RDONLY);
+ if (fd == -1)
+ perror("Error opening core file");
+
+ ret = stat(coredump, &buf);
+ if (ret == -1) {
+ printf("Coredump does not exist!\n");
+ return;
+ }
+ core_size = buf.st_size;
+
+ core = mmap(NULL, core_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (core == MAP_FAILED) {
+ perror("Error mmaping core file");
+ return;
+ }
+ c->p = core;
+ c->len = core_size;
+}
+
+static Elf64_Nhdr *next_note(Elf64_Nhdr *nhdr)
+{
+ return (void *) nhdr + sizeof(*nhdr) +
+ __ALIGN_KERNEL(nhdr->n_namesz, 4) +
+ __ALIGN_KERNEL(nhdr->n_descsz, 4);
+}
+
+/* Parse elf in memory and return TM SPRS values */
+static void parse_elf(Elf64_Ehdr *ehdr, struct tm_sprs *ret)
+{
+ void *p = ehdr;
+ Elf64_Phdr *phdr;
+ Elf64_Nhdr *nhdr;
+ size_t phdr_size;
+ unsigned long *regs, *note;
+
+ assert(memcmp(ehdr->e_ident, ELFMAG, SELFMAG) == 0);
+
+ assert(ehdr->e_type == ET_CORE);
+ assert(ehdr->e_machine == EM_PPC64);
+ assert(ehdr->e_phoff != 0 || ehdr->e_phnum != 0);
+
+ phdr_size = sizeof(*phdr) * ehdr->e_phnum;
+
+ for (phdr = p + ehdr->e_phoff;
+ (void *) phdr < p + ehdr->e_phoff + phdr_size;
+ phdr += ehdr->e_phentsize)
+ /* Stop at NOTES section type */
+ if (phdr->p_type == PT_NOTE)
+ break;
+
+ for (nhdr = p + phdr->p_offset;
+ (void *) nhdr < p + phdr->p_offset + phdr->p_filesz;
+ nhdr = next_note(nhdr))
+ /* Stop at TM SPR segment */
+ if (nhdr->n_type == NT_PPC_TM_SPR)
+ break;
+
+ assert(nhdr->n_descsz != 0);
+
+ p = nhdr;
+ note = p + sizeof(*nhdr) + __ALIGN_KERNEL(nhdr->n_namesz, 4);
+ regs = (unsigned long *) note;
+
+ ret->texasr = regs[1];
+ ret->tfhar = regs[0];
+ ret->tfiar = regs[2];
+}
+
+static int check_return_value(struct tm_sprs *s)
+{
+ if ((s->texasr == texasr) &&
+ (s->tfiar == tfiar) &&
+ (s->tfhar == tfhar)) {
+ return 0;
+ }
+
+ /* Corrupted values detected */
+ printf("Detected one or more TM SPR corrupted:\n");
+ printf("TFIAR : %016lx vs %016lx\n", s->texasr, texasr);
+ printf("TEXASR: %016lx vs %016lx\n", s->tfiar, tfiar);
+ printf("TFHAR : %016lx vs %016lx\n", s->tfhar, tfhar);
+
+ return 1;
+}
+
+/* Remove coredump file generated by this test */
+static int clear_coredump(void)
+{
+ char file[LEN_MAX];
+ int ret;
+
+ sprintf(file, COREDUMP(.%d), child);
+ ret = remove(file);
+ if (ret != 0)
+ perror("Not able to remove coredump file");
+
+ return ret;
+}
+
+/* Main function */
+static int tm_core_test(void)
+{
+ unsigned long time = DEFAULT_SLEEP_TIME;
+ struct tm_sprs sprs;
+ struct coremem mem;
+ int ret;
+
+ /* Skip if TM is not available */
+ SKIP_IF(!have_htm());
+
+ /*
+ * Skip if not root. Could not change the default coredump name
+ * pattern
+ */
+ SKIP_IF(geteuid() != 0);
+
+ printf("Sleeping for %lu cycles\n", time);
+ ret = start_pong_thread();
+ if (ret != 0)
+ return ret;
+
+ ret = start_main_thread(time);
+ if (ret != 0)
+ return ret;
+
+ open_coredump(&mem);
+ if (mem.p == NULL) {
+ /* if open_coredump failed, mem.p returns NULL */
+ pr_err(1, "Open coredump file failed");
+ return -1;
+ }
+
+ parse_elf(mem.p, &sprs);
+
+ /* unmap memory allocated in open_coredump() */
+ munmap(mem.p, mem.len);
+
+ ret = clear_coredump();
+ if (ret != 0)
+ return ret;
+
+ ret = check_return_value(&sprs);
+ if (ret == 0)
+ printf("Success!\n");
+ else
+ printf("Failure!\n");
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ return test_harness(tm_core_test, "tm_core_test");
+}
@@ -12,6 +12,10 @@
#include "utils.h"
+#ifndef NT_PPC_TM_SPR
+#define NT_PPC_TM_SPR 0x10c
+#endif
+
static inline bool have_htm(void)
{
#ifdef PPC_FEATURE2_HTM