+/*
+ * This file is part of the Palacios Virtual Machine Monitor developed
+ * by the V3VEE Project with funding from the United States National
+ * Science Foundation and the Department of Energy.
+ *
+ * The V3VEE Project is a joint project between Northwestern University
+ * and the University of New Mexico. You can find out more at
+ * http://www.v3vee.org
+ *
+ * Copyright (c) 2008, Jack Lange <jarusl@cs.northwestern.edu>
+ * Copyright (c) 2008, The V3VEE Project <http://www.v3vee.org>
+ * All rights reserved.
+ *
+ * Author: Jack Lange <jarusl@cs.northwestern.edu>
+ *
+ * This is free software. You are permitted to use,
+ * redistribute, and modify it as specified in the file "V3VEE_LICENSE".
+ */
+
+
#include <palacios/vmm_shadow_paging.h>
+
#include <palacios/vmm.h>
#include <palacios/vm_guest_mem.h>
+#include <palacios/vmm_decoder.h>
+#include <palacios/vmm_ctrl_regs.h>
+
+#include <palacios/vmm_hashtable.h>
+
+#include <palacios/vmm_direct_paging.h>
+
+
+#ifdef CONFIG_SHADOW_PAGING_TELEMETRY
+#include <palacios/vmm_telemetry.h>
+#endif
+
+#ifdef CONFIG_SYMBIOTIC_SWAP
+#include <palacios/vmm_sym_swap.h>
+#endif
+
+#ifndef CONFIG_DEBUG_SHADOW_PAGING
+#undef PrintDebug
+#define PrintDebug(fmt, args...)
+#endif
+
-extern struct vmm_os_hooks * os_hooks;
+/***
+ *** There be dragons
+ ***/
-int init_shadow_page_state(struct shadow_page_state * state) {
- state->guest_mode = PDE32;
- state->shadow_mode = PDE32;
+struct shadow_page_data {
+ v3_reg_t cr3;
+ addr_t page_pa;
- state->guest_cr3.r_reg = 0;
- state->shadow_cr3.r_reg = 0;
+ struct list_head page_list_node;
+};
- return 0;
+
+
+static struct shadow_page_data * create_new_shadow_pt(struct guest_info * info);
+static int inject_guest_pf(struct guest_info * info, addr_t fault_addr, pf_error_t error_code);
+static int is_guest_pf(pt_access_status_t guest_access, pt_access_status_t shadow_access);
+
+
+#include "vmm_shadow_paging_32.h"
+#include "vmm_shadow_paging_32pae.h"
+#include "vmm_shadow_paging_64.h"
+
+
+
+#ifdef CONFIG_SHADOW_PAGING_TELEMETRY
+static void telemetry_cb(struct guest_info * info, void * private_data, char * hdr) {
+ V3_Print("%s Guest Page faults: %d\n", hdr, info->shdw_pg_state.guest_faults);
}
+#endif
+
+
+
+int v3_init_shadow_page_state(struct guest_info * info) {
+ struct shadow_page_state * state = &(info->shdw_pg_state);
+ state->guest_cr3 = 0;
+ state->guest_cr0 = 0;
+ state->guest_efer.value = 0x0LL;
-int wholesale_update_shadow_page_state(struct guest_info * guest_info) {
- unsigned i, j;
- pde32_t * guest_pde;
- pde32_t * shadow_pde;
+ INIT_LIST_HEAD(&(state->page_list));
- struct shadow_page_state * state = &(guest_info->shdw_pg_state);
+#ifdef CONFIG_SHADOW_PAGING_TELEMETRY
+ if (info->enable_telemetry) {
+ v3_add_telemetry_cb(info, telemetry_cb, NULL);
+ }
+#endif
+
+ return 0;
+}
- // For now, we'll only work with PDE32
- if (state->guest_mode != PDE32) {
- return -1;
- }
- shadow_pde = (pde32_t *)(CR3_TO_PDE32(state->shadow_cr3.e_reg.low));
+// Reads the guest CR3 register
+// creates new shadow page tables
+// updates the shadow CR3 register to point to the new pts
+int v3_activate_shadow_pt(struct guest_info * info) {
+ switch (v3_get_vm_cpu_mode(info)) {
- if (host_pa_to_host_va(CR3_TO_PDE32(state->guest_cr3.e_reg.low), (addr_t*)&guest_pde) != 0) {
- return -1;
- }
+ case PROTECTED:
+ return activate_shadow_pt_32(info);
+ case PROTECTED_PAE:
+ return activate_shadow_pt_32pae(info);
+ case LONG:
+ case LONG_32_COMPAT:
+ case LONG_16_COMPAT:
+ return activate_shadow_pt_64(info);
+ default:
+ PrintError("Invalid CPU mode: %s\n", v3_cpu_mode_to_str(v3_get_vm_cpu_mode(info)));
+ return -1;
+ }
- // Delete the current page table
- delete_page_tables_pde32(shadow_pde);
+ return 0;
+}
- shadow_pde = os_hooks->allocate_pages(1);
- state->shadow_cr3.e_reg.low = (addr_t)shadow_pde;
- state->shadow_mode = PDE32;
+// This must flush any caches
+// and reset the cr3 value to the correct value
+int v3_invalidate_shadow_pts(struct guest_info * info) {
+ return v3_activate_shadow_pt(info);
+}
- for (i = 0; i < MAX_PDE32_ENTRIES; i++) {
- shadow_pde[i] = guest_pde[i];
- // The shadow can be identical to the guest if it's not present
- if (!shadow_pde[i].present) {
- continue;
+int v3_handle_shadow_pagefault(struct guest_info * info, addr_t fault_addr, pf_error_t error_code) {
+
+ if (v3_get_vm_mem_mode(info) == PHYSICAL_MEM) {
+ // If paging is not turned on we need to handle the special cases
+ return v3_handle_passthrough_pagefault(info, fault_addr, error_code);
+ } else if (v3_get_vm_mem_mode(info) == VIRTUAL_MEM) {
+
+ switch (v3_get_vm_cpu_mode(info)) {
+ case PROTECTED:
+ return handle_shadow_pagefault_32(info, fault_addr, error_code);
+ break;
+ case PROTECTED_PAE:
+ return handle_shadow_pagefault_32pae(info, fault_addr, error_code);
+ case LONG:
+ case LONG_32_COMPAT:
+ case LONG_16_COMPAT:
+ return handle_shadow_pagefault_64(info, fault_addr, error_code);
+ break;
+ default:
+ PrintError("Unhandled CPU Mode: %s\n", v3_cpu_mode_to_str(v3_get_vm_cpu_mode(info)));
+ return -1;
+ }
+ } else {
+ PrintError("Invalid Memory mode\n");
+ return -1;
}
+}
+
+
+int v3_handle_shadow_invlpg(struct guest_info * info) {
+ uchar_t instr[15];
+ struct x86_instr dec_instr;
+ int ret = 0;
+ addr_t vaddr = 0;
- if (shadow_pde[i].large_pages) {
- // large page - just map it through shadow map to generate its physical location
- addr_t guest_addr = PAGE_ADDR(shadow_pde[i].pt_base_addr);
- addr_t host_addr;
- shadow_region_t * ent;
-
- ent = get_shadow_region_by_addr(&(guest_info->mem_map), guest_addr);
-
- if (!ent) {
- // FIXME Panic here - guest is trying to map to physical memory
- // it does not own in any way!
+ if (v3_get_vm_mem_mode(info) != VIRTUAL_MEM) {
+ // Paging must be turned on...
+ // should handle with some sort of fault I think
+ PrintError("ERROR: INVLPG called in non paged mode\n");
return -1;
- }
-
- // FIXME Bounds check here to see if it's trying to trick us
-
- switch (ent->host_type) {
- case HOST_REGION_PHYSICAL_MEMORY:
- // points into currently allocated physical memory, so we just
- // set up the shadow to point to the mapped location
- if (guest_pa_to_host_pa(guest_info, guest_addr, &host_addr)) {
- // Panic here
- return -1;
- }
+ }
+
+ if (v3_get_vm_mem_mode(info) == PHYSICAL_MEM) {
+ ret = read_guest_pa_memory(info, get_addr_linear(info, info->rip, &(info->segments.cs)), 15, instr);
+ } else {
+ ret = read_guest_va_memory(info, get_addr_linear(info, info->rip, &(info->segments.cs)), 15, instr);
+ }
- shadow_pde[i].pt_base_addr = PAGE_ALIGNED_ADDR(host_addr);
- // FIXME set vmm_info bits here
- break;
- case HOST_REGION_UNALLOCATED:
- // points to physical memory that is *allowed* but that we
- // have not yet allocated. We mark as not present and set a
- // bit to remind us to allocate it later
- shadow_pde[i].present = 0;
- // FIXME Set vminfo bits here so that we know that we will be
- // allocating it later
- break;
- case HOST_REGION_NOTHING:
- // points to physical memory that is NOT ALLOWED.
- // We will mark it as not present and set a bit to remind
- // us that it's bad later and insert a GPF then
- shadow_pde[i].present = 0;
- break;
- case HOST_REGION_MEMORY_MAPPED_DEVICE:
- case HOST_REGION_REMOTE:
- case HOST_REGION_SWAPPED:
- default:
- // Panic. Currently unhandled
+ if (ret == -1) {
+ PrintError("Could not read instruction into buffer\n");
return -1;
- break;
- }
- } else {
- pte32_t * guest_pte;
- pte32_t * shadow_pte;
- addr_t guest_addr;
- addr_t guest_pte_host_addr;
- shadow_region_t * ent;
+ }
- // small page - set PDE and follow down to the child table
- shadow_pde[i] = guest_pde[i];
+ if (v3_decode(info, (addr_t)instr, &dec_instr) == -1) {
+ PrintError("Decoding Error\n");
+ return -1;
+ }
+
+ if ((dec_instr.op_type != V3_OP_INVLPG) ||
+ (dec_instr.num_operands != 1) ||
+ (dec_instr.dst_operand.type != MEM_OPERAND)) {
+ PrintError("Decoder Error: Not a valid INVLPG instruction...\n");
+ return -1;
+ }
- guest_addr = PAGE_ADDR(guest_pde[i].pt_base_addr);
+ vaddr = dec_instr.dst_operand.operand;
- // Allocate a new second level page table for the shadow
- shadow_pte = os_hooks->allocate_pages(1);
+ info->rip += dec_instr.instr_length;
- // make our first level page table in the shadow point to it
- shadow_pde[i].pt_base_addr = PAGE_ALIGNED_ADDR(shadow_pte);
-
- ent = get_shadow_region_by_addr(&(guest_info->mem_map), guest_addr);
-
+ switch (v3_get_vm_cpu_mode(info)) {
+ case PROTECTED:
+ return handle_shadow_invlpg_32(info, vaddr);
+ case PROTECTED_PAE:
+ return handle_shadow_invlpg_32pae(info, vaddr);
+ case LONG:
+ case LONG_32_COMPAT:
+ case LONG_16_COMPAT:
+ return handle_shadow_invlpg_64(info, vaddr);
+ default:
+ PrintError("Invalid CPU mode: %s\n", v3_cpu_mode_to_str(v3_get_vm_cpu_mode(info)));
+ return -1;
+ }
+}
- /* JRL: This is bad.... */
- // For now the guest Page Table must always be mapped to host physical memory
- /* If we swap out a page table or if it isn't present for some reason, this turns real ugly */
- if ((!ent) || (ent->host_type != HOST_REGION_PHYSICAL_MEMORY)) {
- // FIXME Panic here - guest is trying to map to physical memory
- // it does not own in any way!
- return -1;
- }
- // Address of the relevant second level page table in the guest
- if (guest_pa_to_host_pa(guest_info, guest_addr, &guest_pte_host_addr)) {
- // Panic here
- return -1;
- }
+static struct shadow_page_data * create_new_shadow_pt(struct guest_info * info) {
+ struct shadow_page_state * state = &(info->shdw_pg_state);
+ v3_reg_t cur_cr3 = info->ctrl_regs.cr3;
+ struct shadow_page_data * page_tail = NULL;
+ addr_t shdw_page = 0;
- // host_addr now contains the host physical address for the guest's 2nd level page table
- // Now we transform it to relevant virtual address
- guest_pte = os_hooks->paddr_to_vaddr((void *)guest_pte_host_addr);
+ if (!list_empty(&(state->page_list))) {
+ page_tail = list_tail_entry(&(state->page_list), struct shadow_page_data, page_list_node);
+
+ if (page_tail->cr3 != cur_cr3) {
+ PrintDebug("Reusing old shadow Page: %p (cur_CR3=%p)(page_cr3=%p) \n",
+ (void *)(addr_t)page_tail->page_pa,
+ (void *)(addr_t)cur_cr3,
+ (void *)(addr_t)(page_tail->cr3));
- // Now we walk through the second level guest page table
- // and clone it into the shadow
- for (j = 0; j < MAX_PTE32_ENTRIES; j++) {
- shadow_pte[j] = guest_pte[j];
+ list_move(&(page_tail->page_list_node), &(state->page_list));
- addr_t guest_addr = PAGE_ADDR(shadow_pte[j].page_base_addr);
-
- shadow_region_t * ent;
-
- ent = get_shadow_region_by_addr(&(guest_info->mem_map), guest_addr);
-
- if (!ent) {
- // FIXME Panic here - guest is trying to map to physical memory
- // it does not own in any way!
- return -1;
+ memset(V3_VAddr((void *)(page_tail->page_pa)), 0, PAGE_SIZE_4KB);
+
+
+ return page_tail;
+ }
+ }
+
+ // else
+
+ page_tail = (struct shadow_page_data *)V3_Malloc(sizeof(struct shadow_page_data));
+ page_tail->page_pa = (addr_t)V3_AllocPages(1);
+
+ PrintDebug("Allocating new shadow Page: %p (cur_cr3=%p)\n",
+ (void *)(addr_t)page_tail->page_pa,
+ (void *)(addr_t)cur_cr3);
+
+ page_tail->cr3 = cur_cr3;
+ list_add(&(page_tail->page_list_node), &(state->page_list));
+
+ shdw_page = (addr_t)V3_VAddr((void *)(page_tail->page_pa));
+ memset((void *)shdw_page, 0, PAGE_SIZE_4KB);
+
+ return page_tail;
+}
+
+
+static int inject_guest_pf(struct guest_info * info, addr_t fault_addr, pf_error_t error_code) {
+ info->ctrl_regs.cr2 = fault_addr;
+
+#ifdef CONFIG_SHADOW_PAGING_TELEMETRY
+ info->shdw_pg_state.guest_faults++;
+#endif
+
+ return v3_raise_exception_with_error(info, PF_EXCEPTION, *(uint_t *)&error_code);
+}
+
+
+static int is_guest_pf(pt_access_status_t guest_access, pt_access_status_t shadow_access) {
+ /* basically the reasoning is that there can be multiple reasons for a page fault:
+ If there is a permissions failure for a page present in the guest _BUT_
+ the reason for the fault was that the page is not present in the shadow,
+ _THEN_ we have to map the shadow page in and reexecute, this will generate
+ a permissions fault which is _THEN_ valid to send to the guest
+ _UNLESS_ both the guest and shadow have marked the page as not present
+
+ whew...
+ */
+ if (guest_access != PT_ACCESS_OK) {
+ // Guest Access Error
+
+ if ((shadow_access != PT_ACCESS_NOT_PRESENT) &&
+ (guest_access != PT_ACCESS_NOT_PRESENT)) {
+ // aka (guest permission error)
+ return 1;
}
- switch (ent->host_type) {
- case HOST_REGION_PHYSICAL_MEMORY:
- {
- addr_t host_addr;
-
- // points into currently allocated physical memory, so we just
- // set up the shadow to point to the mapped location
- if (guest_pa_to_host_pa(guest_info, guest_addr, &host_addr)) {
- // Panic here
- return -1;
- }
-
- shadow_pte[j].page_base_addr = PAGE_ALIGNED_ADDR(host_addr);
- // FIXME set vmm_info bits here
- break;
+ /*
+ if ((shadow_access == PT_ACCESS_NOT_PRESENT) &&
+ (guest_access == PT_ACCESS_NOT_PRESENT)) {
+ // Page tables completely blank, handle guest first
+ return 1;
}
- case HOST_REGION_UNALLOCATED:
- // points to physical memory that is *allowed* but that we
- // have not yet allocated. We mark as not present and set a
- // bit to remind us to allocate it later
- shadow_pte[j].present = 0;
- // FIXME Set vminfo bits here so that we know that we will be
- // allocating it later
- break;
- case HOST_REGION_NOTHING:
- // points to physical memory that is NOT ALLOWED.
- // We will mark it as not present and set a bit to remind
- // us that it's bad later and insert a GPF then
- shadow_pte[j].present = 0;
- break;
- case HOST_REGION_MEMORY_MAPPED_DEVICE:
- case HOST_REGION_REMOTE:
- case HOST_REGION_SWAPPED:
- default:
- // Panic. Currently unhandled
- return -1;
- break;
+ */
+
+ if (guest_access == PT_ACCESS_NOT_PRESENT) {
+ // Page tables completely blank, handle guest first
+ return 1;
}
- }
+
+ // Otherwise we'll handle the guest fault later...?
}
- }
- return 0;
+
+ return 0;
}
-
+