1#!/usr/bin/env python3 2# 3# Functional test that boots a Linux kernel and checks the console 4# 5# Copyright IBM Corp. 2023 6# 7# Author: 8# Pierre Morel <pmorel@linux.ibm.com> 9# 10# This work is licensed under the terms of the GNU GPL, version 2 or 11# later. See the COPYING file in the top-level directory. 12 13from qemu_test import QemuSystemTest, Asset 14from qemu_test import exec_command 15from qemu_test import exec_command_and_wait_for_pattern 16from qemu_test import wait_for_console_pattern 17from qemu_test.utils import lzma_uncompress 18 19 20class S390CPUTopology(QemuSystemTest): 21 """ 22 S390x CPU topology consists of 4 topology layers, from bottom to top, 23 the cores, sockets, books and drawers and 2 modifiers attributes, 24 the entitlement and the dedication. 25 See: docs/system/s390x/cpu-topology.rst. 26 27 S390x CPU topology is setup in different ways: 28 - implicitly from the '-smp' argument by completing each topology 29 level one after the other beginning with drawer 0, book 0 and 30 socket 0. 31 - explicitly from the '-device' argument on the QEMU command line 32 - explicitly by hotplug of a new CPU using QMP or HMP 33 - it is modified by using QMP 'set-cpu-topology' 34 35 The S390x modifier attribute entitlement depends on the machine 36 polarization, which can be horizontal or vertical. 37 The polarization is changed on a request from the guest. 38 """ 39 timeout = 90 40 event_timeout = 10 41 42 KERNEL_COMMON_COMMAND_LINE = ('printk.time=0 ' 43 'root=/dev/ram ' 44 'selinux=0 ' 45 'rdinit=/bin/sh') 46 ASSET_F35_KERNEL = Asset( 47 ('https://archives.fedoraproject.org/pub/archive' 48 '/fedora-secondary/releases/35/Server/s390x/os' 49 '/images/kernel.img'), 50 '1f2dddfd11bb1393dd2eb2e784036fbf6fc11057a6d7d27f9eb12d3edc67ef73') 51 52 ASSET_F35_INITRD = Asset( 53 ('https://archives.fedoraproject.org/pub/archive' 54 '/fedora-secondary/releases/35/Server/s390x/os' 55 '/images/initrd.img'), 56 '1100145fbca00240c8c372ae4b89b48c99844bc189b3dfbc3f481dc60055ca46') 57 58 def wait_until_booted(self): 59 wait_for_console_pattern(self, 'no job control', 60 failure_message='Kernel panic - not syncing', 61 vm=None) 62 63 def check_topology(self, c, s, b, d, e, t): 64 res = self.vm.qmp('query-cpus-fast') 65 cpus = res['return'] 66 for cpu in cpus: 67 core = cpu['props']['core-id'] 68 socket = cpu['props']['socket-id'] 69 book = cpu['props']['book-id'] 70 drawer = cpu['props']['drawer-id'] 71 entitlement = cpu.get('entitlement') 72 dedicated = cpu.get('dedicated') 73 if core == c: 74 self.assertEqual(drawer, d) 75 self.assertEqual(book, b) 76 self.assertEqual(socket, s) 77 self.assertEqual(entitlement, e) 78 self.assertEqual(dedicated, t) 79 80 def kernel_init(self): 81 """ 82 We need a VM that supports CPU topology, 83 currently this only the case when using KVM, not TCG. 84 We need a kernel supporting the CPU topology. 85 We need a minimal root filesystem with a shell. 86 """ 87 self.require_accelerator("kvm") 88 kernel_path = self.ASSET_F35_KERNEL.fetch() 89 initrd_path_xz = self.ASSET_F35_INITRD.fetch() 90 initrd_path = self.scratch_file('initrd-raw.img') 91 lzma_uncompress(initrd_path_xz, initrd_path) 92 93 self.vm.set_console() 94 kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE 95 self.vm.add_args('-nographic', 96 '-enable-kvm', 97 '-cpu', 'max,ctop=on', 98 '-m', '512', 99 '-kernel', kernel_path, 100 '-initrd', initrd_path, 101 '-append', kernel_command_line) 102 103 def system_init(self): 104 self.log.info("System init") 105 exec_command_and_wait_for_pattern(self, 106 """ mount proc -t proc /proc; 107 mount sys -t sysfs /sys; 108 cat /sys/devices/system/cpu/dispatching """, 109 '0') 110 111 def test_single(self): 112 """ 113 This test checks the simplest topology with a single CPU. 114 """ 115 self.set_machine('s390-ccw-virtio') 116 self.kernel_init() 117 self.vm.launch() 118 self.wait_until_booted() 119 self.check_topology(0, 0, 0, 0, 'medium', False) 120 121 def test_default(self): 122 """ 123 This test checks the implicit topology. 124 """ 125 self.set_machine('s390-ccw-virtio') 126 self.kernel_init() 127 self.vm.add_args('-smp', 128 '13,drawers=2,books=2,sockets=3,cores=2,maxcpus=24') 129 self.vm.launch() 130 self.wait_until_booted() 131 self.check_topology(0, 0, 0, 0, 'medium', False) 132 self.check_topology(1, 0, 0, 0, 'medium', False) 133 self.check_topology(2, 1, 0, 0, 'medium', False) 134 self.check_topology(3, 1, 0, 0, 'medium', False) 135 self.check_topology(4, 2, 0, 0, 'medium', False) 136 self.check_topology(5, 2, 0, 0, 'medium', False) 137 self.check_topology(6, 0, 1, 0, 'medium', False) 138 self.check_topology(7, 0, 1, 0, 'medium', False) 139 self.check_topology(8, 1, 1, 0, 'medium', False) 140 self.check_topology(9, 1, 1, 0, 'medium', False) 141 self.check_topology(10, 2, 1, 0, 'medium', False) 142 self.check_topology(11, 2, 1, 0, 'medium', False) 143 self.check_topology(12, 0, 0, 1, 'medium', False) 144 145 def test_move(self): 146 """ 147 This test checks the topology modification by moving a CPU 148 to another socket: CPU 0 is moved from socket 0 to socket 2. 149 """ 150 self.set_machine('s390-ccw-virtio') 151 self.kernel_init() 152 self.vm.add_args('-smp', 153 '1,drawers=2,books=2,sockets=3,cores=2,maxcpus=24') 154 self.vm.launch() 155 self.wait_until_booted() 156 157 self.check_topology(0, 0, 0, 0, 'medium', False) 158 res = self.vm.qmp('set-cpu-topology', 159 {'core-id': 0, 'socket-id': 2, 'entitlement': 'low'}) 160 self.assertEqual(res['return'], {}) 161 self.check_topology(0, 2, 0, 0, 'low', False) 162 163 def test_dash_device(self): 164 """ 165 This test verifies that a CPU defined with the '-device' 166 command line option finds its right place inside the topology. 167 """ 168 self.set_machine('s390-ccw-virtio') 169 self.kernel_init() 170 self.vm.add_args('-smp', 171 '1,drawers=2,books=2,sockets=3,cores=2,maxcpus=24') 172 self.vm.add_args('-device', 'max-s390x-cpu,core-id=10') 173 self.vm.add_args('-device', 174 'max-s390x-cpu,' 175 'core-id=1,socket-id=0,book-id=1,drawer-id=1,entitlement=low') 176 self.vm.add_args('-device', 177 'max-s390x-cpu,' 178 'core-id=2,socket-id=0,book-id=1,drawer-id=1,entitlement=medium') 179 self.vm.add_args('-device', 180 'max-s390x-cpu,' 181 'core-id=3,socket-id=1,book-id=1,drawer-id=1,entitlement=high') 182 self.vm.add_args('-device', 183 'max-s390x-cpu,' 184 'core-id=4,socket-id=1,book-id=1,drawer-id=1') 185 self.vm.add_args('-device', 186 'max-s390x-cpu,' 187 'core-id=5,socket-id=2,book-id=1,drawer-id=1,dedicated=true') 188 189 self.vm.launch() 190 self.wait_until_booted() 191 192 self.check_topology(10, 2, 1, 0, 'medium', False) 193 self.check_topology(1, 0, 1, 1, 'low', False) 194 self.check_topology(2, 0, 1, 1, 'medium', False) 195 self.check_topology(3, 1, 1, 1, 'high', False) 196 self.check_topology(4, 1, 1, 1, 'medium', False) 197 self.check_topology(5, 2, 1, 1, 'high', True) 198 199 200 def guest_set_dispatching(self, dispatching): 201 exec_command(self, 202 f'echo {dispatching} > /sys/devices/system/cpu/dispatching') 203 self.vm.event_wait('CPU_POLARIZATION_CHANGE', self.event_timeout) 204 exec_command_and_wait_for_pattern(self, 205 'cat /sys/devices/system/cpu/dispatching', dispatching) 206 207 208 def test_polarization(self): 209 """ 210 This test verifies that QEMU modifies the entitlement change after 211 several guest polarization change requests. 212 """ 213 self.set_machine('s390-ccw-virtio') 214 self.kernel_init() 215 self.vm.launch() 216 self.wait_until_booted() 217 218 self.system_init() 219 res = self.vm.qmp('query-s390x-cpu-polarization') 220 self.assertEqual(res['return']['polarization'], 'horizontal') 221 self.check_topology(0, 0, 0, 0, 'medium', False) 222 223 self.guest_set_dispatching('1'); 224 res = self.vm.qmp('query-s390x-cpu-polarization') 225 self.assertEqual(res['return']['polarization'], 'vertical') 226 self.check_topology(0, 0, 0, 0, 'medium', False) 227 228 self.guest_set_dispatching('0'); 229 res = self.vm.qmp('query-s390x-cpu-polarization') 230 self.assertEqual(res['return']['polarization'], 'horizontal') 231 self.check_topology(0, 0, 0, 0, 'medium', False) 232 233 234 def check_polarization(self, polarization): 235 #We need to wait for the change to have been propagated to the kernel 236 exec_command_and_wait_for_pattern(self, 237 "\n".join([ 238 "timeout 1 sh -c 'while true", 239 'do', 240 ' syspath="/sys/devices/system/cpu/cpu0/polarization"', 241 ' polarization="$(cat "$syspath")" || exit', 242 f' if [ "$polarization" = "{polarization}" ]; then', 243 ' exit 0', 244 ' fi', 245 ' sleep 0.01', 246 #searched for strings mustn't show up in command, '' to obfuscate 247 "done' && echo succ''ess || echo fail''ure", 248 ]), 249 "success", "failure") 250 251 252 def test_entitlement(self): 253 """ 254 This test verifies that QEMU modifies the entitlement 255 after a guest request and that the guest sees the change. 256 """ 257 self.set_machine('s390-ccw-virtio') 258 self.kernel_init() 259 self.vm.launch() 260 self.wait_until_booted() 261 262 self.system_init() 263 264 self.check_polarization('horizontal') 265 self.check_topology(0, 0, 0, 0, 'medium', False) 266 267 self.guest_set_dispatching('1') 268 self.check_polarization('vertical:medium') 269 self.check_topology(0, 0, 0, 0, 'medium', False) 270 271 res = self.vm.qmp('set-cpu-topology', 272 {'core-id': 0, 'entitlement': 'low'}) 273 self.assertEqual(res['return'], {}) 274 self.check_polarization('vertical:low') 275 self.check_topology(0, 0, 0, 0, 'low', False) 276 277 res = self.vm.qmp('set-cpu-topology', 278 {'core-id': 0, 'entitlement': 'medium'}) 279 self.assertEqual(res['return'], {}) 280 self.check_polarization('vertical:medium') 281 self.check_topology(0, 0, 0, 0, 'medium', False) 282 283 res = self.vm.qmp('set-cpu-topology', 284 {'core-id': 0, 'entitlement': 'high'}) 285 self.assertEqual(res['return'], {}) 286 self.check_polarization('vertical:high') 287 self.check_topology(0, 0, 0, 0, 'high', False) 288 289 self.guest_set_dispatching('0'); 290 self.check_polarization("horizontal") 291 self.check_topology(0, 0, 0, 0, 'high', False) 292 293 294 def test_dedicated(self): 295 """ 296 This test verifies that QEMU adjusts the entitlement correctly when a 297 CPU is made dedicated. 298 QEMU retains the entitlement value when horizontal polarization is in effect. 299 For the guest, the field shows the effective value of the entitlement. 300 """ 301 self.set_machine('s390-ccw-virtio') 302 self.kernel_init() 303 self.vm.launch() 304 self.wait_until_booted() 305 306 self.system_init() 307 308 self.check_polarization("horizontal") 309 310 res = self.vm.qmp('set-cpu-topology', 311 {'core-id': 0, 'dedicated': True}) 312 self.assertEqual(res['return'], {}) 313 self.check_topology(0, 0, 0, 0, 'high', True) 314 self.check_polarization("horizontal") 315 316 self.guest_set_dispatching('1'); 317 self.check_topology(0, 0, 0, 0, 'high', True) 318 self.check_polarization("vertical:high") 319 320 self.guest_set_dispatching('0'); 321 self.check_topology(0, 0, 0, 0, 'high', True) 322 self.check_polarization("horizontal") 323 324 325 def test_socket_full(self): 326 """ 327 This test verifies that QEMU does not accept to overload a socket. 328 The socket-id 0 on book-id 0 already contains CPUs 0 and 1 and can 329 not accept any new CPU while socket-id 0 on book-id 1 is free. 330 """ 331 self.set_machine('s390-ccw-virtio') 332 self.kernel_init() 333 self.vm.add_args('-smp', 334 '3,drawers=2,books=2,sockets=3,cores=2,maxcpus=24') 335 self.vm.launch() 336 self.wait_until_booted() 337 338 self.system_init() 339 340 res = self.vm.qmp('set-cpu-topology', 341 {'core-id': 2, 'socket-id': 0, 'book-id': 0}) 342 self.assertEqual(res['error']['class'], 'GenericError') 343 344 res = self.vm.qmp('set-cpu-topology', 345 {'core-id': 2, 'socket-id': 0, 'book-id': 1}) 346 self.assertEqual(res['return'], {}) 347 348 def test_dedicated_error(self): 349 """ 350 This test verifies that QEMU refuses to lower the entitlement 351 of a dedicated CPU 352 """ 353 self.set_machine('s390-ccw-virtio') 354 self.kernel_init() 355 self.vm.launch() 356 self.wait_until_booted() 357 358 self.system_init() 359 360 res = self.vm.qmp('set-cpu-topology', 361 {'core-id': 0, 'dedicated': True}) 362 self.assertEqual(res['return'], {}) 363 364 self.check_topology(0, 0, 0, 0, 'high', True) 365 366 self.guest_set_dispatching('1'); 367 368 self.check_topology(0, 0, 0, 0, 'high', True) 369 370 res = self.vm.qmp('set-cpu-topology', 371 {'core-id': 0, 'entitlement': 'low', 'dedicated': True}) 372 self.assertEqual(res['error']['class'], 'GenericError') 373 374 res = self.vm.qmp('set-cpu-topology', 375 {'core-id': 0, 'entitlement': 'low'}) 376 self.assertEqual(res['error']['class'], 'GenericError') 377 378 res = self.vm.qmp('set-cpu-topology', 379 {'core-id': 0, 'entitlement': 'medium', 'dedicated': True}) 380 self.assertEqual(res['error']['class'], 'GenericError') 381 382 res = self.vm.qmp('set-cpu-topology', 383 {'core-id': 0, 'entitlement': 'medium'}) 384 self.assertEqual(res['error']['class'], 'GenericError') 385 386 res = self.vm.qmp('set-cpu-topology', 387 {'core-id': 0, 'entitlement': 'low', 'dedicated': False}) 388 self.assertEqual(res['return'], {}) 389 390 res = self.vm.qmp('set-cpu-topology', 391 {'core-id': 0, 'entitlement': 'medium', 'dedicated': False}) 392 self.assertEqual(res['return'], {}) 393 394 def test_move_error(self): 395 """ 396 This test verifies that QEMU refuses to move a CPU to an 397 nonexistent location 398 """ 399 self.set_machine('s390-ccw-virtio') 400 self.kernel_init() 401 self.vm.launch() 402 self.wait_until_booted() 403 404 self.system_init() 405 406 res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'drawer-id': 1}) 407 self.assertEqual(res['error']['class'], 'GenericError') 408 409 res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'book-id': 1}) 410 self.assertEqual(res['error']['class'], 'GenericError') 411 412 res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'socket-id': 1}) 413 self.assertEqual(res['error']['class'], 'GenericError') 414 415 self.check_topology(0, 0, 0, 0, 'medium', False) 416 417if __name__ == '__main__': 418 QemuSystemTest.main() 419