Path: blob/master/tools/testing/selftests/kvm/coalesced_io_test.c
38189 views
// SPDX-License-Identifier: GPL-2.01#include <signal.h>2#include <stdio.h>3#include <stdlib.h>4#include <string.h>5#include <sys/ioctl.h>67#include <linux/sizes.h>89#include <kvm_util.h>10#include <processor.h>1112#include "ucall_common.h"1314struct kvm_coalesced_io {15struct kvm_coalesced_mmio_ring *ring;16uint32_t ring_size;17uint64_t mmio_gpa;18uint64_t *mmio;1920/*21* x86-only, but define pio_port for all architectures to minimize the22* amount of #ifdeffery and complexity, without having to sacrifice23* verbose error messages.24*/25uint8_t pio_port;26};2728static struct kvm_coalesced_io kvm_builtin_io_ring;2930#ifdef __x86_64__31static const int has_pio = 1;32#else33static const int has_pio = 0;34#endif3536static void guest_code(struct kvm_coalesced_io *io)37{38int i, j;3940for (;;) {41for (j = 0; j < 1 + has_pio; j++) {42/*43* KVM always leaves one free entry, i.e. exits to44* userspace before the last entry is filled.45*/46for (i = 0; i < io->ring_size - 1; i++) {47#ifdef __x86_64__48if (i & 1)49outl(io->pio_port, io->pio_port + i);50else51#endif52WRITE_ONCE(*io->mmio, io->mmio_gpa + i);53}54#ifdef __x86_64__55if (j & 1)56outl(io->pio_port, io->pio_port + i);57else58#endif59WRITE_ONCE(*io->mmio, io->mmio_gpa + i);60}61GUEST_SYNC(0);6263WRITE_ONCE(*io->mmio, io->mmio_gpa + i);64#ifdef __x86_64__65outl(io->pio_port, io->pio_port + i);66#endif67}68}6970static void vcpu_run_and_verify_io_exit(struct kvm_vcpu *vcpu,71struct kvm_coalesced_io *io,72uint32_t ring_start,73uint32_t expected_exit)74{75const bool want_pio = expected_exit == KVM_EXIT_IO;76struct kvm_coalesced_mmio_ring *ring = io->ring;77struct kvm_run *run = vcpu->run;78uint32_t pio_value;7980WRITE_ONCE(ring->first, ring_start);81WRITE_ONCE(ring->last, ring_start);8283vcpu_run(vcpu);8485/*86* Annoyingly, reading PIO data is safe only for PIO exits, otherwise87* data_offset is garbage, e.g. an MMIO gpa.88*/89if (run->exit_reason == KVM_EXIT_IO)90pio_value = *(uint32_t *)((void *)run + run->io.data_offset);91else92pio_value = 0;9394TEST_ASSERT((!want_pio && (run->exit_reason == KVM_EXIT_MMIO && run->mmio.is_write &&95run->mmio.phys_addr == io->mmio_gpa && run->mmio.len == 8 &&96*(uint64_t *)run->mmio.data == io->mmio_gpa + io->ring_size - 1)) ||97(want_pio && (run->exit_reason == KVM_EXIT_IO && run->io.port == io->pio_port &&98run->io.direction == KVM_EXIT_IO_OUT && run->io.count == 1 &&99pio_value == io->pio_port + io->ring_size - 1)),100"For start = %u, expected exit on %u-byte %s write 0x%llx = %lx, got exit_reason = %u (%s)\n "101"(MMIO addr = 0x%llx, write = %u, len = %u, data = %lx)\n "102"(PIO port = 0x%x, write = %u, len = %u, count = %u, data = %x",103ring_start, want_pio ? 4 : 8, want_pio ? "PIO" : "MMIO",104want_pio ? (unsigned long long)io->pio_port : io->mmio_gpa,105(want_pio ? io->pio_port : io->mmio_gpa) + io->ring_size - 1, run->exit_reason,106run->exit_reason == KVM_EXIT_MMIO ? "MMIO" : run->exit_reason == KVM_EXIT_IO ? "PIO" : "other",107run->mmio.phys_addr, run->mmio.is_write, run->mmio.len, *(uint64_t *)run->mmio.data,108run->io.port, run->io.direction, run->io.size, run->io.count, pio_value);109}110111static void vcpu_run_and_verify_coalesced_io(struct kvm_vcpu *vcpu,112struct kvm_coalesced_io *io,113uint32_t ring_start,114uint32_t expected_exit)115{116struct kvm_coalesced_mmio_ring *ring = io->ring;117int i;118119vcpu_run_and_verify_io_exit(vcpu, io, ring_start, expected_exit);120121TEST_ASSERT((ring->last + 1) % io->ring_size == ring->first,122"Expected ring to be full (minus 1), first = %u, last = %u, max = %u, start = %u",123ring->first, ring->last, io->ring_size, ring_start);124125for (i = 0; i < io->ring_size - 1; i++) {126uint32_t idx = (ring->first + i) % io->ring_size;127struct kvm_coalesced_mmio *entry = &ring->coalesced_mmio[idx];128129#ifdef __x86_64__130if (i & 1)131TEST_ASSERT(entry->phys_addr == io->pio_port &&132entry->len == 4 && entry->pio &&133*(uint32_t *)entry->data == io->pio_port + i,134"Wanted 4-byte port I/O 0x%x = 0x%x in entry %u, got %u-byte %s 0x%llx = 0x%x",135io->pio_port, io->pio_port + i, i,136entry->len, entry->pio ? "PIO" : "MMIO",137entry->phys_addr, *(uint32_t *)entry->data);138else139#endif140TEST_ASSERT(entry->phys_addr == io->mmio_gpa &&141entry->len == 8 && !entry->pio,142"Wanted 8-byte MMIO to 0x%lx = %lx in entry %u, got %u-byte %s 0x%llx = 0x%lx",143io->mmio_gpa, io->mmio_gpa + i, i,144entry->len, entry->pio ? "PIO" : "MMIO",145entry->phys_addr, *(uint64_t *)entry->data);146}147}148149static void test_coalesced_io(struct kvm_vcpu *vcpu,150struct kvm_coalesced_io *io, uint32_t ring_start)151{152struct kvm_coalesced_mmio_ring *ring = io->ring;153154kvm_vm_register_coalesced_io(vcpu->vm, io->mmio_gpa, 8, false /* pio */);155#ifdef __x86_64__156kvm_vm_register_coalesced_io(vcpu->vm, io->pio_port, 8, true /* pio */);157#endif158159vcpu_run_and_verify_coalesced_io(vcpu, io, ring_start, KVM_EXIT_MMIO);160#ifdef __x86_64__161vcpu_run_and_verify_coalesced_io(vcpu, io, ring_start, KVM_EXIT_IO);162#endif163164/*165* Verify ucall, which may use non-coalesced MMIO or PIO, generates an166* immediate exit.167*/168WRITE_ONCE(ring->first, ring_start);169WRITE_ONCE(ring->last, ring_start);170vcpu_run(vcpu);171TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_SYNC);172TEST_ASSERT_EQ(ring->first, ring_start);173TEST_ASSERT_EQ(ring->last, ring_start);174175/* Verify that non-coalesced MMIO/PIO generates an exit to userspace. */176kvm_vm_unregister_coalesced_io(vcpu->vm, io->mmio_gpa, 8, false /* pio */);177vcpu_run_and_verify_io_exit(vcpu, io, ring_start, KVM_EXIT_MMIO);178179#ifdef __x86_64__180kvm_vm_unregister_coalesced_io(vcpu->vm, io->pio_port, 8, true /* pio */);181vcpu_run_and_verify_io_exit(vcpu, io, ring_start, KVM_EXIT_IO);182#endif183}184185int main(int argc, char *argv[])186{187struct kvm_vcpu *vcpu;188struct kvm_vm *vm;189int i;190191TEST_REQUIRE(kvm_has_cap(KVM_CAP_COALESCED_MMIO));192193#ifdef __x86_64__194TEST_REQUIRE(kvm_has_cap(KVM_CAP_COALESCED_PIO));195#endif196197vm = vm_create_with_one_vcpu(&vcpu, guest_code);198199kvm_builtin_io_ring = (struct kvm_coalesced_io) {200/*201* The I/O ring is a kernel-allocated page whose address is202* relative to each vCPU's run page, with the page offset203* provided by KVM in the return of KVM_CAP_COALESCED_MMIO.204*/205.ring = (void *)vcpu->run +206(kvm_check_cap(KVM_CAP_COALESCED_MMIO) * getpagesize()),207208/*209* The size of the I/O ring is fixed, but KVM defines the sized210* based on the kernel's PAGE_SIZE. Thus, userspace must query211* the host's page size at runtime to compute the ring size.212*/213.ring_size = (getpagesize() - sizeof(struct kvm_coalesced_mmio_ring)) /214sizeof(struct kvm_coalesced_mmio),215216/*217* Arbitrary address+port (MMIO mustn't overlap memslots), with218* the MMIO GPA identity mapped in the guest.219*/220.mmio_gpa = 4ull * SZ_1G,221.mmio = (uint64_t *)(4ull * SZ_1G),222.pio_port = 0x80,223};224225virt_map(vm, (uint64_t)kvm_builtin_io_ring.mmio, kvm_builtin_io_ring.mmio_gpa, 1);226227sync_global_to_guest(vm, kvm_builtin_io_ring);228vcpu_args_set(vcpu, 1, &kvm_builtin_io_ring);229230for (i = 0; i < kvm_builtin_io_ring.ring_size; i++)231test_coalesced_io(vcpu, &kvm_builtin_io_ring, i);232233kvm_vm_free(vm);234return 0;235}236237238