/*
 * 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);
}