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