1 # Functional test that boots a Linux kernel and checks the console
3 # Copyright IBM Corp. 2023
6 # Pierre Morel <pmorel@linux.ibm.com>
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.
15 from avocado_qemu
import QemuSystemTest
16 from avocado_qemu
import exec_command
17 from avocado_qemu
import exec_command_and_wait_for_pattern
18 from avocado_qemu
import interrupt_interactive_console_until_pattern
19 from avocado_qemu
import wait_for_console_pattern
20 from avocado
.utils
import process
21 from avocado
.utils
import archive
24 class S390CPUTopology(QemuSystemTest
):
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.
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
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'
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.
46 KERNEL_COMMON_COMMAND_LINE
= ('printk.time=0 '
51 def wait_until_booted(self
):
52 wait_for_console_pattern(self
, 'no job control',
53 failure_message
='Kernel panic - not syncing',
56 def check_topology(self
, c
, s
, b
, d
, e
, t
):
57 res
= self
.vm
.qmp('query-cpus-fast')
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')
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
)
73 def kernel_init(self
):
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.
80 self
.require_accelerator("kvm")
81 kernel_url
= ('https://archives.fedoraproject.org/pub/archive'
82 '/fedora-secondary/releases/35/Server/s390x/os'
84 kernel_hash
= '0d1aaaf303f07cf0160c8c48e56fe638'
85 kernel_path
= self
.fetch_asset(kernel_url
, algorithm
='md5',
86 asset_hash
=kernel_hash
)
88 initrd_url
= ('https://archives.fedoraproject.org/pub/archive'
89 '/fedora-secondary/releases/35/Server/s390x/os'
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
)
98 kernel_command_line
= self
.KERNEL_COMMON_COMMAND_LINE
99 self
.vm
.add_args('-nographic',
101 '-cpu', 'max,ctop=on',
103 '-kernel', kernel_path
,
104 '-initrd', initrd_path
,
105 '-append', kernel_command_line
)
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 """,
115 def test_single(self
):
117 This test checks the simplest topology with a single CPU.
119 :avocado: tags=arch:s390x
120 :avocado: tags=machine:s390-ccw-virtio
124 self
.wait_until_booted()
125 self
.check_topology(0, 0, 0, 0, 'medium', False)
127 def test_default(self
):
129 This test checks the implicit topology.
131 :avocado: tags=arch:s390x
132 :avocado: tags=machine:s390-ccw-virtio
135 self
.vm
.add_args('-smp',
136 '13,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
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)
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.
158 :avocado: tags=arch:s390x
159 :avocado: tags=machine:s390-ccw-virtio
162 self
.vm
.add_args('-smp',
163 '1,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
165 self
.wait_until_booted()
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)
173 def test_dash_device(self
):
175 This test verifies that a CPU defined with the '-device'
176 command line option finds its right place inside the topology.
178 :avocado: tags=arch:s390x
179 :avocado: tags=machine:s390-ccw-virtio
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',
187 'core-id=1,socket-id=0,book-id=1,drawer-id=1,entitlement=low')
188 self
.vm
.add_args('-device',
190 'core-id=2,socket-id=0,book-id=1,drawer-id=1,entitlement=medium')
191 self
.vm
.add_args('-device',
193 'core-id=3,socket-id=1,book-id=1,drawer-id=1,entitlement=high')
194 self
.vm
.add_args('-device',
196 'core-id=4,socket-id=1,book-id=1,drawer-id=1')
197 self
.vm
.add_args('-device',
199 'core-id=5,socket-id=2,book-id=1,drawer-id=1,dedicated=true')
202 self
.wait_until_booted()
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)
212 def guest_set_dispatching(self
, dispatching
):
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
)
220 def test_polarization(self
):
222 This test verifies that QEMU modifies the entitlement change after
223 several guest polarization change requests.
225 :avocado: tags=arch:s390x
226 :avocado: tags=machine:s390-ccw-virtio
230 self
.wait_until_booted()
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)
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)
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)
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
,
252 "timeout 1 sh -c 'while true",
254 ' syspath="/sys/devices/system/cpu/cpu0/polarization"',
255 ' polarization="$(cat "$syspath")" || exit',
256 f
' if [ "$polarization" = "{polarization}" ]; then',
260 #searched for strings mustn't show up in command, '' to obfuscate
261 "done' && echo succ''ess || echo fail''ure",
263 "success", "failure")
266 def test_entitlement(self
):
268 This test verifies that QEMU modifies the entitlement
269 after a guest request and that the guest sees the change.
271 :avocado: tags=arch:s390x
272 :avocado: tags=machine:s390-ccw-virtio
276 self
.wait_until_booted()
280 self
.check_polarization('horizontal')
281 self
.check_topology(0, 0, 0, 0, 'medium', False)
283 self
.guest_set_dispatching('1')
284 self
.check_polarization('vertical:medium')
285 self
.check_topology(0, 0, 0, 0, 'medium', False)
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)
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)
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)
305 self
.guest_set_dispatching('0');
306 self
.check_polarization("horizontal")
307 self
.check_topology(0, 0, 0, 0, 'high', False)
310 def test_dedicated(self
):
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.
317 :avocado: tags=arch:s390x
318 :avocado: tags=machine:s390-ccw-virtio
322 self
.wait_until_booted()
326 self
.check_polarization("horizontal")
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")
334 self
.guest_set_dispatching('1');
335 self
.check_topology(0, 0, 0, 0, 'high', True)
336 self
.check_polarization("vertical:high")
338 self
.guest_set_dispatching('0');
339 self
.check_topology(0, 0, 0, 0, 'high', True)
340 self
.check_polarization("horizontal")
343 def test_socket_full(self
):
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.
349 :avocado: tags=arch:s390x
350 :avocado: tags=machine:s390-ccw-virtio
353 self
.vm
.add_args('-smp',
354 '3,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
356 self
.wait_until_booted()
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')
364 res
= self
.vm
.qmp('set-cpu-topology',
365 {'core-id': 2, 'socket-id': 0, 'book-id': 1})
366 self
.assertEqual(res
['return'], {})
368 def test_dedicated_error(self
):
370 This test verifies that QEMU refuses to lower the entitlement
373 :avocado: tags=arch:s390x
374 :avocado: tags=machine:s390-ccw-virtio
378 self
.wait_until_booted()
382 res
= self
.vm
.qmp('set-cpu-topology',
383 {'core-id': 0, 'dedicated': True})
384 self
.assertEqual(res
['return'], {})
386 self
.check_topology(0, 0, 0, 0, 'high', True)
388 self
.guest_set_dispatching('1');
390 self
.check_topology(0, 0, 0, 0, 'high', True)
392 res
= self
.vm
.qmp('set-cpu-topology',
393 {'core-id': 0, 'entitlement': 'low', 'dedicated': True})
394 self
.assertEqual(res
['error']['class'], 'GenericError')
396 res
= self
.vm
.qmp('set-cpu-topology',
397 {'core-id': 0, 'entitlement': 'low'})
398 self
.assertEqual(res
['error']['class'], 'GenericError')
400 res
= self
.vm
.qmp('set-cpu-topology',
401 {'core-id': 0, 'entitlement': 'medium', 'dedicated': True})
402 self
.assertEqual(res
['error']['class'], 'GenericError')
404 res
= self
.vm
.qmp('set-cpu-topology',
405 {'core-id': 0, 'entitlement': 'medium'})
406 self
.assertEqual(res
['error']['class'], 'GenericError')
408 res
= self
.vm
.qmp('set-cpu-topology',
409 {'core-id': 0, 'entitlement': 'low', 'dedicated': False})
410 self
.assertEqual(res
['return'], {})
412 res
= self
.vm
.qmp('set-cpu-topology',
413 {'core-id': 0, 'entitlement': 'medium', 'dedicated': False})
414 self
.assertEqual(res
['return'], {})
416 def test_move_error(self
):
418 This test verifies that QEMU refuses to move a CPU to an
421 :avocado: tags=arch:s390x
422 :avocado: tags=machine:s390-ccw-virtio
426 self
.wait_until_booted()
430 res
= self
.vm
.qmp('set-cpu-topology', {'core-id': 0, 'drawer-id': 1})
431 self
.assertEqual(res
['error']['class'], 'GenericError')
433 res
= self
.vm
.qmp('set-cpu-topology', {'core-id': 0, 'book-id': 1})
434 self
.assertEqual(res
['error']['class'], 'GenericError')
436 res
= self
.vm
.qmp('set-cpu-topology', {'core-id': 0, 'socket-id': 1})
437 self
.assertEqual(res
['error']['class'], 'GenericError')
439 self
.check_topology(0, 0, 0, 0, 'medium', False)