xref: /openbmc/qemu/target/i386/hvf/x86_mmu.c (revision f8436a16054a13eaf969bba9b2733ec43969f952)
169e0a03cSPaolo Bonzini /*
269e0a03cSPaolo Bonzini  * Copyright (C) 2016 Veertu Inc,
369e0a03cSPaolo Bonzini  * Copyright (C) 2017 Google Inc,
469e0a03cSPaolo Bonzini  *
569e0a03cSPaolo Bonzini  * This program is free software; you can redistribute it and/or
669e0a03cSPaolo Bonzini  * modify it under the terms of the GNU Lesser General Public
769e0a03cSPaolo Bonzini  * License as published by the Free Software Foundation; either
88af82b8eSChetan Pant  * version 2.1 of the License, or (at your option) any later version.
969e0a03cSPaolo Bonzini  *
1069e0a03cSPaolo Bonzini  * This program is distributed in the hope that it will be useful,
1169e0a03cSPaolo Bonzini  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1269e0a03cSPaolo Bonzini  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1369e0a03cSPaolo Bonzini  * Lesser General Public License for more details.
1469e0a03cSPaolo Bonzini  *
1569e0a03cSPaolo Bonzini  * You should have received a copy of the GNU Lesser General Public
1669e0a03cSPaolo Bonzini  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
1769e0a03cSPaolo Bonzini  */
1869e0a03cSPaolo Bonzini 
19d8e39b70SMarkus Armbruster #include "qemu/osdep.h"
20d8e39b70SMarkus Armbruster #include "panic.h"
21ff2de166SPaolo Bonzini #include "cpu.h"
2269e0a03cSPaolo Bonzini #include "x86.h"
2369e0a03cSPaolo Bonzini #include "x86_mmu.h"
2469e0a03cSPaolo Bonzini #include "vmcs.h"
2569e0a03cSPaolo Bonzini #include "vmx.h"
2669e0a03cSPaolo Bonzini 
2769e0a03cSPaolo Bonzini #define pte_present(pte) (pte & PT_PRESENT)
2869e0a03cSPaolo Bonzini #define pte_write_access(pte) (pte & PT_WRITE)
2969e0a03cSPaolo Bonzini #define pte_user_access(pte) (pte & PT_USER)
3069e0a03cSPaolo Bonzini #define pte_exec_access(pte) (!(pte & PT_NX))
3169e0a03cSPaolo Bonzini 
3269e0a03cSPaolo Bonzini #define pte_large_page(pte) (pte & PT_PS)
3369e0a03cSPaolo Bonzini #define pte_global_access(pte) (pte & PT_GLOBAL)
3469e0a03cSPaolo Bonzini 
3569e0a03cSPaolo Bonzini #define PAE_CR3_MASK                (~0x1fllu)
3669e0a03cSPaolo Bonzini #define LEGACY_CR3_MASK             (0xffffffff)
3769e0a03cSPaolo Bonzini 
3869e0a03cSPaolo Bonzini #define LEGACY_PTE_PAGE_MASK        (0xffffffffllu << 12)
3969e0a03cSPaolo Bonzini #define PAE_PTE_PAGE_MASK           ((-1llu << 12) & ((1llu << 52) - 1))
4069e0a03cSPaolo Bonzini #define PAE_PTE_LARGE_PAGE_MASK     ((-1llu << (21)) & ((1llu << 52) - 1))
4169e0a03cSPaolo Bonzini 
4269e0a03cSPaolo Bonzini struct gpt_translation {
43ff2de166SPaolo Bonzini     target_ulong  gva;
44ff2de166SPaolo Bonzini     uint64_t gpa;
4569e0a03cSPaolo Bonzini     int    err_code;
4669e0a03cSPaolo Bonzini     uint64_t pte[5];
4769e0a03cSPaolo Bonzini     bool write_access;
4869e0a03cSPaolo Bonzini     bool user_access;
4969e0a03cSPaolo Bonzini     bool exec_access;
5069e0a03cSPaolo Bonzini };
5169e0a03cSPaolo Bonzini 
52*f8436a16SPhilippe Mathieu-Daudé static int gpt_top_level(CPUState *cpu, bool pae)
5369e0a03cSPaolo Bonzini {
5469e0a03cSPaolo Bonzini     if (!pae) {
5569e0a03cSPaolo Bonzini         return 2;
5669e0a03cSPaolo Bonzini     }
5769e0a03cSPaolo Bonzini     if (x86_is_long_mode(cpu)) {
5869e0a03cSPaolo Bonzini         return 4;
5969e0a03cSPaolo Bonzini     }
6069e0a03cSPaolo Bonzini 
6169e0a03cSPaolo Bonzini     return 3;
6269e0a03cSPaolo Bonzini }
6369e0a03cSPaolo Bonzini 
64ff2de166SPaolo Bonzini static inline int gpt_entry(target_ulong addr, int level, bool pae)
6569e0a03cSPaolo Bonzini {
6669e0a03cSPaolo Bonzini     int level_shift = pae ? 9 : 10;
6769e0a03cSPaolo Bonzini     return (addr >> (level_shift * (level - 1) + 12)) & ((1 << level_shift) - 1);
6869e0a03cSPaolo Bonzini }
6969e0a03cSPaolo Bonzini 
7069e0a03cSPaolo Bonzini static inline int pte_size(bool pae)
7169e0a03cSPaolo Bonzini {
7269e0a03cSPaolo Bonzini     return pae ? 8 : 4;
7369e0a03cSPaolo Bonzini }
7469e0a03cSPaolo Bonzini 
7569e0a03cSPaolo Bonzini 
76*f8436a16SPhilippe Mathieu-Daudé static bool get_pt_entry(CPUState *cpu, struct gpt_translation *pt,
7769e0a03cSPaolo Bonzini                          int level, bool pae)
7869e0a03cSPaolo Bonzini {
7969e0a03cSPaolo Bonzini     int index;
8069e0a03cSPaolo Bonzini     uint64_t pte = 0;
81ff2de166SPaolo Bonzini     uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK;
82ff2de166SPaolo Bonzini     uint64_t gpa = pt->pte[level] & page_mask;
8369e0a03cSPaolo Bonzini 
8469e0a03cSPaolo Bonzini     if (level == 3 && !x86_is_long_mode(cpu)) {
8569e0a03cSPaolo Bonzini         gpa = pt->pte[level];
8669e0a03cSPaolo Bonzini     }
8769e0a03cSPaolo Bonzini 
8869e0a03cSPaolo Bonzini     index = gpt_entry(pt->gva, level, pae);
8919f70347SPeter Maydell     address_space_read(&address_space_memory, gpa + index * pte_size(pae),
9019f70347SPeter Maydell                        MEMTXATTRS_UNSPECIFIED, &pte, pte_size(pae));
9169e0a03cSPaolo Bonzini 
9269e0a03cSPaolo Bonzini     pt->pte[level - 1] = pte;
9369e0a03cSPaolo Bonzini 
9469e0a03cSPaolo Bonzini     return true;
9569e0a03cSPaolo Bonzini }
9669e0a03cSPaolo Bonzini 
9769e0a03cSPaolo Bonzini /* test page table entry */
98*f8436a16SPhilippe Mathieu-Daudé static bool test_pt_entry(CPUState *cpu, struct gpt_translation *pt,
9969e0a03cSPaolo Bonzini                           int level, bool *is_large, bool pae)
10069e0a03cSPaolo Bonzini {
10169e0a03cSPaolo Bonzini     uint64_t pte = pt->pte[level];
10269e0a03cSPaolo Bonzini 
10369e0a03cSPaolo Bonzini     if (pt->write_access) {
10469e0a03cSPaolo Bonzini         pt->err_code |= MMU_PAGE_WT;
10569e0a03cSPaolo Bonzini     }
10669e0a03cSPaolo Bonzini     if (pt->user_access) {
10769e0a03cSPaolo Bonzini         pt->err_code |= MMU_PAGE_US;
10869e0a03cSPaolo Bonzini     }
10969e0a03cSPaolo Bonzini     if (pt->exec_access) {
11069e0a03cSPaolo Bonzini         pt->err_code |= MMU_PAGE_NX;
11169e0a03cSPaolo Bonzini     }
11269e0a03cSPaolo Bonzini 
11369e0a03cSPaolo Bonzini     if (!pte_present(pte)) {
11469e0a03cSPaolo Bonzini         return false;
11569e0a03cSPaolo Bonzini     }
11669e0a03cSPaolo Bonzini 
11769e0a03cSPaolo Bonzini     if (pae && !x86_is_long_mode(cpu) && 2 == level) {
11869e0a03cSPaolo Bonzini         goto exit;
11969e0a03cSPaolo Bonzini     }
12069e0a03cSPaolo Bonzini 
12169e0a03cSPaolo Bonzini     if (1 == level && pte_large_page(pte)) {
12269e0a03cSPaolo Bonzini         pt->err_code |= MMU_PAGE_PT;
12369e0a03cSPaolo Bonzini         *is_large = true;
12469e0a03cSPaolo Bonzini     }
12569e0a03cSPaolo Bonzini     if (!level) {
12669e0a03cSPaolo Bonzini         pt->err_code |= MMU_PAGE_PT;
12769e0a03cSPaolo Bonzini     }
12869e0a03cSPaolo Bonzini 
1293b295bcbSPhilippe Mathieu-Daudé     uint32_t cr0 = rvmcs(cpu->accel->fd, VMCS_GUEST_CR0);
13069e0a03cSPaolo Bonzini     /* check protection */
131704afe34SCameron Esfahani     if (cr0 & CR0_WP_MASK) {
13269e0a03cSPaolo Bonzini         if (pt->write_access && !pte_write_access(pte)) {
13369e0a03cSPaolo Bonzini             return false;
13469e0a03cSPaolo Bonzini         }
13569e0a03cSPaolo Bonzini     }
13669e0a03cSPaolo Bonzini 
13769e0a03cSPaolo Bonzini     if (pt->user_access && !pte_user_access(pte)) {
13869e0a03cSPaolo Bonzini         return false;
13969e0a03cSPaolo Bonzini     }
14069e0a03cSPaolo Bonzini 
14169e0a03cSPaolo Bonzini     if (pae && pt->exec_access && !pte_exec_access(pte)) {
14269e0a03cSPaolo Bonzini         return false;
14369e0a03cSPaolo Bonzini     }
14469e0a03cSPaolo Bonzini 
14569e0a03cSPaolo Bonzini exit:
14669e0a03cSPaolo Bonzini     /* TODO: check reserved bits */
14769e0a03cSPaolo Bonzini     return true;
14869e0a03cSPaolo Bonzini }
14969e0a03cSPaolo Bonzini 
15069e0a03cSPaolo Bonzini static inline uint64_t pse_pte_to_page(uint64_t pte)
15169e0a03cSPaolo Bonzini {
15269e0a03cSPaolo Bonzini     return ((pte & 0x1fe000) << 19) | (pte & 0xffc00000);
15369e0a03cSPaolo Bonzini }
15469e0a03cSPaolo Bonzini 
15569e0a03cSPaolo Bonzini static inline uint64_t large_page_gpa(struct gpt_translation *pt, bool pae)
15669e0a03cSPaolo Bonzini {
15769e0a03cSPaolo Bonzini     VM_PANIC_ON(!pte_large_page(pt->pte[1]))
15869e0a03cSPaolo Bonzini     /* 2Mb large page  */
15969e0a03cSPaolo Bonzini     if (pae) {
16069e0a03cSPaolo Bonzini         return (pt->pte[1] & PAE_PTE_LARGE_PAGE_MASK) | (pt->gva & 0x1fffff);
16169e0a03cSPaolo Bonzini     }
16269e0a03cSPaolo Bonzini 
16369e0a03cSPaolo Bonzini     /* 4Mb large page */
16469e0a03cSPaolo Bonzini     return pse_pte_to_page(pt->pte[1]) | (pt->gva & 0x3fffff);
16569e0a03cSPaolo Bonzini }
16669e0a03cSPaolo Bonzini 
16769e0a03cSPaolo Bonzini 
16869e0a03cSPaolo Bonzini 
169*f8436a16SPhilippe Mathieu-Daudé static bool walk_gpt(CPUState *cpu, target_ulong addr, int err_code,
17069e0a03cSPaolo Bonzini                      struct gpt_translation *pt, bool pae)
17169e0a03cSPaolo Bonzini {
17269e0a03cSPaolo Bonzini     int top_level, level;
17369e0a03cSPaolo Bonzini     bool is_large = false;
1743b295bcbSPhilippe Mathieu-Daudé     target_ulong cr3 = rvmcs(cpu->accel->fd, VMCS_GUEST_CR3);
175ff2de166SPaolo Bonzini     uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK;
17669e0a03cSPaolo Bonzini 
17769e0a03cSPaolo Bonzini     memset(pt, 0, sizeof(*pt));
17869e0a03cSPaolo Bonzini     top_level = gpt_top_level(cpu, pae);
17969e0a03cSPaolo Bonzini 
18069e0a03cSPaolo Bonzini     pt->pte[top_level] = pae ? (cr3 & PAE_CR3_MASK) : (cr3 & LEGACY_CR3_MASK);
18169e0a03cSPaolo Bonzini     pt->gva = addr;
18269e0a03cSPaolo Bonzini     pt->user_access = (err_code & MMU_PAGE_US);
18369e0a03cSPaolo Bonzini     pt->write_access = (err_code & MMU_PAGE_WT);
18469e0a03cSPaolo Bonzini     pt->exec_access = (err_code & MMU_PAGE_NX);
18569e0a03cSPaolo Bonzini 
18669e0a03cSPaolo Bonzini     for (level = top_level; level > 0; level--) {
18769e0a03cSPaolo Bonzini         get_pt_entry(cpu, pt, level, pae);
18869e0a03cSPaolo Bonzini 
18969e0a03cSPaolo Bonzini         if (!test_pt_entry(cpu, pt, level - 1, &is_large, pae)) {
19069e0a03cSPaolo Bonzini             return false;
19169e0a03cSPaolo Bonzini         }
19269e0a03cSPaolo Bonzini 
19369e0a03cSPaolo Bonzini         if (is_large) {
19469e0a03cSPaolo Bonzini             break;
19569e0a03cSPaolo Bonzini         }
19669e0a03cSPaolo Bonzini     }
19769e0a03cSPaolo Bonzini 
19869e0a03cSPaolo Bonzini     if (!is_large) {
19969e0a03cSPaolo Bonzini         pt->gpa = (pt->pte[0] & page_mask) | (pt->gva & 0xfff);
20069e0a03cSPaolo Bonzini     } else {
20169e0a03cSPaolo Bonzini         pt->gpa = large_page_gpa(pt, pae);
20269e0a03cSPaolo Bonzini     }
20369e0a03cSPaolo Bonzini 
20469e0a03cSPaolo Bonzini     return true;
20569e0a03cSPaolo Bonzini }
20669e0a03cSPaolo Bonzini 
20769e0a03cSPaolo Bonzini 
208*f8436a16SPhilippe Mathieu-Daudé bool mmu_gva_to_gpa(CPUState *cpu, target_ulong gva, uint64_t *gpa)
20969e0a03cSPaolo Bonzini {
21069e0a03cSPaolo Bonzini     bool res;
21169e0a03cSPaolo Bonzini     struct gpt_translation pt;
21269e0a03cSPaolo Bonzini     int err_code = 0;
21369e0a03cSPaolo Bonzini 
21469e0a03cSPaolo Bonzini     if (!x86_is_paging_mode(cpu)) {
21569e0a03cSPaolo Bonzini         *gpa = gva;
21669e0a03cSPaolo Bonzini         return true;
21769e0a03cSPaolo Bonzini     }
21869e0a03cSPaolo Bonzini 
21969e0a03cSPaolo Bonzini     res = walk_gpt(cpu, gva, err_code, &pt, x86_is_pae_enabled(cpu));
22069e0a03cSPaolo Bonzini     if (res) {
22169e0a03cSPaolo Bonzini         *gpa = pt.gpa;
22269e0a03cSPaolo Bonzini         return true;
22369e0a03cSPaolo Bonzini     }
22469e0a03cSPaolo Bonzini 
22569e0a03cSPaolo Bonzini     return false;
22669e0a03cSPaolo Bonzini }
22769e0a03cSPaolo Bonzini 
228*f8436a16SPhilippe Mathieu-Daudé void vmx_write_mem(CPUState *cpu, target_ulong gva, void *data, int bytes)
22969e0a03cSPaolo Bonzini {
230ff2de166SPaolo Bonzini     uint64_t gpa;
23169e0a03cSPaolo Bonzini 
23269e0a03cSPaolo Bonzini     while (bytes > 0) {
23369e0a03cSPaolo Bonzini         /* copy page */
23469e0a03cSPaolo Bonzini         int copy = MIN(bytes, 0x1000 - (gva & 0xfff));
23569e0a03cSPaolo Bonzini 
23669e0a03cSPaolo Bonzini         if (!mmu_gva_to_gpa(cpu, gva, &gpa)) {
23774682782SPaolo Bonzini             VM_PANIC_EX("%s: mmu_gva_to_gpa %llx failed\n", __func__, gva);
23869e0a03cSPaolo Bonzini         } else {
23919f70347SPeter Maydell             address_space_write(&address_space_memory, gpa,
24019f70347SPeter Maydell                                 MEMTXATTRS_UNSPECIFIED, data, copy);
24169e0a03cSPaolo Bonzini         }
24269e0a03cSPaolo Bonzini 
24369e0a03cSPaolo Bonzini         bytes -= copy;
24469e0a03cSPaolo Bonzini         gva += copy;
24569e0a03cSPaolo Bonzini         data += copy;
24669e0a03cSPaolo Bonzini     }
24769e0a03cSPaolo Bonzini }
24869e0a03cSPaolo Bonzini 
249*f8436a16SPhilippe Mathieu-Daudé void vmx_read_mem(CPUState *cpu, void *data, target_ulong gva, int bytes)
25069e0a03cSPaolo Bonzini {
251ff2de166SPaolo Bonzini     uint64_t gpa;
25269e0a03cSPaolo Bonzini 
25369e0a03cSPaolo Bonzini     while (bytes > 0) {
25469e0a03cSPaolo Bonzini         /* copy page */
25569e0a03cSPaolo Bonzini         int copy = MIN(bytes, 0x1000 - (gva & 0xfff));
25669e0a03cSPaolo Bonzini 
25769e0a03cSPaolo Bonzini         if (!mmu_gva_to_gpa(cpu, gva, &gpa)) {
25874682782SPaolo Bonzini             VM_PANIC_EX("%s: mmu_gva_to_gpa %llx failed\n", __func__, gva);
25969e0a03cSPaolo Bonzini         }
26019f70347SPeter Maydell         address_space_read(&address_space_memory, gpa, MEMTXATTRS_UNSPECIFIED,
26119f70347SPeter Maydell                            data, copy);
26269e0a03cSPaolo Bonzini 
26369e0a03cSPaolo Bonzini         bytes -= copy;
26469e0a03cSPaolo Bonzini         gva += copy;
26569e0a03cSPaolo Bonzini         data += copy;
26669e0a03cSPaolo Bonzini     }
26769e0a03cSPaolo Bonzini }
268