| 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Check for crash when using memory beyond the available guest processor |
| 4 | # address space. |
| 5 | # |
| 6 | # Copyright (c) 2023 Red Hat, Inc. |
| 7 | # |
| 8 | # Author: |
| 9 | # Ani Sinha <anisinha@redhat.com> |
| 10 | # |
| 11 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 12 | |
| 13 | from qemu_test import QemuSystemTest |
| 14 | import time |
| 15 | |
| 16 | class MemAddrCheck(QemuSystemTest): |
| 17 | # after launch, in order to generate the logs from QEMU we need to |
| 18 | # wait for some time. Launching and then immediately shutting down |
| 19 | # the VM generates empty logs. A delay of 1 second is added for |
| 20 | # this reason. |
| 21 | DELAY_Q35_BOOT_SEQUENCE = 1 |
| 22 | |
| 23 | # This helper can go away when the 32-bit host deprecation |
| 24 | # turns into full & final removal of support. |
| 25 | def ensure_64bit_binary(self): |
| 26 | with open(self.qemu_bin, "rb") as fh: |
| 27 | ident = fh.read(4) |
| 28 | |
| 29 | # "\x7fELF" |
| 30 | if ident != bytes([0x7f, 0x45, 0x4C, 0x46]): |
| 31 | # Non-ELF file implies macOS or Windows which |
| 32 | # we already assume to be 64-bit only |
| 33 | return |
| 34 | |
| 35 | # bits == 1 -> 32-bit; bits == 2 -> 64-bit |
| 36 | bits = int.from_bytes(fh.read(1), byteorder='little') |
| 37 | if bits != 2: |
| 38 | # 32-bit ELF builds won't be able to address sufficient |
| 39 | # RAM to run the tests |
| 40 | self.skipTest("64-bit build host is required") |
| 41 | |
| 42 | # first, lets test some 32-bit processors. |
| 43 | # for all 32-bit cases, pci64_hole_size is 0. |
| 44 | def test_phybits_low_pse36(self): |
| 45 | """ |
| 46 | With pse36 feature ON, a processor has 36 bits of addressing. So it can |
| 47 | access up to a maximum of 64GiB of memory. Memory hotplug region begins |
| 48 | at 4 GiB boundary when "above_4g_mem_size" is 0 (this would be true when |
| 49 | we have 0.5 GiB of VM memory, see pc_q35_init()). This means total |
| 50 | hotpluggable memory size is 60 GiB. Per slot, we reserve 1 GiB of memory |
| 51 | for dimm alignment for all machines. That leaves total hotpluggable |
| 52 | actual memory size of 59 GiB. If the VM is started with 0.5 GiB of |
| 53 | memory, maxmem should be set to a maximum value of 59.5 GiB to ensure |
| 54 | that the processor can address all memory directly. |
| 55 | Note that 64-bit pci hole size is 0 in this case. If maxmem is set to |
| 56 | 59.6G, QEMU should fail to start with a message "phy-bits are too low". |
| 57 | If maxmem is set to 59.5G with all other QEMU parameters identical, QEMU |
| 58 | should start fine. |
| 59 | """ |
| 60 | self.ensure_64bit_binary() |
| 61 | self.set_machine('q35') |
| 62 | self.vm.add_args('-S', '-m', '512,slots=1,maxmem=59.6G', |
| 63 | '-cpu', 'pentium,pse36=on', '-display', 'none', |
| 64 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 65 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 66 | self.vm.set_qmp_monitor(enabled=False) |
| 67 | self.vm.launch() |
| 68 | self.vm.wait() |
| 69 | self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") |
| 70 | self.assertRegex(self.vm.get_log(), r'phys-bits too low') |
| 71 | |
| 72 | def test_phybits_low_pae(self): |
| 73 | """ |
| 74 | With pae feature ON, a processor has 36 bits of addressing. So it can |
| 75 | access up to a maximum of 64GiB of memory. Rest is the same as the case |
| 76 | with pse36 above. |
| 77 | """ |
| 78 | self.ensure_64bit_binary() |
| 79 | self.set_machine('q35') |
| 80 | self.vm.add_args('-S', '-m', '512,slots=1,maxmem=59.6G', |
| 81 | '-cpu', 'pentium,pae=on', '-display', 'none', |
| 82 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 83 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 84 | self.vm.set_qmp_monitor(enabled=False) |
| 85 | self.vm.launch() |
| 86 | self.vm.wait() |
| 87 | self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") |
| 88 | self.assertRegex(self.vm.get_log(), r'phys-bits too low') |
| 89 | |
| 90 | def test_phybits_ok_pentium_pse36(self): |
| 91 | """ |
| 92 | Setting maxmem to 59.5G and making sure that QEMU can start with the |
| 93 | same options as the failing case above with pse36 cpu feature. |
| 94 | """ |
| 95 | self.ensure_64bit_binary() |
| 96 | self.set_machine('q35') |
| 97 | self.vm.add_args('-m', '512,slots=1,maxmem=59.5G', |
| 98 | '-cpu', 'pentium,pse36=on', '-display', 'none', |
| 99 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 100 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 101 | self.vm.set_qmp_monitor(enabled=False) |
| 102 | self.vm.launch() |
| 103 | time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) |
| 104 | self.vm.shutdown() |
| 105 | self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') |
| 106 | |
| 107 | def test_phybits_ok_pentium_pae(self): |
| 108 | """ |
| 109 | Test is same as above but now with pae cpu feature turned on. |
| 110 | Setting maxmem to 59.5G and making sure that QEMU can start fine |
| 111 | with the same options as the case above. |
| 112 | """ |
| 113 | self.ensure_64bit_binary() |
| 114 | self.set_machine('q35') |
| 115 | self.vm.add_args('-m', '512,slots=1,maxmem=59.5G', |
| 116 | '-cpu', 'pentium,pae=on', '-display', 'none', |
| 117 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 118 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 119 | self.vm.set_qmp_monitor(enabled=False) |
| 120 | self.vm.launch() |
| 121 | time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) |
| 122 | self.vm.shutdown() |
| 123 | self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') |
| 124 | |
| 125 | def test_phybits_ok_pentium2(self): |
| 126 | """ |
| 127 | Pentium2 has 36 bits of addressing, so its same as pentium |
| 128 | with pse36 ON. |
| 129 | """ |
| 130 | self.ensure_64bit_binary() |
| 131 | self.set_machine('q35') |
| 132 | self.vm.add_args('-m', '512,slots=1,maxmem=59.5G', |
| 133 | '-cpu', 'pentium2', '-display', 'none', |
| 134 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 135 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 136 | self.vm.set_qmp_monitor(enabled=False) |
| 137 | self.vm.launch() |
| 138 | time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) |
| 139 | self.vm.shutdown() |
| 140 | self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') |
| 141 | |
| 142 | def test_phybits_low_nonpse36(self): |
| 143 | """ |
| 144 | Pentium processor has 32 bits of addressing without pse36 or pae |
| 145 | so it can access physical address up to 4 GiB. Setting maxmem to |
| 146 | 4 GiB should make QEMU fail to start with "phys-bits too low" |
| 147 | message because the region for memory hotplug is always placed |
| 148 | above 4 GiB due to the PCI hole and simplicity. |
| 149 | """ |
| 150 | self.ensure_64bit_binary() |
| 151 | self.set_machine('q35') |
| 152 | self.vm.add_args('-S', '-m', '512,slots=1,maxmem=4G', |
| 153 | '-cpu', 'pentium', '-display', 'none', |
| 154 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 155 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 156 | self.vm.set_qmp_monitor(enabled=False) |
| 157 | self.vm.launch() |
| 158 | self.vm.wait() |
| 159 | self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") |
| 160 | self.assertRegex(self.vm.get_log(), r'phys-bits too low') |
| 161 | |
| 162 | # now lets test some 64-bit CPU cases. |
| 163 | def test_phybits_low_tcg_q35_70_amd(self): |
| 164 | """ |
| 165 | For q35 7.1 machines and above, there is a HT window that starts at |
| 166 | 1024 GiB and ends at 1 TiB - 1. If the max GPA falls in this range, |
| 167 | "above_4G" memory is adjusted to start at 1 TiB boundary for AMD cpus |
| 168 | in the default case. Lets test without that case for machines 7.0. |
| 169 | For q35-7.0 machines, "above 4G" memory starts are 4G. |
| 170 | pci64_hole size is 32 GiB. Since TCG_PHYS_ADDR_BITS is defined to |
| 171 | be 40, TCG emulated CPUs have maximum of 1 TiB (1024 GiB) of |
| 172 | directly addressable memory. |
| 173 | Hence, maxmem value at most can be |
| 174 | 1024 GiB - 4 GiB - 1 GiB per slot for alignment - 32 GiB + 0.5 GiB |
| 175 | which is equal to 987.5 GiB. Setting the value to 988 GiB should |
| 176 | make QEMU fail with the error message. |
| 177 | """ |
| 178 | self.ensure_64bit_binary() |
| 179 | self.set_machine('pc-q35-7.0') |
| 180 | self.vm.add_args('-S', '-m', '512,slots=1,maxmem=988G', |
| 181 | '-display', 'none', |
| 182 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 183 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 184 | self.vm.set_qmp_monitor(enabled=False) |
| 185 | self.vm.launch() |
| 186 | self.vm.wait() |
| 187 | self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") |
| 188 | self.assertRegex(self.vm.get_log(), r'phys-bits too low') |
| 189 | |
| 190 | def test_phybits_low_tcg_q35_71_amd(self): |
| 191 | """ |
| 192 | AMD_HT_START is defined to be at 1012 GiB. So for q35 machines |
| 193 | version > 7.0 and AMD cpus, instead of 1024 GiB limit for 40 bit |
| 194 | processor address space, it has to be 1012 GiB , that is 12 GiB |
| 195 | less than the case above in order to accommodate HT hole. |
| 196 | Make sure QEMU fails when maxmem size is 976 GiB (12 GiB less |
| 197 | than 988 GiB). |
| 198 | """ |
| 199 | self.ensure_64bit_binary() |
| 200 | self.set_machine('pc-q35-7.1') |
| 201 | self.vm.add_args('-S', '-m', '512,slots=1,maxmem=976G', |
| 202 | '-display', 'none', |
| 203 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 204 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 205 | self.vm.set_qmp_monitor(enabled=False) |
| 206 | self.vm.launch() |
| 207 | self.vm.wait() |
| 208 | self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") |
| 209 | self.assertRegex(self.vm.get_log(), r'phys-bits too low') |
| 210 | |
| 211 | def test_phybits_ok_tcg_q35_70_amd(self): |
| 212 | """ |
| 213 | Same as q35-7.0 AMD case except that here we check that QEMU can |
| 214 | successfully start when maxmem is < 988G. |
| 215 | """ |
| 216 | self.ensure_64bit_binary() |
| 217 | self.set_machine('pc-q35-7.0') |
| 218 | self.vm.add_args('-S', '-m', '512,slots=1,maxmem=987.5G', |
| 219 | '-display', 'none', |
| 220 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 221 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 222 | self.vm.set_qmp_monitor(enabled=False) |
| 223 | self.vm.launch() |
| 224 | time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) |
| 225 | self.vm.shutdown() |
| 226 | self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') |
| 227 | |
| 228 | def test_phybits_ok_tcg_q35_71_amd(self): |
| 229 | """ |
| 230 | Same as q35-7.1 AMD case except that here we check that QEMU can |
| 231 | successfully start when maxmem is < 976G. |
| 232 | """ |
| 233 | self.ensure_64bit_binary() |
| 234 | self.set_machine('pc-q35-7.1') |
| 235 | self.vm.add_args('-S', '-m', '512,slots=1,maxmem=975.5G', |
| 236 | '-display', 'none', |
| 237 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 238 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 239 | self.vm.set_qmp_monitor(enabled=False) |
| 240 | self.vm.launch() |
| 241 | time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) |
| 242 | self.vm.shutdown() |
| 243 | self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') |
| 244 | |
| 245 | def test_phybits_ok_tcg_q35_71_intel(self): |
| 246 | """ |
| 247 | Same parameters as test_phybits_low_tcg_q35_71_amd() but use |
| 248 | Intel cpu instead. QEMU should start fine in this case as |
| 249 | "above_4G" memory starts at 4G. |
| 250 | """ |
| 251 | self.ensure_64bit_binary() |
| 252 | self.set_machine('pc-q35-7.1') |
| 253 | self.vm.add_args('-S', '-cpu', 'Skylake-Server', |
| 254 | '-m', '512,slots=1,maxmem=976G', |
| 255 | '-display', 'none', |
| 256 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 257 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 258 | self.vm.set_qmp_monitor(enabled=False) |
| 259 | self.vm.launch() |
| 260 | time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) |
| 261 | self.vm.shutdown() |
| 262 | self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') |
| 263 | |
| 264 | def test_phybits_low_tcg_q35_71_amd_41bits(self): |
| 265 | """ |
| 266 | AMD processor with 41 bits. Max cpu hw address = 2 TiB. |
| 267 | By setting maxram above 1012 GiB - 32 GiB - 4 GiB = 976 GiB, we can |
| 268 | force "above_4G" memory to start at 1 TiB for q35-7.1 machines |
| 269 | (max GPA will be above AMD_HT_START which is defined as 1012 GiB). |
| 270 | |
| 271 | With pci_64_hole size at 32 GiB, in this case, maxmem should be 991.5 |
| 272 | GiB with 1 GiB per slot for alignment and 0.5 GiB as non-hotplug |
| 273 | memory for the VM (1024 - 32 - 1 + 0.5). With 992 GiB, QEMU should |
| 274 | fail to start. |
| 275 | """ |
| 276 | self.ensure_64bit_binary() |
| 277 | self.set_machine('pc-q35-7.1') |
| 278 | self.vm.add_args('-S', '-cpu', 'EPYC-v4,phys-bits=41', |
| 279 | '-m', '512,slots=1,maxmem=992G', |
| 280 | '-display', 'none', |
| 281 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 282 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 283 | self.vm.set_qmp_monitor(enabled=False) |
| 284 | self.vm.launch() |
| 285 | self.vm.wait() |
| 286 | self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") |
| 287 | self.assertRegex(self.vm.get_log(), r'phys-bits too low') |
| 288 | |
| 289 | def test_phybits_ok_tcg_q35_71_amd_41bits(self): |
| 290 | """ |
| 291 | AMD processor with 41 bits. Max cpu hw address = 2 TiB. |
| 292 | Same as above but by setting maxram between 976 GiB and 992 Gib, |
| 293 | QEMU should start fine. |
| 294 | """ |
| 295 | self.ensure_64bit_binary() |
| 296 | self.set_machine('pc-q35-7.1') |
| 297 | self.vm.add_args('-S', '-cpu', 'EPYC-v4,phys-bits=41', |
| 298 | '-m', '512,slots=1,maxmem=990G', |
| 299 | '-display', 'none', |
| 300 | '-object', 'memory-backend-ram,id=mem1,size=1G', |
| 301 | '-device', 'pc-dimm,id=vm0,memdev=mem1') |
| 302 | self.vm.set_qmp_monitor(enabled=False) |
| 303 | self.vm.launch() |
| 304 | time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) |
| 305 | self.vm.shutdown() |
| 306 | self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') |
| 307 | |
| 308 | def test_phybits_low_tcg_q35_intel_cxl(self): |
| 309 | """ |
| 310 | cxl memory window starts after memory device range. Here, we use 1 GiB |
| 311 | of cxl window memory. 4G_mem end aligns at 4G. pci64_hole is 32 GiB and |
| 312 | starts after the cxl memory window. |
| 313 | So maxmem here should be at most 986 GiB considering all memory boundary |
| 314 | alignment constraints with 40 bits (1 TiB) of processor physical bits. |
| 315 | """ |
| 316 | self.ensure_64bit_binary() |
| 317 | self.set_machine('q35') |
| 318 | self.vm.add_args('-S', '-cpu', 'Skylake-Server,phys-bits=40', |
| 319 | '-m', '512,slots=1,maxmem=987G', |
| 320 | '-display', 'none', |
| 321 | '-device', 'pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1', |
| 322 | '-M', 'cxl=on,cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=1G') |
| 323 | self.vm.set_qmp_monitor(enabled=False) |
| 324 | self.vm.launch() |
| 325 | self.vm.wait() |
| 326 | self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1") |
| 327 | self.assertRegex(self.vm.get_log(), r'phys-bits too low') |
| 328 | |
| 329 | def test_phybits_ok_tcg_q35_intel_cxl(self): |
| 330 | """ |
| 331 | Same as above but here we do not reserve any cxl memory window. Hence, |
| 332 | with the exact same parameters as above, QEMU should start fine even |
| 333 | with cxl enabled. |
| 334 | """ |
| 335 | self.ensure_64bit_binary() |
| 336 | self.set_machine('q35') |
| 337 | self.vm.add_args('-S', '-cpu', 'Skylake-Server,phys-bits=40', |
| 338 | '-machine', 'cxl=on', |
| 339 | '-m', '512,slots=1,maxmem=987G', |
| 340 | '-display', 'none', |
| 341 | '-device', 'pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1') |
| 342 | self.vm.set_qmp_monitor(enabled=False) |
| 343 | self.vm.launch() |
| 344 | time.sleep(self.DELAY_Q35_BOOT_SEQUENCE) |
| 345 | self.vm.shutdown() |
| 346 | self.assertNotRegex(self.vm.get_log(), r'phys-bits too low') |
| 347 | |
| 348 | if __name__ == '__main__': |
| 349 | QemuSystemTest.main() |