root/usr/src/test/bhyve-tests/tests/vmm/mem_partial.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2022 Oxide Computer Company
 */


#include <stdio.h>
#include <unistd.h>
#include <stropts.h>
#include <strings.h>
#include <signal.h>
#include <setjmp.h>
#include <libgen.h>

#include <sys/vmm.h>
#include <sys/vmm_dev.h>
#include <sys/mman.h>
#include <vmmapi.h>

#include "common.h"

/* Half of a leaf page table is 256 pages */
#define LOWER_SZ        (256 * 4096)
#define UPPER_SZ        LOWER_SZ
#define TOTAL_SZ        (LOWER_SZ + UPPER_SZ)

#define LOWER_OFF       0
#define UPPER_OFF       LOWER_SZ

enum test_memsegs {
        MSEG_LOW = 0,
        MSEG_HIGH = 1,
};

static sigjmp_buf segv_env;

void
sigsegv_handler(int sig)
{
        siglongjmp(segv_env, 1);
}


int
main(int argc, char *argv[])
{
        struct vmctx *ctx;
        int res, fd;
        void *guest_mem;
        const char *suite_name = basename(argv[0]);

        ctx = create_test_vm(suite_name);
        if (ctx == NULL) {
                perror("could open test VM");
                return (1);
        }
        fd = vm_get_device_fd(ctx);

        res = alloc_memseg(ctx, MSEG_LOW, LOWER_SZ, "mseg_low");
        if (res != 0) {
                perror("could not alloc low memseg");
                goto bail;
        }
        res = alloc_memseg(ctx, MSEG_HIGH, UPPER_SZ, "mseg_high");
        if (res != 0) {
                perror("could not alloc high memseg");
                goto bail;
        }


        res = vm_mmap_memseg(ctx, LOWER_OFF, MSEG_LOW, 0, LOWER_SZ, PROT_ALL);
        if (res != 0) {
                perror("could not map low memseg");
                goto bail;
        }
        res = vm_mmap_memseg(ctx, UPPER_OFF, MSEG_HIGH, 0, UPPER_SZ, PROT_ALL);
        if (res != 0) {
                perror("could not map high memseg");
                goto bail;
        }

        guest_mem = mmap(NULL, TOTAL_SZ, PROT_READ | PROT_WRITE, MAP_SHARED,
            fd, 0);
        if (guest_mem == MAP_FAILED) {
                perror("could not mmap guest memory");
                goto bail;
        }

        /* Fill memory with 0xff */
        for (uintptr_t gpa = 0; gpa < TOTAL_SZ; gpa++) {
                uint8_t *ptr = guest_mem + gpa;
                *ptr = 0xff;
        }

        /* Unmap the lower memseg */
        res = vm_munmap_memseg(ctx, LOWER_OFF, LOWER_SZ);
        if (guest_mem == NULL) {
                perror("could not unmap lower memseg");
                goto bail;
        }

        /* Confirm upper contents are still correct/accessible */
        for (uintptr_t gpa = UPPER_OFF; gpa < UPPER_OFF + UPPER_SZ; gpa++) {
                uint8_t *ptr = guest_mem + gpa;
                if (*ptr != 0xff) {
                        (void) printf("invalid mem contents at GPA %lx: %x\n",
                            gpa, *ptr);
                        goto bail;
                }
                *ptr = 0xee;
        }

        /*
         * Attempt to access the lower contents, which should result in an
         * expected (and thus handled) SIGSEGV.
         */
        struct sigaction sa = {
                .sa_handler = sigsegv_handler,
        };
        struct sigaction old_sa;
        res = sigaction(SIGSEGV, &sa, &old_sa);
        if (res != 0) {
                perror("could not prep signal handling for bad access");
                goto bail;
        }

        if (sigsetjmp(segv_env, 1) == 0) {
                volatile uint8_t *ptr = guest_mem;

                /*
                 * This access to the guest space should fail, since the memseg
                 * covering the lower part of the VM space has been unmapped.
                 */
                uint8_t tmp = *ptr;

                (void) printf("access to %p (%x) should have failed\n", tmp);
                goto bail;
        }

        /*
         * Unmap and remap the space so any cached entries are dropped for the
         * portion we expect is still accessible.
         */
        res = munmap(guest_mem, TOTAL_SZ);
        if (res != 0) {
                perror("could not unmap lower memseg");
                goto bail;
        }
        guest_mem = mmap(NULL, TOTAL_SZ, PROT_READ | PROT_WRITE, MAP_SHARED,
            fd, 0);
        if (guest_mem == MAP_FAILED) {
                perror("could not re-mmap guest memory");
                goto bail;
        }

        /* Check the upper portion for accessibility. */
        if (sigsetjmp(segv_env, 1) == 0) {
                volatile uint8_t *ptr = guest_mem + UPPER_OFF;

                uint8_t tmp = *ptr;
                if (tmp != 0xee) {
                        (void) printf("unexpected value at %p (%x)\n", ptr,
                            tmp);
                        goto bail;
                }

                res = sigaction(SIGSEGV, &old_sa, NULL);
                if (res != 0) {
                        perror("could not restore SIGSEGV handler");
                        goto bail;
                }
        } else {
                (void) printf("unexpected fault in upper mapping\n");
                goto bail;
        }


        /* Unmap the upper memseg */
        res = vm_munmap_memseg(ctx, UPPER_OFF, UPPER_SZ);
        if (guest_mem == NULL) {
                perror("could not unmap upper memseg");
                goto bail;
        }

        /* mission accomplished */
        (void) printf("%s\tPASS\n", suite_name);
        vm_destroy(ctx);
        return (0);

bail:
        vm_destroy(ctx);
        return (1);
}