Path: blob/master/tools/testing/selftests/arm64/gcs/gcs-stress.c
26292 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (C) 2022-3 ARM Limited.3*/45#define _GNU_SOURCE6#define _POSIX_C_SOURCE 199309L78#include <errno.h>9#include <getopt.h>10#include <poll.h>11#include <signal.h>12#include <stdbool.h>13#include <stddef.h>14#include <stdio.h>15#include <stdlib.h>16#include <string.h>17#include <unistd.h>18#include <sys/auxv.h>19#include <sys/epoll.h>20#include <sys/prctl.h>21#include <sys/types.h>22#include <sys/uio.h>23#include <sys/wait.h>24#include <asm/hwcap.h>2526#include "../../kselftest.h"2728struct child_data {29char *name, *output;30pid_t pid;31int stdout;32bool output_seen;33bool exited;34int exit_status;35int exit_signal;36};3738static int epoll_fd;39static struct child_data *children;40static struct epoll_event *evs;41static int tests;42static int num_children;43static bool terminate;4445static int startup_pipe[2];4647static int num_processors(void)48{49long nproc = sysconf(_SC_NPROCESSORS_CONF);50if (nproc < 0) {51perror("Unable to read number of processors\n");52exit(EXIT_FAILURE);53}5455return nproc;56}5758static void start_thread(struct child_data *child, int id)59{60int ret, pipefd[2], i;61struct epoll_event ev;6263ret = pipe(pipefd);64if (ret != 0)65ksft_exit_fail_msg("Failed to create stdout pipe: %s (%d)\n",66strerror(errno), errno);6768child->pid = fork();69if (child->pid == -1)70ksft_exit_fail_msg("fork() failed: %s (%d)\n",71strerror(errno), errno);7273if (!child->pid) {74/*75* In child, replace stdout with the pipe, errors to76* stderr from here as kselftest prints to stdout.77*/78ret = dup2(pipefd[1], 1);79if (ret == -1) {80fprintf(stderr, "dup2() %d\n", errno);81exit(EXIT_FAILURE);82}8384/*85* Duplicate the read side of the startup pipe to86* FD 3 so we can close everything else.87*/88ret = dup2(startup_pipe[0], 3);89if (ret == -1) {90fprintf(stderr, "dup2() %d\n", errno);91exit(EXIT_FAILURE);92}9394/*95* Very dumb mechanism to clean open FDs other than96* stdio. We don't want O_CLOEXEC for the pipes...97*/98for (i = 4; i < 8192; i++)99close(i);100101/*102* Read from the startup pipe, there should be no data103* and we should block until it is closed. We just104* carry on on error since this isn't super critical.105*/106ret = read(3, &i, sizeof(i));107if (ret < 0)108fprintf(stderr, "read(startp pipe) failed: %s (%d)\n",109strerror(errno), errno);110if (ret > 0)111fprintf(stderr, "%d bytes of data on startup pipe\n",112ret);113close(3);114115ret = execl("gcs-stress-thread", "gcs-stress-thread", NULL);116fprintf(stderr, "execl(gcs-stress-thread) failed: %d (%s)\n",117errno, strerror(errno));118119exit(EXIT_FAILURE);120} else {121/*122* In parent, remember the child and close our copy of the123* write side of stdout.124*/125close(pipefd[1]);126child->stdout = pipefd[0];127child->output = NULL;128child->exited = false;129child->output_seen = false;130131ev.events = EPOLLIN | EPOLLHUP;132ev.data.ptr = child;133134ret = asprintf(&child->name, "Thread-%d", id);135if (ret == -1)136ksft_exit_fail_msg("asprintf() failed\n");137138ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, child->stdout, &ev);139if (ret < 0) {140ksft_exit_fail_msg("%s EPOLL_CTL_ADD failed: %s (%d)\n",141child->name, strerror(errno), errno);142}143}144145ksft_print_msg("Started %s\n", child->name);146num_children++;147}148149static bool child_output_read(struct child_data *child)150{151char read_data[1024];152char work[1024];153int ret, len, cur_work, cur_read;154155ret = read(child->stdout, read_data, sizeof(read_data));156if (ret < 0) {157if (errno == EINTR)158return true;159160ksft_print_msg("%s: read() failed: %s (%d)\n",161child->name, strerror(errno),162errno);163return false;164}165len = ret;166167child->output_seen = true;168169/* Pick up any partial read */170if (child->output) {171strncpy(work, child->output, sizeof(work) - 1);172cur_work = strnlen(work, sizeof(work));173free(child->output);174child->output = NULL;175} else {176cur_work = 0;177}178179cur_read = 0;180while (cur_read < len) {181work[cur_work] = read_data[cur_read++];182183if (work[cur_work] == '\n') {184work[cur_work] = '\0';185ksft_print_msg("%s: %s\n", child->name, work);186cur_work = 0;187} else {188cur_work++;189}190}191192if (cur_work) {193work[cur_work] = '\0';194ret = asprintf(&child->output, "%s", work);195if (ret == -1)196ksft_exit_fail_msg("Out of memory\n");197}198199return false;200}201202static void child_output(struct child_data *child, uint32_t events,203bool flush)204{205bool read_more;206207if (events & EPOLLIN) {208do {209read_more = child_output_read(child);210} while (read_more);211}212213if (events & EPOLLHUP) {214close(child->stdout);215child->stdout = -1;216flush = true;217}218219if (flush && child->output) {220ksft_print_msg("%s: %s<EOF>\n", child->name, child->output);221free(child->output);222child->output = NULL;223}224}225226static void child_tickle(struct child_data *child)227{228if (child->output_seen && !child->exited)229kill(child->pid, SIGUSR1);230}231232static void child_stop(struct child_data *child)233{234if (!child->exited)235kill(child->pid, SIGTERM);236}237238static void child_cleanup(struct child_data *child)239{240pid_t ret;241int status;242bool fail = false;243244if (!child->exited) {245do {246ret = waitpid(child->pid, &status, 0);247if (ret == -1 && errno == EINTR)248continue;249250if (ret == -1) {251ksft_print_msg("waitpid(%d) failed: %s (%d)\n",252child->pid, strerror(errno),253errno);254fail = true;255break;256}257258if (WIFEXITED(status)) {259child->exit_status = WEXITSTATUS(status);260child->exited = true;261}262263if (WIFSIGNALED(status)) {264child->exit_signal = WTERMSIG(status);265ksft_print_msg("%s: Exited due to signal %d\n",266child->name, child->exit_signal);267fail = true;268child->exited = true;269}270} while (!child->exited);271}272273if (!child->output_seen) {274ksft_print_msg("%s no output seen\n", child->name);275fail = true;276}277278if (child->exit_status != 0) {279ksft_print_msg("%s exited with error code %d\n",280child->name, child->exit_status);281fail = true;282}283284ksft_test_result(!fail, "%s\n", child->name);285}286287static void handle_child_signal(int sig, siginfo_t *info, void *context)288{289int i;290bool found = false;291292for (i = 0; i < num_children; i++) {293if (children[i].pid == info->si_pid) {294children[i].exited = true;295children[i].exit_status = info->si_status;296found = true;297break;298}299}300301if (!found)302ksft_print_msg("SIGCHLD for unknown PID %d with status %d\n",303info->si_pid, info->si_status);304}305306static void handle_exit_signal(int sig, siginfo_t *info, void *context)307{308int i;309310/* If we're already exiting then don't signal again */311if (terminate)312return;313314ksft_print_msg("Got signal, exiting...\n");315316terminate = true;317318/*319* This should be redundant, the main loop should clean up320* after us, but for safety stop everything we can here.321*/322for (i = 0; i < num_children; i++)323child_stop(&children[i]);324}325326/* Handle any pending output without blocking */327static void drain_output(bool flush)328{329int ret = 1;330int i;331332while (ret > 0) {333ret = epoll_wait(epoll_fd, evs, tests, 0);334if (ret < 0) {335if (errno == EINTR)336continue;337ksft_print_msg("epoll_wait() failed: %s (%d)\n",338strerror(errno), errno);339}340341for (i = 0; i < ret; i++)342child_output(evs[i].data.ptr, evs[i].events, flush);343}344}345346static const struct option options[] = {347{ "timeout", required_argument, NULL, 't' },348{ }349};350351int main(int argc, char **argv)352{353int seen_children;354bool all_children_started = false;355int gcs_threads;356int timeout = 10;357int ret, cpus, i, c;358struct sigaction sa;359360while ((c = getopt_long(argc, argv, "t:", options, NULL)) != -1) {361switch (c) {362case 't':363ret = sscanf(optarg, "%d", &timeout);364if (ret != 1)365ksft_exit_fail_msg("Failed to parse timeout %s\n",366optarg);367break;368default:369ksft_exit_fail_msg("Unknown argument\n");370}371}372373cpus = num_processors();374tests = 0;375376if (getauxval(AT_HWCAP) & HWCAP_GCS) {377/* One extra thread, trying to trigger migrations */378gcs_threads = cpus + 1;379tests += gcs_threads;380} else {381gcs_threads = 0;382}383384ksft_print_header();385ksft_set_plan(tests);386387ksft_print_msg("%d CPUs, %d GCS threads\n",388cpus, gcs_threads);389390if (!tests)391ksft_exit_skip("No tests scheduled\n");392393if (timeout > 0)394ksft_print_msg("Will run for %ds\n", timeout);395else396ksft_print_msg("Will run until terminated\n");397398children = calloc(sizeof(*children), tests);399if (!children)400ksft_exit_fail_msg("Unable to allocate child data\n");401402ret = epoll_create1(EPOLL_CLOEXEC);403if (ret < 0)404ksft_exit_fail_msg("epoll_create1() failed: %s (%d)\n",405strerror(errno), ret);406epoll_fd = ret;407408/* Create a pipe which children will block on before execing */409ret = pipe(startup_pipe);410if (ret != 0)411ksft_exit_fail_msg("Failed to create startup pipe: %s (%d)\n",412strerror(errno), errno);413414/* Get signal handers ready before we start any children */415memset(&sa, 0, sizeof(sa));416sa.sa_sigaction = handle_exit_signal;417sa.sa_flags = SA_RESTART | SA_SIGINFO;418sigemptyset(&sa.sa_mask);419ret = sigaction(SIGINT, &sa, NULL);420if (ret < 0)421ksft_print_msg("Failed to install SIGINT handler: %s (%d)\n",422strerror(errno), errno);423ret = sigaction(SIGTERM, &sa, NULL);424if (ret < 0)425ksft_print_msg("Failed to install SIGTERM handler: %s (%d)\n",426strerror(errno), errno);427sa.sa_sigaction = handle_child_signal;428ret = sigaction(SIGCHLD, &sa, NULL);429if (ret < 0)430ksft_print_msg("Failed to install SIGCHLD handler: %s (%d)\n",431strerror(errno), errno);432433evs = calloc(tests, sizeof(*evs));434if (!evs)435ksft_exit_fail_msg("Failed to allocated %d epoll events\n",436tests);437438for (i = 0; i < gcs_threads; i++)439start_thread(&children[i], i);440441/*442* All children started, close the startup pipe and let them443* run.444*/445close(startup_pipe[0]);446close(startup_pipe[1]);447448timeout *= 10;449for (;;) {450/* Did we get a signal asking us to exit? */451if (terminate)452break;453454/*455* Timeout is counted in 100ms with no output, the456* tests print during startup then are silent when457* running so this should ensure they all ran enough458* to install the signal handler, this is especially459* useful in emulation where we will both be slow and460* likely to have a large set of VLs.461*/462ret = epoll_wait(epoll_fd, evs, tests, 100);463if (ret < 0) {464if (errno == EINTR)465continue;466ksft_exit_fail_msg("epoll_wait() failed: %s (%d)\n",467strerror(errno), errno);468}469470/* Output? */471if (ret > 0) {472for (i = 0; i < ret; i++) {473child_output(evs[i].data.ptr, evs[i].events,474false);475}476continue;477}478479/* Otherwise epoll_wait() timed out */480481/*482* If the child processes have not produced output they483* aren't actually running the tests yet.484*/485if (!all_children_started) {486seen_children = 0;487488for (i = 0; i < num_children; i++)489if (children[i].output_seen ||490children[i].exited)491seen_children++;492493if (seen_children != num_children) {494ksft_print_msg("Waiting for %d children\n",495num_children - seen_children);496continue;497}498499all_children_started = true;500}501502ksft_print_msg("Sending signals, timeout remaining: %d00ms\n",503timeout);504505for (i = 0; i < num_children; i++)506child_tickle(&children[i]);507508/* Negative timeout means run indefinitely */509if (timeout < 0)510continue;511if (--timeout == 0)512break;513}514515ksft_print_msg("Finishing up...\n");516terminate = true;517518for (i = 0; i < tests; i++)519child_stop(&children[i]);520521drain_output(false);522523for (i = 0; i < tests; i++)524child_cleanup(&children[i]);525526drain_output(true);527528ksft_finished();529}530531532