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