| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * This test is intended to reproduce a crash that happens when |
| * kvm_arch_hardware_disable is called and it attempts to unregister the user |
| * return notifiers. |
| */ |
| |
| #define _GNU_SOURCE |
| |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <semaphore.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/wait.h> |
| |
| #include <test_util.h> |
| |
| #include "kvm_util.h" |
| |
| #define VCPU_NUM 4 |
| #define SLEEPING_THREAD_NUM (1 << 4) |
| #define FORK_NUM (1ULL << 9) |
| #define DELAY_US_MAX 2000 |
| #define GUEST_CODE_PIO_PORT 4 |
| |
| sem_t *sem; |
| |
| /* Arguments for the pthreads */ |
| struct payload { |
| struct kvm_vm *vm; |
| uint32_t index; |
| }; |
| |
| static void guest_code(void) |
| { |
| for (;;) |
| ; /* Some busy work */ |
| printf("Should not be reached.\n"); |
| } |
| |
| static void *run_vcpu(void *arg) |
| { |
| struct payload *payload = (struct payload *)arg; |
| struct kvm_run *state = vcpu_state(payload->vm, payload->index); |
| |
| vcpu_run(payload->vm, payload->index); |
| |
| TEST_ASSERT(false, "%s: exited with reason %d: %s\n", |
| __func__, state->exit_reason, |
| exit_reason_str(state->exit_reason)); |
| pthread_exit(NULL); |
| } |
| |
| static void *sleeping_thread(void *arg) |
| { |
| int fd; |
| |
| while (true) { |
| fd = open("/dev/null", O_RDWR); |
| close(fd); |
| } |
| TEST_ASSERT(false, "%s: exited\n", __func__); |
| pthread_exit(NULL); |
| } |
| |
| static inline void check_create_thread(pthread_t *thread, pthread_attr_t *attr, |
| void *(*f)(void *), void *arg) |
| { |
| int r; |
| |
| r = pthread_create(thread, attr, f, arg); |
| TEST_ASSERT(r == 0, "%s: failed to create thread", __func__); |
| } |
| |
| static inline void check_set_affinity(pthread_t thread, cpu_set_t *cpu_set) |
| { |
| int r; |
| |
| r = pthread_setaffinity_np(thread, sizeof(cpu_set_t), cpu_set); |
| TEST_ASSERT(r == 0, "%s: failed set affinity", __func__); |
| } |
| |
| static inline void check_join(pthread_t thread, void **retval) |
| { |
| int r; |
| |
| r = pthread_join(thread, retval); |
| TEST_ASSERT(r == 0, "%s: failed to join thread", __func__); |
| } |
| |
| static void run_test(uint32_t run) |
| { |
| struct kvm_vm *vm; |
| cpu_set_t cpu_set; |
| pthread_t threads[VCPU_NUM]; |
| pthread_t throw_away; |
| struct payload payloads[VCPU_NUM]; |
| void *b; |
| uint32_t i, j; |
| |
| CPU_ZERO(&cpu_set); |
| for (i = 0; i < VCPU_NUM; i++) |
| CPU_SET(i, &cpu_set); |
| |
| vm = vm_create(VM_MODE_DEFAULT, DEFAULT_GUEST_PHY_PAGES, O_RDWR); |
| kvm_vm_elf_load(vm, program_invocation_name); |
| vm_create_irqchip(vm); |
| |
| pr_debug("%s: [%d] start vcpus\n", __func__, run); |
| for (i = 0; i < VCPU_NUM; ++i) { |
| vm_vcpu_add_default(vm, i, guest_code); |
| payloads[i].vm = vm; |
| payloads[i].index = i; |
| |
| check_create_thread(&threads[i], NULL, run_vcpu, |
| (void *)&payloads[i]); |
| check_set_affinity(threads[i], &cpu_set); |
| |
| for (j = 0; j < SLEEPING_THREAD_NUM; ++j) { |
| check_create_thread(&throw_away, NULL, sleeping_thread, |
| (void *)NULL); |
| check_set_affinity(throw_away, &cpu_set); |
| } |
| } |
| pr_debug("%s: [%d] all threads launched\n", __func__, run); |
| sem_post(sem); |
| for (i = 0; i < VCPU_NUM; ++i) |
| check_join(threads[i], &b); |
| /* Should not be reached */ |
| TEST_ASSERT(false, "%s: [%d] child escaped the ninja\n", __func__, run); |
| } |
| |
| void wait_for_child_setup(pid_t pid) |
| { |
| /* |
| * Wait for the child to post to the semaphore, but wake up periodically |
| * to check if the child exited prematurely. |
| */ |
| for (;;) { |
| const struct timespec wait_period = { .tv_sec = 1 }; |
| int status; |
| |
| if (!sem_timedwait(sem, &wait_period)) |
| return; |
| |
| /* Child is still running, keep waiting. */ |
| if (pid != waitpid(pid, &status, WNOHANG)) |
| continue; |
| |
| /* |
| * Child is no longer running, which is not expected. |
| * |
| * If it exited with a non-zero status, we explicitly forward |
| * the child's status in case it exited with KSFT_SKIP. |
| */ |
| if (WIFEXITED(status)) |
| exit(WEXITSTATUS(status)); |
| else |
| TEST_ASSERT(false, "Child exited unexpectedly"); |
| } |
| } |
| |
| int main(int argc, char **argv) |
| { |
| uint32_t i; |
| int s, r; |
| pid_t pid; |
| |
| sem = sem_open("vm_sem", O_CREAT | O_EXCL, 0644, 0); |
| sem_unlink("vm_sem"); |
| |
| for (i = 0; i < FORK_NUM; ++i) { |
| pid = fork(); |
| TEST_ASSERT(pid >= 0, "%s: unable to fork", __func__); |
| if (pid == 0) |
| run_test(i); /* This function always exits */ |
| |
| pr_debug("%s: [%d] waiting semaphore\n", __func__, i); |
| wait_for_child_setup(pid); |
| r = (rand() % DELAY_US_MAX) + 1; |
| pr_debug("%s: [%d] waiting %dus\n", __func__, i, r); |
| usleep(r); |
| r = waitpid(pid, &s, WNOHANG); |
| TEST_ASSERT(r != pid, |
| "%s: [%d] child exited unexpectedly status: [%d]", |
| __func__, i, s); |
| pr_debug("%s: [%d] killing child\n", __func__, i); |
| kill(pid, SIGKILL); |
| } |
| |
| sem_destroy(sem); |
| exit(0); |
| } |