| 1 | /* |
| 2 | * QEMU MSHV support |
| 3 | * |
| 4 | * Copyright Microsoft, Corp. 2025 |
| 5 | * |
| 6 | * Authors: Magnus Kulke <magnuskulke@microsoft.com> |
| 7 | * |
| 8 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 9 | */ |
| 10 | |
| 11 | #include "qemu/osdep.h" |
| 12 | |
| 13 | #include "cpu.h" |
| 14 | #include "emulate/x86_decode.h" |
| 15 | #include "emulate/x86_emu.h" |
| 16 | #include "emulate/x86_mmu.h" |
| 17 | #include "qemu/error-report.h" |
| 18 | #include "system/mshv.h" |
| 19 | |
| 20 | /* RW or Exec segment */ |
| 21 | static const uint8_t RWRX_SEGMENT_TYPE = 0x2; |
| 22 | static const uint8_t CODE_SEGMENT_TYPE = 0x8; |
| 23 | static const uint8_t EXPAND_DOWN_SEGMENT_TYPE = 0x4; |
| 24 | |
| 25 | typedef enum CpuMode { |
| 26 | REAL_MODE, |
| 27 | PROTECTED_MODE, |
| 28 | LONG_MODE, |
| 29 | } CpuMode; |
| 30 | |
| 31 | static CpuMode cpu_mode(CPUState *cpu) |
| 32 | { |
| 33 | enum CpuMode m = REAL_MODE; |
| 34 | |
| 35 | if (x86_is_protected(cpu)) { |
| 36 | m = PROTECTED_MODE; |
| 37 | |
| 38 | if (x86_is_long_mode(cpu)) { |
| 39 | m = LONG_MODE; |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | return m; |
| 44 | } |
| 45 | |
| 46 | static bool segment_type_ro(const x86_segment_descriptor desc) |
| 47 | { |
| 48 | uint32_t type_ = desc.type; |
| 49 | return (type_ & (~RWRX_SEGMENT_TYPE)) == 0; |
| 50 | } |
| 51 | |
| 52 | static bool segment_type_code(const x86_segment_descriptor desc) |
| 53 | { |
| 54 | uint32_t type_ = desc.type; |
| 55 | return (type_ & CODE_SEGMENT_TYPE) != 0; |
| 56 | } |
| 57 | |
| 58 | static bool segment_expands_down(const x86_segment_descriptor desc) |
| 59 | { |
| 60 | uint32_t type_ = desc.type; |
| 61 | |
| 62 | if (segment_type_code(desc)) { |
| 63 | return false; |
| 64 | } |
| 65 | |
| 66 | return (type_ & EXPAND_DOWN_SEGMENT_TYPE) != 0; |
| 67 | } |
| 68 | |
| 69 | static uint8_t segment_db(const x86_segment_descriptor desc) |
| 70 | { |
| 71 | return desc.db; |
| 72 | } |
| 73 | |
| 74 | static uint32_t segment_max_limit(const x86_segment_descriptor desc) |
| 75 | { |
| 76 | if (segment_db(desc) != 0) { |
| 77 | return 0xFFFFFFFF; |
| 78 | } |
| 79 | return 0xFFFF; |
| 80 | } |
| 81 | |
| 82 | static int linearize(CPUState *cpu, |
| 83 | target_ulong logical_addr, target_ulong *linear_addr, |
| 84 | X86Seg seg_idx) |
| 85 | { |
| 86 | enum CpuMode mode; |
| 87 | struct x86_segment_descriptor desc; |
| 88 | target_ulong base; |
| 89 | target_ulong logical_addr_32b; |
| 90 | uint32_t limit; |
| 91 | /* TODO: the emulator will not pass us "write" indicator yet */ |
| 92 | bool write = false; |
| 93 | |
| 94 | emul_ops->read_segment_descriptor(cpu, &desc, seg_idx); |
| 95 | base = x86_segment_base(&desc); |
| 96 | mode = cpu_mode(cpu); |
| 97 | |
| 98 | switch (mode) { |
| 99 | case LONG_MODE: |
| 100 | if (__builtin_add_overflow(logical_addr, base, linear_addr)) { |
| 101 | error_report("Address overflow"); |
| 102 | return -1; |
| 103 | } |
| 104 | break; |
| 105 | case PROTECTED_MODE: |
| 106 | case REAL_MODE: |
| 107 | if (segment_type_ro(desc) && write) { |
| 108 | error_report("Cannot write to read-only segment"); |
| 109 | return -1; |
| 110 | } |
| 111 | |
| 112 | logical_addr_32b = logical_addr & 0xFFFFFFFF; |
| 113 | limit = x86_segment_limit(&desc); |
| 114 | |
| 115 | if (segment_expands_down(desc)) { |
| 116 | if (logical_addr_32b >= limit) { |
| 117 | error_report("Address exceeds limit (expands down)"); |
| 118 | return -1; |
| 119 | } |
| 120 | |
| 121 | limit = segment_max_limit(desc); |
| 122 | } |
| 123 | |
| 124 | if (logical_addr_32b > limit) { |
| 125 | error_report("Address exceeds limit %u", limit); |
| 126 | return -1; |
| 127 | } |
| 128 | *linear_addr = logical_addr_32b + base; |
| 129 | break; |
| 130 | default: |
| 131 | error_report("Unknown cpu mode: %d", mode); |
| 132 | return -1; |
| 133 | } |
| 134 | |
| 135 | return 0; |
| 136 | } |
| 137 | |
| 138 | bool x86_read_segment_descriptor(CPUState *cpu, |
| 139 | struct x86_segment_descriptor *desc, |
| 140 | x86_segment_selector sel) |
| 141 | { |
| 142 | target_ulong base; |
| 143 | uint32_t limit; |
| 144 | X86CPU *x86_cpu = X86_CPU(cpu); |
| 145 | CPUX86State *env = &x86_cpu->env; |
| 146 | target_ulong gva; |
| 147 | |
| 148 | memset(desc, 0, sizeof(*desc)); |
| 149 | |
| 150 | /* valid gdt descriptors start from index 1 */ |
| 151 | if (!sel.index && GDT_SEL == sel.ti) { |
| 152 | return false; |
| 153 | } |
| 154 | |
| 155 | if (GDT_SEL == sel.ti) { |
| 156 | base = env->gdt.base; |
| 157 | limit = env->gdt.limit; |
| 158 | } else { |
| 159 | base = env->ldt.base; |
| 160 | limit = env->ldt.limit; |
| 161 | } |
| 162 | |
| 163 | if (sel.index * 8 >= limit) { |
| 164 | return false; |
| 165 | } |
| 166 | |
| 167 | gva = base + sel.index * 8; |
| 168 | x86_read_mem_priv(cpu, desc, gva, sizeof(*desc)); |
| 169 | |
| 170 | return true; |
| 171 | } |
| 172 | |
| 173 | bool x86_read_call_gate(CPUState *cpu, struct x86_call_gate *idt_desc, |
| 174 | int gate) |
| 175 | { |
| 176 | target_ulong base; |
| 177 | uint32_t limit; |
| 178 | X86CPU *x86_cpu = X86_CPU(cpu); |
| 179 | CPUX86State *env = &x86_cpu->env; |
| 180 | target_ulong gva; |
| 181 | |
| 182 | base = env->idt.base; |
| 183 | limit = env->idt.limit; |
| 184 | |
| 185 | memset(idt_desc, 0, sizeof(*idt_desc)); |
| 186 | if (gate * 8 >= limit) { |
| 187 | perror("call gate exceeds idt limit"); |
| 188 | return false; |
| 189 | } |
| 190 | |
| 191 | gva = base + gate * 8; |
| 192 | x86_read_mem_priv(cpu, idt_desc, gva, sizeof(*idt_desc)); |
| 193 | |
| 194 | return true; |
| 195 | } |
| 196 | |
| 197 | target_ulong x86_read_cr(CPUState *cpu, int cr) |
| 198 | { |
| 199 | X86CPU *x86_cpu = X86_CPU(cpu); |
| 200 | CPUX86State *env = &x86_cpu->env; |
| 201 | |
| 202 | if (emul_ops->read_cr) { |
| 203 | return emul_ops->read_cr(cpu, cr); |
| 204 | } |
| 205 | return env->cr[cr]; |
| 206 | } |
| 207 | |
| 208 | bool x86_is_protected(CPUState *cpu) |
| 209 | { |
| 210 | uint64_t cr0; |
| 211 | |
| 212 | if (emul_ops->is_protected_mode) { |
| 213 | return emul_ops->is_protected_mode(cpu); |
| 214 | } |
| 215 | |
| 216 | cr0 = x86_read_cr(cpu, 0); |
| 217 | return cr0 & CR0_PE_MASK; |
| 218 | } |
| 219 | |
| 220 | bool x86_is_real(CPUState *cpu) |
| 221 | { |
| 222 | return !x86_is_protected(cpu); |
| 223 | } |
| 224 | |
| 225 | bool x86_is_v8086(CPUState *cpu) |
| 226 | { |
| 227 | X86CPU *x86_cpu = X86_CPU(cpu); |
| 228 | CPUX86State *env = &x86_cpu->env; |
| 229 | return x86_is_protected(cpu) && (env->eflags & VM_MASK); |
| 230 | } |
| 231 | |
| 232 | bool x86_is_long_mode(CPUState *cpu) |
| 233 | { |
| 234 | X86CPU *x86_cpu = X86_CPU(cpu); |
| 235 | CPUX86State *env = &x86_cpu->env; |
| 236 | uint64_t efer = env->efer; |
| 237 | uint64_t lme_lma = (MSR_EFER_LME | MSR_EFER_LMA); |
| 238 | |
| 239 | if (emul_ops->is_long_mode) { |
| 240 | return emul_ops->is_long_mode(cpu); |
| 241 | } |
| 242 | return ((efer & lme_lma) == lme_lma); |
| 243 | } |
| 244 | |
| 245 | bool x86_is_la57(CPUState *cpu) |
| 246 | { |
| 247 | uint64_t is_la57 = x86_read_cr(cpu, 4) & CR4_LA57_MASK; |
| 248 | return is_la57; |
| 249 | } |
| 250 | |
| 251 | bool x86_is_long64_mode(CPUState *cpu) |
| 252 | { |
| 253 | error_report("unimplemented: is_long64_mode()"); |
| 254 | abort(); |
| 255 | } |
| 256 | |
| 257 | bool x86_is_paging_mode(CPUState *cpu) |
| 258 | { |
| 259 | uint64_t cr0 = x86_read_cr(cpu, 0); |
| 260 | |
| 261 | return cr0 & CR0_PG_MASK; |
| 262 | } |
| 263 | |
| 264 | bool x86_is_pae_enabled(CPUState *cpu) |
| 265 | { |
| 266 | uint64_t cr4 = x86_read_cr(cpu, 4); |
| 267 | |
| 268 | return cr4 & CR4_PAE_MASK; |
| 269 | } |
| 270 | |
| 271 | target_ulong linear_addr(CPUState *cpu, target_ulong addr, X86Seg seg) |
| 272 | { |
| 273 | int ret; |
| 274 | target_ulong linear_addr; |
| 275 | |
| 276 | ret = linearize(cpu, addr, &linear_addr, seg); |
| 277 | if (ret < 0) { |
| 278 | error_report("failed to linearize address"); |
| 279 | abort(); |
| 280 | } |
| 281 | |
| 282 | return linear_addr; |
| 283 | } |
| 284 | |
| 285 | target_ulong linear_addr_size(CPUState *cpu, target_ulong addr, int size, |
| 286 | X86Seg seg) |
| 287 | { |
| 288 | switch (size) { |
| 289 | case 2: |
| 290 | addr = (uint16_t)addr; |
| 291 | break; |
| 292 | case 4: |
| 293 | addr = (uint32_t)addr; |
| 294 | break; |
| 295 | default: |
| 296 | break; |
| 297 | } |
| 298 | return linear_addr(cpu, addr, seg); |
| 299 | } |
| 300 | |
| 301 | target_ulong linear_rip(CPUState *cpu, target_ulong rip) |
| 302 | { |
| 303 | return linear_addr(cpu, rip, R_CS); |
| 304 | } |