| /* |
| * Copyright (C) 2016 Veertu Inc, |
| * Copyright (C) 2017 Google Inc, |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this program; if not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "panic.h" |
| #include "cpu.h" |
| #include "x86.h" |
| #include "x86_mmu.h" |
| #include "vmcs.h" |
| #include "vmx.h" |
| |
| #define pte_present(pte) (pte & PT_PRESENT) |
| #define pte_write_access(pte) (pte & PT_WRITE) |
| #define pte_user_access(pte) (pte & PT_USER) |
| #define pte_exec_access(pte) (!(pte & PT_NX)) |
| |
| #define pte_large_page(pte) (pte & PT_PS) |
| #define pte_global_access(pte) (pte & PT_GLOBAL) |
| |
| #define PAE_CR3_MASK (~0x1fllu) |
| #define LEGACY_CR3_MASK (0xffffffff) |
| |
| #define LEGACY_PTE_PAGE_MASK (0xffffffffllu << 12) |
| #define PAE_PTE_PAGE_MASK ((-1llu << 12) & ((1llu << 52) - 1)) |
| #define PAE_PTE_LARGE_PAGE_MASK ((-1llu << (21)) & ((1llu << 52) - 1)) |
| |
| struct gpt_translation { |
| target_ulong gva; |
| uint64_t gpa; |
| int err_code; |
| uint64_t pte[5]; |
| bool write_access; |
| bool user_access; |
| bool exec_access; |
| }; |
| |
| static int gpt_top_level(struct CPUState *cpu, bool pae) |
| { |
| if (!pae) { |
| return 2; |
| } |
| if (x86_is_long_mode(cpu)) { |
| return 4; |
| } |
| |
| return 3; |
| } |
| |
| static inline int gpt_entry(target_ulong addr, int level, bool pae) |
| { |
| int level_shift = pae ? 9 : 10; |
| return (addr >> (level_shift * (level - 1) + 12)) & ((1 << level_shift) - 1); |
| } |
| |
| static inline int pte_size(bool pae) |
| { |
| return pae ? 8 : 4; |
| } |
| |
| |
| static bool get_pt_entry(struct CPUState *cpu, struct gpt_translation *pt, |
| int level, bool pae) |
| { |
| int index; |
| uint64_t pte = 0; |
| uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; |
| uint64_t gpa = pt->pte[level] & page_mask; |
| |
| if (level == 3 && !x86_is_long_mode(cpu)) { |
| gpa = pt->pte[level]; |
| } |
| |
| index = gpt_entry(pt->gva, level, pae); |
| address_space_read(&address_space_memory, gpa + index * pte_size(pae), |
| MEMTXATTRS_UNSPECIFIED, &pte, pte_size(pae)); |
| |
| pt->pte[level - 1] = pte; |
| |
| return true; |
| } |
| |
| /* test page table entry */ |
| static bool test_pt_entry(struct CPUState *cpu, struct gpt_translation *pt, |
| int level, bool *is_large, bool pae) |
| { |
| uint64_t pte = pt->pte[level]; |
| |
| if (pt->write_access) { |
| pt->err_code |= MMU_PAGE_WT; |
| } |
| if (pt->user_access) { |
| pt->err_code |= MMU_PAGE_US; |
| } |
| if (pt->exec_access) { |
| pt->err_code |= MMU_PAGE_NX; |
| } |
| |
| if (!pte_present(pte)) { |
| return false; |
| } |
| |
| if (pae && !x86_is_long_mode(cpu) && 2 == level) { |
| goto exit; |
| } |
| |
| if (1 == level && pte_large_page(pte)) { |
| pt->err_code |= MMU_PAGE_PT; |
| *is_large = true; |
| } |
| if (!level) { |
| pt->err_code |= MMU_PAGE_PT; |
| } |
| |
| uint32_t cr0 = rvmcs(cpu->hvf->fd, VMCS_GUEST_CR0); |
| /* check protection */ |
| if (cr0 & CR0_WP_MASK) { |
| if (pt->write_access && !pte_write_access(pte)) { |
| return false; |
| } |
| } |
| |
| if (pt->user_access && !pte_user_access(pte)) { |
| return false; |
| } |
| |
| if (pae && pt->exec_access && !pte_exec_access(pte)) { |
| return false; |
| } |
| |
| exit: |
| /* TODO: check reserved bits */ |
| return true; |
| } |
| |
| static inline uint64_t pse_pte_to_page(uint64_t pte) |
| { |
| return ((pte & 0x1fe000) << 19) | (pte & 0xffc00000); |
| } |
| |
| static inline uint64_t large_page_gpa(struct gpt_translation *pt, bool pae) |
| { |
| VM_PANIC_ON(!pte_large_page(pt->pte[1])) |
| /* 2Mb large page */ |
| if (pae) { |
| return (pt->pte[1] & PAE_PTE_LARGE_PAGE_MASK) | (pt->gva & 0x1fffff); |
| } |
| |
| /* 4Mb large page */ |
| return pse_pte_to_page(pt->pte[1]) | (pt->gva & 0x3fffff); |
| } |
| |
| |
| |
| static bool walk_gpt(struct CPUState *cpu, target_ulong addr, int err_code, |
| struct gpt_translation *pt, bool pae) |
| { |
| int top_level, level; |
| bool is_large = false; |
| target_ulong cr3 = rvmcs(cpu->hvf->fd, VMCS_GUEST_CR3); |
| uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; |
| |
| memset(pt, 0, sizeof(*pt)); |
| top_level = gpt_top_level(cpu, pae); |
| |
| pt->pte[top_level] = pae ? (cr3 & PAE_CR3_MASK) : (cr3 & LEGACY_CR3_MASK); |
| pt->gva = addr; |
| pt->user_access = (err_code & MMU_PAGE_US); |
| pt->write_access = (err_code & MMU_PAGE_WT); |
| pt->exec_access = (err_code & MMU_PAGE_NX); |
| |
| for (level = top_level; level > 0; level--) { |
| get_pt_entry(cpu, pt, level, pae); |
| |
| if (!test_pt_entry(cpu, pt, level - 1, &is_large, pae)) { |
| return false; |
| } |
| |
| if (is_large) { |
| break; |
| } |
| } |
| |
| if (!is_large) { |
| pt->gpa = (pt->pte[0] & page_mask) | (pt->gva & 0xfff); |
| } else { |
| pt->gpa = large_page_gpa(pt, pae); |
| } |
| |
| return true; |
| } |
| |
| |
| bool mmu_gva_to_gpa(struct CPUState *cpu, target_ulong gva, uint64_t *gpa) |
| { |
| bool res; |
| struct gpt_translation pt; |
| int err_code = 0; |
| |
| if (!x86_is_paging_mode(cpu)) { |
| *gpa = gva; |
| return true; |
| } |
| |
| res = walk_gpt(cpu, gva, err_code, &pt, x86_is_pae_enabled(cpu)); |
| if (res) { |
| *gpa = pt.gpa; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void vmx_write_mem(struct CPUState *cpu, target_ulong gva, void *data, int bytes) |
| { |
| uint64_t gpa; |
| |
| while (bytes > 0) { |
| /* copy page */ |
| int copy = MIN(bytes, 0x1000 - (gva & 0xfff)); |
| |
| if (!mmu_gva_to_gpa(cpu, gva, &gpa)) { |
| VM_PANIC_EX("%s: mmu_gva_to_gpa %llx failed\n", __func__, gva); |
| } else { |
| address_space_write(&address_space_memory, gpa, |
| MEMTXATTRS_UNSPECIFIED, data, copy); |
| } |
| |
| bytes -= copy; |
| gva += copy; |
| data += copy; |
| } |
| } |
| |
| void vmx_read_mem(struct CPUState *cpu, void *data, target_ulong gva, int bytes) |
| { |
| uint64_t gpa; |
| |
| while (bytes > 0) { |
| /* copy page */ |
| int copy = MIN(bytes, 0x1000 - (gva & 0xfff)); |
| |
| if (!mmu_gva_to_gpa(cpu, gva, &gpa)) { |
| VM_PANIC_EX("%s: mmu_gva_to_gpa %llx failed\n", __func__, gva); |
| } |
| address_space_read(&address_space_memory, gpa, MEMTXATTRS_UNSPECIFIED, |
| data, copy); |
| |
| bytes -= copy; |
| gva += copy; |
| data += copy; |
| } |
| } |