/* * Kernel-based Virtual Machine test driver * * This test driver provides a simple way of testing kvm, without a full * device model. * * Copyright (C) 2006 Qumranet * Copyright IBM Corp. 2008 * * Authors: * * Avi Kivity * Yaniv Kamay * Hollis Blanchard * * This work is licensed under the GNU LGPL license, version 2. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iotable.h" static int gettid(void) { return syscall(__NR_gettid); } kvm_context_t kvm; #define IPI_SIGNAL (SIGRTMIN + 4) struct io_table mmio_table; static int ncpus = 1; static sem_t exited_sem; static __thread int vcpu; static sigset_t kernel_sigmask; static sigset_t ipi_sigmask; static uint64_t memory_size = 128 * 1024 * 1024; struct vcpu_info { pid_t tid; }; struct vcpu_info *vcpus; /* Must match flat.lds linker script */ #define VM_TEST_LOAD_ADDRESS 0x100000 static int test_debug(void *opaque, void *vcpu) { printf("test_debug\n"); return 0; } static int test_halt(void *opaque, int vcpu) { int n; sigwait(&ipi_sigmask, &n); return 0; } static int test_io_window(void *opaque) { return 0; } static int test_try_push_interrupts(void *opaque) { return 0; } static void test_post_kvm_run(void *opaque, void *vcpu) { } static int test_pre_kvm_run(void *opaque, void *vcpu) { return 0; } static int mmio_handler(void *opaque, int len, int is_write, uint64_t offset, uint64_t *data) { int r = 0; switch (offset) { case 0: /* putc */ putc(*(char *)data, stdout); fflush(stdout); break; case 1: /* exit */ r = *(char *)data; break; default: printf("%s: offset %"PRIx64" len %d data %"PRIx64"\n", __func__, offset, len, *(uint64_t *)data); r = -EINVAL; } return r; } static int test_mem_read(void *opaque, uint64_t addr, uint8_t *data, int len) { struct io_table_entry *iodev; #if 0 printf("%s: addr %"PRIx64" len %d\n", __func__, addr, len); #endif iodev = io_table_lookup(&mmio_table, addr); if (!iodev) { printf("couldn't find device\n"); return -ENODEV; } return iodev->handler(iodev->opaque, len, 0, addr - iodev->start, (uint64_t *)data); } static int test_mem_write(void *opaque, uint64_t addr, uint8_t *data, int len) { struct io_table_entry *iodev; #if 0 printf("%s: addr %"PRIx64" len %d data %"PRIx64"\n", __func__, addr, len, *(uint64_t *)data); #endif iodev = io_table_lookup(&mmio_table, addr); if (!iodev) { printf("couldn't find device\n"); return -ENODEV; } return iodev->handler(iodev->opaque, len, 1, addr - iodev->start, (uint64_t *)data); } static int test_dcr_read(int vcpu, uint32_t dcrn, uint32_t *data) { printf("%s: dcrn %04X\n", __func__, dcrn); *data = 0; return 0; } static int test_dcr_write(int vcpu, uint32_t dcrn, uint32_t data) { printf("%s: dcrn %04X data %04X\n", __func__, dcrn, data); return 0; } static struct kvm_callbacks test_callbacks = { .mmio_read = test_mem_read, .mmio_write = test_mem_write, .debug = test_debug, .halt = test_halt, .io_window = test_io_window, .try_push_interrupts = test_try_push_interrupts, .post_kvm_run = test_post_kvm_run, .pre_kvm_run = test_pre_kvm_run, .powerpc_dcr_read = test_dcr_read, .powerpc_dcr_write = test_dcr_write, }; static unsigned long load_file(void *mem, const char *fname, int inval_icache) { ssize_t r; int fd; unsigned long bytes = 0; fd = open(fname, O_RDONLY); if (fd == -1) { perror("open"); exit(1); } while ((r = read(fd, mem, 4096)) != -1 && r != 0) { mem += r; bytes += r; } if (r == -1) { perror("read"); printf("read %d bytes\n", bytes); exit(1); } return bytes; } #define ICACHE_LINE_SIZE 32 void sync_caches(void *mem, unsigned long len) { unsigned long i; for (i = 0; i < len; i += ICACHE_LINE_SIZE) asm volatile ("dcbst %0, %1" : : "g"(mem), "r"(i)); asm volatile ("sync"); for (i = 0; i < len; i += ICACHE_LINE_SIZE) asm volatile ("icbi %0, %1" : : "g"(mem), "r"(i)); asm volatile ("sync; isync"); } static void init_vcpu(int n) { sigemptyset(&ipi_sigmask); sigaddset(&ipi_sigmask, IPI_SIGNAL); sigprocmask(SIG_UNBLOCK, &ipi_sigmask, NULL); sigprocmask(SIG_BLOCK, &ipi_sigmask, &kernel_sigmask); vcpus[n].tid = gettid(); vcpu = n; kvm_set_signal_mask(kvm, n, &kernel_sigmask); } static void *do_create_vcpu(void *_n) { struct kvm_regs regs; int n = (long)_n; kvm_create_vcpu(kvm, n); init_vcpu(n); kvm_get_regs(kvm, n, ®s); regs.pc = VM_TEST_LOAD_ADDRESS; kvm_set_regs(kvm, n, ®s); kvm_run(kvm, n, &vcpus[n]); sem_post(&exited_sem); return NULL; } static void start_vcpu(int n) { pthread_t thread; pthread_create(&thread, NULL, do_create_vcpu, (void *)(long)n); } static void usage(const char *progname) { fprintf(stderr, "Usage: %s [OPTIONS] [bootstrap] flatfile\n" "KVM test harness.\n" "\n" " -s, --smp=NUM create a VM with NUM virtual CPUs\n" " -m, --memory=NUM[GMKB] allocate NUM memory for virtual machine. A suffix\n" " can be used to change the unit (default: `M')\n" " -h, --help display this help screen and exit\n" "\n" "Report bugs to .\n" , progname); } static void sig_ignore(int sig) { write(1, "boo\n", 4); } int main(int argc, char **argv) { void *vm_mem; unsigned long len; int i; const char *sopts = "s:phm:"; struct option lopts[] = { { "smp", 1, 0, 's' }, { "memory", 1, 0, 'm' }, { "help", 0, 0, 'h' }, { 0 }, }; int opt_ind, ch; int nb_args; char *endptr; while ((ch = getopt_long(argc, argv, sopts, lopts, &opt_ind)) != -1) { switch (ch) { case 's': ncpus = atoi(optarg); break; case 'm': memory_size = strtoull(optarg, &endptr, 0); switch (*endptr) { case 'G': case 'g': memory_size <<= 30; break; case '\0': case 'M': case 'm': memory_size <<= 20; break; case 'K': case 'k': memory_size <<= 10; break; default: fprintf(stderr, "Unrecongized memory suffix: %c\n", *endptr); exit(1); } if (memory_size == 0) { fprintf(stderr, "Invalid memory size: 0\n"); exit(1); } break; case 'h': usage(argv[0]); exit(0); case '?': default: fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]); exit(1); } } nb_args = argc - optind; if (nb_args < 1 || nb_args > 2) { fprintf(stderr, "Incorrect number of arguments.\n" "Try `%s --help' for more information.\n", argv[0]); exit(1); } signal(IPI_SIGNAL, sig_ignore); vcpus = calloc(ncpus, sizeof *vcpus); if (!vcpus) { fprintf(stderr, "calloc failed\n"); return 1; } kvm = kvm_init(&test_callbacks, 0); if (!kvm) { fprintf(stderr, "kvm_init failed\n"); return 1; } if (kvm_create(kvm, memory_size, &vm_mem) < 0) { kvm_finalize(kvm); fprintf(stderr, "kvm_create failed\n"); return 1; } vm_mem = kvm_create_phys_mem(kvm, 0, memory_size, 0, 1); len = load_file(vm_mem + VM_TEST_LOAD_ADDRESS, argv[optind], 1); sync_caches(vm_mem + VM_TEST_LOAD_ADDRESS, len); io_table_register(&mmio_table, 0xf0000000, 64, mmio_handler, NULL); sem_init(&exited_sem, 0, 0); for (i = 0; i < ncpus; ++i) start_vcpu(i); /* Wait for all vcpus to exit. */ for (i = 0; i < ncpus; ++i) sem_wait(&exited_sem); return 0; }