]>
Commit | Line | Data |
---|---|---|
c97d6d2c SAGDR |
1 | /* |
2 | * Copyright (C) 2016 Veertu Inc, | |
3 | * Copyright (C) 2017 Google Inc, | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or | |
996feed4 SAGDR |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either | |
8 | * version 2 of the License, or (at your option) any later version. | |
c97d6d2c SAGDR |
9 | * |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
996feed4 SAGDR |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. | |
c97d6d2c | 14 | * |
996feed4 SAGDR |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this program; if not, see <http://www.gnu.org/licenses/>. | |
c97d6d2c | 17 | */ |
d8e39b70 | 18 | |
c97d6d2c | 19 | #include "qemu/osdep.h" |
895f9fdf | 20 | #include "panic.h" |
c97d6d2c | 21 | #include "qemu-common.h" |
ff2de166 | 22 | #include "cpu.h" |
c97d6d2c SAGDR |
23 | #include "x86.h" |
24 | #include "x86_mmu.h" | |
c97d6d2c SAGDR |
25 | #include "vmcs.h" |
26 | #include "vmx.h" | |
c97d6d2c SAGDR |
27 | #include "exec/address-spaces.h" |
28 | ||
29 | #define pte_present(pte) (pte & PT_PRESENT) | |
30 | #define pte_write_access(pte) (pte & PT_WRITE) | |
31 | #define pte_user_access(pte) (pte & PT_USER) | |
32 | #define pte_exec_access(pte) (!(pte & PT_NX)) | |
33 | ||
34 | #define pte_large_page(pte) (pte & PT_PS) | |
35 | #define pte_global_access(pte) (pte & PT_GLOBAL) | |
36 | ||
37 | #define PAE_CR3_MASK (~0x1fllu) | |
38 | #define LEGACY_CR3_MASK (0xffffffff) | |
39 | ||
40 | #define LEGACY_PTE_PAGE_MASK (0xffffffffllu << 12) | |
41 | #define PAE_PTE_PAGE_MASK ((-1llu << 12) & ((1llu << 52) - 1)) | |
42 | #define PAE_PTE_LARGE_PAGE_MASK ((-1llu << (21)) & ((1llu << 52) - 1)) | |
43 | ||
44 | struct gpt_translation { | |
ff2de166 PB |
45 | target_ulong gva; |
46 | uint64_t gpa; | |
c97d6d2c SAGDR |
47 | int err_code; |
48 | uint64_t pte[5]; | |
49 | bool write_access; | |
50 | bool user_access; | |
51 | bool exec_access; | |
52 | }; | |
53 | ||
54 | static int gpt_top_level(struct CPUState *cpu, bool pae) | |
55 | { | |
56 | if (!pae) { | |
57 | return 2; | |
58 | } | |
59 | if (x86_is_long_mode(cpu)) { | |
60 | return 4; | |
61 | } | |
62 | ||
63 | return 3; | |
64 | } | |
65 | ||
ff2de166 | 66 | static inline int gpt_entry(target_ulong addr, int level, bool pae) |
c97d6d2c SAGDR |
67 | { |
68 | int level_shift = pae ? 9 : 10; | |
69 | return (addr >> (level_shift * (level - 1) + 12)) & ((1 << level_shift) - 1); | |
70 | } | |
71 | ||
72 | static inline int pte_size(bool pae) | |
73 | { | |
74 | return pae ? 8 : 4; | |
75 | } | |
76 | ||
77 | ||
78 | static bool get_pt_entry(struct CPUState *cpu, struct gpt_translation *pt, | |
79 | int level, bool pae) | |
80 | { | |
81 | int index; | |
82 | uint64_t pte = 0; | |
ff2de166 PB |
83 | uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; |
84 | uint64_t gpa = pt->pte[level] & page_mask; | |
c97d6d2c SAGDR |
85 | |
86 | if (level == 3 && !x86_is_long_mode(cpu)) { | |
87 | gpa = pt->pte[level]; | |
88 | } | |
89 | ||
90 | index = gpt_entry(pt->gva, level, pae); | |
19f70347 PM |
91 | address_space_read(&address_space_memory, gpa + index * pte_size(pae), |
92 | MEMTXATTRS_UNSPECIFIED, &pte, pte_size(pae)); | |
c97d6d2c SAGDR |
93 | |
94 | pt->pte[level - 1] = pte; | |
95 | ||
96 | return true; | |
97 | } | |
98 | ||
99 | /* test page table entry */ | |
100 | static bool test_pt_entry(struct CPUState *cpu, struct gpt_translation *pt, | |
101 | int level, bool *is_large, bool pae) | |
102 | { | |
103 | uint64_t pte = pt->pte[level]; | |
104 | ||
105 | if (pt->write_access) { | |
106 | pt->err_code |= MMU_PAGE_WT; | |
107 | } | |
108 | if (pt->user_access) { | |
109 | pt->err_code |= MMU_PAGE_US; | |
110 | } | |
111 | if (pt->exec_access) { | |
112 | pt->err_code |= MMU_PAGE_NX; | |
113 | } | |
114 | ||
115 | if (!pte_present(pte)) { | |
c97d6d2c SAGDR |
116 | return false; |
117 | } | |
118 | ||
119 | if (pae && !x86_is_long_mode(cpu) && 2 == level) { | |
120 | goto exit; | |
121 | } | |
122 | ||
123 | if (1 == level && pte_large_page(pte)) { | |
124 | pt->err_code |= MMU_PAGE_PT; | |
125 | *is_large = true; | |
126 | } | |
127 | if (!level) { | |
128 | pt->err_code |= MMU_PAGE_PT; | |
129 | } | |
130 | ||
ff2de166 | 131 | uint32_t cr0 = rvmcs(cpu->hvf_fd, VMCS_GUEST_CR0); |
c97d6d2c SAGDR |
132 | /* check protection */ |
133 | if (cr0 & CR0_WP) { | |
134 | if (pt->write_access && !pte_write_access(pte)) { | |
135 | return false; | |
136 | } | |
137 | } | |
138 | ||
139 | if (pt->user_access && !pte_user_access(pte)) { | |
140 | return false; | |
141 | } | |
142 | ||
143 | if (pae && pt->exec_access && !pte_exec_access(pte)) { | |
144 | return false; | |
145 | } | |
146 | ||
147 | exit: | |
148 | /* TODO: check reserved bits */ | |
149 | return true; | |
150 | } | |
151 | ||
152 | static inline uint64_t pse_pte_to_page(uint64_t pte) | |
153 | { | |
154 | return ((pte & 0x1fe000) << 19) | (pte & 0xffc00000); | |
155 | } | |
156 | ||
157 | static inline uint64_t large_page_gpa(struct gpt_translation *pt, bool pae) | |
158 | { | |
159 | VM_PANIC_ON(!pte_large_page(pt->pte[1])) | |
160 | /* 2Mb large page */ | |
161 | if (pae) { | |
162 | return (pt->pte[1] & PAE_PTE_LARGE_PAGE_MASK) | (pt->gva & 0x1fffff); | |
163 | } | |
164 | ||
165 | /* 4Mb large page */ | |
166 | return pse_pte_to_page(pt->pte[1]) | (pt->gva & 0x3fffff); | |
167 | } | |
168 | ||
169 | ||
170 | ||
ff2de166 | 171 | static bool walk_gpt(struct CPUState *cpu, target_ulong addr, int err_code, |
c97d6d2c SAGDR |
172 | struct gpt_translation *pt, bool pae) |
173 | { | |
174 | int top_level, level; | |
175 | bool is_large = false; | |
ff2de166 PB |
176 | target_ulong cr3 = rvmcs(cpu->hvf_fd, VMCS_GUEST_CR3); |
177 | uint64_t page_mask = pae ? PAE_PTE_PAGE_MASK : LEGACY_PTE_PAGE_MASK; | |
c97d6d2c SAGDR |
178 | |
179 | memset(pt, 0, sizeof(*pt)); | |
180 | top_level = gpt_top_level(cpu, pae); | |
181 | ||
182 | pt->pte[top_level] = pae ? (cr3 & PAE_CR3_MASK) : (cr3 & LEGACY_CR3_MASK); | |
183 | pt->gva = addr; | |
184 | pt->user_access = (err_code & MMU_PAGE_US); | |
185 | pt->write_access = (err_code & MMU_PAGE_WT); | |
186 | pt->exec_access = (err_code & MMU_PAGE_NX); | |
187 | ||
188 | for (level = top_level; level > 0; level--) { | |
189 | get_pt_entry(cpu, pt, level, pae); | |
190 | ||
191 | if (!test_pt_entry(cpu, pt, level - 1, &is_large, pae)) { | |
192 | return false; | |
193 | } | |
194 | ||
195 | if (is_large) { | |
196 | break; | |
197 | } | |
198 | } | |
199 | ||
200 | if (!is_large) { | |
201 | pt->gpa = (pt->pte[0] & page_mask) | (pt->gva & 0xfff); | |
202 | } else { | |
203 | pt->gpa = large_page_gpa(pt, pae); | |
204 | } | |
205 | ||
206 | return true; | |
207 | } | |
208 | ||
209 | ||
ff2de166 | 210 | bool mmu_gva_to_gpa(struct CPUState *cpu, target_ulong gva, uint64_t *gpa) |
c97d6d2c SAGDR |
211 | { |
212 | bool res; | |
213 | struct gpt_translation pt; | |
214 | int err_code = 0; | |
215 | ||
216 | if (!x86_is_paging_mode(cpu)) { | |
217 | *gpa = gva; | |
218 | return true; | |
219 | } | |
220 | ||
221 | res = walk_gpt(cpu, gva, err_code, &pt, x86_is_pae_enabled(cpu)); | |
222 | if (res) { | |
223 | *gpa = pt.gpa; | |
224 | return true; | |
225 | } | |
226 | ||
227 | return false; | |
228 | } | |
229 | ||
ff2de166 | 230 | void vmx_write_mem(struct CPUState *cpu, target_ulong gva, void *data, int bytes) |
c97d6d2c | 231 | { |
ff2de166 | 232 | uint64_t gpa; |
c97d6d2c SAGDR |
233 | |
234 | while (bytes > 0) { | |
235 | /* copy page */ | |
236 | int copy = MIN(bytes, 0x1000 - (gva & 0xfff)); | |
237 | ||
238 | if (!mmu_gva_to_gpa(cpu, gva, &gpa)) { | |
74682782 | 239 | VM_PANIC_EX("%s: mmu_gva_to_gpa %llx failed\n", __func__, gva); |
c97d6d2c | 240 | } else { |
19f70347 PM |
241 | address_space_write(&address_space_memory, gpa, |
242 | MEMTXATTRS_UNSPECIFIED, data, copy); | |
c97d6d2c SAGDR |
243 | } |
244 | ||
245 | bytes -= copy; | |
246 | gva += copy; | |
247 | data += copy; | |
248 | } | |
249 | } | |
250 | ||
ff2de166 | 251 | void vmx_read_mem(struct CPUState *cpu, void *data, target_ulong gva, int bytes) |
c97d6d2c | 252 | { |
ff2de166 | 253 | uint64_t gpa; |
c97d6d2c SAGDR |
254 | |
255 | while (bytes > 0) { | |
256 | /* copy page */ | |
257 | int copy = MIN(bytes, 0x1000 - (gva & 0xfff)); | |
258 | ||
259 | if (!mmu_gva_to_gpa(cpu, gva, &gpa)) { | |
74682782 | 260 | VM_PANIC_EX("%s: mmu_gva_to_gpa %llx failed\n", __func__, gva); |
c97d6d2c | 261 | } |
19f70347 PM |
262 | address_space_read(&address_space_memory, gpa, MEMTXATTRS_UNSPECIFIED, |
263 | data, copy); | |
c97d6d2c SAGDR |
264 | |
265 | bytes -= copy; | |
266 | gva += copy; | |
267 | data += copy; | |
268 | } | |
269 | } |